Ejemplo n.º 1
0
class A2MXPath():
	def __init__(self, A=None, B=None, T=None, UA=None, UB=None, M=None, PB=None, PF=None, PD=None, P=None, SA=None, SB=None, D=None, DS=None):
		# A = node A public key data (address, sign and encrypt compressed public keys)
		# B = node B public key data
		# T = timestamp
		# UA = AX URI node A
		# UB = AX URI node B
		# M = MaxSize
		# PB = POW broadcast
		# PF = POW forward
		# PD = POW direct
		# P = path proof of work
		# SA = node A signature (over A, B, T and UA if present)
		# SB = node B signature (over A, B, T and UB if present)
		# D = deleted timestamp
		# DS = deleted signature (over A, B, T, SA, SB and D)
		# A must always be the smaller binary value

		if not isinstance(A, ECC):
			self.__a = ECC(pubkey_data=A)
		else:
			self.__a = A
			if not SA and self.__a.hasPrivkey() and not PB < 0:
				UA = config['publish_axuri']

		if not isinstance(B, ECC):
			self.__b = ECC(pubkey_data=B)
		else:
			self.__b = B
			if not SB and self.__b.hasPrivkey() and not PB < 0:
				UB = config['publish_axuri']

		def testURI(uri):
			if uri == None:
				return
			if len(uri) > 32:
				raise ValueError('URI too long')
			try:
				host, port = uri.split(':')
			except ValueError:
				raise ValueError('Invalid URI')
			try:
				int(port)
			except ValueError:
				raise ValueError('Invalid URI')
			validChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789'
			if not all(c in validChars for c in host):
				raise ValueError('Invalid chars in URI')
		testURI(UA)
		testURI(UB)

		self.__ua = UA
		self.__ub = UB

		if self.__a.pubkeyData() > self.__b.pubkeyData():
			self.__a, self.__b = self.__b, self.__a
			self.__ua, self.__ub = self.__ub, self.__ua

		if T:
			if T > datetime.datetime.now(datetime.timezone.utc):
				raise ValueError('timestamp is in the future')
			self.__t = T
		else:
			self.__t = now()

		assert isinstance(M, int)
		assert isinstance(PB, float)
		assert isinstance(PF, float)
		assert isinstance(PD, float)
		self.__maxsize = M
		self.__pb = PB
		self.__pf = PF
		self.__pd = PD

		self.__sigod = OrderedDict()
		self.__sigod['A'] = self.__a.pubkeyData()
		self.__sigod['B'] = self.__b.pubkeyData()
		self.__sigod['T'] = self.__t
		self.__sigod['M'] = self.__maxsize
		self.__sigod['PB'] = PB
		self.__sigod['PF'] = PF
		self.__sigod['PD'] = PD

		if self.__ua:
			self.__sigod['UA'] = self.__ua
			sigdata_a = BSON.encode(self.__sigod)
			del self.__sigod['UA']
		else:
			sigdata_a = BSON.encode(self.__sigod)

		if self.__ub:
			self.__sigod['UB'] = self.__ub
			sigdata_b = BSON.encode(self.__sigod)
			del self.__sigod['UB']
		else:
			sigdata_b = BSON.encode(self.__sigod)

		self.__sa = SA
		if SA == None:
			if self.__a.hasPrivkey():
				self.__sa = self.__a.signAddress(sigdata_a)
		else:
			verify = self.__a.verifyAddress(SA, sigdata_a)
			if not verify:
				raise InvalidDataException('SA signature verify failed.')

		self.__sb = SB
		if SB == None:
			if self.__b.hasPrivkey():
				self.__sb = self.__b.signAddress(sigdata_b)
		else:
			verify = self.__b.verifyAddress(SB, sigdata_b)
			if not verify:
				raise InvalidDataException('SA signature verify failed.')

		if not (self.__sa or self.__sb):
			raise ValueError('Invalid signatures.')

		pow_data = BSON.encode(self.__sigod)
		if P == True:
			self.pow_done = None
			def setpow(nonce):
				self.__pow = nonce
				if self.pow_done:
					self.pow_done()
			calculatePOW(message=pow_data, difficulty=3.0, callback=setpow)
		else:
			self.__pow = P
			if isinstance(P, int):
				if not checkPOW(message=pow_data, difficulty=3.0, nonce=P):
					raise ValueError('Invalid POW')
			elif P != None:
				raise ValueError('Invalid POW value')

		self.__d = D
		self.__ds = DS

		if self.__d:
			if self.__d > datetime.datetime.now(datetime.timezone.utc):
				raise ValueError('Deleted timestamp is in the future.')
			if self.__d < self.__t:
				raise ValueError('Deleted timestamp is older than timestamp.')
			if not self.isComplete:
				raise ValueError('Deleted path may not be incomplete.')

			self.__sigod['SA'] = self.__sa
			self.__sigod['SB'] = self.__sb
			self.__sigod['D'] = self.__d
			sigdata = BSON.encode(self.__sigod)
			verify = self.__a.verifyAddress(self.__ds, sigdata) or self.__b.verifyAddress(self.__ds, sigdata)
			if not verify:
				raise InvalidDataException('DS signature verify failed.')

		self.__hash = hash(self.AHash + self.BHash)
		self.__longhash = hashlib.sha256(BSON.encode(self.__sigod)).digest()

	def __getstate__(self):
		state = { 'A': self.__a.pubkeyData(), 'B': self.__b.pubkeyData(), 'T': self.__t, 'SA': self.__sa, 'SB': self.__sb, 'M': self.__maxsize, 'PB': self.__pb, 'PF': self.__pf, 'PD': self.__pd }
		if self.__ua:
			state['UA'] = self.__ua
		if self.__ub:
			state['UB'] = self.__ub
		if self.__pow:
			state['P'] = self.__pow
		if self.__d:
			state['D'] = self.__d
			state['DS'] = self.__ds
		return state

	def __setstate__(self, state):
		return A2MXPath.__init__(self, **state)

	def __hash__(self):
		return self.__hash

	@property
	def longHash(self):
		return self.__longhash

	@property
	def isComplete(self):
		return self.__sa != None and self.__sb != None and self.__pow != None

	@property
	def data(self):
		return self.__getstate__()

	@property
	def A(self):
		return self.__a.pubkeyData()
	@property
	def AHash(self):
		return self.__a.pubkeyHash()
	@property
	def AURI(self):
		return self.__ua

	@property
	def B(self):
		return self.__b.pubkeyData()
	@property
	def BHash(self):
		return self.__b.pubkeyHash()
	@property
	def BURI(self):
		return self.__ub

	@property
	def deleted(self):
		return self.__d

	@property
	def timestamp(self):
		return self.__t
	@property
	def newest_timestamp(self):
		return self.__d or self.__t

	def markdelete(self):
		assert self.__d == None
		assert 'SA' not in self.__sigod
		assert 'SB' not in self.__sigod
		assert 'D' not in self.__sigod

		self.__sigod['SA'] = self.__sa
		self.__sigod['SB'] = self.__sb
		self.__d = now()
		self.__sigod['D'] = self.__d
		sigdata = BSON.encode(self.__sigod)
		if self.__a.hasPrivkey():
			self.__ds = self.__a.signAddress(sigdata)
		elif self.__b.hasPrivkey():
			self.__ds = self.__b.signAddress(sigdata)
		else:
			raise ValueError('Cannot mark path as deleted without private key.')

	def __eq__(self, other):
		if not isinstance(other, A2MXPath):
			return False
		return self.A == other.A and self.B == other.B

	def equal(self, other):
		if not isinstance(other, A2MXPath):
			return False
		return self.data == other.data

	def otherHash(self, otherHash):
		if otherHash == self.AHash:
			return self.BHash
		elif otherHash == self.BHash:
			return self.AHash
		raise ValueError('otherHash is neither A or B.')

	def ecc(self, h):
		if h == self.AHash:
			return self.A
		if h == self.BHash:
			return self.B
		raise ValueError("Hash is neither A nor B")

	@property
	def hashes(self):
		return (self.AHash, self.BHash)

	def is_better_than(self, other):
		if self != other:
			raise ValueError('Cannot compare paths with different nodes')
		return self.newest_timestamp > other.newest_timestamp

	def __str__(self):
		return 'A: {}{} B: {}{} Timestamp: {} M: {} PB: {} PF: {} PD: {} POW: {} Deleted: {}{}'.format(
			self.__a.pubkeyHashBase58(), " ({})".format(self.__ua) if self.__ua else "",
			self.__b.pubkeyHashBase58(), " ({})".format(self.__ub) if self.__ub else "",
			self.__t.isoformat(),
			self.__maxsize, self.__pb, self.__pf, self.__pd, self.__pow,
			self.__d.isoformat() if self.__d else False,
			"" if self.isComplete else " Incomplete")
