A library for ATProtocol identities.
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}