def Authenticate(self, PubKey, PubKeySignature): ecc = ECC(pubkey_data=PubKey) if not ecc.verifyAddress(PubKeySignature, PubKey): raise ValueError('Failed to verify public key data.') self.stream.remote_ecc = ecc self.auth = True self.stream.node.add_stream(self.stream) return True
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)
assert l > 4 assert len(data) == l got_rid = data[:4] assert got_rid == rid bs = BSON.decode(bytes(data[4:]), tz_aware=True) if 'error' in bs: raise RemoteException(bs['error']) if len(bs) == 1 and 'data' in bs: return bs['data'] return bs r = send({'access': ecc.pubkeyData()}) rauth = r['auth'] pubkey = r['pubkey'] remote_ecc = ECC(pubkey_data=pubkey) sigdata = BSON.encode({ 'auth': rauth }) sig = ecc.signAddress(sigdata) auth = send({'sig': sig}) assert 'sig' in auth peer_verified = remote_ecc.verifyAddress(auth['sig'], sigdata) assert peer_verified == True #docs = send(request('find', { 'timestamp': { '$gt': datetime.datetime.min }}, {})) #for doc in docs: # print(send(request('find', doc, None))) paths = send(request('paths')) for path in paths: p = A2MXPath(**path) print(p)
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")