Ejemplo n.º 2
0
class A2MXAccess():
	def __init__(self, node, sendfun):
		self.node = node
		self.sendfun = sendfun
		self.ecc = None
		self.auth = False

	def disconnected(self):
		if self.auth:
			connected_clients.remove(self)
		print("A2MXAccess disconnect", self.ecc.pubkeyHashBase58() if self.ecc else "unknown", "authenticated" if self.auth == True else "not authenticated")

	def process(self, data):
		rid = data[:4]
		bs = BSON.decode(bytes(data[4:]), tz_aware=True)
		try:
			value = self.process_bson(bs)
		except Exception as e:
			import traceback
			traceback.print_exc()
			value = None
			error = 'Exception occured: {} {}'.format(str(type(e)), str(e))
		else:
			error = None
		response = {}
		if isinstance(value, (dict, OrderedDict)):
			response = value
		else:
			if value != None:
				response = { 'data': value }
			if error != None:
				response = { 'error': error }

		if len(response):
			bv = BSON.encode(response)
			self.sendfun(rid + bv)

	def process_bson(self, bs):
		if self.ecc == None:
			self.ecc = ECC(pubkey_data=bs['access'])
			if self.ecc.pubkeyHash() != self.node.ecc.pubkeyHash():
				if mongoclient == None or self.ecc.pubkeyHashBase58() not in mongoclient.database_names():
					return { 'error': 'Unknown Node {}'.format(self.ecc.pubkeyHashBase58()) }
				self.db = mongoclient[self.ecc.pubkeyHashBase58()]
				print("access request to", self.ecc.pubkeyHashBase58())
			else:
				print("access to me")
			self.auth = now()
			return { 'auth': self.auth, 'pubkey': self.node.ecc.pubkeyAddress() }
		if isinstance(self.auth, datetime.datetime):
			sigdata = BSON.encode({ 'auth': self.auth })
			verify = self.ecc.verifyAddress(bs['sig'], sigdata)
			lsig = self.node.ecc.signAddress(sigdata)
			if not verify:
				return { 'error': 'Not authenticated' }

			self.auth = True
			connected_clients.add(self)

			return { 'sig': lsig }

		if self.auth != True:
			return { 'error': 'Not authenticated' }

		if len(bs) != 1:
			raise A2MXAccessException('Only one command at a time supported')
		for k, v in bs.items():
			f = getattr(self, k, None)
			if getattr(f, 'A2MXAccessRequest__marker__', False) != True:
				raise A2MXAccessException('Invalid request {}'.format(k))
			if isinstance(v, (dict, OrderedDict)):
				return f(**v)
			elif v == None:
				return f()
			raise A2MXAccessException('Invalid argument '.format(v))

	@A2MXAccessRequest
	def getpath(self):
		p = A2MXPath(self.node.ecc, self.ecc, no_URI=True)
		return p.data

	@A2MXAccessRequest
	def setpath(self, **kwargs):
		p = A2MXPath(**kwargs)
		assert p.isComplete

		self.db['path'].remove()
		self.db['path'].insert(p.data)
		self.node.new_path(p)
		return True

	@A2MXAccessRequest
	def paths(self):
		paths = [ p.data for p in self.node.paths ]
		return paths

	@A2MXAccessRequest
	def find(self, query, rep):
		return [ x for x in self.db['inbox'].find(query, rep) ]

	@A2MXAccessRequest
	def save(self, doc):
		return self.db['inbox'].save(doc)

	@A2MXAccessRequest
	def find_routes(self, src, dst, min_hops, max_hops, max_count):
		if not src or src == b'\x00':
			src = self.node.ecc.pubkeyHash()
		routes = self.node.find_routes_from(src, dst, max_hops)
		send = []
		for route in routes:
			if len(route) < min_hops:
				continue
			send.append(route.routes)
			if len(send) >= max_count:
				break
		return send

	@A2MXAccessRequest
	def sendto(self, node, data):
		return self.node.sendto(node, data)
