ContextBase keySchedule(byte mode, byte[] sharedSecret, byte[] info, byte[] psk, byte[] pskId) {
        verifyPskInputs(mode, psk, pskId);
        verifyInputLengthRestrictions(psk, pskId, info);

        byte[] pskIdHash = labeledExtractHpke(EMPTY_LABEL, PSK_ID_HASH_LABEL, pskId); // Kdf.nH() bytes returned
        byte[] infoHash  = labeledExtractHpke(EMPTY_LABEL, INFO_HASH_LABEL,   info );
        byte[] keyScheduleContext = concat(new byte[]{mode}, pskIdHash, infoHash);

        byte[] secret = labeledExtractHpke(sharedSecret, SECRET_LABEL, psk);

        byte[] key            = labeledExpandHpke(secret, KEY_LABEL,        keyScheduleContext, aead.nK());
        byte[] baseNonce      = labeledExpandHpke(secret, BASE_NONCE_LABEL, keyScheduleContext, aead.nN());
        byte[] exporterSecret = labeledExpandHpke(secret, EXP_LABEL,        keyScheduleContext, kdf.nH());

        return new ContextBase(key, baseNonce, 0, exporterSecret);
    }
Section 5.1 Creating the Encryption Context:

<pre>
def KeySchedule&lt;ROLE&gt;(mode, shared_secret, info, psk, psk_id):
  VerifyPSKInputs(mode, psk, psk_id)

  psk_id_hash = LabeledExtract("", "psk_id_hash", psk_id)
  info_hash = LabeledExtract("", "info_hash", info)
  key_schedule_context = concat(mode, psk_id_hash, info_hash)

  secret = LabeledExtract(shared_secret, "secret", psk)

  key = LabeledExpand(secret, "key", key_schedule_context, Nk)
  base_nonce = LabeledExpand(secret, "base_nonce",
                             key_schedule_context, Nn)
  exporter_secret = LabeledExpand(secret, "exp",
                                  key_schedule_context, Nh)

  return Context&lt;ROLE&gt;(key, base_nonce, 0, exporter_secret)
</pre>

Note: Labeled*-functions above implicitly include the HPKE suite_id. We do it explicitly.
We also throw in an input length check as recommended in Section 7.2.1.
security-utils/src/main/java/com/yahoo/security/hpke/Hpke.java