Skip to content

Commit 8e5851d

Browse files
feat(webvh): Added did:webvh resolver
Signed-off-by: Brian Richter <brian@aviary.tech>
1 parent bea846b commit 8e5851d

17 files changed

+1429
-6
lines changed

.changeset/webvh-did-method.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@credo-ts/webvh": minor
3+
---
4+
5+
Add WebVH DID method implementation using didwebvh-ts package

packages/webvh/jest.config.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { Config } from '@jest/types'
2+
3+
import base from '../../jest.config.base'
4+
5+
import packageJson from './package.json'
6+
7+
const config: Config.InitialOptions = {
8+
...base,
9+
displayName: packageJson.name,
10+
setupFilesAfterEnv: ['./tests/setup.ts'],
11+
}
12+
13+
export default config

packages/webvh/package.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "@credo-ts/webvh",
3+
"version": "0.4.0",
4+
"description": "Credo WebVH DID method implementation",
5+
"main": "build/index.js",
6+
"types": "build/index.d.ts",
7+
"files": [
8+
"build"
9+
],
10+
"publishConfig": {
11+
"main": "build/index",
12+
"types": "build/index",
13+
"access": "public"
14+
},
15+
"license": "Apache-2.0",
16+
"scripts": {
17+
"build": "pnpm run clean && pnpm run compile",
18+
"clean": "rimraf -rf ./build",
19+
"compile": "tsc -p tsconfig.build.json",
20+
"prepublishOnly": "pnpm run build",
21+
"test": "jest",
22+
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand --config jest.config.js"
23+
},
24+
"dependencies": {
25+
"@credo-ts/core": "workspace:*",
26+
"didwebvh-ts": "^2.0.0",
27+
"tsyringe": "^4.8.0"
28+
},
29+
"devDependencies": {
30+
"rimraf": "^4.4.0",
31+
"typescript": "~4.9.5"
32+
}
33+
}

packages/webvh/src/WebvhApi.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { AgentContext } from '@credo-ts/core'
2+
import { injectable } from 'tsyringe'
3+
4+
import { WebvhDidResolver } from './dids/WebvhDidResolver'
5+
6+
@injectable()
7+
export class WebvhApi {
8+
private agentContext: AgentContext
9+
10+
public constructor(agentContext: AgentContext) {
11+
this.agentContext = agentContext
12+
}
13+
14+
public async resolveResource(resourceUrl: string) {
15+
const webvhDidResolver = this.agentContext.dependencyManager.resolve(WebvhDidResolver)
16+
return await webvhDidResolver.resolveResource(this.agentContext, resourceUrl)
17+
}
18+
}

