trustweave-did-registrar-server (Ktor)
The trustweave-did-registrar-server-ktor module provides a Ktor-based HTTP server implementation of the Universal Registrar protocol, allowing you to host your own Universal Registrar service.
1
2
3
4
5
6
dependencies {
implementation("com.trustweave.did:registrar-server-ktor:1.0.0-SNAPSHOT")
implementation("com.trustweave:trustweave-did-registrar:1.0.0-SNAPSHOT")
implementation("com.trustweave:trustweave-did:1.0.0-SNAPSHOT")
implementation("com.trustweave:trustweave-kms:1.0.0-SNAPSHOT")
}
Result: Gradle exposes a Universal Registrar server that implements the DID Registration specification endpoints, allowing clients to create, update, and deactivate DIDs through HTTP.
Note: For Spring Boot applications, use the
registrar-server-springmodule instead. See trustweave-did-registrar-server-spring for details.
Overview
The trustweave-did-registrar-server-ktor module provides:
- DID Registrar Server – Ktor-based HTTP server implementing both Universal Registrar protocol and RESTful endpoints
- RESTful API Endpoints – recommended endpoints for DID operations (
POST /1.0/dids,PUT /1.0/dids/{did},DELETE /1.0/dids/{did},GET /1.0/jobs/{jobId}) - Protocol Endpoints – Universal Registrar protocol-compliant endpoints (
POST /1.0/operations,GET /1.0/operations/{jobId}) for specification compliance - Type-Safe DTOs – request/response DTOs for type-safe API usage
- Job Storage – interface and implementation for tracking long-running operations
- Spec Compliance – full compliance with DID Registration specification
- Pluggable Backend – works with any
DidRegistrarimplementation
Architecture
graph TB
subgraph "trustweave-did-registrar-server-ktor Module"
Server[DidRegistrarServer]
Routes[DidRegistrarRoutes]
Storage[JobStorage Interface]
InMemoryStorage[InMemoryJobStorage]
DatabaseStorage[DatabaseJobStorage]
end
subgraph "Database"
DB[(PostgreSQL/MySQL/H2)]
end
subgraph "Ktor Server"
KtorApp[Ktor Application]
HTTP[HTTP Endpoints]
end
subgraph "Registrar Backend"
DidRegistrar[DidRegistrar Implementation]
KmsRegistrar[KmsBasedRegistrar]
DefaultUR[DefaultUniversalRegistrar]
end
subgraph "Clients"
HTTPClient[HTTP Client]
RegistrarClient[Registrar Client]
end
Server --> KtorApp
KtorApp --> Routes
Routes --> Storage
Storage --> InMemoryStorage
Storage --> DatabaseStorage
DatabaseStorage --> DB
Routes --> DidRegistrar
DidRegistrar --> KmsRegistrar
DidRegistrar --> DefaultUR
HTTPClient --> HTTP
RegistrarClient --> HTTP
style Server fill:#e1f5ff
style Routes fill:#fff4e1
style Storage fill:#e8f5e9
Key Components
DidRegistrarServer
Main server class that configures and runs the DID Registrar HTTP service:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import com.trustweave.did.registrar.server.*
import com.trustweave.did.registrar.client.*
import com.trustweave.kms.*
// Create registrar backend
val kms = InMemoryKeyManagementService()
val registrar = KmsBasedRegistrar(kms)
// Create and start server
val server = DidRegistrarServer(
registrar = registrar,
port = 8080,
host = "0.0.0.0",
jobStorage = InMemoryJobStorage()
)
server.start(wait = true) // Block until server stops
What this does: Provides a complete HTTP server implementation with both RESTful and protocol-compliant endpoints.
Outcome: Enables hosting your own DID Registrar service that clients can use for DID operations.
DidRegistrarRoutes
Ktor routing configuration that implements both RESTful and Universal Registrar protocol endpoints:
RESTful Endpoints (Recommended):
POST /1.0/dids– Create DIDPUT /1.0/dids/{did}– Update DIDDELETE /1.0/dids/{did}– Deactivate DIDGET /1.0/jobs/{jobId}– Get job status
Protocol Endpoints (For Specification Compliance):
POST /1.0/operations– Create, update, or deactivate DIDsGET /1.0/operations/{jobId}– Get status of long-running operations
1
2
3
4
5
import io.ktor.server.routing.*
routing {
configureDidRegistrarRoutes(registrar, jobStorage)
}
What this does: Configures HTTP routes with both RESTful and protocol-compliant endpoints.
Outcome: Provides type-safe RESTful endpoints for new integrations and protocol-compliant endpoints for specification compliance.
JobStorage
Interface for tracking long-running DID registration operations:
1
2
3
4
5
6
interface JobStorage {
fun store(jobId: String, response: DidRegistrationResponse)
fun get(jobId: String): DidRegistrationResponse?
fun remove(jobId: String): Boolean
fun exists(jobId: String): Boolean
}
Implementations:
InMemoryJobStorage– in-memory implementation (for development/testing)DatabaseJobStorage– database-backed implementation (for production)- Custom implementations – can use Redis, etc. for production
What this does: Provides storage for tracking long-running operations that return jobId.
Outcome: Enables proper handling of asynchronous DID registration operations.
DatabaseJobStorage
Database-backed implementation that stores job responses in a relational database:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import com.trustweave.did.registrar.server.*
import com.zaxxer.hikari.HikariDataSource
import javax.sql.DataSource
// Create DataSource (using HikariCP)
val dataSource = HikariDataSource().apply {
jdbcUrl = "jdbc:postgresql://localhost:5432/trustweave"
username = "user"
password = "password"
maximumPoolSize = 10
}
// Create database job storage
val jobStorage = DatabaseJobStorage(
dataSource = dataSource,
tableName = "did_registration_jobs" // Optional, defaults to "did_registration_jobs"
)
// Use with server
val server = DidRegistrarServer(
registrar = registrar,
jobStorage = jobStorage
)
Features:
- Supports PostgreSQL, MySQL, and H2 databases
- Automatic schema initialization
- JSON storage for
DidRegistrationResponse - Cleanup methods for old completed jobs
- Thread-safe operations
Database Schema:
1
2
3
4
5
6
7
8
CREATE TABLE did_registration_jobs (
job_id VARCHAR(255) PRIMARY KEY,
response_data TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_did_registration_jobs_created_at ON did_registration_jobs(created_at);
Additional Methods:
cleanupCompletedJobs(olderThanDays: Int)– Removes old completed jobscount()– Returns total number of jobs in storage
API Endpoints
RESTful Endpoints (Recommended)
The following RESTful endpoints are recommended for new integrations. They provide type-safe request/response DTOs and follow RESTful conventions.
POST /1.0/dids
Creates a new DID.
Request:
1
2
3
4
5
6
7
{
"method": "web",
"options": {
"keyManagementMode": "internal-secret",
"returnSecrets": true
}
}
Response:
1
2
3
4
5
6
7
8
9
{
"jobId": "job-123", // Present if long-running
"didState": {
"state": "finished",
"did": "did:web:example.com",
"didDocument": { /* DID Document */ },
"secret": { /* Optional secrets */ }
}
}
PUT /1.0/dids/{did}
Updates an existing DID.
Request:
1
2
3
4
5
6
7
8
9
{
"didDocument": {
"id": "did:web:example.com",
"verificationMethod": [ /* ... */ ]
},
"options": {
"secret": { /* Authorization secrets */ }
}
}
Response:
1
2
3
4
5
6
7
8
{
"jobId": "job-123",
"didState": {
"state": "finished",
"did": "did:web:example.com",
"didDocument": { /* Updated DID Document */ }
}
}
DELETE /1.0/dids/{did}
Deactivates a DID.
Request:
1
2
3
4
5
{
"options": {
"secret": { /* Authorization secrets */ }
}
}
Response:
1
2
3
4
5
6
7
{
"jobId": "job-123",
"didState": {
"state": "finished",
"did": "did:web:example.com"
}
}
GET /1.0/jobs/{jobId}
Gets the status of a long-running operation.
Response:
1
2
3
4
5
6
7
8
{
"jobId": "job-123",
"didState": {
"state": "finished",
"did": "did:web:example.com",
"didDocument": { /* DID Document */ }
}
}
Universal Registrar Protocol Endpoints
The following endpoints implement the Universal Registrar protocol specification. They are kept for specification compliance and backward compatibility. For new integrations, prefer the RESTful endpoints above.
POST /1.0/operations
Creates, updates, or deactivates a DID based on the request body.
Create Operation:
1
2
3
4
5
6
7
{
"method": "web",
"options": {
"keyManagementMode": "internal-secret",
"returnSecrets": true
}
}
Update Operation:
1
2
3
4
5
6
7
{
"did": "did:web:example.com",
"didDocument": { /* Updated DID Document */ },
"options": {
"secret": { /* Authorization secrets */ }
}
}
Deactivate Operation:
1
2
3
4
5
6
7
{
"did": "did:web:example.com",
"operation": "deactivate",
"options": {
"secret": { /* Authorization secrets */ }
}
}
GET /1.0/operations/{jobId}
Gets the status of a long-running operation.
Response:
1
2
3
4
5
6
7
8
{
"jobId": "job-123",
"didState": {
"state": "finished",
"did": "did:web:example.com",
"didDocument": { /* DID Document */ }
}
}
Request/Response Flow
sequenceDiagram
participant Client
participant Server as DidRegistrarServer
participant Routes as DidRegistrarRoutes
participant Registrar as DidRegistrar
participant Storage as JobStorage
Client->>Server: POST /1.0/operations
Server->>Routes: Route request
Routes->>Routes: Parse request body
Routes->>Registrar: createDid/updateDid/deactivateDid
Registrar-->>Routes: DidRegistrationResponse
alt Long-Running Operation
Routes->>Storage: store(jobId, response)
Routes-->>Client: 200 OK (with jobId)
Client->>Server: GET /1.0/operations/{jobId}
Server->>Routes: Route request
Routes->>Storage: get(jobId)
Storage-->>Routes: DidRegistrationResponse
Routes-->>Client: 200 OK (with current state)
else Immediate Completion
Routes-->>Client: 200 OK (with final state)
end
Usage Examples
Basic Server Setup
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import com.trustweave.did.registrar.server.*
import com.trustweave.did.registrar.client.*
import com.trustweave.kms.*
fun main() {
// Create registrar backend
val kms = InMemoryKeyManagementService()
val registrar = KmsBasedRegistrar(kms)
// Create and start server
val server = DidRegistrarServer(
registrar = registrar,
port = 8080
)
server.start(wait = true)
}
Using DatabaseJobStorage
For production deployments, use the database-backed implementation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import com.trustweave.did.registrar.server.*
import com.zaxxer.hikari.HikariDataSource
// Create DataSource
val dataSource = HikariDataSource().apply {
jdbcUrl = "jdbc:postgresql://localhost:5432/trustweave"
username = "user"
password = "password"
maximumPoolSize = 10
minimumIdle = 2
}
// Create database job storage
val jobStorage = DatabaseJobStorage(dataSource)
// Use with server
val server = DidRegistrarServer(
registrar = registrar,
jobStorage = jobStorage
)
// Optional: Cleanup old jobs periodically
// jobStorage.cleanupCompletedJobs(olderThanDays = 30)
Note: To use DatabaseJobStorage, you need to add database dependencies to your build.gradle.kts:
1
2
3
4
dependencies {
// Database dependencies for DatabaseJobStorage
implementation(libs.bundles.database)
}
Custom Job Storage
You can also implement your own JobStorage:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import com.trustweave.did.registrar.server.*
import com.trustweave.did.registrar.model.*
// Custom job storage implementation (e.g., Redis)
class RedisJobStorage : JobStorage {
// Implement using Redis
override fun store(jobId: String, response: DidRegistrationResponse) {
// Store in Redis
}
override fun get(jobId: String): DidRegistrationResponse? {
// Retrieve from Redis
}
// ... other methods
}
// Use custom storage
val server = DidRegistrarServer(
registrar = registrar,
jobStorage = RedisJobStorage()
)
Using with DefaultUniversalRegistrar
You can chain registrars, using a local server that delegates to another Universal Registrar:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import com.trustweave.did.registrar.server.*
import com.trustweave.did.registrar.client.*
// Create upstream registrar
val upstreamRegistrar = DefaultUniversalRegistrar(
baseUrl = "https://dev.uniregistrar.io"
)
// Create server that uses upstream registrar
val server = DidRegistrarServer(
registrar = upstreamRegistrar,
port = 8080
)
server.start()
Client Usage
Once the server is running, clients can use it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import com.trustweave.did.registrar.client.*
// Client connects to your server
val client = DefaultUniversalRegistrar(
baseUrl = "http://localhost:8080"
)
// Create DID through your server
val response = client.createDid(
method = "web",
options = CreateDidOptions(
keyManagementMode = KeyManagementMode.INTERNAL_SECRET
)
)
Module Structure
graph TD
subgraph "did:registrar-server-ktor Package Structure"
A[com.trustweave.did.registrar.server]
A --> B[DidRegistrarServer.kt]
A --> C[DidRegistrarRoutes.kt]
A --> D[dto/]
D --> D1[CreateDidRequest.kt]
D --> D2[UpdateDidRequest.kt]
D --> D3[DeactivateDidRequest.kt]
D --> D4[ErrorResponse.kt]
A --> D[JobStorage.kt]
D --> D1[JobStorage Interface]
D --> D2[InMemoryJobStorage]
end
subgraph "Dependencies"
E[Ktor Server]
F[DidRegistrar]
G[Registration Models]
end
B --> E
C --> F
C --> G
C --> D
style A fill:#e1f5ff
style B fill:#fff4e1
style C fill:#e8f5e9
Deployment Considerations
Production Deployment
For production use, consider:
- Persistent Job Storage – Use
DatabaseJobStorageinstead ofInMemoryJobStorage - Connection Pooling – Use HikariCP or similar for database connections
- Job Cleanup – Periodically clean up old completed jobs using
cleanupCompletedJobs() - Authentication – Add authentication middleware to protect endpoints
- Rate Limiting – Implement rate limiting to prevent abuse
- Monitoring – Add logging and metrics for observability
- HTTPS – Use HTTPS/TLS for secure communication
- Load Balancing – Use multiple server instances behind a load balancer
Example with Authentication
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import io.ktor.server.application.*
import io.ktor.server.auth.*
fun Application.module() {
install(Authentication) {
bearer {
validate { token ->
// Validate API key or JWT token
if (token == "your-api-key") {
UserIdPrincipal("user")
} else null
}
}
}
routing {
authenticate {
configureDidRegistrarRoutes(registrar, jobStorage)
}
}
}
Dependencies
- Depends on
trustweave-did-registrarforDidRegistrarimplementations - Depends on
trustweave-didfor models and interfaces - Depends on
ktor-server-coreandktor-server-nettyfor HTTP server - Depends on
ktor-serialization-kotlinx-jsonfor JSON serialization
Related Modules
- trustweave-did – Core DID module with
DidRegistrarinterface - trustweave-did-registrar – DID Registrar client implementations
Next Steps
- Review DID Registration Specification for detailed spec compliance
- See Universal Registrar Protocol for endpoint specifications
- Check Ktor Documentation for server configuration options
- Explore DID Registrar Module for client implementations