Skip to main content

ma_core/
key.rs

1use ed25519_dalek::{Signer, SigningKey as Ed25519SigningKey, VerifyingKey};
2use rand_core::OsRng;
3use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret};
4
5use crate::{
6    did::Did,
7    error::{MaError, MaResult as Result},
8    multiformat::{public_key_multibase_decode, public_key_multibase_encode},
9};
10
11pub const ASSERTION_METHOD_KEY_TYPE: &str = "Multikey";
12pub const KEY_AGREEMENT_KEY_TYPE: &str = "Multikey";
13
14// https://github.com/multiformats/multicodec/blob/master/table.csv
15pub const CODEC_X25519_PUB: u64 = 0xec;
16pub const CODEC_ED25519_PUB: u64 = 0xed;
17pub const CODEC_EDDSA_SIG: u64 = 0xd0ed;
18
19/// Ed25519 signing key for document proofs and message signatures.
20///
21/// # Examples
22///
23/// ```
24/// use ma_core::{Did, SigningKey};
25///
26/// let did = Did::new_url("k51qzi5uqu5dj9807pbuod1pplf0vxh8m4lfy3ewl9qbm2s8dsf9ugdf9gedhr", None::<String>).unwrap();
27/// let key = SigningKey::generate(did).unwrap();
28///
29/// let signature = key.sign(b"hello world");
30/// assert!(!signature.is_empty());
31///
32/// // Export and reimport private key bytes
33/// let bytes = key.private_key_bytes();
34/// let did2 = Did::new_url("k51qzi5uqu5dj9807pbuod1pplf0vxh8m4lfy3ewl9qbm2s8dsf9ugdf9gedhr", None::<String>).unwrap();
35/// let restored = SigningKey::from_private_key_bytes(did2, bytes).unwrap();
36/// assert_eq!(key.public_key_multibase, restored.public_key_multibase);
37/// ```
38#[derive(Clone)]
39pub struct SigningKey {
40    pub did: Did,
41    pub key_type: String,
42    secret_key: Ed25519SigningKey,
43    pub public_key_multibase: String,
44}
45
46impl SigningKey {
47    pub fn generate(did: Did) -> Result<Self> {
48        let signing_key = Ed25519SigningKey::generate(&mut OsRng);
49        let public_key_multibase =
50            public_key_multibase_encode(CODEC_ED25519_PUB, signing_key.verifying_key().as_bytes());
51
52        Ok(Self {
53            did,
54            key_type: ASSERTION_METHOD_KEY_TYPE.to_string(),
55            secret_key: signing_key,
56            public_key_multibase,
57        })
58    }
59
60    #[must_use]
61    pub fn sign(&self, data: &[u8]) -> Vec<u8> {
62        self.secret_key.sign(data).to_bytes().to_vec()
63    }
64
65    #[must_use]
66    pub fn verifying_key(&self) -> VerifyingKey {
67        self.secret_key.verifying_key()
68    }
69
70    #[must_use]
71    pub fn private_key_bytes(&self) -> [u8; ed25519_dalek::SECRET_KEY_LENGTH] {
72        self.secret_key.to_bytes()
73    }
74
75    pub fn from_private_key_bytes(
76        did: Did,
77        private_key: [u8; ed25519_dalek::SECRET_KEY_LENGTH],
78    ) -> Result<Self> {
79        let signing_key = Ed25519SigningKey::from_bytes(&private_key);
80        let public_key_multibase =
81            public_key_multibase_encode(CODEC_ED25519_PUB, signing_key.verifying_key().as_bytes());
82
83        Ok(Self {
84            did,
85            key_type: ASSERTION_METHOD_KEY_TYPE.to_string(),
86            secret_key: signing_key,
87            public_key_multibase,
88        })
89    }
90
91    pub fn validate(&self) -> Result<()> {
92        Did::validate(&self.did.id())?;
93
94        if self.key_type != ASSERTION_METHOD_KEY_TYPE {
95            return Err(MaError::InvalidKeyType);
96        }
97
98        let (codec, key_bytes) = public_key_multibase_decode(&self.public_key_multibase)?;
99        if codec != CODEC_ED25519_PUB {
100            return Err(MaError::InvalidMulticodec {
101                expected: CODEC_ED25519_PUB,
102                actual: codec,
103            });
104        }
105
106        if key_bytes.len() != ed25519_dalek::PUBLIC_KEY_LENGTH {
107            return Err(MaError::InvalidKeyLength {
108                expected: ed25519_dalek::PUBLIC_KEY_LENGTH,
109                actual: key_bytes.len(),
110            });
111        }
112
113        Ok(())
114    }
115}
116
117/// X25519 encryption key for envelope key agreement.
118///
119/// Used to compute shared secrets via Diffie-Hellman for encrypting
120/// and decrypting [`Envelope`](crate::Envelope) payloads.
121///
122/// # Examples
123///
124/// ```
125/// use ma_core::{Did, EncryptionKey};
126///
127/// let did = Did::new_url("k51qzi5uqu5dj9807pbuod1pplf0vxh8m4lfy3ewl9qbm2s8dsf9ugdf9gedhr", None::<String>).unwrap();
128/// let key = EncryptionKey::generate(did).unwrap();
129///
130/// // Export and reimport
131/// let bytes = key.private_key_bytes();
132/// let did2 = Did::new_url("k51qzi5uqu5dj9807pbuod1pplf0vxh8m4lfy3ewl9qbm2s8dsf9ugdf9gedhr", None::<String>).unwrap();
133/// let restored = EncryptionKey::from_private_key_bytes(did2, bytes).unwrap();
134/// assert_eq!(key.public_key_multibase, restored.public_key_multibase);
135/// ```
136#[derive(Clone)]
137pub struct EncryptionKey {
138    pub did: Did,
139    pub key_type: String,
140    private_key: StaticSecret,
141    pub public_key: X25519PublicKey,
142    pub public_key_multibase: String,
143}
144
145impl EncryptionKey {
146    pub fn generate(did: Did) -> Result<Self> {
147        let private_key = StaticSecret::random_from_rng(OsRng);
148        let public_key = X25519PublicKey::from(&private_key);
149        let public_key_multibase =
150            public_key_multibase_encode(CODEC_X25519_PUB, public_key.as_bytes());
151
152        Ok(Self {
153            did,
154            key_type: KEY_AGREEMENT_KEY_TYPE.to_string(),
155            private_key,
156            public_key,
157            public_key_multibase,
158        })
159    }
160
161    #[must_use]
162    pub fn shared_secret(&self, other: &X25519PublicKey) -> [u8; 32] {
163        self.private_key.diffie_hellman(other).to_bytes()
164    }
165
166    #[must_use]
167    pub fn private_key_bytes(&self) -> [u8; 32] {
168        self.private_key.to_bytes()
169    }
170
171    pub fn from_private_key_bytes(did: Did, private_key: [u8; 32]) -> Result<Self> {
172        let private_key = StaticSecret::from(private_key);
173        let public_key = X25519PublicKey::from(&private_key);
174        let public_key_multibase =
175            public_key_multibase_encode(CODEC_X25519_PUB, public_key.as_bytes());
176
177        Ok(Self {
178            did,
179            key_type: KEY_AGREEMENT_KEY_TYPE.to_string(),
180            private_key,
181            public_key,
182            public_key_multibase,
183        })
184    }
185
186    pub fn validate(&self) -> Result<()> {
187        Did::validate(&self.did.id())?;
188
189        if self.key_type != KEY_AGREEMENT_KEY_TYPE {
190            return Err(MaError::InvalidKeyType);
191        }
192
193        let (codec, key_bytes) = public_key_multibase_decode(&self.public_key_multibase)?;
194        if codec != CODEC_X25519_PUB {
195            return Err(MaError::InvalidMulticodec {
196                expected: CODEC_X25519_PUB,
197                actual: codec,
198            });
199        }
200
201        if key_bytes.len() != 32 {
202            return Err(MaError::InvalidKeyLength {
203                expected: 32,
204                actual: key_bytes.len(),
205            });
206        }
207
208        Ok(())
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215    use ed25519_dalek::{Signature, Verifier};
216
217    fn test_did() -> Did {
218        Did::new_url(
219            "k51qzi5uqu5dj9807pbuod1pplf0vxh8m4lfy3ewl9qbm2s8dsf9ugdf9gedhr",
220            None::<String>,
221        )
222        .expect("valid did")
223    }
224
225    fn test_did2() -> Did {
226        Did::new_url(
227            "k51qzi5uqu5dkkciu33khkzbcmxtyhn376i1e83tya8kuy7z9euedzyr5nhoew",
228            None::<String>,
229        )
230        .expect("valid did 2")
231    }
232
233    // ── SigningKey ───────────────────────────────────────────────────────────
234
235    #[test]
236    fn signing_key_sign_and_verify() {
237        let sk = SigningKey::generate(test_did()).unwrap();
238        let data = b"sign me please";
239        let sig_bytes = sk.sign(data);
240        let signature = Signature::from_slice(&sig_bytes).expect("valid signature bytes");
241        sk.verifying_key()
242            .verify(data, &signature)
243            .expect("signature should verify");
244    }
245
246    #[test]
247    fn signing_key_different_data_does_not_verify() {
248        let sk = SigningKey::generate(test_did()).unwrap();
249        let sig_bytes = sk.sign(b"original");
250        let signature = Signature::from_slice(&sig_bytes).unwrap();
251        assert!(
252            sk.verifying_key().verify(b"tampered", &signature).is_err(),
253            "signature over different data must not verify"
254        );
255    }
256
257    #[test]
258    fn signing_key_from_private_key_bytes_round_trip() {
259        let sk = SigningKey::generate(test_did()).unwrap();
260        let bytes = sk.private_key_bytes();
261        let restored = SigningKey::from_private_key_bytes(test_did(), bytes).unwrap();
262        assert_eq!(sk.public_key_multibase, restored.public_key_multibase);
263    }
264
265    #[test]
266    fn signing_key_restored_key_verifies_original_signature() {
267        let sk = SigningKey::generate(test_did()).unwrap();
268        let data = b"persist me";
269        let sig_bytes = sk.sign(data);
270        let restored =
271            SigningKey::from_private_key_bytes(test_did(), sk.private_key_bytes()).unwrap();
272        let signature = Signature::from_slice(&sig_bytes).unwrap();
273        restored
274            .verifying_key()
275            .verify(data, &signature)
276            .expect("restored key must verify original signature");
277    }
278
279    #[test]
280    fn signing_key_validate_passes_for_valid_key() {
281        let sk = SigningKey::generate(test_did()).unwrap();
282        sk.validate().unwrap();
283    }
284
285    // ── EncryptionKey ────────────────────────────────────────────────────────
286
287    #[test]
288    fn encryption_key_from_private_key_bytes_round_trip() {
289        let ek = EncryptionKey::generate(test_did()).unwrap();
290        let bytes = ek.private_key_bytes();
291        let restored = EncryptionKey::from_private_key_bytes(test_did(), bytes).unwrap();
292        assert_eq!(ek.public_key_multibase, restored.public_key_multibase);
293    }
294
295    #[test]
296    fn encryption_key_shared_secret_is_symmetric() {
297        let ek_a = EncryptionKey::generate(test_did()).unwrap();
298        let ek_b = EncryptionKey::generate(test_did2()).unwrap();
299        let secret_a = ek_a.shared_secret(&ek_b.public_key);
300        let secret_b = ek_b.shared_secret(&ek_a.public_key);
301        assert_eq!(secret_a, secret_b, "DH shared secret must be symmetric");
302    }
303
304    #[test]
305    fn encryption_key_different_pairs_produce_different_secrets() {
306        let ek_a = EncryptionKey::generate(test_did()).unwrap();
307        let ek_b = EncryptionKey::generate(test_did2()).unwrap();
308        let ek_unrelated = EncryptionKey::generate(test_did()).unwrap();
309        let shared_with_b = ek_a.shared_secret(&ek_b.public_key);
310        let shared_with_unrelated = ek_a.shared_secret(&ek_unrelated.public_key);
311        assert_ne!(
312            shared_with_b, shared_with_unrelated,
313            "different peer keys must yield different secrets"
314        );
315    }
316
317    #[test]
318    fn encryption_key_validate_passes_for_valid_key() {
319        let ek = EncryptionKey::generate(test_did()).unwrap();
320        ek.validate().unwrap();
321    }
322}