ksign 0.4.0

OpenWRT's `usign` utility rewritten in Rust. The crate provides both the executable and the library.
Documentation
use ed25519_dalek::Signer;
use ed25519_dalek::SECRET_KEY_LENGTH;
use rand::rngs::OsRng;
use sha2::Digest;
use sha2::Sha512;

use crate::Checksum;
use crate::Error;
use crate::Fingerprint;
use crate::Salt;
use crate::Signature;
use crate::UntrustedComment;
use crate::VerifyingKey;
use crate::IO;
use crate::KDF_ALGO;
use crate::PK_ALGO;
use crate::SIGNING_KEY_BYTES_LEN;

/// Signing (secret) key.
pub struct SigningKey {
    pub(crate) signing_key: ed25519_dalek::SigningKey,
    pub(crate) salt: Salt,
    pub(crate) checksum: Checksum,
    pub(crate) fingerprint: Fingerprint,
    pub(crate) comment: Option<String>,
}

impl SigningKey {
    /// Create new signing key from the provided items.
    #[allow(clippy::expect_used)]
    pub fn new(
        signing_key: ed25519_dalek::SigningKey,
        salt: Salt,
        fingerprint: Fingerprint,
        comment: Option<String>,
    ) -> Self {
        let mut hasher = Sha512::new();
        hasher.update(signing_key.as_bytes());
        let checksum: Checksum = hasher.finalize()[..Checksum::LEN]
            .try_into()
            .expect("Same length");
        Self {
            signing_key,
            salt,
            checksum,
            fingerprint,
            comment: comment.map(|s| s.replace('\n', " ")),
        }
    }

    /// Generate new signing key.
    pub fn generate(comment: Option<String>) -> Self {
        let signing_key = ed25519_dalek::SigningKey::generate(&mut OsRng);
        let salt = Salt::generate();
        let fingerprint = Fingerprint::generate();
        Self::new(signing_key, salt, fingerprint, comment)
    }

    /// Sign the message using the signing key.
    pub fn sign(&self, message: &[u8]) -> Signature {
        let signature = self.signing_key.sign(message);
        Signature {
            signature,
            fingerprint: self.fingerprint,
            comment: self.comment.clone(),
        }
    }

    /// Get the corresponding verifying key.
    pub fn to_verifying_key(&self) -> VerifyingKey {
        VerifyingKey {
            verifying_key: self.signing_key.verifying_key(),
            fingerprint: self.fingerprint,
            comment: self.comment.clone(),
        }
    }

    /// Get fingerprint.
    pub fn fingerprint(&self) -> &Fingerprint {
        &self.fingerprint
    }
}

impl IO for SigningKey {
    fn to_bytes(&self) -> Vec<u8> {
        let mut bytes = Vec::with_capacity(SIGNING_KEY_BYTES_LEN);
        bytes.extend_from_slice(PK_ALGO.as_bytes());
        bytes.extend_from_slice(KDF_ALGO.as_bytes());
        bytes.extend_from_slice(&[0, 0, 0, 0]);
        bytes.extend_from_slice(&self.salt[..]);
        bytes.extend_from_slice(&self.checksum[..]);
        bytes.extend_from_slice(&self.fingerprint[..]);
        bytes.extend_from_slice(self.signing_key.as_bytes());
        bytes.extend_from_slice(self.signing_key.verifying_key().as_bytes());
        bytes
    }

    fn from_bytes(bytes: &[u8], comment: Option<String>) -> Result<Self, Error> {
        let algo =
            std::str::from_utf8(bytes.get(..2).ok_or(Error::Format)?).map_err(|_| Error::Format)?;
        if algo != PK_ALGO {
            return Err(Error::Algorithm);
        }
        let algo = std::str::from_utf8(bytes.get(2..4).ok_or(Error::Format)?)
            .map_err(|_| Error::Format)?;
        if algo != KDF_ALGO {
            return Err(Error::Algorithm);
        }
        let kdf_rounds = u32::from_be_bytes(
            bytes
                .get(4..8)
                .ok_or(Error::Format)?
                .try_into()
                .map_err(|_| Error::Format)?,
        );
        if kdf_rounds != 0 {
            return Err(Error::Algorithm);
        }
        const SALT_OFFSET: usize = 8;
        const CHECKSUM_OFFSET: usize = SALT_OFFSET + Salt::LEN;
        const FINGERPRINT_OFFSET: usize = CHECKSUM_OFFSET + Checksum::LEN;
        const SIGNING_KEY_OFFSET: usize = FINGERPRINT_OFFSET + Fingerprint::LEN;
        const VERIFYING_KEY_OFFSET: usize = SIGNING_KEY_OFFSET + SECRET_KEY_LENGTH;
        let salt: Salt = bytes
            .get(SALT_OFFSET..CHECKSUM_OFFSET)
            .ok_or(Error::Format)?
            .try_into()?;
        let checksum: Checksum = bytes
            .get(CHECKSUM_OFFSET..FINGERPRINT_OFFSET)
            .ok_or(Error::Format)?
            .try_into()?;
        let fingerprint: Fingerprint = bytes
            .get(FINGERPRINT_OFFSET..SIGNING_KEY_OFFSET)
            .ok_or(Error::Format)?
            .try_into()?;
        let signing_key: ed25519_dalek::SigningKey = bytes
            .get(SIGNING_KEY_OFFSET..VERIFYING_KEY_OFFSET)
            .ok_or(Error::Format)?
            .try_into()
            .map_err(|_| Error::Format)?;
        let verifying_key: ed25519_dalek::VerifyingKey = bytes
            .get(VERIFYING_KEY_OFFSET..)
            .ok_or(Error::Format)?
            .try_into()
            .map_err(|_| Error::Format)?;
        if signing_key.verifying_key() != verifying_key {
            return Err(Error::Format);
        }
        Ok(Self {
            signing_key,
            salt,
            fingerprint,
            checksum,
            comment,
        })
    }

    fn get_comment(&self) -> UntrustedComment {
        match self.comment.as_ref() {
            Some(s) => UntrustedComment::String(s),
            None => UntrustedComment::Fingerprint("private key", self.fingerprint),
        }
    }
}