oxirs-did 0.4.1

W3C DID and Verifiable Credentials implementation with Signed RDF Graphs for OxiRS
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
//! # OxiRS DID
//!
//! [![Version](https://img.shields.io/badge/version-0.4.1-blue)](https://github.com/cool-japan/oxirs/releases)
//!
//! **Status**: Production Release (v0.4.1)
//!
//! W3C Decentralized Identifiers (DID) and Verifiable Credentials (VC) implementation
//! for OxiRS, enabling signed RDF graphs and trust layer for data sovereignty.
//!
//! ## Features
//!
//! - **DID Methods**: did:key (Ed25519), did:web (HTTP-based)
//! - **Verifiable Credentials**: W3C VC Data Model 2.0
//! - **Signed Graphs**: RDF Dataset Canonicalization + Ed25519 signatures
//! - **Key Management**: Ed25519/X25519/P-256 key lifecycle with real keypair
//!   generation; a pluggable [`kms::KmsBackend`] trait for external HSM/cloud
//!   KMS integration. NOTE: no cloud KMS SDK backend ships with this crate; the
//!   only bundled backends are INSECURE test mocks gated behind the non-default
//!   `insecure-mock-kms` feature (see the `kms` module security notice).
//!
//! ## Example
//!
//! ```rust,ignore
//! use oxirs_did::{Did, DidResolver, VerifiableCredential, CredentialIssuer};
//!
//! // Create DID from key
//! let did = Did::new_key(&public_key)?;
//!
//! // Issue credential
//! let issuer = CredentialIssuer::new(keystore);
//! let vc = issuer.issue(subject, types, &issuer_did).await?;
//!
//! // Verify credential
//! let verifier = CredentialVerifier::new(resolver);
//! let result = verifier.verify(&vc).await?;
//! ```

pub mod did;
pub mod did_web;
pub mod key_management;
pub mod kms;
pub mod proof;
pub mod rdf_integration;
pub mod revocation;
#[cfg(feature = "bbs-plus")]
pub mod signatures;
pub mod signed_graph;
pub mod url;
pub mod vc;
#[cfg(feature = "zkp")]
pub mod zkp;

// v1.1.0 DID document versioning
pub mod document_versioning;

// v1.1.0: Verifiable Credential exchange protocols (VP creation, verification, JWT-like encoding)
pub mod credential_exchange;

// v1.1.0 round 5: DH/ECDH key agreement for DID-based communication
pub mod key_agreement;

// v1.1.0 round 6: W3C Verifiable Presentation builder
pub mod presentation_builder;

// v1.1.0 round 7: Verifiable Credential structural verification (W3C VC Data Model)
pub mod vc_verifier;

// v1.1.0 round 13: VP construction, credential selection, proof stubs, and selective disclosure
pub mod vc_presenter;

// v1.1.0 round 14: DID trust chain validation (leaf→root certification chain)
pub mod trust_chain;

// v1.1.0 round 15: DID authentication method management and challenge-response
pub mod authentication;

// v1.1.0 round 16: Verifiable Presentation request/response handling and validation
pub mod presentation_request;

// v1.1.0 round 11: In-memory DID document resolver with registration, deactivation and service management
pub mod did_resolver;

// v1.1.0 round 12: DID identity registry with resolution, update, deactivation, and method lookup
pub mod identity_registry;

// v1.1.0 round 13: W3C Verifiable Credential schema validation
pub mod credential_schema;

// v1.1.0 round 12: DID key lifecycle management (generation, rotation, status, purposes)
pub mod key_manager;

// v1.1.0 round 11: Linked Data Proof purpose validation (authentication, assertion, key agreement, capability)
pub mod proof_purpose;
pub(crate) mod proof_purpose_registry;
#[cfg(test)]
mod proof_purpose_tests;
pub mod proof_purpose_types;
pub(crate) mod proof_purpose_verifier;

// DID-based access control (ACL) engine
pub mod access_control;

// In-memory W3C Verifiable Credential store with revocation tracking
pub mod credential_store;

// HKDF (RFC 5869) / PBKDF2 (RFC 8018) key derivation over HMAC-SHA-2
pub mod key_derivation;

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use thiserror::Error;

