ErrorType encrypt(unsigned char* packet,
                    uint32_t packet_len,
                    uint32_t seqno) {
    ErrorType r = kErrNone;
    size_t sig_len = 16;
    int outlen = 0;

    // `packet` layout:
    //   <packet length> <padding length> <payload> <padding> <poly1305 mac>
    uint32_t data_len = packet_len - POLY1305_TAGLEN;

    unsigned char polykey[POLY1305_KEYLEN] = {0};

    uint8_t seqbuf[16] = {0};
    ((uint8_t*)(seqbuf))[12] = (seqno >> 24) & 0xff;
    ((uint8_t*)(seqbuf))[13] = (seqno >> 16) & 0xff;
    ((uint8_t*)(seqbuf))[14] = (seqno >> 8) & 0xff;
    ((uint8_t*)(seqbuf))[15] = seqno & 0xff;

    // Generate Poly1305 key
    if (EVP_EncryptInit_ex(ctx_main_, nullptr, nullptr, nullptr, seqbuf) != 1) {
      r = kErrOpenSSL;
      goto out;
    }
    if (EVP_EncryptUpdate(ctx_main_,
                          polykey,
                          &outlen,
                          polykey,
                          sizeof(polykey)) != 1) {
      r = kErrOpenSSL;
      goto out;
    }
    if (static_cast<size_t>(outlen) != sizeof(polykey)) {
      r = kErrPartialEncrypt;
      goto out;
    }

    // Encrypt packet length
    if (EVP_EncryptInit_ex(ctx_pktlen_,
                           nullptr,
                           nullptr,
                           nullptr,
                           seqbuf) != 1) {
      r = kErrOpenSSL;
      goto out;
    }
    if (EVP_EncryptUpdate(ctx_pktlen_, packet, &outlen, packet, 4) != 1) {
      r = kErrOpenSSL;
      goto out;
    }
    if (static_cast<size_t>(outlen) != 4) {
      r = kErrPartialEncrypt;
      goto out;
    }

    // Encrypt rest of packet
    seqbuf[0] = 1;
    if (EVP_EncryptInit_ex(ctx_main_, nullptr, nullptr, nullptr, seqbuf) != 1) {
      r = kErrOpenSSL;
      goto out;
    }
    if (EVP_EncryptUpdate(ctx_main_,
                          packet + 4,
                          &outlen,
                          packet + 4,
                          data_len - 4) != 1) {
      r = kErrOpenSSL;
      goto out;
    }
    if (static_cast<size_t>(outlen) != data_len - 4) {
      r = kErrPartialEncrypt;
      goto out;
    }

    // Poly1305 over ciphertext
    if (polykey_) {
      if (EVP_PKEY_CTX_ctrl(polykey_ctx_,
                            -1,
                            EVP_PKEY_OP_SIGNCTX,
                            EVP_PKEY_CTRL_SET_MAC_KEY,
                            sizeof(polykey),
                            (void*)polykey) <= 0) {
        r = kErrOpenSSL;
        goto out;
      }
    } else {
      polykey_ = EVP_PKEY_new_raw_private_key(EVP_PKEY_POLY1305,
                                              nullptr,
                                              polykey,
                                              sizeof(polykey));
      if (polykey_ == nullptr) {
        r = kErrOpenSSL;
        goto out;
      }

      if (!EVP_DigestSignInit(md_ctx_,
                              &polykey_ctx_,
                              nullptr,
                              nullptr,
                              polykey_)) {
        r = kErrOpenSSL;
        goto out;
      }
    }

    // Generate and write Poly1305 tag
    if (EVP_DigestSign(md_ctx_,
                       packet + data_len,
                       &sig_len,
                       packet,
                       data_len) != 1) {
      r = kErrOpenSSL;
      goto out;
    }

  out:
    return r;
  }