Skip to content

Commit 99b8af0

Browse files
authored
cheqd plugin: new feature /did/import endpoint (openwallet-foundation#1976)
* feat: Add /did/import endpoint Signed-off-by: Sownak Roy <sownak@cheqd.io> * Add cheqd did integration test Signed-off-by: Sownak Roy <sownak@cheqd.io> * fix: e2e did:cheqd import and verify test Signed-off-by: Sownak Roy <sownak@cheqd.io> * fix: add check for keytype Signed-off-by: Sownak Roy <sownak@cheqd.io> * updated log levels Signed-off-by: Sownak Roy <sownak@cheqd.io> --------- Signed-off-by: Sownak Roy <sownak@cheqd.io>
1 parent 0b92e1c commit 99b8af0

11 files changed

Lines changed: 906 additions & 516 deletions

File tree

cheqd/cheqd/did/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""DID Manager Base Classes."""
22

33
from abc import ABC, abstractmethod
4-
from typing import List, Optional, Union, Literal, Dict
4+
from typing import Dict, List, Literal, Optional, Union
55

66
from acapy_agent.core.error import BaseError
77
from acapy_agent.core.profile import Profile

cheqd/cheqd/did/helpers.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
from typing import Dict, List, Union
66
from uuid import uuid4
77

8-
from acapy_agent.wallet.util import b64_to_bytes, bytes_to_b58, bytes_to_b64
98
from acapy_agent.utils.multiformats import multibase, multicodec
9+
from acapy_agent.wallet.util import b64_to_bytes, bytes_to_b58, bytes_to_b64
10+
from base58 import b58encode
1011

1112

1213
class CheqdNetwork(Enum):
@@ -119,12 +120,14 @@ def create_did_verification_method(
119120
}
120121
)
121122
elif type_ == VerificationMethods.Ed255192018:
123+
public_key_bytes = b64_to_bytes(key["publicKey"])
124+
public_key_base58 = b58encode(public_key_bytes).decode()
122125
methods.append(
123126
{
124127
"id": key["keyId"],
125128
"type": type_.value,
126129
"controller": key["didUrl"],
127-
"publicKeyBase58": key["methodSpecificId"][1:],
130+
"publicKeyBase58": public_key_base58,
128131
}
129132
)
130133
elif type_ == VerificationMethods.JWK:

cheqd/cheqd/did/manager.py

Lines changed: 98 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,39 @@
66
from acapy_agent.resolver.base import DIDNotFound
77
from acapy_agent.wallet.base import BaseWallet
88
from acapy_agent.wallet.crypto import validate_seed
9+
from acapy_agent.wallet.did_info import DIDInfo
910
from acapy_agent.wallet.did_method import DIDMethods
1011
from acapy_agent.wallet.did_parameters_validation import DIDParametersValidation
1112
from acapy_agent.wallet.error import WalletError
12-
from acapy_agent.wallet.key_type import ED25519
13+
from acapy_agent.wallet.key_type import BLS12381G2, ED25519, P256
14+
from acapy_agent.wallet.keys.manager import multikey_to_verkey
15+
from acapy_agent.wallet.routes import format_did_info
1316
from acapy_agent.wallet.util import b58_to_bytes, bytes_to_b64
1417
from aiohttp import web
1518

16-
from .base import (
17-
DidUpdateRequestOptions,
18-
SubmitSignatureOptions,
19-
DIDDocumentSchema,
20-
DidActionState,
21-
)
22-
from .helpers import (
23-
create_verification_keys,
24-
create_did_verification_method,
25-
VerificationMethods,
26-
create_did_payload,
27-
CheqdNetwork,
28-
)
2919
from ..did.base import (
3020
BaseDIDManager,
3121
CheqdDIDManagerError,
32-
Secret,
33-
DidDeactivateRequestOptions,
3422
DidCreateRequestOptions,
23+
DidDeactivateRequestOptions,
3524
Options,
25+
Secret,
3626
)
3727
from ..did_method import CHEQD
3828
from ..resolver.resolver import CheqdDIDResolver
29+
from .base import (
30+
DidActionState,
31+
DIDDocumentSchema,
32+
DidUpdateRequestOptions,
33+
SubmitSignatureOptions,
34+
)
35+
from .helpers import (
36+
CheqdNetwork,
37+
VerificationMethods,
38+
create_did_payload,
39+
create_did_verification_method,
40+
create_verification_keys,
41+
)
3942
from .registrar import DIDRegistrar
4043

4144
LOGGER = logging.getLogger(__name__)
@@ -86,9 +89,11 @@ async def create(
8689
verkey = key.verkey
8790
verkey_bytes = b58_to_bytes(verkey)
8891
public_key_b64 = bytes_to_b64(verkey_bytes)
89-
verification_method = (
90-
options.get("verification_method") or VerificationMethods.Ed255192020
91-
)
92+
verification_method = VerificationMethods.Ed255192020
93+
if options.get("verification_method"):
94+
verification_method = VerificationMethods(
95+
options.get("verification_method")
96+
)
9297

9398
if did_doc is None:
9499
# generate payload
@@ -159,7 +164,7 @@ async def create(
159164
return {
160165
"did": did,
161166
"verkey": verkey,
162-
"didDocument": publish_did_state.didDocument.dict(),
167+
"didDocument": publish_did_state.didDocument.model_dump(),
163168
}
164169

165170
async def update(self, did: str, did_doc: dict, options: dict = None) -> dict:
@@ -218,7 +223,7 @@ async def update(self, did: str, did_doc: dict, options: dict = None) -> dict:
218223
except Exception as ex:
219224
raise ex
220225

221-
return {"did": did, "didDocument": publish_did_state.didDocument.dict()}
226+
return {"did": did, "didDocument": publish_did_state.didDocument.model_dump()}
222227

223228
async def deactivate(self, did: str, options: dict = None) -> dict:
224229
"""Deactivate a Cheqd DID."""
@@ -277,6 +282,77 @@ async def deactivate(self, did: str, options: dict = None) -> dict:
277282
raise ex
278283
return {
279284
"did": did,
280-
"didDocument": publish_did_state.didDocument.dict(),
285+
"didDocument": publish_did_state.didDocument.model_dump(),
281286
"didDocumentMetadata": metadata,
282287
}
288+
289+
async def import_did(self, did_document: dict, metadata: dict = None) -> dict:
290+
"""Import a DID."""
291+
metadata = metadata or {}
292+
293+
async with self.profile.session() as session:
294+
try:
295+
wallet = session.inject(BaseWallet)
296+
if not wallet:
297+
raise WalletError(reason="No wallet available")
298+
299+
did = did_document.get("id")
300+
if not did:
301+
raise WalletError(reason="DID document must contain an 'id' field")
302+
303+
# Extract verification method (assuming first one for simplicity)
304+
verification_methods = did_document.get("verificationMethod", [])
305+
if not verification_methods:
306+
raise WalletError(
307+
reason="DID document must contain verification methods"
308+
)
309+
# Get the first verification method's public key
310+
first_vm = verification_methods[0]
311+
# Handle both publicKeyBase58 and publicKeyMultibase formats
312+
public_key_base58 = first_vm.get("publicKeyBase58")
313+
public_key_multibase = first_vm.get("publicKeyMultibase")
314+
if public_key_base58:
315+
# If we have publicKeyBase58, use it directly
316+
verkey = public_key_base58
317+
elif public_key_multibase:
318+
# If we have publicKeyMultibase, convert it to verkey format
319+
verkey = multikey_to_verkey(public_key_multibase)
320+
else:
321+
raise WalletError(
322+
reason=(
323+
"Verification method must contain either "
324+
"'publicKeyBase58' or 'publicKeyMultibase'"
325+
)
326+
)
327+
# Determine key type from verification method
328+
key_type = ED25519 # Default fallback
329+
330+
vm_type = first_vm.get("type", "")
331+
if "Ed25519" in vm_type:
332+
key_type = ED25519
333+
elif "P256" in vm_type or "secp256r1" in vm_type:
334+
key_type = P256
335+
elif "BLS" in vm_type:
336+
key_type = BLS12381G2
337+
LOGGER.debug("Detected key type %s for DID %s", key_type.key_type, did)
338+
# Determine and validate DID method
339+
did_methods: DIDMethods = self.profile.inject(DIDMethods)
340+
method = did_methods.from_did(did)
341+
did_validation = DIDParametersValidation(did_methods)
342+
did_validation.validate_key_type(method, key_type)
343+
344+
# Create DIDInfo object
345+
did_info = DIDInfo(
346+
did=did,
347+
verkey=verkey,
348+
metadata=metadata,
349+
method=method,
350+
key_type=key_type,
351+
)
352+
stored_info = await wallet.store_did(did_info)
353+
354+
LOGGER.info("Successfully imported DID: %s", did)
355+
except Exception as ex:
356+
raise ex
357+
358+
return {"result": format_did_info(stored_info)}

cheqd/cheqd/routes.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from acapy_agent.admin.request_context import AdminRequestContext
77
from acapy_agent.messaging.models.openapi import OpenAPISchema
88
from acapy_agent.wallet.error import WalletError
9+
from acapy_agent.wallet.routes import DIDSchema
910
from aiohttp import web
1011
from aiohttp_apispec import docs, request_schema, response_schema
1112
from marshmallow import Schema, fields
@@ -270,6 +271,41 @@ class UpdateCheqdDIDResponseSchema(OpenAPISchema):
270271
)
271272

272273

274+
class DIDImportSchema(OpenAPISchema):
275+
"""Request schema for importing a DID."""
276+
277+
did_document = fields.Raw(
278+
required=True,
279+
metadata={
280+
"description": "The DID document to import",
281+
"example": {
282+
"id": "did:example:123456789",
283+
"verificationMethod": [
284+
{
285+
"id": "did:example:123456789#key-1",
286+
"type": "Ed25519VerificationKey2018",
287+
"controller": "did:example:123456789",
288+
"publicKeyBase58": "H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV",
289+
}
290+
],
291+
},
292+
},
293+
)
294+
295+
metadata = fields.Dict(
296+
required=False,
297+
metadata={
298+
"description": "Additional metadata to associate with the imported DID"
299+
},
300+
)
301+
302+
303+
class DIDImportResponseSchema(OpenAPISchema):
304+
"""Response schema for DID import."""
305+
306+
result = fields.Nested(DIDSchema())
307+
308+
273309
@docs(tags=["did"], summary="Create a did:cheqd")
274310
@request_schema(CreateCheqdDIDRequestSchema())
275311
@response_schema(CreateCheqdDIDResponseSchema, HTTPStatus.OK)
@@ -363,13 +399,58 @@ async def deactivate_cheqd_did(request: web.BaseRequest):
363399
raise web.HTTPBadRequest(reason=err.roll_up)
364400

365401

402+
@docs(
403+
tags=["did"],
404+
summary="Import an existing DID into the wallet",
405+
)
406+
@request_schema(DIDImportSchema)
407+
@response_schema(DIDImportResponseSchema(), description="")
408+
@tenant_authentication
409+
async def import_did(request: web.BaseRequest):
410+
"""Request handler for importing a DID into the wallet.
411+
412+
Args:
413+
request: aiohttp request object
414+
415+
Returns:
416+
The imported DID information
417+
418+
"""
419+
context: AdminRequestContext = request["context"]
420+
config = context.settings.get("plugin_config")
421+
resolver_url = None
422+
registrar_url = None
423+
if config:
424+
registrar_url = config.get("registrar_url")
425+
resolver_url = config.get("resolver_url")
426+
try:
427+
body = await request.json()
428+
except Exception:
429+
body = {}
430+
431+
try:
432+
result = await CheqdDIDManager(
433+
context.profile, registrar_url, resolver_url
434+
).import_did(
435+
body.get("did_document"),
436+
body.get("metadata"),
437+
)
438+
except CheqdDIDManagerError as err:
439+
raise web.HTTPInternalServerError(reason=err.roll_up)
440+
except WalletError as err:
441+
raise web.HTTPBadRequest(reason=err.roll_up)
442+
443+
return web.json_response(result)
444+
445+
366446
async def register(app: web.Application):
367447
"""Register routes."""
368448
app.add_routes(
369449
[
370450
web.post("/did/cheqd/create", create_cheqd_did),
371451
web.post("/did/cheqd/update", update_cheqd_did),
372452
web.post("/did/cheqd/deactivate", deactivate_cheqd_did),
453+
web.post("/did/import", import_did),
373454
]
374455
)
375456

cheqd/docker/default.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,4 @@ wallet-key: "cheq-1"
5252
wallet-storage-type: postgres_storage
5353
wallet-storage-creds: '{"account":"postgres","password":"postgres","admin_account":"postgres","admin_password":"postgres"}'
5454
wallet-storage-config: '{"url":"localhost:5432","max_connections":5}'
55+
wallet-allow-insecure-seed: true

cheqd/docker/integration.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,4 @@ wallet-key: "cheq-1"
5050
wallet-storage-type: postgres_storage
5151
wallet-storage-creds: '{"account":"postgres","password":"postgres","admin_account":"postgres","admin_password":"postgres"}'
5252
wallet-storage-config: '{"url":"host.docker.internal:5432","max_connections":5}'
53+
wallet-allow-insecure-seed: true

cheqd/integration/docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ services:
3232
--wallet-type askar-anoncreds
3333
--wallet-name agency
3434
--wallet-key insecure
35+
--wallet-allow-insecure-seed
3536
--auto-provision
3637
--log-level info
3738
--debug-webhooks
@@ -96,6 +97,7 @@ services:
9697
- "9089:3000"
9798
restart: on-failure
9899
environment:
100+
- NPM_CONFIG_LOGLEVEL=warn
99101
- FEE_PAYER_TESTNET_MNEMONIC=${FEE_PAYER_TESTNET_MNEMONIC}
100102
- FEE_PAYER_MAINNET_MNEMONIC=${FEE_PAYER_MAINNET_MNEMONIC}
101103

0 commit comments

Comments
 (0)