diff --git a/Cargo.lock b/Cargo.lock
index 375dbef..4bcaaa2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -619,6 +619,20 @@ dependencies = [
  "windows-sys 0.45.0",
 ]
 
+[[package]]
+name = "dnapi-rs"
+version = "0.1.0"
+dependencies = [
+ "base64 0.21.0",
+ "base64-serde",
+ "log",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "trifid-pki",
+ "url",
+]
+
 [[package]]
 name = "dotenvy"
 version = "0.15.6"
@@ -2061,18 +2075,18 @@ dependencies = [
 
 [[package]]
 name = "serde"
-version = "1.0.158"
+version = "1.0.159"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9"
+checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.158"
+version = "1.0.159"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e801c1712f48475582b7696ac71e0ca34ebb30e09338425384269d9717c62cad"
+checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -2081,9 +2095,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.94"
+version = "1.0.95"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
+checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744"
 dependencies = [
  "itoa",
  "ryu",
@@ -2436,6 +2450,7 @@ dependencies = [
  "clap",
  "ctrlc",
  "dirs 5.0.0",
+ "dnapi-rs",
  "flate2",
  "hex",
  "ipnet",
diff --git a/Cargo.toml b/Cargo.toml
index 1ad3cfa..31bce56 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,5 +2,6 @@
 members = [
     "trifid-api",
     "tfclient",
-    "trifid-pki"
+    "trifid-pki",
+    "dnapi-rs"
 ]
\ No newline at end of file
diff --git a/dnapi-rs/Cargo.toml b/dnapi-rs/Cargo.toml
new file mode 100644
index 0000000..8f4b645
--- /dev/null
+++ b/dnapi-rs/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "dnapi-rs"
+version = "0.1.0"
+edition = "2021"
+description = "A rust client for the Defined Networking API"
+license = "AGPL-3.0-or-later"
+documentation = "https://docs.rs/dnapi-rs"
+homepage = "https://git.e3t.cc/~core/trifid"
+repository = "https://git.e3t.cc/~core/trifid"
+
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+serde = { version = "1.0.159", features = ["derive"] }
+base64-serde = "0.7.0"
+log = "0.4.17"
+reqwest = { version = "0.11.16", features = ["blocking", "json"] }
+url = "2.3.1"
+base64 = "0.21.0"
+serde_json = "1.0.95"
+trifid-pki = { version = "0.1.6", path = "../trifid-pki" }
\ No newline at end of file
diff --git a/dnapi-rs/src/client.rs b/dnapi-rs/src/client.rs
new file mode 100644
index 0000000..4dc1f83
--- /dev/null
+++ b/dnapi-rs/src/client.rs
@@ -0,0 +1 @@
+//! Client structs to handle communication with the Defined Networking API.
\ No newline at end of file
diff --git a/dnapi-rs/src/credentials.rs b/dnapi-rs/src/credentials.rs
new file mode 100644
index 0000000..4d55ad4
--- /dev/null
+++ b/dnapi-rs/src/credentials.rs
@@ -0,0 +1,38 @@
+//! Contains the `Credentials` struct, which contains all keys, IDs, organizations and other identity-related and security-related data that is persistent in a `Client`
+
+use std::error::Error;
+use trifid_pki::cert::{deserialize_ed25519_public, serialize_ed25519_public};
+use trifid_pki::ed25519_dalek::{SigningKey, VerifyingKey};
+
+/// Contains information necessary to make requests against the `DNClient` API.
+pub struct Credentials {
+    /// The assigned Host ID that this client represents
+    pub host_id: String,
+    /// The ed25519 private key used to sign requests against the API
+    pub ed_privkey: SigningKey,
+    /// The counter used in the other API requests. It is unknown what the purpose of this is, but the original client persists it and it is needed for API calls.
+    pub counter: u32,
+    /// The set of trusted ed25519 keys that may be used by the API to sign API responses.
+    pub trusted_keys: Vec<VerifyingKey>
+}
+
+/// Converts an array of `VerifyingKey`s to a singular bundle of PEM-encoded keys
+pub fn ed25519_public_keys_to_pem(keys: &[VerifyingKey]) -> Vec<u8> {
+    let mut res = vec![];
+
+    for key in keys {
+        res.append(&mut serialize_ed25519_public(&key.to_bytes()));
+    }
+
+    res
+}
+
+pub fn ed25519_public_keys_from_pem(pem: Vec<u8>) -> Result<Vec<VerifyingKey>, Box<dyn Error>> {
+    let mut keys = vec![];
+
+    for key in keys.chunks(32) {
+
+    }
+
+    Ok(keys)
+}
\ No newline at end of file
diff --git a/dnapi-rs/src/lib.rs b/dnapi-rs/src/lib.rs
new file mode 100644
index 0000000..3cc1117
--- /dev/null
+++ b/dnapi-rs/src/lib.rs
@@ -0,0 +1,20 @@
+//! # dnapi-rs
+//! **dnapi-rs** is a Rust-native crate for interacting with the Defined Networking client API. It is a direct port of `dnapi`, an officially maintained API client by Defined Networking.
+//!
+//! This crate is maintained as a part of the trifid project. Check out the other crates in [the git repository](https://git.e3t.cc/~core/trifid).
+
+#![warn(clippy::pedantic)]
+#![warn(clippy::nursery)]
+#![deny(clippy::unwrap_used)]
+#![deny(clippy::expect_used)]
+#![deny(missing_docs)]
+#![deny(clippy::missing_errors_doc)]
+#![deny(clippy::missing_panics_doc)]
+#![deny(clippy::missing_safety_doc)]
+#![allow(clippy::must_use_candidate)]
+#![allow(clippy::too_many_lines)]
+#![allow(clippy::module_name_repetitions)]
+
+pub mod message;
+pub mod client;
+pub mod credentials;
\ No newline at end of file
diff --git a/dnapi-rs/src/message.rs b/dnapi-rs/src/message.rs
new file mode 100644
index 0000000..e52140e
--- /dev/null
+++ b/dnapi-rs/src/message.rs
@@ -0,0 +1,197 @@
+//! Models for interacting with the Defined Networking API.
+
+use base64_serde::base64_serde_type;
+use serde::{Serialize, Deserialize};
+
+/// The version 1 `DNClient` API endpoint
+const ENDPOINT_V1: &str = "/v1/dnclient";
+
+base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD);
+
+#[derive(Serialize, Deserialize)]
+/// `RequestV1` is the version 1 `DNClient` request message.
+pub struct RequestV1 {
+    /// Version is always 1
+    pub version: i32,
+    #[serde(rename = "hostID")]
+    /// The Host ID of this dnclient instance
+    pub host_id: String,
+    /// The counter last returned by the server
+    pub counter: u32,
+    #[serde(with = "Base64Standard")]
+    /// A base64-encoded message
+    pub message: Vec<u8>,
+    #[serde(with = "Base64Standard")]
+    /// An ed25519 signature over the `message`, which can be verified with the host's previously enrolled ed25519 public key
+    pub signature: Vec<u8>
+}
+
+#[derive(Serialize, Deserialize)]
+/// `RequestWrapper` wraps a `DNClient` request message. It consists of a
+/// type and value, with the type string indicating how to interpret the value blob.
+pub struct RequestWrapper {
+    #[serde(rename = "type")]
+    /// The type of the message. Used to determine how `value` is encoded
+    pub message_type: String,
+    #[serde(with = "Base64Standard")]
+    /// A base64-encoded arbitrary message, the type of which is stated in `message_type`
+    pub value: Vec<u8>,
+    /// The timestamp of when this message was sent. Follows the format `%Y-%m-%dT%H:%M:%S.%f%:z`, or:
+    /// <4-digit year>-<two-digit-month>-<two-digit-day>T<two-digit-hour, 24-hour>:<two-digit-minute>:<two-digit-second>.<nanoseconds, zero-padded><offset with semicolon>
+    /// For example:
+    /// `2023-03-29T09:56:42.380006369-04:00`
+    /// would represent `29 March 03, 2023, 09:56:42.380006369 UTC-4`
+    pub timestamp: String
+}
+
+#[derive(Serialize, Deserialize)]
+/// `SignedResponseWrapper` contains a response message and a signature to validate inside `data`.
+pub struct SignedResponseWrapper {
+    /// The response data contained in this message
+    pub data: SignedResponse
+}
+
+#[derive(Serialize, Deserialize)]
+/// `SignedResponse` contains a response message and a signature to validate.
+pub struct SignedResponse {
+    /// The API version - always 1
+    pub version: i32,
+    #[serde(with = "Base64Standard")]
+    /// The Base64-encoded message signed inside this message
+    pub message: Vec<u8>,
+    #[serde(with = "Base64Standard")]
+    /// The ed25519 signature over the `message`
+    pub signature: Vec<u8>
+}
+
+#[derive(Serialize, Deserialize)]
+/// `CheckForUpdateResponseWrapper` contains a response to `CheckForUpdate` inside "data."
+pub struct CheckForUpdateResponseWrapper {
+    /// The response data contained in this message
+    pub data: CheckForUpdateResponse
+}
+
+#[derive(Serialize, Deserialize)]
+/// `CheckForUpdateResponse` is the response generated for a `CheckForUpdate` request.
+pub struct CheckForUpdateResponse {
+    #[serde(rename = "updateAvailable")]
+    /// Set to true if a config update is available
+    pub update_available: bool
+}
+
+#[derive(Serialize, Deserialize)]
+/// `DoUpdateRequest` is the request sent for a `DoUpdate` request.
+pub struct DoUpdateRequest {
+    #[serde(rename = "edPubkeyPEM")]
+    #[serde(with = "Base64Standard")]
+    /// The new ed25519 public key that should be used for future API requests
+    pub ed_pubkey_pem: Vec<u8>,
+    #[serde(rename = "dhPubkeyPEM")]
+    #[serde(with = "Base64Standard")]
+    /// The new ECDH public key that the Nebula certificate should be signed for
+    pub dh_pubkey_pem: Vec<u8>,
+    #[serde(with = "Base64Standard")]
+    /// A randomized value used to uniquely identify this request.
+    /// The original client uses a randomized, 16-byte value here, which dnapi-rs replicates
+    pub nonce: Vec<u8>
+}
+
+#[derive(Serialize, Deserialize)]
+/// A server response to a `DoUpdateRequest`, with the updated config and key information
+pub struct DoUpdateResponse {
+    #[serde(with = "Base64Standard")]
+    /// The base64-encoded Nebula config. It does **NOT** have a private-key, which must be inserted explicitly before Nebula can be ran
+    pub config: Vec<u8>,
+    /// The new config counter. It is unknown what the purpose of this is, but the original client keeps track of it and it is used later in the api
+    pub counter: u32,
+    #[serde(with = "Base64Standard")]
+    /// The same base64-encoded nonce that was sent in the `DoUpdateRequest`.
+    pub nonce: Vec<u8>,
+    #[serde(rename = "trustedKeys")]
+    #[serde(with = "Base64Standard")]
+    /// A new set of trusted ed25519 keys that can be used by the server to sign messages.
+    pub trusted_keys: Vec<u8>
+}
+
+/// The REST enrollment endpoint
+const ENROLL_ENDPOINT: &str = "/v2/enroll";
+
+#[derive(Serialize, Deserialize)]
+/// `EnrollRequest` is issued to the `ENROLL_ENDPOINT` to enroll this `dnclient` with a dnapi organization
+pub struct EnrollRequest {
+    /// The enrollment code given by the API server.
+    pub code: String,
+    #[serde(rename = "dhPubkey")]
+    #[serde(with = "Base64Standard")]
+    /// The ECDH public-key that should be used to sign the Nebula certificate given to this node.
+    pub dh_pubkey: Vec<u8>,
+    #[serde(rename = "edPubkey")]
+    #[serde(with = "Base64Standard")]
+    /// The Ed25519 public-key that this node will use to sign messages sent to the API.
+    pub ed_pubkey: Vec<u8>,
+    /// The timestamp of when this request was sent. Follows the format `%Y-%m-%dT%H:%M:%S.%f%:z`, or:
+    /// <4-digit year>-<two-digit-month>-<two-digit-day>T<two-digit-hour, 24-hour>:<two-digit-minute>:<two-digit-second>.<nanoseconds, zero-padded><offset with semicolon>
+    /// For example:
+    /// `2023-03-29T09:56:42.380006369-04:00`
+    /// would represent `29 March 03, 2023, 09:56:42.380006369 UTC-4`
+    pub timestamp: String
+}
+
+
+#[derive(Serialize, Deserialize)]
+#[serde(untagged)]
+/// The response to an `EnrollRequest`
+pub enum EnrollResponse {
+    /// A successful enrollment, with a `data` field pointing to an `EnrollResponseData`
+    Success {
+        /// The response data from this response
+        data: EnrollResponseData
+    },
+    /// An unsuccessful enrollment, with an `errors` field pointing to an array of `APIError`s.
+    Error {
+        /// A list of `APIError`s that happened while trying to enroll. `APIErrors` is a type alias to `Vec<APIError>`
+        errors: APIErrors
+    }
+}
+
+#[derive(Serialize, Deserialize)]
+/// The data included in an successful enrollment.
+pub struct EnrollResponseData {
+    #[serde(with = "Base64Standard")]
+    /// The base64-encoded Nebula config. It does **NOT** have a private-key, which must be inserted explicitly before Nebula can be ran
+    pub config: Vec<u8>,
+    #[serde(rename = "hostID")]
+    /// The server-side Host ID that this node now has.
+    pub host_id: String,
+    /// The new config counter. It is unknown what the purpose of this is, but the original client keeps track of it and it is used later in the api
+    pub counter: u32,
+    #[serde(rename = "trustedKeys")]
+    #[serde(with = "Base64Standard")]
+    /// A new set of trusted ed25519 keys that can be used by the server to sign messages.
+    pub trusted_keys: Vec<u8>,
+    /// The organization data that this node is now a part of
+    pub organization: EnrollResponseDataOrg
+}
+
+#[derive(Serialize, Deserialize)]
+/// The organization data that this node is now a part of
+pub struct EnrollResponseDataOrg {
+    /// The organization ID that this node is now a part of
+    pub id: String,
+    /// The name of the organization that this node is now a part of
+    pub name: String
+}
+
+#[derive(Serialize, Deserialize)]
+/// `APIError` represents a single error returned in an API error response.
+pub struct APIError {
+    /// The error code
+    pub code: String,
+    /// The human-readable error message
+    pub message: String,
+    /// An optional path to where the error occured
+    pub path: Option<String>
+}
+
+/// A type alias to a array of `APIErrors`. Just for parity with dnapi.
+pub type APIErrors = Vec<APIError>;
\ No newline at end of file
diff --git a/tfclient/Cargo.toml b/tfclient/Cargo.toml
index df081aa..56fa277 100644
--- a/tfclient/Cargo.toml
+++ b/tfclient/Cargo.toml
@@ -24,6 +24,7 @@ base64 = "0.21.0"
 chrono = "0.4.24"
 ipnet = "2.7.1"
 base64-serde = "0.7.0"
+dnapi-rs = { version = "0.1.0", path = "../dnapi-rs" }
 
 [build-dependencies]
 serde = { version = "1.0.157", features = ["derive"] }
diff --git a/tfclient/src/api.rs b/tfclient/src/api.rs
deleted file mode 100644
index fd3e77e..0000000
--- a/tfclient/src/api.rs
+++ /dev/null
@@ -1,145 +0,0 @@
-use std::error::Error;
-use base64_serde::base64_serde_type;
-use log::trace;
-use reqwest::blocking::Client;
-use serde::{Serialize, Deserialize};
-use url::Url;
-
-const ENDPOINT_V1: &str = "/v1/dnclient";
-
-base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD);
-
-pub fn enroll(server: &Url, request: &EnrollRequest) -> Result<APIResponse, Box<dyn Error>> {
-    let endpoint = server.join("/v2/enroll")?;
-    let client = Client::new();
-
-    let text = serde_json::to_string(request)?;
-
-    trace!("sending enroll: {}", text);
-
-    let resp = client.post(endpoint).body(text).send()?;
-    Ok(resp.json()?)
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct RequestV1 {
-    pub version: i32,
-    #[serde(rename = "hostID")]
-    pub host_id: String,
-    pub counter: u32,
-    pub message: String,
-    #[serde(with = "Base64Standard")]
-    pub signature: Vec<u8>
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct RequestWrapper {
-    #[serde(rename = "type")]
-    pub message_type: String,
-    #[serde(with = "Base64Standard")]
-    pub value: Vec<u8>,
-    pub timestamp: String
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct SignedResponseWrapper {
-    pub data: SignedResponse
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct SignedResponse {
-    pub version: i32,
-    #[serde(with = "Base64Standard")]
-    pub message: Vec<u8>,
-    #[serde(with = "Base64Standard")]
-    pub signature: Vec<u8>
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct CheckForUpdateResponseWrapper {
-    pub data: CheckForUpdateResponse
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct CheckForUpdateResponse {
-    #[serde(rename = "updateAvailable")]
-    pub update_available: bool
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct DoUpdateRequest {
-    #[serde(rename = "edPubkeyPEM")]
-    #[serde(with = "Base64Standard")]
-    pub ed_pubkey_pem: Vec<u8>,
-    #[serde(rename = "dhPubkeyPEM")]
-    #[serde(with = "Base64Standard")]
-    pub dh_pubkey_pem: Vec<u8>,
-    #[serde(with = "Base64Standard")]
-    pub nonce: Vec<u8>
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct DoUpdateResponse {
-    #[serde(with = "Base64Standard")]
-    pub config: Vec<u8>,
-    pub counter: u32,
-    #[serde(with = "Base64Standard")]
-    pub nonce: Vec<u8>,
-    #[serde(rename = "trustedKeys")]
-    #[serde(with = "Base64Standard")]
-    pub trusted_keys: Vec<u8>
-}
-
-const ENROLL_ENDPOINT: &str = "/v2/enroll";
-
-#[derive(Serialize, Deserialize)]
-pub struct EnrollRequest {
-    pub code: String,
-    #[serde(rename = "dhPubkey")]
-    #[serde(with = "Base64Standard")]
-    pub dh_pubkey: Vec<u8>,
-    #[serde(rename = "edPubkey")]
-    #[serde(with = "Base64Standard")]
-    pub ed_pubkey: Vec<u8>,
-    pub timestamp: String
-}
-
-
-#[derive(Serialize, Deserialize)]
-#[serde(untagged)]
-pub enum EnrollResponse {
-    Success {
-        data: EnrollResponseData
-    },
-    Error {
-        errors: APIErrors
-    }
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct EnrollResponseData {
-    #[serde(with = "Base64Standard")]
-    pub config: Vec<u8>,
-    #[serde(rename = "hostID")]
-    pub host_id: String,
-    pub counter: u32,
-    #[serde(rename = "trustedKeys")]
-    #[serde(with = "Base64Standard")]
-    pub trusted_keys: Vec<u8>,
-    pub organization: EnrollResponseDataOrg
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct EnrollResponseDataOrg {
-    pub id: String,
-    pub name: String
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct APIError {
-    pub code: String,
-    pub message: String,
-    pub path: Option<String>
-}
-
-pub type APIErrors = Vec<APIError>;
\ No newline at end of file
diff --git a/tfclient/src/apiworker.rs b/tfclient/src/apiworker.rs
index b410f4a..a5f85ce 100644
--- a/tfclient/src/apiworker.rs
+++ b/tfclient/src/apiworker.rs
@@ -8,7 +8,7 @@ use trifid_pki::cert::{serialize_ed25519_public, serialize_x25519_public};
 use trifid_pki::ed25519_dalek::{SecretKey, SigningKey};
 use trifid_pki::rand_core::OsRng;
 use trifid_pki::x25519_dalek::StaticSecret;
-use crate::api::{APIResponse, enroll, EnrollRequest};
+use crate::message::{APIResponse, enroll, EnrollRequest};
 use crate::config::{load_cdata, save_cdata, TFClientConfig};
 use crate::daemon::ThreadMessageSender;
 
diff --git a/tfclient/src/dnapi/mod.rs b/tfclient/src/dnapi/mod.rs
deleted file mode 100644
index e5697e1..0000000
--- a/tfclient/src/dnapi/mod.rs
+++ /dev/null
@@ -1 +0,0 @@
-/// Essentially a direct port of https://github.com/DefinedNet/dnapi
\ No newline at end of file
diff --git a/tfclient/src/main.rs b/tfclient/src/main.rs
index e8e11fa..4c0adfe 100644
--- a/tfclient/src/main.rs
+++ b/tfclient/src/main.rs
@@ -23,7 +23,6 @@ pub mod config;
 pub mod service;
 pub mod apiworker;
 pub mod socketworker;
-pub mod api;
 pub mod socketclient;
 pub mod timerworker;
 
diff --git a/trifid-pki/src/cert.rs b/trifid-pki/src/cert.rs
index c5495e6..190baf0 100644
--- a/trifid-pki/src/cert.rs
+++ b/trifid-pki/src/cert.rs
@@ -313,6 +313,26 @@ pub fn deserialize_ed25519_public(bytes: &[u8]) -> Result<Vec<u8>, Box<dyn Error
     Ok(pem.contents)
 }
 
+/// Attempt to deserialize multiple PEM encoded Ed25519 public keys
+/// # Errors
+/// This function will return an error if the PEM data is invalid or has the wrong tag
+pub fn deserialize_ed25519_public_many(bytes: &[u8]) -> Result<Vec<Vec<u8>>, Box<dyn Error>> {
+    let mut keys = vec![];
+    let pems = pem::parse_many(bytes)?;
+
+    for pem in pems {
+        if pem.tag != ED25519_PUBLIC_KEY_BANNER {
+            return Err(KeyError::WrongPemTag.into())
+        }
+        if pem.contents.len() != 64 {
+            return Err(KeyError::Not64Bytes.into())
+        }
+        keys.push(pem.contents);
+    }
+
+    Ok(keys)
+}
+
 impl NebulaCertificate {
     /// Sign a nebula certificate with the provided private key
     /// # Errors
diff --git a/trifid-pki/src/test.rs b/trifid-pki/src/test.rs
index 55f465e..3d328dc 100644
--- a/trifid-pki/src/test.rs
+++ b/trifid-pki/src/test.rs
@@ -6,7 +6,7 @@ use std::net::Ipv4Addr;
 use std::ops::{Add, Sub};
 use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH};
 use ipnet::Ipv4Net;
-use crate::cert::{CertificateValidity, deserialize_ed25519_private, deserialize_ed25519_public, deserialize_nebula_certificate, deserialize_nebula_certificate_from_pem, deserialize_x25519_private, deserialize_x25519_public, NebulaCertificate, NebulaCertificateDetails, serialize_ed25519_private, serialize_ed25519_public, serialize_x25519_private, serialize_x25519_public};
+use crate::cert::{CertificateValidity, deserialize_ed25519_private, deserialize_ed25519_public, deserialize_ed25519_public_many, deserialize_nebula_certificate, deserialize_nebula_certificate_from_pem, deserialize_x25519_private, deserialize_x25519_public, NebulaCertificate, NebulaCertificateDetails, serialize_ed25519_private, serialize_ed25519_public, serialize_x25519_private, serialize_x25519_public};
 use std::str::FromStr;
 use ed25519_dalek::{SigningKey, VerifyingKey};
 use quick_protobuf::{MessageWrite, Writer};
@@ -300,6 +300,16 @@ fn ed25519_serialization() {
     assert!(deserialize_ed25519_private(&[0u8; 32]).is_err());
     assert_eq!(deserialize_ed25519_public(&serialize_ed25519_public(&bytes)).unwrap(), bytes);
     assert!(deserialize_ed25519_public(&[0u8; 32]).is_err());
+
+    let mut bytes = vec![];
+    bytes.append(&mut serialize_ed25519_public(&[0u8; 64]));
+    bytes.append(&mut serialize_ed25519_public(&[1u8; 64]));
+    let deser = deserialize_ed25519_public_many(&bytes).unwrap();
+    assert_eq!(deser[0], [0u8; 64]);
+    assert_eq!(deser[1], [1u8; 64]);
+
+    bytes.append(&mut serialize_ed25519_public(&[1u8; 63]));
+    deserialize_ed25519_public_many(&bytes).unwrap_err();
 }
 
 #[test]