# Aca-Py Did:Web and key rotation
#### What support does Aca-py provide currently for did:web
Open PR: https://github.com/hyperledger/aries-cloudagent-python/pull/1143
Ultimately we need to:
- create wallet with a did:web did
- sign and issue json-ld cred using ed25519 signature
What High level changes in aca-py would we need in order to achieve that?
#### Response
ACA-Py Currently supports only resolving did:web DIDs.
To add support for creating did:web DIDs, we can use the DID Methods that we recently made extendable through plugins or by directly adding the new method type alongside other builtin methods. You can find the builtin methods and the DID Method registry in `aries_cloudagent/wallet/did_method.py`.
##### Plugin Approach
To register a new DID Method via plugin, this would look something like:
```python
# My plugin __init__.py
# Some imports elided
from aries_cloudagent.wallet.did_method import DIDMethods, DIDMethod
from aries_cloudagent.wallet.key_type import ED25519
WEB = DIDMethod(name="web", key_types=[ED25519])
async def setup(context: InjectionContext):
methods = context.inject(DIDMethods)
methods.register(WEB)
```
We can then register a new admin route that creates a DID and inserts it into the Askar wallet, similar to the `create_local_did` method found in `aries_cloudagent/wallet/askar.py`.
```python
# Exact structure not shown
# Imports not shown
async def create_did_web(
self,
did: str,
seed: str = None,
metadata: dict = None,
) -> DIDInfo:
"""
Create and store a new did:web DID.
Args:
did: The DID to create
seed: Optional seed to use for DID
metadata: Metadata to store with DID
Returns:
A `DIDInfo` instance representing the created DID
Raises:
WalletDuplicateError: If the DID already exists in the wallet
WalletError: If there is another backend error
"""
if not metadata:
metadata = {}
try:
keypair = _create_keypair(key_type, seed)
verkey_bytes = keypair.get_public_bytes()
verkey = bytes_to_b58(verkey_bytes)
try:
await self._session.handle.insert_key(
verkey, keypair, metadata=json.dumps(metadata)
)
except AskarError as err:
if err.code == AskarErrorCode.DUPLICATE:
# update metadata?
pass
else:
raise WalletError("Error inserting key") from err
item = await self._session.handle.fetch(CATEGORY_DID, did, for_update=True)
if item:
did_info = item.value_json
if did_info.get("verkey") != verkey:
raise WalletDuplicateError("DID already present in wallet")
if did_info.get("metadata") != metadata:
did_info["metadata"] = metadata
await self._session.handle.replace(
CATEGORY_DID, did, value_json=did_info, tags=item.tags
)
else:
await self._session.handle.insert(
CATEGORY_DID,
did,
value_json={
"did": did,
"method": WEB.method_name,
"verkey": verkey,
"verkey_type": key_type.key_type,
"metadata": metadata,
},
tags={
"method": WEB.method_name,
"verkey": verkey,
"verkey_type": key_type.key_type,
},
)
except AskarError as err:
raise WalletError("Error when creating local DID") from err
return DIDInfo(
did=did, verkey=verkey, metadata=metadata, method=WEB, key_type=ED25519
)
```
##### Builtin Approach
First, we would need to add the DID Method to the set of builtin methods:
```python
# aries_cloudagent/wallet/did_method.py
#...
SOV = ...
KEY = ...
WEB = DIDMethod(name="web", key_types=[ED25519])
class DIDMethods:
def __init__(self) -> None:
self._registry = {
SOV.method_name: SOV,
KEY.method_name: KEY,
WEB.method_name: WEB,
# ...
```
Subsequently, some hard-coded method support restrictions in Askar storage would need to be relaxed. In `aries_cloudagent/wallet/askar.py` the `create_local_did` method has this check:
```python
if method not in [SOV, KEY]:
raise WalletError(
f"Unsupported DID method for askar storage: {method.method_name}"
)
```
Also, the creation of the DID string itself would need attention. The DID string is expected to either be provided or generated during local did creation. For DID Key and DID Sov, the generation steps are below:
```python
# aries_cloudagent/wallet/askar.py:208
if method == KEY:
did = DIDKey.from_public_key(verkey_bytes, key_type).did
elif not did:
did = bytes_to_b58(verkey_bytes[:16])
```
The `POST /wallet/did/create` endpoint does not support passing in the DID string. The route would need to be modified to allow specifying this parameter and passing it to `create_local_did`.
You should then be able to create a DID of method did:web by calling the `POST /wallet/did/create` endpoint, specifying `web` as the method type (Ed25519 is the default key type value but you can also explicitly add that to the request, too).
This will only create the DID in the wallet and will of course not publish the information anywhere; the controller would need to make the DID Doc available in accordance with the did:web method specification.
##### Credential Issuance
Signing JSON-LD Credentials with Ed25519 keys is already implemented in ACA-Py. To enable signing with a did:web DID, all that should be required is specifying the DID created in the last step as the issuer of the credential.
#### Key Rotation
Public DID key rotation will also produce ledger specific tasks/txn. correct?
What happens at key-rotation, are old key-pair discarded? if not, can they still be used?
Rotate key without updating ledger? use a non publid did endpoint ```/did/local/rotate-keypair```
It seems that there is no endpoint to retrieve DidDoc for a given Did. Correct?
- Can this be done via plugin?
- implications
- effort
(fyi, idea is to automate, via a controller, DidDoc publishing for did:web)
#### Response
> Public DID key rotation will also produce ledger specific tasks/txn. correct?
> What happens at key-rotation, are old key-pair discarded? if not, can they still be used?
Correct.
For did:web, We would need to have the controller follow the did:wed method specification for [updating](https://w3c-ccg.github.io/did-method-web/#update) public key-pair.
In the case of did:sov, from the ledger perspective, a DID has the public keys written to the ledger, and requires ledger transactions to update them. During key rotation a holder must use the private keys of the original DID verkey to create and sign the transaction to rotate the keys. The old public keys remain on the ledger and are accessible publicly and continue to be used to validate the origin of transactions that occurred from the DID before the time the keys were rotated. One could still use them, but a decentralized governance framework would direct that use.
From the ACA-Py Perspective, the keys are not deleted on rotation. They still exist in the wallet but are no longer associated with the DID. They can continue to be used assuming the operation only uses the key and does not require associate with a DID or connection.
> Rotate key without updating ledger? use a non public did endpoint `/did/local/rotate-keypair`
ACA-Py does not permit rotating a DID's key-pair for a DID that has been posted to the ledger through the `PATCH /wallet/did/local/rotate-keypair` endpoint. To rotate a DID that has been posted, the `PATCH /ledger/rotate-public-did-keypair` endpoint must be used.
If using a did:web DID, the `PATCH /wallet/did/local/rotate-keypair` endpoint would work to rotate keys as long as the metadata on the DID does not report that it has been "posted" to a network.
> It seems that there is no endpoint to retrieve DidDoc for a given Did. Correct?
The `GET /resolver/resolve` endpoint is the way to retrieve a DID Doc for a given DID. Granted this does not "locally" resolve a DID already known to and stored by the wallet and will always follow the resolution process outlined for the registered DID Methods.
If desired, it is possible to write a DID resolution plugin that "resolved" DIDs from the local wallet (if that is what is meant by your question). We might need more details on intent to assess the implications and effort of this.