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}