Something went wrong. Try again.
Rust and WASM did-method-plc tools and structures
Something went wrong. Try again.
10 kB · 310 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311//! Cryptographic operations for signing and verification
use crate::encoding::{base64url_decode, base64url_encode};use crate::error::{PlcError, Result};use k256::ecdsa::{ signature::Signer as K256Signer, signature::Verifier as K256Verifier, Signature as K256Signature, SigningKey as K256SigningKey, VerifyingKey as K256VerifyingKey,};use p256::ecdsa::{ Signature as P256Signature, SigningKey as P256SigningKey, VerifyingKey as P256VerifyingKey,};use zeroize::Zeroizing;
/// Multicodec prefix for secp256k1 public keyconst SECP256K1_MULTICODEC: &[u8] = &[0xe7, 0x01];
/// Multicodec prefix for P-256 public keyconst P256_MULTICODEC: &[u8] = &[0x80, 0x24];
/// Multibase prefix for base58btc encodingconst MULTIBASE_BASE58BTC: u8 = b'z';
/// A signing key that can be either P-256 or secp256k1#[derive(Clone)]pub enum SigningKey { /// NIST P-256 signing key P256(P256SigningKey), /// secp256k1 signing key K256(K256SigningKey),}
impl SigningKey { /// Generate a new P-256 key pair /// /// # Examples /// /// ``` /// use atproto_plc::crypto::SigningKey; /// /// let key = SigningKey::generate_p256(); /// ``` pub fn generate_p256() -> Self { let key = P256SigningKey::random(&mut rand::thread_rng()); SigningKey::P256(key) }
/// Generate a new secp256k1 key pair /// /// # Examples /// /// ``` /// use atproto_plc::crypto::SigningKey; /// /// let key = SigningKey::generate_k256(); /// ``` pub fn generate_k256() -> Self { let key = K256SigningKey::random(&mut rand::thread_rng()); SigningKey::K256(key) }
/// Convert this signing key to a did:key string /// /// The did:key format uses multibase (base58btc) and multicodec encoding /// /// # Examples /// /// ``` /// use atproto_plc::crypto::SigningKey; /// /// let key = SigningKey::generate_p256(); /// let did_key = key.to_did_key(); /// assert!(did_key.starts_with("did:key:")); /// ``` pub fn to_did_key(&self) -> String { let verifying_key = self.verifying_key(); verifying_key.to_did_key() }
/// Get the verifying key (public key) for this signing key pub fn verifying_key(&self) -> VerifyingKey { match self { SigningKey::P256(key) => VerifyingKey::P256(*key.verifying_key()), SigningKey::K256(key) => VerifyingKey::K256(*key.verifying_key()), } }
/// Sign data with this key /// /// # Errors /// /// Returns `PlcError::CryptoError` if signing fails pub fn sign(&self, data: &[u8]) -> Result<Vec<u8>> { match self { SigningKey::P256(key) => { let signature: P256Signature = key.sign(data); Ok(signature.to_vec()) } SigningKey::K256(key) => { let signature: K256Signature = key.sign(data); Ok(signature.to_vec()) } } }
/// Sign data and return base64url-encoded signature pub fn sign_base64url(&self, data: &[u8]) -> Result<String> { let signature = self.sign(data)?; Ok(base64url_encode(&signature)) }}
impl Drop for SigningKey { fn drop(&mut self) { // Zeroize the key material when dropped match self { SigningKey::P256(key) => { let bytes = Zeroizing::new(key.to_bytes()); drop(bytes); } SigningKey::K256(key) => { let bytes = Zeroizing::new(key.to_bytes()); drop(bytes); } } }}
/// A verifying key (public key) that can be either P-256 or secp256k1#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum VerifyingKey { /// NIST P-256 verifying key P256(P256VerifyingKey), /// secp256k1 verifying key K256(K256VerifyingKey),}
impl VerifyingKey { /// Parse a verifying key from a did:key string /// /// # Errors /// /// Returns `PlcError::InvalidDidKey` if the string is not a valid did:key pub fn from_did_key(did_key: &str) -> Result<Self> { if !did_key.starts_with("did:key:") { return Err(PlcError::InvalidDidKey( "DID key must start with 'did:key:'".to_string(), )); }
let multibase_str = &did_key[8..]; // Skip "did:key:"
// Decode multibase (base58btc) if !multibase_str.starts_with(char::from(MULTIBASE_BASE58BTC)) { return Err(PlcError::InvalidDidKey( "Only base58btc multibase encoding is supported".to_string(), )); }
let base58_str = &multibase_str[1..]; // Skip 'z' prefix let decoded = bs58::decode(base58_str) .into_vec() .map_err(|e| PlcError::InvalidDidKey(format!("Base58 decode error: {}", e)))?;
// Check multicodec prefix and extract public key bytes if decoded.starts_with(SECP256K1_MULTICODEC) { let key_bytes = &decoded[SECP256K1_MULTICODEC.len()..]; let verifying_key = K256VerifyingKey::from_sec1_bytes(key_bytes) .map_err(|e| PlcError::InvalidDidKey(format!("Invalid secp256k1 key: {}", e)))?; Ok(VerifyingKey::K256(verifying_key)) } else if decoded.starts_with(P256_MULTICODEC) { let key_bytes = &decoded[P256_MULTICODEC.len()..]; let verifying_key = P256VerifyingKey::from_sec1_bytes(key_bytes) .map_err(|e| PlcError::InvalidDidKey(format!("Invalid P-256 key: {}", e)))?; Ok(VerifyingKey::P256(verifying_key)) } else { Err(PlcError::UnsupportedKeyType(format!( "Unknown multicodec prefix: {:?}", &decoded[..2.min(decoded.len())] ))) } }
/// Convert this verifying key to a did:key string pub fn to_did_key(&self) -> String { let (multicodec, key_bytes) = match self { VerifyingKey::P256(key) => (P256_MULTICODEC, key.to_sec1_bytes().to_vec()), VerifyingKey::K256(key) => (SECP256K1_MULTICODEC, key.to_sec1_bytes().to_vec()), };
// Combine multicodec prefix and key bytes let mut combined = Vec::with_capacity(multicodec.len() + key_bytes.len()); combined.extend_from_slice(multicodec); combined.extend_from_slice(&key_bytes);
// Encode with multibase (base58btc) let encoded = format!("{}{}", MULTIBASE_BASE58BTC as char, bs58::encode(combined).into_string());
format!("did:key:{}", encoded) }
/// Verify a signature using this key /// /// # Errors /// /// Returns `PlcError::SignatureVerificationFailed` if verification fails pub fn verify(&self, data: &[u8], signature: &[u8]) -> Result<()> { match self { VerifyingKey::P256(key) => { let sig = P256Signature::from_slice(signature) .map_err(|e| PlcError::CryptoError(format!("Invalid signature format: {}", e)))?; key.verify(data, &sig) .map_err(|_| PlcError::SignatureVerificationFailed)?; } VerifyingKey::K256(key) => { let sig = K256Signature::from_slice(signature) .map_err(|e| PlcError::CryptoError(format!("Invalid signature format: {}", e)))?; key.verify(data, &sig) .map_err(|_| PlcError::SignatureVerificationFailed)?; } } Ok(()) }
/// Verify a base64url-encoded signature pub fn verify_base64url(&self, data: &[u8], signature_b64: &str) -> Result<()> { let signature = base64url_decode(signature_b64)?; self.verify(data, &signature) }}
#[cfg(test)]mod tests { use super::*;
#[test] fn test_p256_keygen_and_sign() { let key = SigningKey::generate_p256(); let data = b"hello world"; let signature = key.sign(data).unwrap(); assert!(!signature.is_empty()); }
#[test] fn test_k256_keygen_and_sign() { let key = SigningKey::generate_k256(); let data = b"hello world"; let signature = key.sign(data).unwrap(); assert!(!signature.is_empty()); }
#[test] fn test_p256_sign_verify() { let key = SigningKey::generate_p256(); let data = b"hello world"; let signature = key.sign(data).unwrap();
let verifying_key = key.verifying_key(); assert!(verifying_key.verify(data, &signature).is_ok());
// Wrong data should fail let wrong_data = b"goodbye world"; assert!(verifying_key.verify(wrong_data, &signature).is_err()); }
#[test] fn test_k256_sign_verify() { let key = SigningKey::generate_k256(); let data = b"hello world"; let signature = key.sign(data).unwrap();
let verifying_key = key.verifying_key(); assert!(verifying_key.verify(data, &signature).is_ok()); }
#[test] fn test_did_key_roundtrip_p256() { let key = SigningKey::generate_p256(); let did_key = key.to_did_key(); assert!(did_key.starts_with("did:key:z"));
let parsed = VerifyingKey::from_did_key(&did_key).unwrap(); assert_eq!(parsed, key.verifying_key()); }
#[test] fn test_did_key_roundtrip_k256() { let key = SigningKey::generate_k256(); let did_key = key.to_did_key(); assert!(did_key.starts_with("did:key:z"));
let parsed = VerifyingKey::from_did_key(&did_key).unwrap(); assert_eq!(parsed, key.verifying_key()); }
#[test] fn test_base64url_sign_verify() { let key = SigningKey::generate_p256(); let data = b"hello world"; let signature_b64 = key.sign_base64url(data).unwrap();
let verifying_key = key.verifying_key(); assert!(verifying_key.verify_base64url(data, &signature_b64).is_ok()); }
#[test] fn test_invalid_did_key() { assert!(VerifyingKey::from_did_key("invalid").is_err()); assert!(VerifyingKey::from_did_key("did:web:example.com").is_err()); }}