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;