A library for ATProtocol identities.
22
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v0.2.0 283 lines 11 kB view raw
1//! Cryptographic key operations for AT Protocol identity management. 2//! 3//! This module provides utilities for working with elliptic curve cryptographic keys 4//! used in AT Protocol DID documents and identity verification. Supports both P-256 5//! (secp256r1) and K-256 (secp256k1) curves with operations for key identification, 6//! signature validation, and content signing. 7//! 8//! # Supported Key Types 9//! 10//! - **P-256** (secp256r1/ES256): NIST standard curve, commonly used in web standards 11//! - **K-256** (secp256k1/ES256K): Bitcoin curve, widely used in blockchain applications 12//! 13//! # Key Operations 14//! 15//! - Key type identification from multibase-encoded DID key strings 16//! - ECDSA signature validation for both public and private keys 17//! - Content signing with private keys 18//! - DID key method prefix handling 19//! 20//! # Example 21//! 22//! ```rust 23//! use atproto_identity::key::{identify_key, validate, KeyType}; 24//! 25//! # fn main() -> Result<(), Box<dyn std::error::Error>> { 26//! let key_data = identify_key("did:key:zQ3shNzMp4oaaQ1gQRzCxMGXFrSW3NEM1M9T6KCY9eA7HhyEA")?; 27//! assert_eq!(key_data.0, KeyType::K256Public); 28//! # Ok(()) 29//! # } 30//! ``` 31 32use anyhow::{anyhow, Result}; 33use ecdsa::signature::Signer; 34 35use crate::errors::KeyError; 36 37/// Cryptographic key types supported for AT Protocol identity. 38#[derive(Debug, PartialEq)] 39pub enum KeyType { 40 /// A p256 (P-256 / secp256r1 / ES256) public key. 41 /// The multibase / multicodec prefix is 8024. 42 P256Public, 43 44 /// A p256 (P-256 / secp256r1 / ES256) private key. 45 /// The multibase / multicodec prefix is 8626. 46 P256Private, 47 48 /// A k256 (K-256 / secp256k1 / ES256K) public key. 49 /// The multibase / multicodec prefix is e701. 50 K256Public, 51 52 /// A k256 (K-256 / secp256k1 / ES256K) private key. 53 /// The multibase / multicodec prefix is 8126. 54 K256Private, 55} 56 57/// DID key method prefix. 58const DID_METHOD_KEY_PREFIX: &str = "did:key:"; 59 60/// Extracts the value portion from a DID key string. 61/// 62/// Removes the "did:key:" prefix if present, otherwise returns the original string. 63pub fn did_method_key_value(key: &str) -> &str { 64 match key.strip_prefix(DID_METHOD_KEY_PREFIX) { 65 Some(value) => value, 66 None => key, 67 } 68} 69 70/// Identifies the key type and extracts the key data from a multibase-encoded key. 71/// 72/// Returns a tuple containing the key type and the raw key bytes. 73pub fn identify_key(key: &str) -> Result<(KeyType, Vec<u8>), KeyError> { 74 let stripped_key = did_method_key_value(key); 75 let (_, decoded_multibase_key) = 76 multibase::decode(stripped_key).map_err(KeyError::DecodeError)?; 77 78 if decoded_multibase_key.len() < 3 { 79 return Err(KeyError::InvalidKey(anyhow!("unidentified key type"))); 80 } 81 82 // These values were verified using the following method: 83 // 84 // 1. Use goat to generate p256 and k256 keys to sample. 85 // `goat key generate -t k256` 86 // 87 // 2. Use `multibase` and `xxd` to view the hex output 88 // `multibase decode zQ3shj41kYrAKpgMvWFZ8L4uFhQ6P57zpiQEuvL1LWWa8sZqN | xxd` 89 // 90 // See also: https://github.com/bluesky-social/indigo/tree/main/cmd/goat 91 // See also: https://github.com/docknetwork/multibase-cli 92 93 match &decoded_multibase_key[..2] { 94 // P-256 / secp256r1 / ES256 private key 95 [0x86, 0x26] => Ok((KeyType::P256Private, decoded_multibase_key[2..].to_vec())), 96 97 // P-256 / secp256r1 / ES256 public key 98 [0x80, 0x24] => Ok((KeyType::P256Public, decoded_multibase_key[2..].to_vec())), 99 100 // K-256 / secp256k1 / ES256K private key 101 [0x81, 0x26] => Ok((KeyType::K256Private, decoded_multibase_key[2..].to_vec())), 102 103 // K-256 / secp256k1 / ES256K public key 104 [0xe7, 0x01] => Ok((KeyType::K256Public, decoded_multibase_key[2..].to_vec())), 105 106 _ => Err(KeyError::InvalidKey(anyhow!( 107 "invalid multibase key type: {:?}", 108 &decoded_multibase_key[..2] 109 ))), 110 } 111} 112 113/// Validates a signature against content using the provided key. 114/// 115/// Supports both public and private keys for signature verification. 116pub fn validate( 117 key_data: &(KeyType, Vec<u8>), 118 signature: &[u8], 119 content: &[u8], 120) -> Result<(), KeyError> { 121 match key_data.0 { 122 KeyType::P256Public => { 123 let signature = 124 ecdsa::Signature::from_slice(signature).map_err(KeyError::SignatureError)?; 125 let verifying_key = p256::ecdsa::VerifyingKey::from_sec1_bytes(&key_data.1) 126 .map_err(KeyError::P256Error)?; 127 ecdsa::signature::Verifier::verify(&verifying_key, content, &signature) 128 .map_err(KeyError::ECDSAError) 129 } 130 KeyType::K256Public => { 131 let signature = 132 ecdsa::Signature::from_slice(signature).map_err(KeyError::SignatureError)?; 133 let verifying_key = k256::ecdsa::VerifyingKey::from_sec1_bytes(&key_data.1) 134 .map_err(KeyError::P256Error)?; 135 ecdsa::signature::Verifier::verify(&verifying_key, content, &signature) 136 .map_err(KeyError::ECDSAError) 137 } 138 KeyType::P256Private => { 139 let signature = 140 ecdsa::Signature::from_slice(signature).map_err(KeyError::SignatureError)?; 141 let secret_key: p256::SecretKey = 142 ecdsa::elliptic_curve::SecretKey::from_slice(&key_data.1) 143 .map_err(KeyError::SecretKeyError)?; 144 let public_key = secret_key.public_key(); 145 let verifying_key = p256::ecdsa::VerifyingKey::from(public_key); 146 ecdsa::signature::Verifier::verify(&verifying_key, content, &signature) 147 .map_err(KeyError::ECDSAError) 148 } 149 KeyType::K256Private => { 150 let signature = 151 ecdsa::Signature::from_slice(signature).map_err(KeyError::SignatureError)?; 152 let secret_key: k256::SecretKey = 153 ecdsa::elliptic_curve::SecretKey::from_slice(&key_data.1) 154 .map_err(KeyError::SecretKeyError)?; 155 let public_key = secret_key.public_key(); 156 let verifying_key = k256::ecdsa::VerifyingKey::from(public_key); 157 ecdsa::signature::Verifier::verify(&verifying_key, content, &signature) 158 .map_err(KeyError::ECDSAError) 159 } 160 } 161} 162 163/// Signs content using a private key. 164/// 165/// Returns an error if a public key is provided instead of a private key. 166pub fn sign(key_data: &(KeyType, Vec<u8>), content: &[u8]) -> Result<Vec<u8>, KeyError> { 167 match key_data.0 { 168 KeyType::K256Public | KeyType::P256Public => Err(KeyError::PrivateKeyRequiredForSignature), 169 KeyType::P256Private => { 170 let secret_key: p256::SecretKey = 171 ecdsa::elliptic_curve::SecretKey::from_slice(&key_data.1) 172 .map_err(KeyError::SecretKeyError)?; 173 let signing_key: p256::ecdsa::SigningKey = p256::ecdsa::SigningKey::from(secret_key); 174 let signature: p256::ecdsa::Signature = signing_key 175 .try_sign(content) 176 .map_err(KeyError::ECDSAError)?; 177 Ok(signature.to_vec()) 178 } 179 KeyType::K256Private => { 180 let secret_key: k256::SecretKey = 181 ecdsa::elliptic_curve::SecretKey::from_slice(&key_data.1) 182 .map_err(KeyError::SecretKeyError)?; 183 let signing_key: k256::ecdsa::SigningKey = k256::ecdsa::SigningKey::from(secret_key); 184 let signature: k256::ecdsa::Signature = signing_key 185 .try_sign(content) 186 .map_err(KeyError::ECDSAError)?; 187 Ok(signature.to_vec()) 188 } 189 } 190} 191 192#[cfg(test)] 193mod tests { 194 use super::*; 195 196 #[test] 197 fn test_identify_key() { 198 assert!(matches!( 199 identify_key("z3vLVqpQveB3w8G6MQsLVseJ1Z2E1JyQzUj6WgRYNNwB9jdE"), 200 Ok((KeyType::K256Private, _)) 201 )); 202 assert!(matches!( 203 identify_key("z3vLVqpQveB3w8G6MQsLVseJ1Z2E1JyQzUj6WgRYNNwB9jdE"), 204 Ok((KeyType::K256Private, _)) 205 )); 206 assert!(matches!( 207 identify_key("z3vLVqpQveB3w8G6MQsLVseJ1Z2E1JyQzUj6WgRYNNwB9jdE"), 208 Ok((KeyType::K256Private, _)) 209 )); 210 assert!(matches!( 211 identify_key("z3vLVqpQveB3w8G6MQsLVseJ1Z2E1JyQzUj6WgRYNNwB9jdE"), 212 Ok((KeyType::K256Private, _)) 213 )); 214 assert!(matches!( 215 identify_key("asdasdasd"), 216 Err(KeyError::DecodeError(_)) 217 )); 218 assert!(matches!( 219 identify_key("z4vLVqpQveB3w8G6MQsLVseJ1Z2E1JyQzUj6WgRYNNwB9jdE"), 220 Err(KeyError::InvalidKey(_)) 221 )); 222 } 223 224 #[test] 225 fn test_sign_p256() -> Result<()> { 226 let private_key = "did:key:z42tnbHmmnhF11nwSnp5kQJbcZQw2Vbw5WF3ABDSxPtDgU2o"; 227 let public_key = "did:key:zDnaeXduWbJ1b1Kgjf3uCdCpMDF1LEDizUiyxAxGwerou3Nh2"; 228 229 let private_key_data = identify_key(private_key); 230 assert!(matches!(private_key_data, Ok((KeyType::P256Private, _)))); 231 let private_key_data = private_key_data.unwrap(); 232 233 let public_key_data = identify_key(public_key); 234 assert!(matches!(public_key_data, Ok((KeyType::P256Public, _)))); 235 let public_key_data = public_key_data.unwrap(); 236 237 let content = "hello world".as_bytes(); 238 239 let signature = sign(&private_key_data, content); 240 assert!(signature.is_ok()); 241 let signature = signature.unwrap(); 242 243 { 244 let validation = validate(&public_key_data, &signature, content); 245 assert!(validation.is_ok()); 246 } 247 { 248 let validation = validate(&private_key_data, &signature, content); 249 assert!(validation.is_ok()); 250 } 251 Ok(()) 252 } 253 254 #[test] 255 fn test_sign_k256() -> Result<()> { 256 let private_key = "did:key:z3vLY4nbXy2rV4Qr65gUtfnSF3A8Be7gmYzUiCX6eo2PR1Rt"; 257 let public_key = "did:key:zQ3shNzMp4oaaQ1gQRzCxMGXFrSW3NEM1M9T6KCY9eA7HhyEA"; 258 259 let private_key_data = identify_key(private_key); 260 assert!(matches!(private_key_data, Ok((KeyType::K256Private, _)))); 261 let private_key_data = private_key_data.unwrap(); 262 263 let public_key_data = identify_key(public_key); 264 assert!(matches!(public_key_data, Ok((KeyType::K256Public, _)))); 265 let public_key_data = public_key_data.unwrap(); 266 267 let content = "hello world".as_bytes(); 268 269 let signature = sign(&private_key_data, content); 270 assert!(signature.is_ok()); 271 let signature = signature.unwrap(); 272 273 { 274 let validation = validate(&public_key_data, &signature, content); 275 assert!(validation.is_ok()); 276 } 277 { 278 let validation = validate(&private_key_data, &signature, content); 279 assert!(validation.is_ok()); 280 } 281 Ok(()) 282 } 283}