packages/webvh/src/WebvhModule.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import type { DependencyManager, Module } from '@credo-ts/core'
2+
3+
import { WebvhModuleConfig } from './WebvhModuleConfig'
4+
import { WebvhDidRegistrar } from './dids/WebvhDidRegistrar'
5+
import { WebvhDidResolver } from './dids/WebvhDidResolver'
6+
7+
export class WebvhModule implements Module {
8+
public readonly config: WebvhModuleConfig
9+
10+
public constructor(config?: WebvhModuleConfig) {
11+
this.config = config ?? new WebvhModuleConfig()
12+
}
13+
14+
/**
15+
* Registers the dependencies of the WebVH module on the dependency manager.
16+
*/
17+
public register(dependencyManager: DependencyManager) {
18+
// Register config
19+
dependencyManager.registerInstance(WebvhModuleConfig, this.config)
20+
21+
// Register did registrar and resolver
22+
dependencyManager.registerSingleton(WebvhDidRegistrar)
23+
dependencyManager.registerSingleton(WebvhDidResolver)
24+
}
25+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { injectable } from 'tsyringe'
2+
3+
export interface WebvhModuleConfigOptions {
4+
/**
5+
* Base URL for the WebVH service
6+
*/
7+
baseUrl?: string
8+
}
9+
10+
@injectable()
11+
export class WebvhModuleConfig {
12+
private options: WebvhModuleConfigOptions
13+
14+
public constructor(options?: WebvhModuleConfigOptions) {
15+
this.options = options ?? {}
16+
}
17+
18+
/** See {@link WebvhModuleConfigOptions.baseUrl} */
19+
public get baseUrl() {
20+
return this.options.baseUrl ?? 'https://webvh.io'
21+
}
22+
}
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
import type {
2+
AgentContext,
3+
DidRegistrar,
4+
DidCreateOptions,
5+
DidCreateResult,
6+
DidDeactivateResult,
7+
DidDocument,
8+
DidUpdateResult,
9+
DidUpdateOptions,
10+
DidDocumentRole,
11+
} from '@credo-ts/core'
12+
13+
import { MultibaseEncoder, Buffer, DidRepository, DidRecord, isValidPrivateKey, KeyType } from '@credo-ts/core'
14+
import { createDID, updateDID } from 'didwebvh-ts'
15+
16+
import { WebvhModuleConfig } from '../WebvhModuleConfig'
17+
18+
import { generateDidDoc } from './didWebvhUtil'
19+
20+
export class WebvhDidRegistrar implements DidRegistrar {
21+
public readonly supportedMethods = ['webvh']
22+
23+
public async create(agentContext: AgentContext, options: WebvhDidCreateOptions): Promise<DidCreateResult> {
24+
const didRepository = agentContext.dependencyManager.resolve(DidRepository)
25+
const webvhModuleConfig = agentContext.dependencyManager.resolve(WebvhModuleConfig)
26+
27+
let didDocument: DidDocument
28+
29+
try {
30+
if (options.didDocument) {
31+
didDocument = options.didDocument
32+
} else if (options.secret?.verificationMethod) {
33+
const verificationMethod = options.secret.verificationMethod
34+
const privateKeyMultibase = verificationMethod.privateKeyMultibase
35+
if (!privateKeyMultibase) {
36+
return {
37+
didDocumentMetadata: {},
38+
didRegistrationMetadata: {},
39+
didState: {
40+
state: 'failed',
41+
reason: 'Invalid private key provided',
42+
},
43+
}
44+
}
45+
const privateKey = Buffer.from(MultiBaseEncoder.decode(privateKeyMultibase).data)
46+
if (privateKeyMultibase && !isValidPrivateKey(privateKey, KeyType.Ed25519)) {
47+
return {
48+
didDocumentMetadata: {},
49+
didRegistrationMetadata: {},
50+
didState: {
51+
state: 'failed',
52+
reason: 'Invalid private key provided',
53+
},
54+
}
55+
}
56+
57+
await agentContext.wallet.createKey({
58+
keyType: KeyType.Ed25519,
59+
privateKey,
60+
})
61+
62+
didDocument = await generateDidDoc({
63+
verificationMethods: [verificationMethod],
64+
updateKeys: [verificationMethod.publicKeyMultibase],
65+
baseUrl: webvhModuleConfig.baseUrl,
66+
}, agentContext)
67+
} else {
68+
return {
69+
didDocumentMetadata: {},
70+
didRegistrationMetadata: {},
71+
didState: {
72+
state: 'failed',
73+
reason: 'Provide a didDocument or at least one verificationMethod with seed in secret',
74+
},
75+
}
76+
}
77+
78+
// Register the DID using didwebvh-ts
79+
const response = await createDID()
80+
81+
// Save the did so we know we created it and can issue with it
82+
const didRecord = new DidRecord({
83+
did: didDocument.id,
84+
role: DidDocumentRole.Created,
85+
didDocument,
86+
})
87+
await didRepository.save(agentContext, didRecord)
88+
89+
return {
90+
didDocumentMetadata: {},
91+
didRegistrationMetadata: {},
92+
didState: {
93+
state: 'finished',
94+
did: didDocument.id,
95+
didDocument,
96+
secret: options.secret,
97+
},
98+
}
99+
} catch (error) {
100+
agentContext.config.logger.error(`Error registering DID`, error)
101+
return {
102+
didDocumentMetadata: {},
103+
didRegistrationMetadata: {},
104+
didState: {
105+
state: 'failed',
106+
reason: `unknownError: ${error.message}`,
107+
},
108+
}
109+
}
110+
}
111+
112+
public async update(agentContext: AgentContext, options: WebvhDidUpdateOptions): Promise<DidUpdateResult> {
113+
const didRepository = agentContext.dependencyManager.resolve(DidRepository)
114+
115+
try {
116+
if (!options.didDocument) {
117+
return {
118+
didDocumentMetadata: {},
119+
didRegistrationMetadata: {},
120+
didState: {
121+
state: 'failed',
122+
reason: 'Provide a valid didDocument',
123+
},
124+
}
125+
}
126+
127+
// Update the DID using didwebvh-ts
128+
const response = await updateDID({doc: options.didDocument.toJSON()})
129+
130+
// Update the did record
131+
const didRecord = await didRepository.findCreatedDid(agentContext, options.did)
132+
if (didRecord) {
133+
didRecord.didDocument = options.didDocument
134+
await didRepository.update(agentContext, didRecord)
135+
}
136+
137+
return {
138+
didDocumentMetadata: {},
139+
didRegistrationMetadata: {},
140+
didState: {
141+
state: 'finished',
142+
did: options.did,
143+
didDocument: options.didDocument,
144+
},
145+
}
146+
} catch (error) {
147+
agentContext.config.logger.error(`Error updating DID`, error)
148+
return {
149+
didDocumentMetadata: {},
150+
didRegistrationMetadata: {},
151+
didState: {
152+
state: 'failed',
153+
reason: `unknownError: ${error.message}`,
154+
},
155+
}
156+
}
157+
}
158+
159+
public async deactivate(agentContext: AgentContext, options: WebvhDidDeactivateOptions): Promise<DidDeactivateResult> {
160+
// const didRepository = agentContext.dependencyManager.resolve(DidRepository)
161+
162+
// try {
163+
// // Deactivate the DID using didwebvh-ts
164+
// const response = await deactivateDID(options.did)
165+
166+
// // Update the did record
167+
// const didRecord = await didRepository.findCreatedDid(agentContext, options.did)
168+
// if (didRecord) {
169+
// await didRepository.update(agentContext, didRecord)
170+
// }
171+
172+
// return {
173+
// didDocumentMetadata: {},
174+
// didRegistrationMetadata: {},
175+
// didState: {
176+
// state: 'finished',
177+
// did: options.did,
178+
// didDocument:,
179+
// },
180+
// }
181+
// } catch (error) {
182+
// agentContext.config.logger.error(`Error deactivating DID`, error)
183+
return {
184+
didDocumentMetadata: {},
185+
didRegistrationMetadata: {},
186+
didState: {
187+
state: 'failed',
188+
reason: `unknownError`,
189+
},
190+
}
191+
// }
192+
}
193+
}
194+
195+
export interface WebvhDidCreateWithoutDidDocumentOptions extends DidCreateOptions {
196+
method: 'webvh'
197+
did?: undefined
198+
didDocument?: undefined
199+
options?: {
200+
versionId?: string
201+
}
202+
secret: {
203+
verificationMethod: IVerificationMethod
204+
}
205+
}
206+
207+
export interface WebvhDidCreateFromDidDocumentOptions extends DidCreateOptions {
208+
method: 'webvh'
209+
did?: undefined
210+
didDocument: DidDocument
211+
options?: {
212+
versionId?: string
213+
}
214+
}
215+
216+
export type WebvhDidCreateOptions = WebvhDidCreateFromDidDocumentOptions | WebvhDidCreateWithoutDidDocumentOptions
217+
218+
export interface WebvhDidUpdateOptions extends DidUpdateOptions {
219+
did: string
220+
didDocument: DidDocument
221+
options?: {
222+
versionId?: string
223+
}
224+
}
225+
226+
export interface WebvhDidDeactivateOptions extends DidCreateOptions {
227+
method: 'webvh'
228+
did: string
229+
options?: {
230+
versionId?: string
231+
}
232+
}
233+
234+
interface IVerificationMethod {
235+
type: string
236+
id: string
237+
privateKeyMultibase?: string
238+
publicKeyMultibase: string
239+
}

0 commit comments

Comments
 (0)