Module mydata_did.v1_0.utils.diddoc

DID Document classes.

Copyright 2017-2019 Government of Canada Public Services and Procurement Canada - buyandsell.gc.ca

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Expand source code
"""
DID Document classes.

Copyright 2017-2019 Government of Canada
Public Services and Procurement Canada - buyandsell.gc.ca

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""


import json
import logging

from typing import List, Sequence, Union

if __name__ == "__main__":
    from mydata_did.v1_0.utils.verification_method import PublicKey, PublicKeyType
    from mydata_did.v1_0.utils.service import Service
    from mydata_did.v1_0.utils.util import canon_did, canon_ref, ok_did, resource, derive_did_type
else:
    from .verification_method import PublicKey, PublicKeyType
    from .service import Service
    from .util import canon_did, canon_ref, ok_did, resource, derive_did_type

LOGGER = logging.getLogger(__name__)


class DIDDoc:
    """
    DID document, grouping a DID with verification keys and services.

    Retains DIDs as raw values (orientated toward indy-facing operations),
    everything else as URIs (oriented toward W3C-facing operations).
    """

    CONTEXT = "https://w3id.org/did/v1"

    def __init__(self, did: str = None) -> None:
        """
        Initialize the DIDDoc instance.

        Retain DID ('id' in DIDDoc context); initialize verification keys
        and services to empty lists.

        Args:
            did: DID for current DIDdoc

        Raises:
            ValueError: for bad input DID.

        """

        # allow specification post-hoc
        self._did = canon_did(did) if did else None
        self._did_type = derive_did_type(did)
        self._pubkey = {}
        self._service = {}
    
    @property
    def did_type(self) -> str:
        return self._did_type

    @property
    def did(self) -> str:
        """Accessor for DID."""

        return self._did

    @did.setter
    def did(self, value: str) -> None:
        """
        Set DID ('id' in DIDDoc context).

        Args:
            value: DID

        Raises:
            ValueError: for bad input DID.

        """

        self._did = canon_did(value) if value else None
        self._did_type = derive_did_type(value)

    @property
    def pubkey(self) -> dict:
        """Accessor for public keys by identifier."""

        return self._pubkey

    @property
    def authnkey(self) -> dict:
        """Accessor for public keys marked as authentication keys, by identifier."""

        return {k: self._pubkey[k] for k in self._pubkey if self._pubkey[k].authn}

    @property
    def service(self) -> dict:
        """Accessor for services by identifier."""

        return self._service

    def set(self, item: Union[Service, PublicKey]) -> "DIDDoc":
        """
        Add or replace service or public key; return current DIDDoc.

        Raises:
            ValueError: if input item is neither service nor public key.

        Args:
            item: service or public key to set

        Returns: the current DIDDoc

        """

        if isinstance(item, Service):
            self.service[item.id] = item
        elif isinstance(item, PublicKey):
            self.pubkey[item.id] = item
        else:
            raise ValueError(
                "Cannot add item {} to DIDDoc on DID {}".format(item, self.did)
            )

    def serialize(self) -> str:
        """
        Dump current object to a JSON-compatible dictionary.

        Returns:
            dict representation of current DIDDoc

        """

        return {
            "@context": DIDDoc.CONTEXT,
            "id": canon_ref(self.did, self.did, did_type=self.did_type),
            "verificationMethod": [pubkey.to_dict() for pubkey in self.pubkey.values()],
            "authentication": [
                {
                    "type": pubkey.type.authn_type,
                    "publicKey": canon_ref(self.did, pubkey.id),
                }
                for pubkey in self.pubkey.values()
                if pubkey.authn
            ],
            "service": [service.to_dict() for service in self.service.values()],
        }

    def to_json(self) -> str:
        """
        Dump current object as json (JSON-LD).

        Returns:
            json representation of current DIDDoc

        """

        return json.dumps(self.serialize())

    def add_service_pubkeys(
        self, service: dict, tags: Union[Sequence[str], str]
    ) -> List[PublicKey]:
        """
        Add public keys specified in service. Return public keys so discovered.

        Args:
            service: service from DID document
            tags: potential tags marking public keys of type of interest
                (the standard is still coalescing)

        Raises:
            ValueError: for public key reference not present in DID document.

        Returns: list of public keys from the document service specification

        """

        rv = []
        for tag in [tags] if isinstance(tags, str) else list(tags):

            for svc_key in service.get(tag, {}):
                canon_key = canon_ref(self.did, svc_key)
                pubkey = None

                if "#" in svc_key:
                    if canon_key in self.pubkey:
                        pubkey = self.pubkey[canon_key]
                    else:  # service key refers to another DID doc
                        LOGGER.debug(
                            "DID document %s has no public key %s", self.did, svc_key
                        )
                        raise ValueError(
                            "DID document {} has no public key {}".format(
                                self.did, svc_key
                            )
                        )
                else:
                    for existing_pubkey in self.pubkey.values():
                        if existing_pubkey.value == svc_key:
                            pubkey = existing_pubkey
                            break
                    else:
                        pubkey = PublicKey(
                            self.did,
                            # industrial-grade uniqueness
                            ident=svc_key[-9:-1],
                            value=svc_key,
                        )
                        self._pubkey[pubkey.id] = pubkey

                if (
                    pubkey and pubkey not in rv
                ):  # perverse case: could specify same key multiple ways; append once
                    rv.append(pubkey)

        return rv

    @classmethod
    def deserialize(cls, did_doc: dict) -> "DIDDoc":
        """
        Construct DIDDoc object from dict representation.

        Args:
            did_doc: DIDDoc dict representation

        Raises:
            ValueError: for bad DID or missing mandatory item.

        Returns: DIDDoc from input json

        """

        rv = None
        if "id" in did_doc:
            rv = DIDDoc(did_doc["id"])
        else:
            # heuristic: get DID to serve as DID document identifier from
            # the first OK-looking public key
            for section in ("verificationMethod", "authentication"):
                if rv is None and section in did_doc:
                    for key_spec in did_doc[section]:
                        try:
                            pubkey_did = canon_did(
                                resource(key_spec.get("id", "")))
                            if ok_did(pubkey_did):
                                rv = DIDDoc(pubkey_did)
                                break
                        except ValueError:  # no identifier here, move on to next
                            break
            if rv is None:
                LOGGER.debug("no identifier in DID document")
                raise ValueError("No identifier in DID document")
        for pubkey in did_doc.get(
            "verificationMethod", {}
        ):  # include all public keys, authentication pubkeys by reference
            pubkey_type = PublicKeyType.get(pubkey["type"])
            authn = any(
                canon_ref(rv.did, ak.get("publicKey", ""))
                == canon_ref(rv.did, pubkey["id"])
                for ak in did_doc.get("authentication", {})
                if isinstance(ak.get("publicKey", None), str)
            )
            key = PublicKey(  # initialization canonicalizes id
                rv.did,
                pubkey["id"],
                pubkey[pubkey_type.specifier],
                pubkey_type,
                canon_did(pubkey["controller"]),
                authn,
            )
            rv.pubkey[key.id] = key

        for akey in did_doc.get(
            "authentication", {}
        ):  # include embedded authentication keys
            if "publicKey" not in akey:  # not yet got it with public keys
                pubkey_type = PublicKeyType.get(akey["type"])
                key = PublicKey(  # initialization canonicalized id
                    rv.did,
                    akey["id"],
                    akey[pubkey_type.specifier],
                    pubkey_type,
                    canon_did(akey["controller"]),
                    True,
                )
                rv.pubkey[key.id] = key

        for service in did_doc.get("service", {}):
            endpoint = service["serviceEndpoint"]
            svc = Service(  # initialization canonicalizes id
                rv.did,
                service.get(
                    "id",
                    canon_ref(
                        rv.did, "assigned-service-{}".format(
                            len(rv.service)), ";"
                    ),
                ),
                service["type"],
                rv.add_service_pubkeys(service, "recipientKeys"),
                rv.add_service_pubkeys(
                    service, ["mediatorKeys", "routingKeys"]),
                canon_ref(rv.did, endpoint,
                          ";") if ";" in endpoint else endpoint,
                service.get("priority", None),
            )
            rv.service[svc.id] = svc

        return rv

    @classmethod
    def from_json(cls, did_doc_json: str) -> "DIDDoc":
        """
        Construct DIDDoc object from json representation.

        Args:
            did_doc_json: DIDDoc json representation

        Returns: DIDDoc from input json

        """

        return cls.deserialize(json.loads(did_doc_json))
    
    def validate(self):
        # FIXME : Code refactor

        # check public key and authentication key is available and if so, a single match item
        if self.pubkey and self.authnkey and len(self.pubkey.values()) == 1 and len(self.authnkey.values()) == 1:
            # check if public key and authentication key match
            if list(self.pubkey.keys())[0] == list(self.authnkey.keys())[0]:
                # check if controller and did and publicKeyBase58 matches
                # check if public key type is Ed25519VerificationKey2018
                public_key: PublicKey = list(self.pubkey.values())[0]
                if public_key.controller == self.did and public_key.type == PublicKeyType.ED25519_SIG_2018:
                    # Optional check, if service is available
                    if self.service:
                        if len(self.service.values()) == 1 and canon_ref(self.did, "didcomm", ";", did_type=self.did_type) == list(self.service.keys())[0]:
                            service: Service = list(self.service.values())[0]
                            if len(service.recip_keys) == 1 and service.recip_keys[0].type == public_key.type and service.recip_keys[0].controller == public_key.controller and service.type == "DIDComm" and service.priority == 0 and service.did == self.did:
                                return True
                    else:
                        return True
        
        return False


    def __str__(self) -> str:
        """Return string representation for abbreviated display."""

        return f"DIDDoc({self.did})"

    def __repr__(self) -> str:
        """Format DIDDoc for logging."""

        return f"<DIDDoc did={self.did}>"


if __name__ == "__main__":
    import json
    from mydata_did.v1_0.utils.diddoc import DIDDoc
    diddoc_json = {
        "@context": "https://w3id.org/did/v1",
        "id": "did:mydata:0:z6MkfiSdYhnLnS6jfwSf2yS2CiwwjZGmFUFL5QbyL2Xu8z2E",
        "verificationMethod": [
            {
                "id": "did:mydata:0:z6MkfiSdYhnLnS6jfwSf2yS2CiwwjZGmFUFL5QbyL2Xu8z2E#1",
                "type": "Ed25519VerificationKey2018",
                "controller": "did:mydata:0:z6MkfiSdYhnLnS6jfwSf2yS2CiwwjZGmFUFL5QbyL2Xu8z2E",
                "publicKeyBase58": "z6MkfiSdYhnLnS6jfwSf2yS2CiwwjZGmFUFL5QbyL2Xu8z2E"
            }
        ],
        "authentication": [
            {
                "type": "Ed25519VerificationKey2018",
                "publicKey": "did:mydata:0:z6MkfiSdYhnLnS6jfwSf2yS2CiwwjZGmFUFL5QbyL2Xu8z2E#1"
            }
        ],
        "service": [
            {
                "id": "did:mydata:0:z6MkfiSdYhnLnS6jfwSf2yS2CiwwjZGmFUFL5QbyL2Xu8z2E;didcomm",
                "type": "DIDComm",
                "priority": 0,
                "recipientKeys": [
                    "z6MkfiSdYhnLnS6jfwSf2yS2CiwwjZGmFUFL5QbyL2Xu8z2E"
                ],
                "serviceEndpoint": "https://ada-agent.example.com/service-x"
            }
        ]
    }
    diddoc_str = json.dumps(diddoc_json)
    diddoc = DIDDoc.from_json(diddoc_str)
    if diddoc.validate():
        print("\nValidation checks passed...")
    else:
        print("\nValidation checks failed...")

Classes

class DIDDoc (did: str = None)

DID document, grouping a DID with verification keys and services.

Retains DIDs as raw values (orientated toward indy-facing operations), everything else as URIs (oriented toward W3C-facing operations).

Initialize the DIDDoc instance.

Retain DID ('id' in DIDDoc context); initialize verification keys and services to empty lists.

Args

did
DID for current DIDdoc

Raises

ValueError
for bad input DID.
Expand source code
class DIDDoc:
    """
    DID document, grouping a DID with verification keys and services.

    Retains DIDs as raw values (orientated toward indy-facing operations),
    everything else as URIs (oriented toward W3C-facing operations).
    """

    CONTEXT = "https://w3id.org/did/v1"

    def __init__(self, did: str = None) -> None:
        """
        Initialize the DIDDoc instance.

        Retain DID ('id' in DIDDoc context); initialize verification keys
        and services to empty lists.

        Args:
            did: DID for current DIDdoc

        Raises:
            ValueError: for bad input DID.

        """

        # allow specification post-hoc
        self._did = canon_did(did) if did else None
        self._did_type = derive_did_type(did)
        self._pubkey = {}
        self._service = {}
    
    @property
    def did_type(self) -> str:
        return self._did_type

    @property
    def did(self) -> str:
        """Accessor for DID."""

        return self._did

    @did.setter
    def did(self, value: str) -> None:
        """
        Set DID ('id' in DIDDoc context).

        Args:
            value: DID

        Raises:
            ValueError: for bad input DID.

        """

        self._did = canon_did(value) if value else None
        self._did_type = derive_did_type(value)

    @property
    def pubkey(self) -> dict:
        """Accessor for public keys by identifier."""

        return self._pubkey

    @property
    def authnkey(self) -> dict:
        """Accessor for public keys marked as authentication keys, by identifier."""

        return {k: self._pubkey[k] for k in self._pubkey if self._pubkey[k].authn}

    @property
    def service(self) -> dict:
        """Accessor for services by identifier."""

        return self._service

    def set(self, item: Union[Service, PublicKey]) -> "DIDDoc":
        """
        Add or replace service or public key; return current DIDDoc.

        Raises:
            ValueError: if input item is neither service nor public key.

        Args:
            item: service or public key to set

        Returns: the current DIDDoc

        """

        if isinstance(item, Service):
            self.service[item.id] = item
        elif isinstance(item, PublicKey):
            self.pubkey[item.id] = item
        else:
            raise ValueError(
                "Cannot add item {} to DIDDoc on DID {}".format(item, self.did)
            )

    def serialize(self) -> str:
        """
        Dump current object to a JSON-compatible dictionary.

        Returns:
            dict representation of current DIDDoc

        """

        return {
            "@context": DIDDoc.CONTEXT,
            "id": canon_ref(self.did, self.did, did_type=self.did_type),
            "verificationMethod": [pubkey.to_dict() for pubkey in self.pubkey.values()],
            "authentication": [
                {
                    "type": pubkey.type.authn_type,
                    "publicKey": canon_ref(self.did, pubkey.id),
                }
                for pubkey in self.pubkey.values()
                if pubkey.authn
            ],
            "service": [service.to_dict() for service in self.service.values()],
        }

    def to_json(self) -> str:
        """
        Dump current object as json (JSON-LD).

        Returns:
            json representation of current DIDDoc

        """

        return json.dumps(self.serialize())

    def add_service_pubkeys(
        self, service: dict, tags: Union[Sequence[str], str]
    ) -> List[PublicKey]:
        """
        Add public keys specified in service. Return public keys so discovered.

        Args:
            service: service from DID document
            tags: potential tags marking public keys of type of interest
                (the standard is still coalescing)

        Raises:
            ValueError: for public key reference not present in DID document.

        Returns: list of public keys from the document service specification

        """

        rv = []
        for tag in [tags] if isinstance(tags, str) else list(tags):

            for svc_key in service.get(tag, {}):
                canon_key = canon_ref(self.did, svc_key)
                pubkey = None

                if "#" in svc_key:
                    if canon_key in self.pubkey:
                        pubkey = self.pubkey[canon_key]
                    else:  # service key refers to another DID doc
                        LOGGER.debug(
                            "DID document %s has no public key %s", self.did, svc_key
                        )
                        raise ValueError(
                            "DID document {} has no public key {}".format(
                                self.did, svc_key
                            )
                        )
                else:
                    for existing_pubkey in self.pubkey.values():
                        if existing_pubkey.value == svc_key:
                            pubkey = existing_pubkey
                            break
                    else:
                        pubkey = PublicKey(
                            self.did,
                            # industrial-grade uniqueness
                            ident=svc_key[-9:-1],
                            value=svc_key,
                        )
                        self._pubkey[pubkey.id] = pubkey

                if (
                    pubkey and pubkey not in rv
                ):  # perverse case: could specify same key multiple ways; append once
                    rv.append(pubkey)

        return rv

    @classmethod
    def deserialize(cls, did_doc: dict) -> "DIDDoc":
        """
        Construct DIDDoc object from dict representation.

        Args:
            did_doc: DIDDoc dict representation

        Raises:
            ValueError: for bad DID or missing mandatory item.

        Returns: DIDDoc from input json

        """

        rv = None
        if "id" in did_doc:
            rv = DIDDoc(did_doc["id"])
        else:
            # heuristic: get DID to serve as DID document identifier from
            # the first OK-looking public key
            for section in ("verificationMethod", "authentication"):
                if rv is None and section in did_doc:
                    for key_spec in did_doc[section]:
                        try:
                            pubkey_did = canon_did(
                                resource(key_spec.get("id", "")))
                            if ok_did(pubkey_did):
                                rv = DIDDoc(pubkey_did)
                                break
                        except ValueError:  # no identifier here, move on to next
                            break
            if rv is None:
                LOGGER.debug("no identifier in DID document")
                raise ValueError("No identifier in DID document")
        for pubkey in did_doc.get(
            "verificationMethod", {}
        ):  # include all public keys, authentication pubkeys by reference
            pubkey_type = PublicKeyType.get(pubkey["type"])
            authn = any(
                canon_ref(rv.did, ak.get("publicKey", ""))
                == canon_ref(rv.did, pubkey["id"])
                for ak in did_doc.get("authentication", {})
                if isinstance(ak.get("publicKey", None), str)
            )
            key = PublicKey(  # initialization canonicalizes id
                rv.did,
                pubkey["id"],
                pubkey[pubkey_type.specifier],
                pubkey_type,
                canon_did(pubkey["controller"]),
                authn,
            )
            rv.pubkey[key.id] = key

        for akey in did_doc.get(
            "authentication", {}
        ):  # include embedded authentication keys
            if "publicKey" not in akey:  # not yet got it with public keys
                pubkey_type = PublicKeyType.get(akey["type"])
                key = PublicKey(  # initialization canonicalized id
                    rv.did,
                    akey["id"],
                    akey[pubkey_type.specifier],
                    pubkey_type,
                    canon_did(akey["controller"]),
                    True,
                )
                rv.pubkey[key.id] = key

        for service in did_doc.get("service", {}):
            endpoint = service["serviceEndpoint"]
            svc = Service(  # initialization canonicalizes id
                rv.did,
                service.get(
                    "id",
                    canon_ref(
                        rv.did, "assigned-service-{}".format(
                            len(rv.service)), ";"
                    ),
                ),
                service["type"],
                rv.add_service_pubkeys(service, "recipientKeys"),
                rv.add_service_pubkeys(
                    service, ["mediatorKeys", "routingKeys"]),
                canon_ref(rv.did, endpoint,
                          ";") if ";" in endpoint else endpoint,
                service.get("priority", None),
            )
            rv.service[svc.id] = svc

        return rv

    @classmethod
    def from_json(cls, did_doc_json: str) -> "DIDDoc":
        """
        Construct DIDDoc object from json representation.

        Args:
            did_doc_json: DIDDoc json representation

        Returns: DIDDoc from input json

        """

        return cls.deserialize(json.loads(did_doc_json))
    
    def validate(self):
        # FIXME : Code refactor

        # check public key and authentication key is available and if so, a single match item
        if self.pubkey and self.authnkey and len(self.pubkey.values()) == 1 and len(self.authnkey.values()) == 1:
            # check if public key and authentication key match
            if list(self.pubkey.keys())[0] == list(self.authnkey.keys())[0]:
                # check if controller and did and publicKeyBase58 matches
                # check if public key type is Ed25519VerificationKey2018
                public_key: PublicKey = list(self.pubkey.values())[0]
                if public_key.controller == self.did and public_key.type == PublicKeyType.ED25519_SIG_2018:
                    # Optional check, if service is available
                    if self.service:
                        if len(self.service.values()) == 1 and canon_ref(self.did, "didcomm", ";", did_type=self.did_type) == list(self.service.keys())[0]:
                            service: Service = list(self.service.values())[0]
                            if len(service.recip_keys) == 1 and service.recip_keys[0].type == public_key.type and service.recip_keys[0].controller == public_key.controller and service.type == "DIDComm" and service.priority == 0 and service.did == self.did:
                                return True
                    else:
                        return True
        
        return False


    def __str__(self) -> str:
        """Return string representation for abbreviated display."""

        return f"DIDDoc({self.did})"

    def __repr__(self) -> str:
        """Format DIDDoc for logging."""

        return f"<DIDDoc did={self.did}>"

Class variables

var CONTEXT

Static methods

def deserialize(did_doc: dict) ‑> DIDDoc

Construct DIDDoc object from dict representation.

Args

did_doc
DIDDoc dict representation

Raises

ValueError
for bad DID or missing mandatory item.

Returns: DIDDoc from input json

Expand source code
@classmethod
def deserialize(cls, did_doc: dict) -> "DIDDoc":
    """
    Construct DIDDoc object from dict representation.

    Args:
        did_doc: DIDDoc dict representation

    Raises:
        ValueError: for bad DID or missing mandatory item.

    Returns: DIDDoc from input json

    """

    rv = None
    if "id" in did_doc:
        rv = DIDDoc(did_doc["id"])
    else:
        # heuristic: get DID to serve as DID document identifier from
        # the first OK-looking public key
        for section in ("verificationMethod", "authentication"):
            if rv is None and section in did_doc:
                for key_spec in did_doc[section]:
                    try:
                        pubkey_did = canon_did(
                            resource(key_spec.get("id", "")))
                        if ok_did(pubkey_did):
                            rv = DIDDoc(pubkey_did)
                            break
                    except ValueError:  # no identifier here, move on to next
                        break
        if rv is None:
            LOGGER.debug("no identifier in DID document")
            raise ValueError("No identifier in DID document")
    for pubkey in did_doc.get(
        "verificationMethod", {}
    ):  # include all public keys, authentication pubkeys by reference
        pubkey_type = PublicKeyType.get(pubkey["type"])
        authn = any(
            canon_ref(rv.did, ak.get("publicKey", ""))
            == canon_ref(rv.did, pubkey["id"])
            for ak in did_doc.get("authentication", {})
            if isinstance(ak.get("publicKey", None), str)
        )
        key = PublicKey(  # initialization canonicalizes id
            rv.did,
            pubkey["id"],
            pubkey[pubkey_type.specifier],
            pubkey_type,
            canon_did(pubkey["controller"]),
            authn,
        )
        rv.pubkey[key.id] = key

    for akey in did_doc.get(
        "authentication", {}
    ):  # include embedded authentication keys
        if "publicKey" not in akey:  # not yet got it with public keys
            pubkey_type = PublicKeyType.get(akey["type"])
            key = PublicKey(  # initialization canonicalized id
                rv.did,
                akey["id"],
                akey[pubkey_type.specifier],
                pubkey_type,
                canon_did(akey["controller"]),
                True,
            )
            rv.pubkey[key.id] = key

    for service in did_doc.get("service", {}):
        endpoint = service["serviceEndpoint"]
        svc = Service(  # initialization canonicalizes id
            rv.did,
            service.get(
                "id",
                canon_ref(
                    rv.did, "assigned-service-{}".format(
                        len(rv.service)), ";"
                ),
            ),
            service["type"],
            rv.add_service_pubkeys(service, "recipientKeys"),
            rv.add_service_pubkeys(
                service, ["mediatorKeys", "routingKeys"]),
            canon_ref(rv.did, endpoint,
                      ";") if ";" in endpoint else endpoint,
            service.get("priority", None),
        )
        rv.service[svc.id] = svc

    return rv
def from_json(did_doc_json: str) ‑> DIDDoc

Construct DIDDoc object from json representation.

Args

did_doc_json
DIDDoc json representation

Returns: DIDDoc from input json

Expand source code
@classmethod
def from_json(cls, did_doc_json: str) -> "DIDDoc":
    """
    Construct DIDDoc object from json representation.

    Args:
        did_doc_json: DIDDoc json representation

    Returns: DIDDoc from input json

    """

    return cls.deserialize(json.loads(did_doc_json))

Instance variables

var authnkey : dict

Accessor for public keys marked as authentication keys, by identifier.

Expand source code
@property
def authnkey(self) -> dict:
    """Accessor for public keys marked as authentication keys, by identifier."""

    return {k: self._pubkey[k] for k in self._pubkey if self._pubkey[k].authn}
var did : str

Accessor for DID.

Expand source code
@property
def did(self) -> str:
    """Accessor for DID."""

    return self._did
var did_type : str
Expand source code
@property
def did_type(self) -> str:
    return self._did_type
var pubkey : dict

Accessor for public keys by identifier.

Expand source code
@property
def pubkey(self) -> dict:
    """Accessor for public keys by identifier."""

    return self._pubkey
var service : dict

Accessor for services by identifier.

Expand source code
@property
def service(self) -> dict:
    """Accessor for services by identifier."""

    return self._service

Methods

def add_service_pubkeys(self, service: dict, tags: Union[Sequence[str], str]) ‑> List[PublicKey]

Add public keys specified in service. Return public keys so discovered.

Args

service
service from DID document
tags
potential tags marking public keys of type of interest (the standard is still coalescing)

Raises

ValueError
for public key reference not present in DID document.

Returns: list of public keys from the document service specification

Expand source code
def add_service_pubkeys(
    self, service: dict, tags: Union[Sequence[str], str]
) -> List[PublicKey]:
    """
    Add public keys specified in service. Return public keys so discovered.

    Args:
        service: service from DID document
        tags: potential tags marking public keys of type of interest
            (the standard is still coalescing)

    Raises:
        ValueError: for public key reference not present in DID document.

    Returns: list of public keys from the document service specification

    """

    rv = []
    for tag in [tags] if isinstance(tags, str) else list(tags):

        for svc_key in service.get(tag, {}):
            canon_key = canon_ref(self.did, svc_key)
            pubkey = None

            if "#" in svc_key:
                if canon_key in self.pubkey:
                    pubkey = self.pubkey[canon_key]
                else:  # service key refers to another DID doc
                    LOGGER.debug(
                        "DID document %s has no public key %s", self.did, svc_key
                    )
                    raise ValueError(
                        "DID document {} has no public key {}".format(
                            self.did, svc_key
                        )
                    )
            else:
                for existing_pubkey in self.pubkey.values():
                    if existing_pubkey.value == svc_key:
                        pubkey = existing_pubkey
                        break
                else:
                    pubkey = PublicKey(
                        self.did,
                        # industrial-grade uniqueness
                        ident=svc_key[-9:-1],
                        value=svc_key,
                    )
                    self._pubkey[pubkey.id] = pubkey

            if (
                pubkey and pubkey not in rv
            ):  # perverse case: could specify same key multiple ways; append once
                rv.append(pubkey)

    return rv
def serialize(self) ‑> str

Dump current object to a JSON-compatible dictionary.

Returns

dict representation of current DIDDoc

Expand source code
def serialize(self) -> str:
    """
    Dump current object to a JSON-compatible dictionary.

    Returns:
        dict representation of current DIDDoc

    """

    return {
        "@context": DIDDoc.CONTEXT,
        "id": canon_ref(self.did, self.did, did_type=self.did_type),
        "verificationMethod": [pubkey.to_dict() for pubkey in self.pubkey.values()],
        "authentication": [
            {
                "type": pubkey.type.authn_type,
                "publicKey": canon_ref(self.did, pubkey.id),
            }
            for pubkey in self.pubkey.values()
            if pubkey.authn
        ],
        "service": [service.to_dict() for service in self.service.values()],
    }
def set(self, item: Union[ServicePublicKey]) ‑> DIDDoc

Add or replace service or public key; return current DIDDoc.

Raises

ValueError
if input item is neither service nor public key.

Args

item
service or public key to set

Returns: the current DIDDoc

Expand source code
def set(self, item: Union[Service, PublicKey]) -> "DIDDoc":
    """
    Add or replace service or public key; return current DIDDoc.

    Raises:
        ValueError: if input item is neither service nor public key.

    Args:
        item: service or public key to set

    Returns: the current DIDDoc

    """

    if isinstance(item, Service):
        self.service[item.id] = item
    elif isinstance(item, PublicKey):
        self.pubkey[item.id] = item
    else:
        raise ValueError(
            "Cannot add item {} to DIDDoc on DID {}".format(item, self.did)
        )
def to_json(self) ‑> str

Dump current object as json (JSON-LD).

Returns

json representation of current DIDDoc

Expand source code
def to_json(self) -> str:
    """
    Dump current object as json (JSON-LD).

    Returns:
        json representation of current DIDDoc

    """

    return json.dumps(self.serialize())
def validate(self)
Expand source code
def validate(self):
    # FIXME : Code refactor

    # check public key and authentication key is available and if so, a single match item
    if self.pubkey and self.authnkey and len(self.pubkey.values()) == 1 and len(self.authnkey.values()) == 1:
        # check if public key and authentication key match
        if list(self.pubkey.keys())[0] == list(self.authnkey.keys())[0]:
            # check if controller and did and publicKeyBase58 matches
            # check if public key type is Ed25519VerificationKey2018
            public_key: PublicKey = list(self.pubkey.values())[0]
            if public_key.controller == self.did and public_key.type == PublicKeyType.ED25519_SIG_2018:
                # Optional check, if service is available
                if self.service:
                    if len(self.service.values()) == 1 and canon_ref(self.did, "didcomm", ";", did_type=self.did_type) == list(self.service.keys())[0]:
                        service: Service = list(self.service.values())[0]
                        if len(service.recip_keys) == 1 and service.recip_keys[0].type == public_key.type and service.recip_keys[0].controller == public_key.controller and service.type == "DIDComm" and service.priority == 0 and service.did == self.did:
                            return True
                else:
                    return True
    
    return False