def hkdf_expand_and_extract( secret: bytes, initiator_node_id: NodeID, recipient_node_id: NodeID, id_nonce: IDNonce, ) -> Tuple[bytes, bytes, bytes]: info = b"".join(( HKDF_INFO, initiator_node_id.to_bytes(32, 'big'), recipient_node_id.to_bytes(32, 'big'), )) hkdf = HKDF( algorithm=SHA256(), length=3 * AES128_KEY_SIZE, salt=id_nonce, info=info, backend=cryptography_default_backend(), ) expanded_key = hkdf.derive(secret) if len(expanded_key) != 3 * AES128_KEY_SIZE: raise Exception("Invariant: Secret is expanded to three AES128 keys") initiator_key = expanded_key[:AES128_KEY_SIZE] recipient_key = expanded_key[AES128_KEY_SIZE:2 * AES128_KEY_SIZE] auth_response_key = expanded_key[2 * AES128_KEY_SIZE:3 * AES128_KEY_SIZE] return initiator_key, recipient_key, auth_response_key
def recover_source_id_from_tag(tag: Tag, destination_node_id: NodeID) -> NodeID: """Recover the node id of the source from the tag in a message packet.""" destination_node_id_hash = hashlib.sha256( destination_node_id.to_bytes(32, 'big')).digest() source_node_id_bytes = _sxor(tag, destination_node_id_hash) return NodeID(int.from_bytes(source_node_id_bytes, 'big'))
def node_from_rpc(rpc_node: Tuple[str, str, int]) -> Node: node_id_as_hex, ip_address, port = rpc_node node_id = NodeID(to_int(hexstr=node_id_as_hex)) node = Node( node_id, Endpoint(ipaddress.IPv4Address(ip_address), port), ) return node
def from_node_uri(cls, uri: str) -> 'Node': from alexandria.validation import validate_node_uri validate_node_uri(uri) # Be no more permissive than the validation parsed = urlparse.urlparse(uri) if parsed.username is None: raise Exception("Unreachable code path") node_id = NodeID(to_int(hexstr=parsed.username)) if parsed.port is None: raise Exception("Unreachable code path") endpoint = Endpoint(ipaddress.IPv4Address(parsed.hostname), parsed.port) return cls(node_id, endpoint)
async def _lookup_occasionally(self) -> None: async with trio.open_nursery() as nursery: async for _ in every(self.config.LOOKUP_INTERVAL): # noqa: F841 if self.routing_table.is_empty: self.logger.debug( 'Aborting scheduled lookup due to empty routing table') continue target_node_id = NodeID(secrets.randbits(256)) found_nodes = await self.network.iterative_lookup( target_node_id) self.logger.debug( 'Lookup for %s yielded %d nodes', humanize_node_id(target_node_id), len(found_nodes), ) for node in found_nodes: if node.node_id == self.client.local_node_id: continue nursery.start_soon(self.network.verify_and_add, node)
def compute_tag(source_node_id: NodeID, destination_node_id: NodeID) -> Tag: """Compute the tag used in message packets sent between two nodes.""" destination_node_id_hash = hashlib.sha256( destination_node_id.to_bytes(32, 'big')).digest() tag = _sxor(destination_node_id_hash, source_node_id.to_bytes(32, 'big')) return Tag(tag)
async def _initialize_network_graph(self) -> GraphAPI: async def do_get_introduction( node: Node, send_channel: trio.abc.SendChannel[Tuple[ SGNodeAPI, TraversalResults]], # noqa: E501 ) -> None: try: with trio.fail_after(INTRODUCTION_TIMEOUT): candidates = await self.network.get_introduction(node) except trio.TooSlowError: self.logger.debug("Timeout getting introduction from %s", node) return self.logger.debug("Got %d introductions from %s", len(candidates), node) async with send_channel: for candidate in candidates: import time start_at = time.monotonic() try: with trio.fail_after(TRAVERSAL_TIMEOUT): result = await get_traversal_result( self.network, self.graph_db, candidate, max_traversal_distance=10, ) except trio.TooSlowError: self.logger.error( "%s: Traversal timeout: %s", self.client.local_node, candidate, ) return else: end_at = time.monotonic() self.logger.info( "%s: Traversal finished in %s seconds", self.client.local_node, end_at - start_at, ) await send_channel.send(result) while self.manager.is_running: if self.config.can_initialize_network_skip_graph: # Use a probabalistic mechanism here so that multiple # bootnodes coming online at the same time are unlikely to # try and concurrently seed the network with content. if secrets.randbits(256) >= self.client.local_node_id: for key in self.content_manager.iter_content_keys(): seed_node = SGNode(content_key_to_graph_key(key)) break else: continue self.logger.info("%s: Seeding network graph", self.client.local_node) self.graph_db.set(seed_node.key, seed_node) return NetworkGraph(self.graph_db, seed_node, self.network) random_content_id = NodeID(secrets.randbits(256)) candidates = await self.network.iterative_lookup(random_content_id) if not candidates: self.logger.debug("No candidates for introduction") await trio.sleep(5) continue send_channel, receive_channel = trio.open_memory_channel[Tuple[ SGNodeAPI, TraversalResults]](256) # noqa: E501 async with trio.open_nursery() as nursery: async with send_channel: for node in candidates: nursery.start_soon(do_get_introduction, node, send_channel.clone()) async with receive_channel: introduction_results = tuple( [result async for result in receive_channel]) if not introduction_results: self.logger.debug("Received no introductions. sleeping....") await trio.sleep(5) continue best_result = sorted(introduction_results, key=lambda result: result.score, reverse=True)[0] if best_result.score > 0.0: self.logger.info( "%s: Network SkipGraph initialized: node=%s score=%s", self.client.local_node, best_result.node, best_result.score, ) return NetworkGraph(self.graph_db, best_result.node, self.network) else: self.logger.info( "Failed to initialize Skip Graph. All introductions were faulty: %s", tuple(str(result) for result in introduction_results), ) await trio.sleep(5)
def public_key_to_node_id(public_key: keys.PublicKey) -> NodeID: return NodeID(int.from_bytes(sha256(public_key.to_bytes()), 'big'))
def content_key_to_node_id(key: bytes) -> NodeID: return NodeID(int.from_bytes(sha256(key), 'big'))
def humanize_node_id(node_id: NodeID) -> str: node_id_bytes = node_id.to_bytes(32, 'big') return humanize_hash(Hash32(node_id_bytes))
def node_id_to_hex(node_id: NodeID) -> str: return encode_hex(node_id.to_bytes(32, 'big'))
def node_id_from_hex(node_id_as_hex: str) -> NodeID: return NodeID(to_int(hexstr=node_id_as_hex))
def compute_handshake_response_magic(destination_node_id: NodeID) -> Hash32: preimage = destination_node_id.to_bytes(32, 'big') + HANDSHAKE_RESPONSE_MAGIC_SUFFIX return Hash32(hashlib.sha256(preimage).digest())