def getSharedSecretActive(
        self,
        other_public_bundle,
        allow_zero_otpks = False
    ):
        """
        Do the key exchange, as the active party. This involves selecting keys from the
        passive parties' public bundle.

        :param other_public_bundle: An instance of PublicBundle, filled with the public
            data of the passive party.
        :param allow_zero_otpks: A flag indicating whether bundles with no one-time pre
            keys are allowed or throw an error. False is the recommended default.
        :returns: A dictionary containing the shared secret, the shared associated data
            and the data the passive party needs to finalize the key exchange.

        The returned structure looks like this::
        
            {
                "to_other": {
                    # The public key of the active parties' identity key pair
                    "ik": bytes,

                    # The public key of the active parties' ephemeral key pair
                    "ek": bytes,

                    # The public key of the used passive parties' one-time pre key or None
                    "otpk": bytes or None,

                    # The public key of the passive parties' signed pre key pair
                    "spk": bytes
                },
                "ad": bytes, # The shared associated data
                "sk": bytes  # The shared secret
            }

        :raises KeyExchangeException: If an error occurs during the key exchange. The
            exception message will contain (human-readable) details.
        """

        self.__checkSPKTimestamp()

        other_ik = self.__KeyPair(pub = other_public_bundle.ik)

        other_spk = {
            "key": self.__KeyPair(pub = other_public_bundle.spk),
            "signature": other_public_bundle.spk_signature
        }

        other_otpks = [
            self.__KeyPair(pub = otpk) for otpk in other_public_bundle.otpks
        ]

        if len(other_otpks) == 0 and not allow_zero_otpks:
            raise KeyExchangeException(
                "The other public bundle does not contain any OTPKs, which is not " +
                "allowed."
            )

        other_spk_serialized = self.__PublicKeyEncoder.encodePublicKey(
            other_spk["key"].pub,
            self.__curve
        )

        if not self.__XEdDSA(mont_pub = other_ik.pub).verify(
            other_spk_serialized,
            other_spk["signature"]
        ):
            raise KeyExchangeException(
                "The signature of this public bundle's spk could not be verifified."
            )

        ek = self.__KeyPair.generate()

        dh1 = self.__ik.getSharedSecret(other_spk["key"])
        dh2 = ek.getSharedSecret(other_ik)
        dh3 = ek.getSharedSecret(other_spk["key"])
        dh4 = b""

        otpk = None
        if len(other_otpks) > 0:
            otpk_index = ord(os.urandom(1)) % len(other_otpks)
            otpk = other_otpks[otpk_index]

            dh4 = ek.getSharedSecret(otpk)

        sk = self.__kdf(dh1 + dh2 + dh3 + dh4)

        ik_pub_serialized = self.__PublicKeyEncoder.encodePublicKey(
            self.__ik.pub,
            self.__curve
        )

        other_ik_pub_serialized = self.__PublicKeyEncoder.encodePublicKey(
            other_ik.pub,
            self.__curve
        )

        ad = ik_pub_serialized + other_ik_pub_serialized

        return {
            "to_other": {
                "ik": self.__ik.pub,
                "ek": ek.pub,
                "otpk": otpk.pub if otpk else None,
                "spk": other_spk["key"].pub
            },
            "ad": ad,
            "sk": sk
        }
Do the key exchange, as the active party. This involves selecting keys from the
        passive parties' public bundle.

        :param other_public_bundle: An instance of PublicBundle, filled with the public
            data of the passive party.
        :param allow_zero_otpks: A flag indicating whether bundles with no one-time pre
            keys are allowed or throw an error. False is the recommended default.
        :returns: A dictionary containing the shared secret, the shared associated data
            and the data the passive party needs to finalize the key exchange.

        The returned structure looks like this::
        
            {
                "to_other": {
                    # The public key of the active parties' identity key pair
                    "ik": bytes,

                    # The public key of the active parties' ephemeral key pair
                    "ek": bytes,

                    # The public key of the used passive parties' one-time pre key or None
                    "otpk": bytes or None,

                    # The public key of the passive parties' signed pre key pair
                    "spk": bytes
                },
                "ad": bytes, # The shared associated data
                "sk": bytes  # The shared secret
            }

        :raises KeyExchangeException: If an error occurs during the key exchange. The
            exception message will contain (human-readable) details.