Skip to main content

prikk_crypto/
lib.rs

1//! Prikk cryptographic primitives.
2//!
3//! v1 scope is intentionally minimal: Ed25519 keypair construction, detached signing, and detached
4//! verification. Trust stores, key persistence, key rotation, revocation, and signature policy are
5//! out of scope here and belong to later phases (RFC-025). This crate is the single home for the v1
6//! signing/verification algorithm so authoring, sealing, and verification cannot diverge.
7
8use ed25519_dalek::{Signature, Signer, SigningKey, VerifyingKey};
9use prikk_error::{PrikkError, Result};
10
11/// Length in bytes of an Ed25519 secret seed and of an Ed25519 public key.
12pub const ED25519_KEY_LEN: usize = 32;
13/// Length in bytes of an Ed25519 detached signature.
14pub const ED25519_SIGNATURE_LEN: usize = 64;
15
16/// An Ed25519 keypair used to produce detached signatures.
17pub struct Ed25519KeyPair {
18    signing: SigningKey,
19}
20
21impl Ed25519KeyPair {
22    /// Construct a keypair from a 32-byte secret seed.
23    ///
24    /// Used for caller-provided key material and for deterministic test keys. The seed is the
25    /// Ed25519 secret scalar source; callers are responsible for its confidentiality.
26    #[must_use]
27    pub fn from_seed(seed: &[u8; ED25519_KEY_LEN]) -> Self {
28        Self {
29            signing: SigningKey::from_bytes(seed),
30        }
31    }
32
33    /// Generate a fresh keypair from the operating-system CSPRNG.
34    ///
35    /// Fails closed if the OS entropy source is unavailable.
36    pub fn generate() -> Result<Self> {
37        let mut seed = [0_u8; ED25519_KEY_LEN];
38        getrandom::fill(&mut seed)
39            .map_err(|e| PrikkError::Integrity(format!("OS CSPRNG unavailable: {e}")))?;
40        Ok(Self::from_seed(&seed))
41    }
42
43    /// The 32-byte public (verifying) key for this keypair.
44    #[must_use]
45    pub fn public_key_bytes(&self) -> [u8; ED25519_KEY_LEN] {
46        self.signing.verifying_key().to_bytes()
47    }
48
49    /// Produce a detached 64-byte Ed25519 signature over `message`.
50    #[must_use]
51    pub fn sign(&self, message: &[u8]) -> [u8; ED25519_SIGNATURE_LEN] {
52        self.signing.sign(message).to_bytes()
53    }
54}
55
56/// Verify a detached Ed25519 `signature` over `message` against a 32-byte `public_key`.
57///
58/// Uses strict verification (rejects non-canonical encodings and small-order keys). Returns an
59/// error if the public key or signature is malformed, or if verification fails.
60pub fn verify_ed25519(
61    public_key: &[u8; ED25519_KEY_LEN],
62    message: &[u8],
63    signature: &[u8],
64) -> Result<()> {
65    let verifying = VerifyingKey::from_bytes(public_key)
66        .map_err(|e| PrikkError::InvalidSignature(format!("malformed public key: {e}")))?;
67    let signature_array: [u8; ED25519_SIGNATURE_LEN] = signature.try_into().map_err(|_| {
68        PrikkError::InvalidSignature(format!(
69            "signature must be {ED25519_SIGNATURE_LEN} bytes, got {}",
70            signature.len()
71        ))
72    })?;
73    let signature = Signature::from_bytes(&signature_array);
74    verifying
75        .verify_strict(message, &signature)
76        .map_err(|e| PrikkError::InvalidSignature(format!("signature verification failed: {e}")))
77}
78
79#[cfg(test)]
80mod tests;