docs: def __encryptKeyTransportMessage(
          self,
          bare_jids,
          encryption_callback,
          bundles = None,
          expect_problems = None,
          ignore_trust = False
      ):
          """
          bare_jids: iterable<string>
          encryption_callback: A function which is called using an instance of cryptography.hazmat.primitives.ciphers.CipherContext, which you can use to encrypt any sort of data. You don't have to return anything.
          bundles: { [bare_jid: string] => { [device_id: int] => ExtendedPublicBundle } }
          expect_problems: { [bare_jid: string] => iterable<int> }
  
          returns: {
              iv: bytes,
              sid: int,
              keys: {
                  [bare_jid: string] => {
                      [device: int] => {
                          "data"    : bytes,
                          "pre_key" : boolean
                      }
                  }
              }
          }
          """
  
          yield self.runInactiveDeviceCleanup()
  
          #########################
          # parameter preparation #
          #########################
  
          if isinstance(bare_jids, string_type):
              bare_jids = set([ bare_jids ])
          else:
              bare_jids = set(bare_jids)
  
          if bundles == None:
              bundles = {}
  
          if expect_problems == None:
              expect_problems = {}
          else:
              for bare_jid in expect_problems:
                  expect_problems[bare_jid] = set(expect_problems[bare_jid])
  
          # Add the own bare jid to the set of jids
          bare_jids.add(self.__my_bare_jid)
  
          ########################################################
          # check all preconditions and prepare missing sessions #
          ########################################################
  
          problems = []
  
          # Prepare the lists of devices to encrypt for
          encrypt_for = {}
  
          for bare_jid in bare_jids:
              devices = yield self.__loadActiveDevices(bare_jid)
  
              if len(devices) == 0:
                  problems.append(NoDevicesException(bare_jid))
              else:
                  encrypt_for[bare_jid] = devices
  
          # Remove the sending devices from the list
          encrypt_for[self.__my_bare_jid].remove(self.__my_device_id)
  
          # Check whether all required bundles are available
          for bare_jid, devices in encrypt_for.items():
              missing_bundles = set()
  
              # Load all sessions
              sessions = yield self.__loadSessions(bare_jid, devices)
  
              for device in devices:
                  session = sessions[device]
  
                  if session == None:
                      if not device in bundles.get(bare_jid, {}):
                          missing_bundles.add(device)
  
              devices -= missing_bundles
  
              for device in missing_bundles:
                  if not device in expect_problems.get(bare_jid, set()):
                      problems.append(MissingBundleException(bare_jid, device))
  
          # Check for missing sessions and simulate the key exchange
          for bare_jid, devices in encrypt_for.items():
              key_exchange_problems = {}
  
              # Load all sessions
              sessions = yield self.__loadSessions(bare_jid, devices)
  
              for device in devices:
                  session = sessions[device]
  
                  # If no session exists, create a new session
                  if session == None:
                      # Get the required bundle
                      bundle = bundles[bare_jid][device]
  
                      try:
                          # Build the session, discarding the result afterwards. This is
                          # just to check that the key exchange works.
                          self.__state.getSharedSecretActive(bundle)
                      except x3dh.exceptions.KeyExchangeException as e:
                          key_exchange_problems[device] = str(e)
  
              encrypt_for[bare_jid] -= set(key_exchange_problems.keys())
  
              for device, message in key_exchange_problems.items():
                  if not device in expect_problems.get(bare_jid, set()):
                      problems.append(KeyExchangeException(
                          bare_jid,
                          device,
                          message
                      ))
  
          if not ignore_trust:
              # Check the trust for each device
              for bare_jid, devices in encrypt_for.items():
                  # Load all trust
                  trusts = yield self.__loadTrusts(bare_jid, devices)
  
                  # Load all sessions
                  sessions = yield self.__loadSessions(bare_jid, devices)
  
                  trust_problems = []
  
                  for device in devices:
                      trust   = trusts[device]
                      session = sessions[device]
  
                      # Get the identity key of the recipient
                      other_ik = (
                          bundles[bare_jid][device].ik
                          if session == None else
                          session.ik
                      )
  
                      if trust == None:
                          trust_problems.append((device, other_ik, "undecided"))
                      elif not (trust["key"] == other_ik and trust["trusted"]):
                          trust_problems.append((device, other_ik, "untrusted"))
  
                  devices -= set(map(lambda x: x[0], trust_problems))
  
                  for device, other_ik, problem_type in trust_problems:
                      if not device in expect_problems.get(bare_jid, set()):
                          problems.append(
                              TrustException(bare_jid, device, other_ik, problem_type)
                          )
  
          # Check for jids with no eligible devices
          for bare_jid, devices in list(encrypt_for.items()):
              # Skip this check for my own bare jid
              if bare_jid == self.__my_bare_jid:
                  continue
  
              if len(devices) == 0:
                  problems.append(NoEligibleDevicesException(bare_jid))
                  del encrypt_for[bare_jid]
  
          # If there were and problems, raise an Exception with a list of those.
          if len(problems) > 0:
              raise EncryptionProblemsException(problems)
  
          ##############
          # encryption #
          ##############
  
          # Prepare AES-GCM key and IV
          aes_gcm_iv  = os.urandom(16)
          aes_gcm_key = os.urandom(16)
  
          # Create the AES-GCM instance
          aes_gcm = Cipher(
              algorithms.AES(aes_gcm_key),
              modes.GCM(aes_gcm_iv),
              backend=default_backend()
          ).encryptor()
  
          # Encrypt the plain data
          encryption_callback(aes_gcm)
  
          # Store the tag
          aes_gcm_tag = aes_gcm.tag
  
          # {
          #     [bare_jid: string] => {
          #         [device: int] => {
          #             "data"    : bytes,
          #             "pre_key" : boolean
          #         }
          #     }
          # }
          encrypted_keys = {}
  
          for bare_jid, devices in encrypt_for.items():
              encrypted_keys[bare_jid] = {}
  
              for device in devices:
                  # Note whether this is a response to a PreKeyMessage
                  if self.__state.hasBoundOTPK(bare_jid, device):
                      self.__state.respondedTo(bare_jid, device)
                      yield self._storage.storeState(self.__state.serialize())
  
                  # Load the session
                  session = yield self.__loadSession(bare_jid, device)
  
                  # If no session exists, this will be a PreKeyMessage
                  pre_key = session == None
  
                  # Create a new session                
                  if pre_key:
                      # Get the required bundle
                      bundle = bundles[bare_jid][device]
  
                      # Build the session
                      session_and_init_data = self.__state.getSharedSecretActive(bundle)
                      
                      session = session_and_init_data["dr"]
                      session_init_data = session_and_init_data["to_other"]
  
                  # Encrypt the AES GCM key and tag
                  encrypted_data = session.encryptMessage(aes_gcm_key + aes_gcm_tag)
  
                  # Store the new/changed session
                  yield self.__storeSession(bare_jid, device, session)
  
                  # Serialize the data into a simple message format
                  serialized = self.__backend.WireFormat.messageToWire(
                      encrypted_data["ciphertext"],
                      encrypted_data["header"],
                      { "DoubleRatchet": encrypted_data["additional"] }
                  )
  
                  # If it is a PreKeyMessage, apply an additional step to the serialization.
                  if pre_key:
                      serialized = self.__backend.WireFormat.preKeyMessageToWire(
                          session_init_data,
                          serialized,
                          { "DoubleRatchet": encrypted_data["additional"] }
                      )
  
                  # Add the final encrypted and serialized data.
                  encrypted_keys[bare_jid][device] = {
                      "data"    : serialized,
                      "pre_key" : pre_key
                  }
  
          promise.returnValue({
              "iv"   : aes_gcm_iv,
              "sid"  : self.__my_device_id,
              "keys" : encrypted_keys
          })