forked from
smokesignal.events/atproto-plc
Rust and WASM did-method-plc tools and structures
1//! Cryptographic operations for signing and verification
2
3use crate::encoding::{base64url_decode, base64url_encode};
4use crate::error::{PlcError, Result};
5use k256::ecdsa::{
6 signature::Signer as K256Signer, signature::Verifier as K256Verifier,
7 Signature as K256Signature, SigningKey as K256SigningKey, VerifyingKey as K256VerifyingKey,
8};
9use p256::ecdsa::{
10 Signature as P256Signature, SigningKey as P256SigningKey, VerifyingKey as P256VerifyingKey,
11};
12use zeroize::Zeroizing;
13
14/// Multicodec prefix for secp256k1 public key
15const SECP256K1_MULTICODEC: &[u8] = &[0xe7, 0x01];
16
17/// Multicodec prefix for P-256 public key
18const P256_MULTICODEC: &[u8] = &[0x80, 0x24];
19
20/// Multibase prefix for base58btc encoding
21const MULTIBASE_BASE58BTC: u8 = b'z';
22
23/// A signing key that can be either P-256 or secp256k1
24#[derive(Clone)]
25pub enum SigningKey {
26 /// NIST P-256 signing key
27 P256(P256SigningKey),
28 /// secp256k1 signing key
29 K256(K256SigningKey),
30}
31
32impl SigningKey {
33 /// Generate a new P-256 key pair
34 ///
35 /// # Examples
36 ///
37 /// ```
38 /// use atproto_plc::crypto::SigningKey;
39 ///
40 /// let key = SigningKey::generate_p256();
41 /// ```
42 pub fn generate_p256() -> Self {
43 let key = P256SigningKey::random(&mut rand::thread_rng());
44 SigningKey::P256(key)
45 }
46
47 /// Generate a new secp256k1 key pair
48 ///
49 /// # Examples
50 ///
51 /// ```
52 /// use atproto_plc::crypto::SigningKey;
53 ///
54 /// let key = SigningKey::generate_k256();
55 /// ```
56 pub fn generate_k256() -> Self {
57 let key = K256SigningKey::random(&mut rand::thread_rng());
58 SigningKey::K256(key)
59 }
60
61 /// Convert this signing key to a did:key string
62 ///
63 /// The did:key format uses multibase (base58btc) and multicodec encoding
64 ///
65 /// # Examples
66 ///
67 /// ```
68 /// use atproto_plc::crypto::SigningKey;
69 ///
70 /// let key = SigningKey::generate_p256();
71 /// let did_key = key.to_did_key();
72 /// assert!(did_key.starts_with("did:key:"));
73 /// ```
74 pub fn to_did_key(&self) -> String {
75 let verifying_key = self.verifying_key();
76 verifying_key.to_did_key()
77 }
78
79 /// Get the verifying key (public key) for this signing key
80 pub fn verifying_key(&self) -> VerifyingKey {
81 match self {
82 SigningKey::P256(key) => VerifyingKey::P256(*key.verifying_key()),
83 SigningKey::K256(key) => VerifyingKey::K256(*key.verifying_key()),
84 }
85 }
86
87 /// Sign data with this key
88 ///
89 /// # Errors
90 ///
91 /// Returns `PlcError::CryptoError` if signing fails
92 pub fn sign(&self, data: &[u8]) -> Result<Vec<u8>> {
93 match self {
94 SigningKey::P256(key) => {
95 let signature: P256Signature = key.sign(data);
96 Ok(signature.to_vec())
97 }
98 SigningKey::K256(key) => {
99 let signature: K256Signature = key.sign(data);
100 Ok(signature.to_vec())
101 }
102 }
103 }
104
105 /// Sign data and return base64url-encoded signature
106 pub fn sign_base64url(&self, data: &[u8]) -> Result<String> {
107 let signature = self.sign(data)?;
108 Ok(base64url_encode(&signature))
109 }
110}
111
112impl Drop for SigningKey {
113 fn drop(&mut self) {
114 // Zeroize the key material when dropped
115 match self {
116 SigningKey::P256(key) => {
117 let bytes = Zeroizing::new(key.to_bytes());
118 drop(bytes);
119 }
120 SigningKey::K256(key) => {
121 let bytes = Zeroizing::new(key.to_bytes());
122 drop(bytes);
123 }
124 }
125 }
126}
127
128/// A verifying key (public key) that can be either P-256 or secp256k1
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130pub enum VerifyingKey {
131 /// NIST P-256 verifying key
132 P256(P256VerifyingKey),
133 /// secp256k1 verifying key
134 K256(K256VerifyingKey),
135}
136
137impl VerifyingKey {
138 /// Parse a verifying key from a did:key string
139 ///
140 /// # Errors
141 ///
142 /// Returns `PlcError::InvalidDidKey` if the string is not a valid did:key
143 pub fn from_did_key(did_key: &str) -> Result<Self> {
144 if !did_key.starts_with("did:key:") {
145 return Err(PlcError::InvalidDidKey(
146 "DID key must start with 'did:key:'".to_string(),
147 ));
148 }
149
150 let multibase_str = &did_key[8..]; // Skip "did:key:"
151
152 // Decode multibase (base58btc)
153 if !multibase_str.starts_with(char::from(MULTIBASE_BASE58BTC)) {
154 return Err(PlcError::InvalidDidKey(
155 "Only base58btc multibase encoding is supported".to_string(),
156 ));
157 }
158
159 let base58_str = &multibase_str[1..]; // Skip 'z' prefix
160 let decoded = bs58::decode(base58_str)
161 .into_vec()
162 .map_err(|e| PlcError::InvalidDidKey(format!("Base58 decode error: {}", e)))?;
163
164 // Check multicodec prefix and extract public key bytes
165 if decoded.starts_with(SECP256K1_MULTICODEC) {
166 let key_bytes = &decoded[SECP256K1_MULTICODEC.len()..];
167 let verifying_key = K256VerifyingKey::from_sec1_bytes(key_bytes)
168 .map_err(|e| PlcError::InvalidDidKey(format!("Invalid secp256k1 key: {}", e)))?;
169 Ok(VerifyingKey::K256(verifying_key))
170 } else if decoded.starts_with(P256_MULTICODEC) {
171 let key_bytes = &decoded[P256_MULTICODEC.len()..];
172 let verifying_key = P256VerifyingKey::from_sec1_bytes(key_bytes)
173 .map_err(|e| PlcError::InvalidDidKey(format!("Invalid P-256 key: {}", e)))?;
174 Ok(VerifyingKey::P256(verifying_key))
175 } else {
176 Err(PlcError::UnsupportedKeyType(format!(
177 "Unknown multicodec prefix: {:?}",
178 &decoded[..2.min(decoded.len())]
179 )))
180 }
181 }
182
183 /// Convert this verifying key to a did:key string
184 pub fn to_did_key(&self) -> String {
185 let (multicodec, key_bytes) = match self {
186 VerifyingKey::P256(key) => (P256_MULTICODEC, key.to_sec1_bytes().to_vec()),
187 VerifyingKey::K256(key) => (SECP256K1_MULTICODEC, key.to_sec1_bytes().to_vec()),
188 };
189
190 // Combine multicodec prefix and key bytes
191 let mut combined = Vec::with_capacity(multicodec.len() + key_bytes.len());
192 combined.extend_from_slice(multicodec);
193 combined.extend_from_slice(&key_bytes);
194
195 // Encode with multibase (base58btc)
196 let encoded = format!("{}{}", MULTIBASE_BASE58BTC as char, bs58::encode(combined).into_string());
197
198 format!("did:key:{}", encoded)
199 }
200
201 /// Verify a signature using this key
202 ///
203 /// # Errors
204 ///
205 /// Returns `PlcError::SignatureVerificationFailed` if verification fails
206 pub fn verify(&self, data: &[u8], signature: &[u8]) -> Result<()> {
207 match self {
208 VerifyingKey::P256(key) => {
209 let sig = P256Signature::from_slice(signature)
210 .map_err(|e| PlcError::CryptoError(format!("Invalid signature format: {}", e)))?;
211 key.verify(data, &sig)
212 .map_err(|_| PlcError::SignatureVerificationFailed)?;
213 }
214 VerifyingKey::K256(key) => {
215 let sig = K256Signature::from_slice(signature)
216 .map_err(|e| PlcError::CryptoError(format!("Invalid signature format: {}", e)))?;
217 key.verify(data, &sig)
218 .map_err(|_| PlcError::SignatureVerificationFailed)?;
219 }
220 }
221 Ok(())
222 }
223
224 /// Verify a base64url-encoded signature
225 pub fn verify_base64url(&self, data: &[u8], signature_b64: &str) -> Result<()> {
226 let signature = base64url_decode(signature_b64)?;
227 self.verify(data, &signature)
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 #[test]
236 fn test_p256_keygen_and_sign() {
237 let key = SigningKey::generate_p256();
238 let data = b"hello world";
239 let signature = key.sign(data).unwrap();
240 assert!(!signature.is_empty());
241 }
242
243 #[test]
244 fn test_k256_keygen_and_sign() {
245 let key = SigningKey::generate_k256();
246 let data = b"hello world";
247 let signature = key.sign(data).unwrap();
248 assert!(!signature.is_empty());
249 }
250
251 #[test]
252 fn test_p256_sign_verify() {
253 let key = SigningKey::generate_p256();
254 let data = b"hello world";
255 let signature = key.sign(data).unwrap();
256
257 let verifying_key = key.verifying_key();
258 assert!(verifying_key.verify(data, &signature).is_ok());
259
260 // Wrong data should fail
261 let wrong_data = b"goodbye world";
262 assert!(verifying_key.verify(wrong_data, &signature).is_err());
263 }
264
265 #[test]
266 fn test_k256_sign_verify() {
267 let key = SigningKey::generate_k256();
268 let data = b"hello world";
269 let signature = key.sign(data).unwrap();
270
271 let verifying_key = key.verifying_key();
272 assert!(verifying_key.verify(data, &signature).is_ok());
273 }
274
275 #[test]
276 fn test_did_key_roundtrip_p256() {
277 let key = SigningKey::generate_p256();
278 let did_key = key.to_did_key();
279 assert!(did_key.starts_with("did:key:z"));
280
281 let parsed = VerifyingKey::from_did_key(&did_key).unwrap();
282 assert_eq!(parsed, key.verifying_key());
283 }
284
285 #[test]
286 fn test_did_key_roundtrip_k256() {
287 let key = SigningKey::generate_k256();
288 let did_key = key.to_did_key();
289 assert!(did_key.starts_with("did:key:z"));
290
291 let parsed = VerifyingKey::from_did_key(&did_key).unwrap();
292 assert_eq!(parsed, key.verifying_key());
293 }
294
295 #[test]
296 fn test_base64url_sign_verify() {
297 let key = SigningKey::generate_p256();
298 let data = b"hello world";
299 let signature_b64 = key.sign_base64url(data).unwrap();
300
301 let verifying_key = key.verifying_key();
302 assert!(verifying_key.verify_base64url(data, &signature_b64).is_ok());
303 }
304
305 #[test]
306 fn test_invalid_did_key() {
307 assert!(VerifyingKey::from_did_key("invalid").is_err());
308 assert!(VerifyingKey::from_did_key("did:web:example.com").is_err());
309 }
310}