// Re-exports
#[cfg(feature = "did-ethr")]
pub use did::methods::{DidEthr, DidEthrMethod, EthNetwork};
#[cfg(feature = "did-ion")]
pub use did::methods::{
    DidIon, DidIonMethod, IonCreateOperation, IonDocument, IonKeyDescriptor, IonKeyPurpose,
    IonOperationType, IonService,
};
pub use did::{ChainNamespace, Did, DidDocument, DidPkh, DidPkhMethod, DidResolver};
pub use key_management::{
    generate_rotation_key, KeyExpiry, KeyRotation, KeyRotationManager, KeyRotationReason,
    KeyRotationRecord, KeyRotationRegistry, Keystore, LifecycleKeyRotationRecord,
    VerificationKey as ManagedVerificationKey,
};
pub use kms::{
    audit::{AuditEvent, AuditEventKind, AuditLog},
    pkcs11::{KeyHandle, Pkcs11Mechanism, Pkcs11Slot},
    KeyUsage, KmsAlgorithm, KmsBackend, KmsDidSigner, KmsKeyMetadata,
};
// NOTE: the bundled insecure mock KMS backends (`InsecureMockAwsKms`, …,
// `create_insecure_mock_kms`) are intentionally NOT re-exported at the crate
// root and are gated behind the non-default `insecure-mock-kms` feature. Reach
// them via `oxirs_did::kms::` only. See the `kms` module security notice.
pub use proof::{
    jws::{
        attach_jws_proof, extract_jws_proof, sign_document, verify_document, CompactJws,
        JsonWebSignature2020, JwsAlgorithm, JwsHeader, JwsSigner, JwsVerifier,
    },
    Proof, ProofPurpose, ProofType,
};
pub use revocation::{
    BloomFilter, CredentialStatus, RevocationEntry, RevocationList2020, RevocationRegistry,
    RevocationRegistry2020, RevocationStatus, StatusList2021, StatusList2021Inner,
    StatusListCredential, StatusPurpose, MIN_LIST_SIZE,
};
#[cfg(feature = "bbs-plus")]
pub use signatures::{
    BbsKeyPair, BbsPlusSignature, BbsProof, BbsProofRequest, EcdsaJwsSigner, EcdsaJwsVerifier,
    Ed25519JwsSigner, Ed25519JwsVerifier, Es256Signer, Es256Verifier,
    JwsAlgorithm as SignaturesJwsAlgorithm, JwsHeader as SignaturesJwsHeader, JwsPayload,
    JwsSignature, JwsSignatureHeader, JwsSigner as SignaturesJwsSigner, JwsSignerTrait,
    JwsVerifier as SignaturesJwsVerifier, JwsVerifierTrait, MockJwsSigner, MockJwsVerifier,
    P256KeyPair, Rs256Signer, Rs256Verifier, RsaKeyPair,
};
pub use signed_graph::SignedGraph;
pub use url::{DereferencedResource, DidDereferencer, DidUrl};
pub use vc::{
    decode_jwt_vc, encode_vc_as_jwt, CredentialIssuer, CredentialSubject, CredentialVerifier,
    JwtVc, JwtVcHeader, JwtVcPayload, VerifiableCredential, VerifiablePresentation,
};
#[cfg(feature = "zkp")]
pub use zkp::{
    prove_selective, verify_selective, AttributeCommitment, CredentialAttribute,
    DisclosurePresentation, PedersenParams, PedersenSelectiveDisclosureProof, SchnorrProof,
    SelectiveDisclosureCredential, SelectiveDisclosureProof, SelectiveDisclosureRequest,
    ZkpProofRequest,
};

/// DID error types
#[derive(Error, Debug)]
pub enum DidError {
    #[error("Invalid DID format: {0}")]
    InvalidFormat(String),

    #[error("Unsupported DID method: {0}")]
    UnsupportedMethod(String),

    #[error("Resolution failed: {0}")]
    ResolutionFailed(String),

    #[error("Verification failed: {0}")]
    VerificationFailed(String),

    #[error("Signing failed: {0}")]
    SigningFailed(String),

    #[error("Key not found: {0}")]
    KeyNotFound(String),

    #[error("Invalid key: {0}")]
    InvalidKey(String),

    #[error("Credential expired")]
    CredentialExpired,

    #[error("Invalid proof: {0}")]
    InvalidProof(String),

    #[error("Canonicalization failed: {0}")]
    CanonicalizationFailed(String),

    #[error("Serialization error: {0}")]
    SerializationError(String),

    #[error("Network error: {0}")]
    NetworkError(String),

    #[error("Internal error: {0}")]
    InternalError(String),

    #[error("Invalid credential: {0}")]
    InvalidCredential(String),
}

pub type DidResult<T> = Result<T, DidError>;

/// Verification method in DID Document
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VerificationMethod {
    /// Verification method ID
    pub id: String,
    /// Type of verification method
    #[serde(rename = "type")]
    pub method_type: String,
    /// Controller DID
    pub controller: String,
    /// Public key in multibase format
    #[serde(skip_serializing_if = "Option::is_none")]
    pub public_key_multibase: Option<String>,
    /// Public key in JWK format
    #[serde(skip_serializing_if = "Option::is_none")]
    pub public_key_jwk: Option<serde_json::Value>,
    /// Blockchain account ID (CAIP-10 format, for did:pkh)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub blockchain_account_id: Option<String>,
}