Ejemplo n.º 3
0
Archivo: a2mx.py Proyecto: p1tt/A2MX
class A2MXNode():
	def __init__(self, selectloop):
		self.selectloop = selectloop
		self.pathlist = a2mxpath.PathList()

		self.streams = []
		self.ecc = ECC(keyfile=config['keyfile'])
		self.connected_nodes = { self.ecc.pubkeyHash(): self }

		mypub = self.ecc.pubkeyHashBase58()
		if sys.stdout.isatty():
			cwd = os.getcwd().rsplit('/', 1)[1]
			sys.stdout.write("\x1b]2;{}: {}\x07".format(cwd, mypub))
		print("I am", mypub)

		self.selectloop.tadd(90, self.find_new_peers)

	def add(self, selectable):
		self.selectloop.add(selectable)
	def remove(self, selectable):
		self.selectloop.remove(selectable)
	def wadd(self, selectable):
		self.selectloop.wadd(selectable)
	def wremove(self, selectable):
		self.selectloop.wremove(selectable)

	def add_stream(self, stream):
#		if stream.remote_ecc.pubkeyHash() in self.connected_nodes:
#			return False
		assert stream not in self.streams
		self.streams.append(stream)
		self.connected_nodes[stream.remote_ecc.pubkeyHash()] = stream
		return True

	def del_stream(self, stream):
		if stream not in self.streams:
			return
		self.streams.remove(stream)
		assert stream.remote_ecc.pubkeyHash() in self.connected_nodes and self.connected_nodes[stream.remote_ecc.pubkeyHash()] == stream
		del self.connected_nodes[stream.remote_ecc.pubkeyHash()]

		if stream.path and stream.path.isComplete:
			self.del_path(stream.path)

	def new_path(self, path, stream=None):
		fromhash = stream.remote_ecc.pubkeyHashBase58() if stream else 'myself'
		if self.pathlist.new(path, fromhash):
			self.send_path(path, stream)
		print(self.pathlist)

	def send_path(self, path, stream=None):
		for ostream in self.streams:
			if ostream == stream:
				continue
			if hasattr(ostream, 'forward'):
				ostream.forward.sendCall('path', path.data)

	def del_path(self, path):
		self.pathlist.delete(path)
		self.send_path(path)

	def update_nodes(self, path):
		def up(h):
			if h not in self.nodes:
				self.nodes[h] = []
			nl = self.nodes[h]
			if path in nl:
				nl.remove(path)
			nl.append(path)
		up(path.AHash)
		up(path.BHash)

	def sendto(self, node, data):
		if node == self.ecc.pubkeyHash():
			data = self.ecc.decrypt(bytes(data))
			self.request.parseRequest(data)
			return

