def validate_compressed_public_key(cls, public_key: bytes) -> None: try: PublicKey.from_compressed_bytes(public_key) except (EthKeysValidationError, ValueError) as error: raise ValidationError( f"Public key {encode_hex(public_key)} is invalid compressed public key: {error}" ) from error
def complete_handshake(self, response_packet: Packet) -> HandshakeResult: if not self.is_response_packet(response_packet): raise ValueError( f"Packet {response_packet} is not the expected response packet" ) if not isinstance(response_packet, WhoAreYouPacket): raise TypeError("Invariant: Only WhoAreYou packets are valid responses") who_are_you_packet = response_packet # compute session keys ( ephemeral_private_key, ephemeral_public_key, ) = self.handshake_scheme.create_handshake_key_pair() remote_public_key_object = PublicKey.from_compressed_bytes( self.remote_enr.public_key ) remote_public_key_uncompressed = remote_public_key_object.to_bytes() session_keys = self.handshake_scheme.compute_session_keys( local_private_key=ephemeral_private_key, remote_public_key=remote_public_key_uncompressed, local_node_id=self.local_enr.node_id, remote_node_id=self.remote_node_id, id_nonce=who_are_you_packet.id_nonce, is_locally_initiated=True, ) # prepare response packet id_nonce_signature = self.handshake_scheme.create_id_nonce_signature( id_nonce=who_are_you_packet.id_nonce, ephemeral_public_key=ephemeral_public_key, private_key=self.local_private_key, ) enr: Optional[ENRAPI] if who_are_you_packet.enr_sequence_number < self.local_enr.sequence_number: enr = self.local_enr else: enr = None auth_header_packet = AuthHeaderPacket.prepare( tag=self.tag, auth_tag=get_random_auth_tag(), id_nonce=who_are_you_packet.id_nonce, message=self.initial_message, initiator_key=session_keys.encryption_key, id_nonce_signature=id_nonce_signature, auth_response_key=session_keys.auth_response_key, enr=enr, ephemeral_public_key=ephemeral_public_key, ) return HandshakeResult( session_keys=session_keys, enr=None, message=None, auth_header_packet=auth_header_packet, )
def validate_signature(cls, enr: "ENR") -> None: public_key = PublicKey.from_compressed_bytes( enr[cls.public_key_enr_key]) message = enr.get_signing_message() try: signature = NonRecoverableSignature(enr.signature) except BadSignature: is_valid = False else: is_valid = signature.verify_msg(message, public_key) if not is_valid: raise ValidationError("Invalid signature")
def validate_signature(cls, *, message: bytes, signature: bytes, public_key: bytes) -> None: public_key_object = PublicKey.from_compressed_bytes(public_key) try: signature_object = NonRecoverableSignature(signature) except BadSignature: is_valid = False else: is_valid = signature_object.verify_msg(message, public_key_object) if not is_valid: raise ValidationError( f"Signature {encode_hex(signature)} is not valid for message {encode_hex(message)} " f"and public key {encode_hex(public_key)}")
def compute_session_keys(cls, *, local_private_key: bytes, remote_public_key: bytes, local_node_id: NodeID, remote_node_id: NodeID, id_nonce: IDNonce, is_locally_initiated: bool) -> SessionKeys: local_private_key_object = PrivateKey(local_private_key) remote_public_key_object = PublicKey.from_compressed_bytes( remote_public_key) secret = ecdh_agree(local_private_key_object, remote_public_key_object) if is_locally_initiated: initiator_node_id, recipient_node_id = local_node_id, remote_node_id else: initiator_node_id, recipient_node_id = remote_node_id, local_node_id info = b"".join(( HKDF_INFO, initiator_node_id, recipient_node_id, )) hkdf = HKDF( algorithm=SHA256(), length=3 * AES128_KEY_SIZE, salt=id_nonce, info=info, backend=cls.cryptography_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] if is_locally_initiated: encryption_key, decryption_key = initiator_key, recipient_key else: encryption_key, decryption_key = recipient_key, initiator_key return SessionKeys( encryption_key=AES128Key(encryption_key), decryption_key=AES128Key(decryption_key), auth_response_key=AES128Key(auth_response_key), )
def ecdh_agree(private_key: bytes, public_key: bytes) -> bytes: """ Perform the ECDH key agreement. The public key is expected in uncompressed format and the resulting secret point will be formatted as a 0x02 or 0x03 prefix (depending on the sign of the secret's y component) followed by 32 bytes of the x component. """ # We cannot use `cryptography.hazmat.primitives.asymmetric.ec.ECDH only gives us the x # component of the shared secret point, but we need both x and y. if len(public_key) == 33: public_key_eth_keys = PublicKey.from_compressed_bytes(public_key) else: public_key_eth_keys = PublicKey(public_key) public_key_compressed = public_key_eth_keys.to_compressed_bytes() public_key_coincurve = coincurve.keys.PublicKey(public_key_compressed) secret_coincurve = public_key_coincurve.multiply(private_key) return secret_coincurve.format() # type: ignore
def extract_node_id(cls, enr: CommonENRAPI) -> NodeID: public_key_object = PublicKey.from_compressed_bytes(enr.public_key) uncompressed_bytes = public_key_object.to_bytes() return NodeID(keccak(uncompressed_bytes))
def extract_node_address(cls, enr: "ENR") -> bytes: public_key = PublicKey.from_compressed_bytes( enr[cls.public_key_enr_key]) return keccak(public_key.to_bytes())