impl VerificationMethod {
    /// Create Ed25519 verification method
    pub fn ed25519(id: &str, controller: &str, public_key: &[u8]) -> Self {
        // Multibase encode with base58btc prefix 'z'
        let multibase = format!("z{}", bs58::encode(public_key).into_string());

        Self {
            id: id.to_string(),
            method_type: "Ed25519VerificationKey2020".to_string(),
            controller: controller.to_string(),
            public_key_multibase: Some(multibase),
            public_key_jwk: None,
            blockchain_account_id: None,
        }
    }

    /// Create a blockchain account verification method (for did:pkh)
    ///
    /// Uses CAIP-10 blockchain account ID format instead of a public key.
    pub fn blockchain(
        id: &str,
        controller: &str,
        method_type: &str,
        blockchain_account_id: &str,
    ) -> Self {
        Self {
            id: id.to_string(),
            method_type: method_type.to_string(),
            controller: controller.to_string(),
            public_key_multibase: None,
            public_key_jwk: None,
            blockchain_account_id: Some(blockchain_account_id.to_string()),
        }
    }

    /// Create a JWK verification method
    pub fn jwk(id: &str, controller: &str, method_type: &str, jwk: serde_json::Value) -> Self {
        Self {
            id: id.to_string(),
            method_type: method_type.to_string(),
            controller: controller.to_string(),
            public_key_multibase: None,
            public_key_jwk: Some(jwk),
            blockchain_account_id: None,
        }
    }

    /// Get public key bytes
    pub fn get_public_key_bytes(&self) -> DidResult<Vec<u8>> {
        if let Some(ref multibase) = self.public_key_multibase {
            // Remove multibase prefix and decode
            if let Some(stripped) = multibase.strip_prefix('z') {
                bs58::decode(stripped)
                    .into_vec()
                    .map_err(|e| DidError::InvalidKey(e.to_string()))
            } else {
                Err(DidError::InvalidKey("Unknown multibase prefix".to_string()))
            }
        } else if self.blockchain_account_id.is_some() {
            Err(DidError::InvalidKey(
                "Blockchain account verification methods do not expose raw public keys".to_string(),
            ))
        } else {
            Err(DidError::InvalidKey("No public key available".to_string()))
        }
    }
}

/// Service endpoint in DID Document
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Service {
    /// Service ID
    pub id: String,
    /// Service type
    #[serde(rename = "type")]
    pub service_type: String,
    /// Service endpoint URL
    pub service_endpoint: String,
}

/// Verification result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerificationResult {
    /// Whether verification succeeded
    pub valid: bool,
    /// Verified issuer DID
    pub issuer: Option<String>,
    /// Verification timestamp
    pub verified_at: DateTime<Utc>,
    /// Error message if verification failed
    pub error: Option<String>,
    /// Checks performed
    pub checks: Vec<VerificationCheck>,
}

/// Individual verification check
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerificationCheck {
    /// Check name
    pub name: String,
    /// Whether check passed
    pub passed: bool,
    /// Details
    pub details: Option<String>,
}

impl VerificationResult {
    pub fn success(issuer: &str) -> Self {
        Self {
            valid: true,
            issuer: Some(issuer.to_string()),
            verified_at: Utc::now(),
            error: None,
            checks: vec![],
        }
    }

    pub fn failure(error: &str) -> Self {
        Self {
            valid: false,
            issuer: None,
            verified_at: Utc::now(),
            error: Some(error.to_string()),
            checks: vec![],
        }
    }

    pub fn with_check(mut self, name: &str, passed: bool, details: Option<&str>) -> Self {
        self.checks.push(VerificationCheck {
            name: name.to_string(),
            passed,
            details: details.map(String::from),
        });
        self
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_verification_method_ed25519() {
        let public_key = vec![
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
            25, 26, 27, 28, 29, 30, 31, 32,
        ];

        let vm = VerificationMethod::ed25519("did:key:z123#key-1", "did:key:z123", &public_key);

        assert_eq!(vm.method_type, "Ed25519VerificationKey2020");
        assert!(vm.public_key_multibase.is_some());

        let recovered = vm.get_public_key_bytes().unwrap();
        assert_eq!(recovered, public_key);
    }

    #[test]
    fn test_verification_result() {
        let result = VerificationResult::success("did:key:z123")
            .with_check("signature", true, None)
            .with_check("expiration", true, Some("Not expired"));

        assert!(result.valid);
        assert_eq!(result.checks.len(), 2);
    }
}