Skip to main content

oxirs_did/
lib.rs

1//! # OxiRS DID
2//!
3//! [![Version](https://img.shields.io/badge/version-0.3.3-blue)](https://github.com/cool-japan/oxirs/releases)
4//!
5//! **Status**: Production Release (v0.3.3)
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
100use chrono::{DateTime, Utc};
101use serde::{Deserialize, Serialize};
102use thiserror::Error;
103
104// Re-exports
105#[cfg(feature = "did-ethr")]
106pub use did::methods::{DidEthr, DidEthrMethod, EthNetwork};
107#[cfg(feature = "did-ion")]
108pub use did::methods::{
109    DidIon, DidIonMethod, IonCreateOperation, IonDocument, IonKeyDescriptor, IonKeyPurpose,
110    IonOperationType, IonService,
111};
112pub use did::{ChainNamespace, Did, DidDocument, DidPkh, DidPkhMethod, DidResolver};
113pub use key_management::{
114    generate_rotation_key, KeyExpiry, KeyRotation, KeyRotationManager, KeyRotationReason,
115    KeyRotationRecord, KeyRotationRegistry, Keystore, LifecycleKeyRotationRecord,
116    VerificationKey as ManagedVerificationKey,
117};
118pub use kms::{
119    audit::{AuditEvent, AuditEventKind, AuditLog},
120    pkcs11::{KeyHandle, Pkcs11Mechanism, Pkcs11Slot},
121    KeyUsage, KmsAlgorithm, KmsBackend, KmsDidSigner, KmsKeyMetadata,
122};
123// NOTE: the bundled insecure mock KMS backends (`InsecureMockAwsKms`, …,
124// `create_insecure_mock_kms`) are intentionally NOT re-exported at the crate
125// root and are gated behind the non-default `insecure-mock-kms` feature. Reach
126// them via `oxirs_did::kms::` only. See the `kms` module security notice.
127pub use proof::{
128    jws::{
129        attach_jws_proof, extract_jws_proof, sign_document, verify_document, CompactJws,
130        JsonWebSignature2020, JwsAlgorithm, JwsHeader, JwsSigner, JwsVerifier,
131    },
132    Proof, ProofPurpose, ProofType,
133};
134pub use revocation::{
135    BloomFilter, CredentialStatus, RevocationEntry, RevocationList2020, RevocationRegistry,
136    RevocationRegistry2020, RevocationStatus, StatusList2021, StatusList2021Inner,
137    StatusListCredential, StatusPurpose, MIN_LIST_SIZE,
138};
139#[cfg(feature = "bbs-plus")]
140pub use signatures::{
141    BbsKeyPair, BbsPlusSignature, BbsProof, BbsProofRequest, EcdsaJwsSigner, EcdsaJwsVerifier,
142    Ed25519JwsSigner, Ed25519JwsVerifier, Es256Signer, Es256Verifier,
143    JwsAlgorithm as SignaturesJwsAlgorithm, JwsHeader as SignaturesJwsHeader, JwsPayload,
144    JwsSignature, JwsSignatureHeader, JwsSigner as SignaturesJwsSigner, JwsSignerTrait,
145    JwsVerifier as SignaturesJwsVerifier, JwsVerifierTrait, MockJwsSigner, MockJwsVerifier,
146    P256KeyPair, Rs256Signer, Rs256Verifier, RsaKeyPair,
147};
148pub use signed_graph::SignedGraph;
149pub use url::{DereferencedResource, DidDereferencer, DidUrl};
150pub use vc::{
151    decode_jwt_vc, encode_vc_as_jwt, CredentialIssuer, CredentialSubject, CredentialVerifier,
152    JwtVc, JwtVcHeader, JwtVcPayload, VerifiableCredential, VerifiablePresentation,
153};
154#[cfg(feature = "zkp")]
155pub use zkp::{
156    prove_selective, verify_selective, AttributeCommitment, CredentialAttribute,
157    DisclosurePresentation, PedersenParams, PedersenSelectiveDisclosureProof, SchnorrProof,
158    SelectiveDisclosureCredential, SelectiveDisclosureProof, SelectiveDisclosureRequest,
159    ZkpProofRequest,
160};
161
162/// DID error types
163#[derive(Error, Debug)]
164pub enum DidError {
165    #[error("Invalid DID format: {0}")]
166    InvalidFormat(String),
167
168    #[error("Unsupported DID method: {0}")]
169    UnsupportedMethod(String),
170
171    #[error("Resolution failed: {0}")]
172    ResolutionFailed(String),
173
174    #[error("Verification failed: {0}")]
175    VerificationFailed(String),
176
177    #[error("Signing failed: {0}")]
178    SigningFailed(String),
179
180    #[error("Key not found: {0}")]
181    KeyNotFound(String),
182
183    #[error("Invalid key: {0}")]
184    InvalidKey(String),
185
186    #[error("Credential expired")]
187    CredentialExpired,
188
189    #[error("Invalid proof: {0}")]
190    InvalidProof(String),
191
192    #[error("Canonicalization failed: {0}")]
193    CanonicalizationFailed(String),
194
195    #[error("Serialization error: {0}")]
196    SerializationError(String),
197
198    #[error("Network error: {0}")]
199    NetworkError(String),
200
201    #[error("Internal error: {0}")]
202    InternalError(String),
203
204    #[error("Invalid credential: {0}")]
205    InvalidCredential(String),
206}
207
208pub type DidResult<T> = Result<T, DidError>;
209
210/// Verification method in DID Document
211#[derive(Debug, Clone, Serialize, Deserialize)]
212#[serde(rename_all = "camelCase")]
213pub struct VerificationMethod {
214    /// Verification method ID
215    pub id: String,
216    /// Type of verification method
217    #[serde(rename = "type")]
218    pub method_type: String,
219    /// Controller DID
220    pub controller: String,
221    /// Public key in multibase format
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub public_key_multibase: Option<String>,
224    /// Public key in JWK format
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub public_key_jwk: Option<serde_json::Value>,
227    /// Blockchain account ID (CAIP-10 format, for did:pkh)
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub blockchain_account_id: Option<String>,
230}
231
232impl VerificationMethod {
233    /// Create Ed25519 verification method
234    pub fn ed25519(id: &str, controller: &str, public_key: &[u8]) -> Self {
235        // Multibase encode with base58btc prefix 'z'
236        let multibase = format!("z{}", bs58::encode(public_key).into_string());
237
238        Self {
239            id: id.to_string(),
240            method_type: "Ed25519VerificationKey2020".to_string(),
241            controller: controller.to_string(),
242            public_key_multibase: Some(multibase),
243            public_key_jwk: None,
244            blockchain_account_id: None,
245        }
246    }
247
248    /// Create a blockchain account verification method (for did:pkh)
249    ///
250    /// Uses CAIP-10 blockchain account ID format instead of a public key.
251    pub fn blockchain(
252        id: &str,
253        controller: &str,
254        method_type: &str,
255        blockchain_account_id: &str,
256    ) -> Self {
257        Self {
258            id: id.to_string(),
259            method_type: method_type.to_string(),
260            controller: controller.to_string(),
261            public_key_multibase: None,
262            public_key_jwk: None,
263            blockchain_account_id: Some(blockchain_account_id.to_string()),
264        }
265    }
266
267    /// Create a JWK verification method
268    pub fn jwk(id: &str, controller: &str, method_type: &str, jwk: serde_json::Value) -> Self {
269        Self {
270            id: id.to_string(),
271            method_type: method_type.to_string(),
272            controller: controller.to_string(),
273            public_key_multibase: None,
274            public_key_jwk: Some(jwk),
275            blockchain_account_id: None,
276        }
277    }
278
279    /// Get public key bytes
280    pub fn get_public_key_bytes(&self) -> DidResult<Vec<u8>> {
281        if let Some(ref multibase) = self.public_key_multibase {
282            // Remove multibase prefix and decode
283            if let Some(stripped) = multibase.strip_prefix('z') {
284                bs58::decode(stripped)
285                    .into_vec()
286                    .map_err(|e| DidError::InvalidKey(e.to_string()))
287            } else {
288                Err(DidError::InvalidKey("Unknown multibase prefix".to_string()))
289            }
290        } else if self.blockchain_account_id.is_some() {
291            Err(DidError::InvalidKey(
292                "Blockchain account verification methods do not expose raw public keys".to_string(),
293            ))
294        } else {
295            Err(DidError::InvalidKey("No public key available".to_string()))
296        }
297    }
298}
299
300/// Service endpoint in DID Document
301#[derive(Debug, Clone, Serialize, Deserialize)]
302#[serde(rename_all = "camelCase")]
303pub struct Service {
304    /// Service ID
305    pub id: String,
306    /// Service type
307    #[serde(rename = "type")]
308    pub service_type: String,
309    /// Service endpoint URL
310    pub service_endpoint: String,
311}
312
313/// Verification result
314#[derive(Debug, Clone, Serialize, Deserialize)]
315pub struct VerificationResult {
316    /// Whether verification succeeded
317    pub valid: bool,
318    /// Verified issuer DID
319    pub issuer: Option<String>,
320    /// Verification timestamp
321    pub verified_at: DateTime<Utc>,
322    /// Error message if verification failed
323    pub error: Option<String>,
324    /// Checks performed
325    pub checks: Vec<VerificationCheck>,
326}
327
328/// Individual verification check
329#[derive(Debug, Clone, Serialize, Deserialize)]
330pub struct VerificationCheck {
331    /// Check name
332    pub name: String,
333    /// Whether check passed
334    pub passed: bool,
335    /// Details
336    pub details: Option<String>,
337}
338
339impl VerificationResult {
340    pub fn success(issuer: &str) -> Self {
341        Self {
342            valid: true,
343            issuer: Some(issuer.to_string()),
344            verified_at: Utc::now(),
345            error: None,
346            checks: vec![],
347        }
348    }
349
350    pub fn failure(error: &str) -> Self {
351        Self {
352            valid: false,
353            issuer: None,
354            verified_at: Utc::now(),
355            error: Some(error.to_string()),
356            checks: vec![],
357        }
358    }
359
360    pub fn with_check(mut self, name: &str, passed: bool, details: Option<&str>) -> Self {
361        self.checks.push(VerificationCheck {
362            name: name.to_string(),
363            passed,
364            details: details.map(String::from),
365        });
366        self
367    }
368}
369
370#[cfg(test)]
371mod tests {
372    use super::*;
373
374    #[test]
375    fn test_verification_method_ed25519() {
376        let public_key = vec![
377            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
378            25, 26, 27, 28, 29, 30, 31, 32,
379        ];
380
381        let vm = VerificationMethod::ed25519("did:key:z123#key-1", "did:key:z123", &public_key);
382
383        assert_eq!(vm.method_type, "Ed25519VerificationKey2020");
384        assert!(vm.public_key_multibase.is_some());
385
386        let recovered = vm.get_public_key_bytes().unwrap();
387        assert_eq!(recovered, public_key);
388    }
389
390    #[test]
391    fn test_verification_result() {
392        let result = VerificationResult::success("did:key:z123")
393            .with_check("signature", true, None)
394            .with_check("expiration", true, Some("Not expired"));
395
396        assert!(result.valid);
397        assert_eq!(result.checks.len(), 2);
398    }
399}