Skip to main content

oxirs_did/
lib.rs

1//! # OxiRS DID
2//!
3//! [![Version](https://img.shields.io/badge/version-0.4.1-blue)](https://github.com/cool-japan/oxirs/releases)
4//!
5//! **Status**: Production Release (v0.4.1)
6//!
7//! W3C Decentralized Identifiers (DID) and Verifiable Credentials (VC) implementation
8//! for OxiRS, enabling signed RDF graphs and trust layer for data sovereignty.
9//!
10//! ## Features
11//!
12//! - **DID Methods**: did:key (Ed25519), did:web (HTTP-based)
13//! - **Verifiable Credentials**: W3C VC Data Model 2.0
14//! - **Signed Graphs**: RDF Dataset Canonicalization + Ed25519 signatures
15//! - **Key Management**: Ed25519/X25519/P-256 key lifecycle with real keypair
16//!   generation; a pluggable [`kms::KmsBackend`] trait for external HSM/cloud
17//!   KMS integration. NOTE: no cloud KMS SDK backend ships with this crate; the
18//!   only bundled backends are INSECURE test mocks gated behind the non-default
19//!   `insecure-mock-kms` feature (see the `kms` module security notice).
20//!
21//! ## Example
22//!
23//! ```rust,ignore
24//! use oxirs_did::{Did, DidResolver, VerifiableCredential, CredentialIssuer};
25//!
26//! // Create DID from key
27//! let did = Did::new_key(&public_key)?;
28//!
29//! // Issue credential
30//! let issuer = CredentialIssuer::new(keystore);
31//! let vc = issuer.issue(subject, types, &issuer_did).await?;
32//!
33//! // Verify credential
34//! let verifier = CredentialVerifier::new(resolver);
35//! let result = verifier.verify(&vc).await?;
36//! ```
37
38pub mod did;
39pub mod did_web;
40pub mod key_management;
41pub mod kms;
42pub mod proof;
43pub mod rdf_integration;
44pub mod revocation;
45#[cfg(feature = "bbs-plus")]
46pub mod signatures;
47pub mod signed_graph;
48pub mod url;
49pub mod vc;
50#[cfg(feature = "zkp")]
51pub mod zkp;
52
53// v1.1.0 DID document versioning
54pub mod document_versioning;
55
56// v1.1.0: Verifiable Credential exchange protocols (VP creation, verification, JWT-like encoding)
57pub mod credential_exchange;
58
59// v1.1.0 round 5: DH/ECDH key agreement for DID-based communication
60pub mod key_agreement;
61
62// v1.1.0 round 6: W3C Verifiable Presentation builder
63pub mod presentation_builder;
64
65// v1.1.0 round 7: Verifiable Credential structural verification (W3C VC Data Model)
66pub mod vc_verifier;
67
68// v1.1.0 round 13: VP construction, credential selection, proof stubs, and selective disclosure
69pub mod vc_presenter;
70
71// v1.1.0 round 14: DID trust chain validation (leaf→root certification chain)
72pub mod trust_chain;
73
74// v1.1.0 round 15: DID authentication method management and challenge-response
75pub mod authentication;
76
77// v1.1.0 round 16: Verifiable Presentation request/response handling and validation
78pub mod presentation_request;
79
80// v1.1.0 round 11: In-memory DID document resolver with registration, deactivation and service management
81pub mod did_resolver;
82
83// v1.1.0 round 12: DID identity registry with resolution, update, deactivation, and method lookup
84pub mod identity_registry;
85
86// v1.1.0 round 13: W3C Verifiable Credential schema validation
87pub mod credential_schema;
88
89// v1.1.0 round 12: DID key lifecycle management (generation, rotation, status, purposes)
90pub mod key_manager;
91
92// v1.1.0 round 11: Linked Data Proof purpose validation (authentication, assertion, key agreement, capability)
93pub mod proof_purpose;
94pub(crate) mod proof_purpose_registry;
95#[cfg(test)]
96mod proof_purpose_tests;
97pub mod proof_purpose_types;
98pub(crate) mod proof_purpose_verifier;
99
100// DID-based access control (ACL) engine
101pub mod access_control;
102
103// In-memory W3C Verifiable Credential store with revocation tracking
104pub mod credential_store;
105
106// HKDF (RFC 5869) / PBKDF2 (RFC 8018) key derivation over HMAC-SHA-2
107pub mod key_derivation;
108
109use chrono::{DateTime, Utc};
110use serde::{Deserialize, Serialize};
111use thiserror::Error;
112
113// Re-exports
114#[cfg(feature = "did-ethr")]
115pub use did::methods::{DidEthr, DidEthrMethod, EthNetwork};
116#[cfg(feature = "did-ion")]
117pub use did::methods::{
118    DidIon, DidIonMethod, IonCreateOperation, IonDocument, IonKeyDescriptor, IonKeyPurpose,
119    IonOperationType, IonService,
120};
121pub use did::{ChainNamespace, Did, DidDocument, DidPkh, DidPkhMethod, DidResolver};
122pub use key_management::{
123    generate_rotation_key, KeyExpiry, KeyRotation, KeyRotationManager, KeyRotationReason,
124    KeyRotationRecord, KeyRotationRegistry, Keystore, LifecycleKeyRotationRecord,
125    VerificationKey as ManagedVerificationKey,
126};
127pub use kms::{
128    audit::{AuditEvent, AuditEventKind, AuditLog},
129    pkcs11::{KeyHandle, Pkcs11Mechanism, Pkcs11Slot},
130    KeyUsage, KmsAlgorithm, KmsBackend, KmsDidSigner, KmsKeyMetadata,
131};
132// NOTE: the bundled insecure mock KMS backends (`InsecureMockAwsKms`, …,
133// `create_insecure_mock_kms`) are intentionally NOT re-exported at the crate
134// root and are gated behind the non-default `insecure-mock-kms` feature. Reach
135// them via `oxirs_did::kms::` only. See the `kms` module security notice.
136pub use proof::{
137    jws::{
138        attach_jws_proof, extract_jws_proof, sign_document, verify_document, CompactJws,
139        JsonWebSignature2020, JwsAlgorithm, JwsHeader, JwsSigner, JwsVerifier,
140    },
141    Proof, ProofPurpose, ProofType,
142};
143pub use revocation::{
144    BloomFilter, CredentialStatus, RevocationEntry, RevocationList2020, RevocationRegistry,
145    RevocationRegistry2020, RevocationStatus, StatusList2021, StatusList2021Inner,
146    StatusListCredential, StatusPurpose, MIN_LIST_SIZE,
147};
148#[cfg(feature = "bbs-plus")]
149pub use signatures::{
150    BbsKeyPair, BbsPlusSignature, BbsProof, BbsProofRequest, EcdsaJwsSigner, EcdsaJwsVerifier,
151    Ed25519JwsSigner, Ed25519JwsVerifier, Es256Signer, Es256Verifier,
152    JwsAlgorithm as SignaturesJwsAlgorithm, JwsHeader as SignaturesJwsHeader, JwsPayload,
153    JwsSignature, JwsSignatureHeader, JwsSigner as SignaturesJwsSigner, JwsSignerTrait,
154    JwsVerifier as SignaturesJwsVerifier, JwsVerifierTrait, MockJwsSigner, MockJwsVerifier,
155    P256KeyPair, Rs256Signer, Rs256Verifier, RsaKeyPair,
156};
157pub use signed_graph::SignedGraph;
158pub use url::{DereferencedResource, DidDereferencer, DidUrl};
159pub use vc::{
160    decode_jwt_vc, encode_vc_as_jwt, CredentialIssuer, CredentialSubject, CredentialVerifier,
161    JwtVc, JwtVcHeader, JwtVcPayload, VerifiableCredential, VerifiablePresentation,
162};
163#[cfg(feature = "zkp")]
164pub use zkp::{
165    prove_selective, verify_selective, AttributeCommitment, CredentialAttribute,
166    DisclosurePresentation, PedersenParams, PedersenSelectiveDisclosureProof, SchnorrProof,
167    SelectiveDisclosureCredential, SelectiveDisclosureProof, SelectiveDisclosureRequest,
168    ZkpProofRequest,
169};
170
171/// DID error types
172#[derive(Error, Debug)]
173pub enum DidError {
174    #[error("Invalid DID format: {0}")]
175    InvalidFormat(String),
176
177    #[error("Unsupported DID method: {0}")]
178    UnsupportedMethod(String),
179
180    #[error("Resolution failed: {0}")]
181    ResolutionFailed(String),
182
183    #[error("Verification failed: {0}")]
184    VerificationFailed(String),
185
186    #[error("Signing failed: {0}")]
187    SigningFailed(String),
188
189    #[error("Key not found: {0}")]
190    KeyNotFound(String),
191
192    #[error("Invalid key: {0}")]
193    InvalidKey(String),
194
195    #[error("Credential expired")]
196    CredentialExpired,
197
198    #[error("Invalid proof: {0}")]
199    InvalidProof(String),
200
201    #[error("Canonicalization failed: {0}")]
202    CanonicalizationFailed(String),
203
204    #[error("Serialization error: {0}")]
205    SerializationError(String),
206
207    #[error("Network error: {0}")]
208    NetworkError(String),
209
210    #[error("Internal error: {0}")]
211    InternalError(String),
212
213    #[error("Invalid credential: {0}")]
214    InvalidCredential(String),
215}
216
217pub type DidResult<T> = Result<T, DidError>;
218
219/// Verification method in DID Document
220#[derive(Debug, Clone, Serialize, Deserialize)]
221#[serde(rename_all = "camelCase")]
222pub struct VerificationMethod {
223    /// Verification method ID
224    pub id: String,
225    /// Type of verification method
226    #[serde(rename = "type")]
227    pub method_type: String,
228    /// Controller DID
229    pub controller: String,
230    /// Public key in multibase format
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub public_key_multibase: Option<String>,
233    /// Public key in JWK format
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub public_key_jwk: Option<serde_json::Value>,
236    /// Blockchain account ID (CAIP-10 format, for did:pkh)
237    #[serde(skip_serializing_if = "Option::is_none")]
238    pub blockchain_account_id: Option<String>,
239}
240
241impl VerificationMethod {
242    /// Create Ed25519 verification method
243    pub fn ed25519(id: &str, controller: &str, public_key: &[u8]) -> Self {
244        // Multibase encode with base58btc prefix 'z'
245        let multibase = format!("z{}", bs58::encode(public_key).into_string());
246
247        Self {
248            id: id.to_string(),
249            method_type: "Ed25519VerificationKey2020".to_string(),
250            controller: controller.to_string(),
251            public_key_multibase: Some(multibase),
252            public_key_jwk: None,
253            blockchain_account_id: None,
254        }
255    }
256
257    /// Create a blockchain account verification method (for did:pkh)
258    ///
259    /// Uses CAIP-10 blockchain account ID format instead of a public key.
260    pub fn blockchain(
261        id: &str,
262        controller: &str,
263        method_type: &str,
264        blockchain_account_id: &str,
265    ) -> Self {
266        Self {
267            id: id.to_string(),
268            method_type: method_type.to_string(),
269            controller: controller.to_string(),
270            public_key_multibase: None,
271            public_key_jwk: None,
272            blockchain_account_id: Some(blockchain_account_id.to_string()),
273        }
274    }
275
276    /// Create a JWK verification method
277    pub fn jwk(id: &str, controller: &str, method_type: &str, jwk: serde_json::Value) -> Self {
278        Self {
279            id: id.to_string(),
280            method_type: method_type.to_string(),
281            controller: controller.to_string(),
282            public_key_multibase: None,
283            public_key_jwk: Some(jwk),
284            blockchain_account_id: None,
285        }
286    }
287
288    /// Get public key bytes
289    pub fn get_public_key_bytes(&self) -> DidResult<Vec<u8>> {
290        if let Some(ref multibase) = self.public_key_multibase {
291            // Remove multibase prefix and decode
292            if let Some(stripped) = multibase.strip_prefix('z') {
293                bs58::decode(stripped)
294                    .into_vec()
295                    .map_err(|e| DidError::InvalidKey(e.to_string()))
296            } else {
297                Err(DidError::InvalidKey("Unknown multibase prefix".to_string()))
298            }
299        } else if self.blockchain_account_id.is_some() {
300            Err(DidError::InvalidKey(
301                "Blockchain account verification methods do not expose raw public keys".to_string(),
302            ))
303        } else {
304            Err(DidError::InvalidKey("No public key available".to_string()))
305        }
306    }
307}
308
309/// Service endpoint in DID Document
310#[derive(Debug, Clone, Serialize, Deserialize)]
311#[serde(rename_all = "camelCase")]
312pub struct Service {
313    /// Service ID
314    pub id: String,
315    /// Service type
316    #[serde(rename = "type")]
317    pub service_type: String,
318    /// Service endpoint URL
319    pub service_endpoint: String,
320}
321
322/// Verification result
323#[derive(Debug, Clone, Serialize, Deserialize)]
324pub struct VerificationResult {
325    /// Whether verification succeeded
326    pub valid: bool,
327    /// Verified issuer DID
328    pub issuer: Option<String>,
329    /// Verification timestamp
330    pub verified_at: DateTime<Utc>,
331    /// Error message if verification failed
332    pub error: Option<String>,
333    /// Checks performed
334    pub checks: Vec<VerificationCheck>,
335}
336
337/// Individual verification check
338#[derive(Debug, Clone, Serialize, Deserialize)]
339pub struct VerificationCheck {
340    /// Check name
341    pub name: String,
342    /// Whether check passed
343    pub passed: bool,
344    /// Details
345    pub details: Option<String>,
346}
347
348impl VerificationResult {
349    pub fn success(issuer: &str) -> Self {
350        Self {
351            valid: true,
352            issuer: Some(issuer.to_string()),
353            verified_at: Utc::now(),
354            error: None,
355            checks: vec![],
356        }
357    }
358
359    pub fn failure(error: &str) -> Self {
360        Self {
361            valid: false,
362            issuer: None,
363            verified_at: Utc::now(),
364            error: Some(error.to_string()),
365            checks: vec![],
366        }
367    }
368
369    pub fn with_check(mut self, name: &str, passed: bool, details: Option<&str>) -> Self {
370        self.checks.push(VerificationCheck {
371            name: name.to_string(),
372            passed,
373            details: details.map(String::from),
374        });
375        self
376    }
377}
378
379#[cfg(test)]
380mod tests {
381    use super::*;
382
383    #[test]
384    fn test_verification_method_ed25519() {
385        let public_key = vec![
386            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
387            25, 26, 27, 28, 29, 30, 31, 32,
388        ];
389
390        let vm = VerificationMethod::ed25519("did:key:z123#key-1", "did:key:z123", &public_key);
391
392        assert_eq!(vm.method_type, "Ed25519VerificationKey2020");
393        assert!(vm.public_key_multibase.is_some());
394
395        let recovered = vm.get_public_key_bytes().unwrap();
396        assert_eq!(recovered, public_key);
397    }
398
399    #[test]
400    fn test_verification_result() {
401        let result = VerificationResult::success("did:key:z123")
402            .with_check("signature", true, None)
403            .with_check("expiration", true, Some("Not expired"));
404
405        assert!(result.valid);
406        assert_eq!(result.checks.len(), 2);
407    }
408}