Skip to main content

idprova_core/crypto/
keys.rs

1use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
2use rand::rngs::OsRng;
3use serde::{Deserialize, Serialize};
4use zeroize::ZeroizeOnDrop;
5
6use crate::{IdprovaError, Result};
7
8/// An Ed25519 keypair for IDProva identity operations.
9///
10/// # Security: SR-1 (zeroize on drop)
11///
12/// The signing key bytes are zeroed from memory when this struct is dropped,
13/// preventing private key material from being retained in process memory.
14#[derive(Debug, ZeroizeOnDrop)]
15pub struct KeyPair {
16    signing_key: SigningKey,
17}
18
19/// Public key representation for serialization.
20#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
21pub struct PublicKey {
22    /// The key type identifier.
23    #[serde(rename = "type")]
24    pub key_type: String,
25    /// The public key encoded as multibase (base58btc).
26    #[serde(rename = "publicKeyMultibase")]
27    pub public_key_multibase: String,
28}
29
30impl KeyPair {
31    /// Generate a new random Ed25519 keypair.
32    pub fn generate() -> Self {
33        let signing_key = SigningKey::generate(&mut OsRng);
34        Self { signing_key }
35    }
36
37    /// Create a keypair from existing secret key bytes (32 bytes).
38    pub fn from_secret_bytes(bytes: &[u8; 32]) -> Self {
39        let signing_key = SigningKey::from_bytes(bytes);
40        Self { signing_key }
41    }
42
43    /// Get the secret key bytes for serialization.
44    ///
45    /// # Security: S5 (restricted API)
46    ///
47    /// This method is intentionally `pub(crate)` — external callers should never
48    /// access raw private key bytes directly. Use `sign()` for cryptographic operations.
49    /// For key persistence, use the encrypted export (SR-7, Phase 8).
50    #[doc(hidden)]
51    pub fn secret_bytes(&self) -> &[u8; 32] {
52        self.signing_key.as_bytes()
53    }
54
55    /// Get the verifying (public) key.
56    pub fn verifying_key(&self) -> VerifyingKey {
57        self.signing_key.verifying_key()
58    }
59
60    /// Get the public key bytes.
61    pub fn public_key_bytes(&self) -> [u8; 32] {
62        self.verifying_key().to_bytes()
63    }
64
65    /// Get the public key as a multibase-encoded string (base58btc, prefix 'z').
66    pub fn public_key_multibase(&self) -> String {
67        let bytes = self.public_key_bytes();
68        multibase::encode(multibase::Base::Base58Btc, bytes)
69    }
70
71    /// Get the public key as a serializable struct.
72    pub fn public_key(&self) -> PublicKey {
73        PublicKey {
74            key_type: "Ed25519VerificationKey2020".to_string(),
75            public_key_multibase: self.public_key_multibase(),
76        }
77    }
78
79    /// Sign a message.
80    pub fn sign(&self, message: &[u8]) -> Vec<u8> {
81        let signature = self.signing_key.sign(message);
82        signature.to_bytes().to_vec()
83    }
84
85    /// Verify a signature against a public key.
86    pub fn verify(
87        public_key_bytes: &[u8; 32],
88        message: &[u8],
89        signature_bytes: &[u8],
90    ) -> Result<()> {
91        let verifying_key = VerifyingKey::from_bytes(public_key_bytes)
92            .map_err(|e| IdprovaError::InvalidKey(e.to_string()))?;
93
94        let signature_array: [u8; 64] = signature_bytes
95            .try_into()
96            .map_err(|_| IdprovaError::VerificationFailed("invalid signature length".into()))?;
97
98        let signature = Signature::from_bytes(&signature_array);
99
100        verifying_key
101            .verify(message, &signature)
102            .map_err(|e| IdprovaError::VerificationFailed(e.to_string()))
103    }
104
105    /// Decode a multibase-encoded public key to raw bytes.
106    pub fn decode_multibase_pubkey(multibase_str: &str) -> Result<[u8; 32]> {
107        let (_, bytes) = multibase::decode(multibase_str)
108            .map_err(|e| IdprovaError::InvalidKey(format!("multibase decode: {e}")))?;
109
110        bytes
111            .try_into()
112            .map_err(|_| IdprovaError::InvalidKey("expected 32-byte public key".into()))
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn test_generate_and_sign_verify() {
122        let kp = KeyPair::generate();
123        let message = b"hello idprova";
124        let signature = kp.sign(message);
125
126        let pub_bytes = kp.public_key_bytes();
127        assert!(KeyPair::verify(&pub_bytes, message, &signature).is_ok());
128    }
129
130    #[test]
131    fn test_invalid_signature_fails() {
132        let kp = KeyPair::generate();
133        let message = b"hello idprova";
134        let mut signature = kp.sign(message);
135        signature[0] ^= 0xFF; // corrupt signature
136
137        let pub_bytes = kp.public_key_bytes();
138        assert!(KeyPair::verify(&pub_bytes, message, &signature).is_err());
139    }
140
141    #[test]
142    fn test_wrong_key_fails() {
143        let kp1 = KeyPair::generate();
144        let kp2 = KeyPair::generate();
145        let message = b"hello idprova";
146        let signature = kp1.sign(message);
147
148        let wrong_pub = kp2.public_key_bytes();
149        assert!(KeyPair::verify(&wrong_pub, message, &signature).is_err());
150    }
151
152    #[test]
153    fn test_multibase_roundtrip() {
154        let kp = KeyPair::generate();
155        let multibase = kp.public_key_multibase();
156        let decoded = KeyPair::decode_multibase_pubkey(&multibase).unwrap();
157        assert_eq!(decoded, kp.public_key_bytes());
158    }
159
160    #[test]
161    fn test_deterministic_from_bytes() {
162        let kp1 = KeyPair::generate();
163        let secret = *kp1.secret_bytes();
164        let kp2 = KeyPair::from_secret_bytes(&secret);
165        assert_eq!(kp1.public_key_bytes(), kp2.public_key_bytes());
166    }
167}