Something went wrong. Try again.
Rust and WASM did-method-plc tools and structures
Something went wrong. Try again.
7.2 kB · 230 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231//! # atproto-plc//!//! Rust implementation of did:plc with WASM support for ATProto.//!//! ## Features//!//! - ✅ Validate did:plc identifiers//! - ✅ Parse and validate DID documents//! - ✅ Create new did:plc identities//! - ✅ Validate operation chains//! - ✅ Native Rust and WASM support//! - ✅ Recovery mechanism with 72-hour window//!//! ## Quick Start//!//! ### Rust//!//! ```rust//! use atproto_plc::{Did, DidBuilder, SigningKey, ServiceEndpoint};//!//! // Validate a DID//! let did = Did::parse("did:plc:ewvi7nxzyoun6zhxrhs64oiz")?;//!//! // Create a new DID//! let rotation_key = SigningKey::generate_p256();//! let signing_key = SigningKey::generate_k256();//!//! let (did, operation, keys) = DidBuilder::new()//! .add_rotation_key(rotation_key)//! .add_verification_method("atproto".into(), signing_key)//! .add_also_known_as("at://alice.example.com".into())//! .add_service(//! "atproto_pds".into(),//! ServiceEndpoint::new(//! "AtprotoPersonalDataServer".into(),//! "https://pds.example.com".into(),//! ),//! )//! .build()?;//!//! println!("Created DID: {}", did);//! # Ok::<(), atproto_plc::PlcError>(())//! ```//!//! ## Specification//!//! This library implements the did:plc specification as defined at://! <https://web.plc.directory/spec/v0.1/did-plc>//!//! ### DID Format//!//! A did:plc identifier consists of://! - Prefix: "did:plc:"//! - Identifier: 24 lowercase base32 characters (alphabet: abcdefghijklmnopqrstuvwxyz234567)//!//! Example: `did:plc:ewvi7nxzyoun6zhxrhs64oiz`//!//! ### Key Points//!//! - **Rotation Keys**: 1-5 keys used to sign operations and recover control//! - **Verification Methods**: Up to 10 keys for authentication and signing//! - **Recovery Window**: 72 hours to recover control with higher-priority rotation keys//! - **Operation Size**: Maximum 7500 bytes per operation (DAG-CBOR encoded)//!//! ## Security Considerations//!//! ### Key Management//!//! - Private keys are zeroized from memory when dropped//! - Never compare ECDSA signatures directly - they are non-deterministic//! - Always use cryptographic verification functions//!//! ### Operation Signing//!//! - Operations are signed using DAG-CBOR encoding//! - Signatures use base64url encoding without padding//! - Both P-256 and secp256k1 curves are supported//!//! ## License//!//! Licensed under either of://!//! - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)//! - MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)//!//! at your option.
#![warn(missing_docs)]
// Core modulespub mod builder;pub mod crypto;pub mod did;pub mod document;pub mod encoding;pub mod error;pub mod operations;pub mod validation;
// WASM bindings (only compiled for wasm32 target with "wasm" feature)#[cfg(all(target_arch = "wasm32", feature = "wasm"))]pub mod wasm;
// Re-exports for conveniencepub use builder::{BuilderKeys, DidBuilder};pub use crypto::{SigningKey, VerifyingKey};pub use did::Did;pub use document::{DidDocument, PlcState, Service, ServiceEndpoint, VerificationMethod};pub use error::{PlcError, Result};pub use operations::{Operation, UnsignedOperation};pub use validation::OperationChainValidator;
// Re-export WASM types when targeting wasm32 with "wasm" feature#[cfg(all(target_arch = "wasm32", feature = "wasm"))]pub use wasm::{ WasmDid, WasmDidBuilder, WasmDidDocument, WasmOperation, WasmServiceEndpoint, WasmSigningKey, WasmVerifyingKey,};
/// Library versionpub const VERSION: &str = env!("CARGO_PKG_VERSION");
/// Library namepub const NAME: &str = env!("CARGO_PKG_NAME");
/// Get library informationpub fn library_info() -> String { format!("{} v{}", NAME, VERSION)}
#[cfg(test)]mod tests { use super::*;
#[test] fn test_library_info() { let info = library_info(); assert!(info.contains("atproto-plc")); assert!(info.contains("0.2.0")); }
#[test] fn test_full_workflow() { // Create a new DID let rotation_key = SigningKey::generate_p256(); let signing_key = SigningKey::generate_k256();
let (did, operation, keys) = DidBuilder::new() .add_rotation_key(rotation_key) .add_verification_method("atproto".into(), signing_key) .add_also_known_as("at://alice.example.com".into()) .add_service( "atproto_pds".into(), ServiceEndpoint::new( "AtprotoPersonalDataServer".into(), "https://pds.example.com".into(), ), ) .build() .unwrap();
// Verify the DID format assert!(did.as_str().starts_with("did:plc:")); assert_eq!(did.identifier().len(), 24);
// Verify the operation assert!(operation.is_genesis()); assert_eq!(operation.prev(), None);
// Verify we got the keys back assert_eq!(keys.rotation_keys.len(), 1); assert_eq!(keys.verification_methods.len(), 1);
// Validate the operation chain let state = OperationChainValidator::validate_chain(&[operation]).unwrap(); assert_eq!(state.rotation_keys.len(), 1); assert_eq!(state.verification_methods.len(), 1); assert_eq!(state.also_known_as.len(), 1); assert_eq!(state.services.len(), 1);
// Convert to DID document let doc = state.to_did_document(&did); assert_eq!(doc.id, did); assert!(!doc.verification_method.is_empty()); assert!(!doc.service.is_empty()); }
#[test] fn test_did_parsing() { // Valid DID let did = Did::parse("did:plc:ewvi7nxzyoun6zhxrhs64oiz").unwrap(); assert_eq!(did.identifier(), "ewvi7nxzyoun6zhxrhs64oiz");
// Invalid DIDs assert!(Did::parse("did:web:example.com").is_err()); assert!(Did::parse("did:plc:tooshort").is_err()); assert!(Did::parse("did:plc:UPPERCASE234567890123").is_err()); assert!(Did::parse("did:plc:0189abcd2345678901234567").is_err()); }
#[test] fn test_crypto_roundtrip() { let key = SigningKey::generate_p256(); let data = b"hello world";
// Sign let signature = key.sign(data).unwrap();
// Verify with correct key let verifying_key = key.verifying_key(); assert!(verifying_key.verify(data, &signature).is_ok());
// Verify with wrong key should fail let wrong_key = SigningKey::generate_p256(); let wrong_verifying_key = wrong_key.verifying_key(); assert!(wrong_verifying_key.verify(data, &signature).is_err()); }
#[test] fn test_did_key_roundtrip() { let key = SigningKey::generate_p256(); let did_key = key.to_did_key();
assert!(did_key.starts_with("did:key:z"));
// Parse back let verifying_key = VerifyingKey::from_did_key(&did_key).unwrap(); assert_eq!(verifying_key, key.verifying_key()); }}