#		if node not in self.connected_nodes:
#			try:
#				a2mxaccess.A2MXAccessStore(node, data)
#				print("stored data for {}".format(ECC.b58(node)))
#			except a2mxaccess.A2MXAccessException:
#				print("cannot send to node {}".format(ECC.b58(node)))
#			return False
		self.connected_nodes[node].raw_send(data)
		return True

	def find_new_peers(self):
		self.selectloop.tadd(random.randint(30, 90), self.find_new_peers)

		if len(self.streams) >= config['connections']:
			return

		try:
			new_hash = random.sample(self.pathlist.axuris.keys() - self.connected_nodes.keys(), 1)[0]
		except ValueError:
			return
		A2MXStream(self, uri='ax://' + self.pathlist.axuris[new_hash], pubkey_hash=new_hash)

	def find_routes_from(self, src, dst, maxhops=None):
		if dst not in self.nodes:
			return []
		pathlist = self.nodes[dst]
		if len(pathlist) == 0:
			return []
		dst_pubc = pathlist[0].pubkeyCompressed(dst)

		def find_path(pathlist, lasthop, thispath=None, step=1):
			if maxhops != None and step >= maxhops:
				return
			if thispath == None:
				thispath = [dst_pubc]
			for path in pathlist:
				if path.deleted:
					continue
				nexthop = path.otherHash(lasthop)
				nexthop_pubc = path.pubkeyCompressed(nexthop)
				if nexthop == src:
					ytp = thispath[:]
					ytp.append(nexthop_pubc)
					yield A2MXRoute(ytp)
					continue
				if nexthop == dst:
					continue
				if nexthop_pubc in thispath:
					continue
				tp = thispath[:]
				tp.append(nexthop_pubc)
				for p in find_path(self.nodes[lasthop], lasthop, tp, step+1):
					yield p
		return find_path(pathlist, dst)

	def shortest_route(self, src, dst):
		try:
			routes = [ x for x in self.find_routes_from(src, dst) ]
		except KeyError:
			return A2MXRoute([])
		if len(routes) == 0:
			return A2MXRoute([])
		return min(routes, key=len)