did:webvh implementation
A complete implementation of the did:webvh method in Rust. Supports version 1.0 spec.
A helpful implementation site is the webvh DID Method Information site
Change log
Features
- Create a did:webvh LogEntry and DID Document
- Resolve a did:webvh method
- Validate webvh LogEntries to v1.0 specification
- Update webvh DID
- Revoke webvh DID
- Witness webvh DID
- Migration of DID (portability)
- Validate witness information
- DID Query Parameters versionId, versionTime, and versionNumber implemented
- WebVH DID specification version support (v1.0 and pre-v1.0)
- Export WebVH to a did:web document
- Generate did:scid:vh alsoKnownAs alias from did:webvh DIDs
- URL validation rejects IP addresses per spec (domain names required)
- WASM friendly for inclusion in other projects
- WebVH DID Create routines to make it easier to create DIDs programmatically
- Pluggable signing via the
Signertrait — use HSMs, KMS, or any external signing service without exposing secret key material to the library - Structured error types for programmatic error handling (e.g.
NetworkErrorexposesurl,status_code, andmessagefields) - Convenience API:
update_document(),rotate_keys(),deactivate()onDIDWebVHState -
WitnessesBuilderfor ergonomic witness configuration - Cache serialization:
save_state()/load_state()for offline caching -
async_traitre-exported soSignerimplementors don't need a separate dependency - Feature flags:
network(default),rustls,native-tlsfor TLS backend selection - In-memory log verification via
resolve_log()— verify DID documents without filesystem or network access
Usage
Add this to your Cargo.toml:
[]
= "0.3.1"
Then:
use *;
let mut webvh = default;
// Load LogEntries from a file
webvh.load_log_entries_from_file?;
The prelude module re-exports the most commonly needed types:
DIDWebVHError, DIDWebVHState, LogEntryMethods, Parameters,
CreateDIDConfig, create_did, Witnesses, WitnessProofCollection,
Signer, KeyType, and async_trait.
Feature Flags
| Feature | Default | Description |
|---|---|---|
network |
yes | Enables HTTP(S) resolution via reqwest. Disable with default-features = false for local-only validation. |
ssi |
no | Enables integration with the ssi crate (implies network). |
rustls |
no | Use rustls TLS backend (implies network). |
native-tls |
no | Use platform-native TLS backend (implies network). |
To use the library without network support (e.g. for local file validation only):
[]
= { = "0.3.0", = false }
Convenience API
DIDWebVHState provides high-level methods for common DID lifecycle operations:
// Update the DID document
state.update_document.await?;
// Rotate update keys
state.rotate_keys.await?;
// Deactivate the DID
state.deactivate.await?;
See the examples/update_did.rs, examples/rotate_keys.rs, and
examples/deactivate_did.rs examples for full usage.
WitnessesBuilder
Build witness configurations ergonomically:
use *;
let witnesses = builder
.threshold
.witness
.witness
.build?;
Cache Serialization
Save and load DIDWebVHState for offline caching:
// Save state to disk
state.save_state?;
// Load state from disk (re-validate before use)
let state = load_state?;
Important: Loaded state should be re-validated because computed fields
(active_update_keys, active_witness) use #[serde(skip)] and will be
at their defaults after deserialization.
Everyone likes a wizard
Getting started with webvh at first can be daunting given the complexity of the specification and supporting infrastructure such as witness and watcher nodes.
To help with getting started, a wizard for webvh has been created to help you.
To run this wizard, you need to have Rust installed on your machine.
WARNING: This wizard will generate secrets locally on your machine, and display the secret on the screen.
The wizard is meant for demonstration purposes only. Use in a production environment is not recommended.
Default Wizard Files
did.jsonl is the default WebVH LogEntry file that the wizard will create.
did-witness.json where Witness Proofs are saved.
did.jsonl-secrets is the default file containing key secrets
Is WebVH performant?
There is a lot going on with the WebVH DID method. A lot of keys, signing and validations
Depending on how often you are creating LogEntries, number of witnesses etc can have a big impact on performance.
To help with testing different usage scenario's, there is an example tool that can help you with testing real-world performance of the WebVH method.
To get options for the generate_history performance tool, run:
For example, to generate 200 LogEntries with 10 witnesses each, you can run:
This tool will save the output to
- did.jsonl (LogEntries)
- did-witness.json (Witness Proofs)
Criterion Benchmarks (stable Rust)
Run the full benchmark suite using Criterion:
Run a specific benchmark group or individual benchmark:
HTML reports are generated in target/criterion/.
Nightly Benchmarks
If you have the Rust nightly toolchain installed, you can also run the built-in
#[bench] benchmarks:
Benchmark Groups
| Group | Benchmarks | Description |
|---|---|---|
did_creation |
basic, with_aliases |
DID creation with minimal config and with alsoKnownAs aliases |
did_resolution |
single_entry, large_with_witnesses_120_entries |
File-based DID resolution with 1 and 120+ log entries |
validation |
single_entry, large_with_witnesses_120_entries |
Log entry and witness proof validation |
Creating a DID Programmatically
The create module provides a library API for creating a DID without any
interactive prompts. Use CreateDIDConfig::builder() to construct the
configuration:
use *;
use Secret;
use json;
use Arc;
// Generate or load a signing key
let signing_key = generate_ed25519;
// Build parameters with the signing key as an update key
let parameters = Parameters ;
// Build the DID document
let did_document = json!;
// Create the DID
let config = builder
.address
.authorization_key
.did_document
.parameters
.also_known_as_web
.also_known_as_scid
.build
.unwrap;
let result = create_did.unwrap;
// result.did — the resolved DID identifier (with SCID)
// result.log_entry — the signed first log entry (serialize to JSON for did.jsonl)
// result.witness_proofs — witness proofs (empty if no witnesses configured)
Bring Your Own Signer (HSM / KMS)
The library does not require you to hold secret key material in memory. All
signing operations go through the Signer trait, so you can delegate to an
HSM, cloud KMS, or any other external signing service. The built-in Secret
type implements Signer for local Ed25519 keys, but you can replace it with
your own implementation:
use *; // async_trait is re-exported here
Then use your custom signer with CreateDIDConfig::builder_generic():
let kms_signer = MyKmsSigner ;
let config = builder_generic
.address
.authorization_key
.did_document
.parameters
.build
.unwrap;
let result = create_did.await.unwrap;
The same applies to witness signing — sign_witness_proofs() accepts any
HashMap<String, W> where W: Signer.
Witness Support
If your DID uses witnesses, provide the witness signers via the builder:
// For each witness, add its DID and signer
let config = builder
.address
.authorization_key
.did_document
.parameters
.witness_secret
.build
.unwrap;
The sign_witness_proofs() function is also available separately if you need
to sign witness proofs outside of the full DID creation flow.
License
Licensed under:
- Apache License, Version 2.0, (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)