DID Class

Overview

The DID class implements W3C Decentralized Identifiers (DIDs) as defined in the W3C DID specification. DIDs are a standardized way to identify entities in a decentralized manner, consisting of three components: scheme, method, and method-specific identifier.

Package: keychain.identity.did

from keychain.identity.did import DID

Class Definition

class DID(SerializableObject):
    """W3C Decentralized Identifier implementation."""

Constructor

def __init__(
    self,
    did_string: Optional[str] = None,
    c_pointer: Optional[ctypes.c_void_p] = None,
) -> None

Initialize a DID object from a DID string or C pointer.

Parameters:

  • did_string (str, optional) - DID string to parse (e.g., "did:method:identifier")

  • c_pointer (ctypes.c_void_p, optional) - Existing C pointer to wrap

Example:

# Create from DID string
did = DID("did:example:123456789abcdefghi")

# Create empty DID
empty_did = DID()

# Create from existing C pointer (typically from library functions)
did = DID(c_pointer=existing_pointer)

Properties

scheme

@property
def scheme(self) -> str

Get the DID scheme.

Returns: The scheme component of the DID (usually "did")

method

@property
def method(self) -> str

Get the DID method.

Returns: The method component of the DID

method_specific_id

@property
def method_specific_id(self) -> str

Get the method-specific identifier.

Returns: The method-specific identifier component of the DID

Methods

copy()

def copy(self) -> "DID"

Create a deep copy of this DID.

Returns: New DID object that is a deep copy

Example:

original_did = DID("did:example:123456")
copied_did = original_did.copy()

serialize()

def serialize(self) -> bytes

Serialize the DID to bytes as defined in the W3C specification.

Returns: The DID serialized as UTF-8 encoded bytes

Example:

did = DID("did:example:123456")
serialized = did.serialize()
# Returns: b"did:example:123456"

data_type()

def data_type(self) -> DataType

Get the data type.

Returns: DataType.DID

is_null()

def is_null(self) -> bool

Check if this is a null DID.

Returns: True if this is a null/empty DID, False otherwise

parse_components()

def parse_components(self) -> Tuple[str, str, str]

Parse the DID into its component parts.

Returns: A tuple of (scheme, method, method_specific_id)

Example:

did = DID("did:example:123456")
scheme, method, method_id = did.parse_components()
print(f"Scheme: {scheme}, Method: {method}, ID: {method_id}")
# Output: Scheme: did, Method: example, ID: 123456

Class Methods

from_components(method, method_specific_id, scheme="did")

@classmethod
def from_components(
    cls, method: str, method_specific_id: str, scheme: str = "did"
) -> "DID"

Create a DID from its component parts.

Parameters:

  • method (str) - The DID method

  • method_specific_id (str) - The method-specific identifier

  • scheme (str) - The DID scheme (defaults to "did")

Returns: New DID object constructed from the components

Example:

did = DID.from_components("example", "123456789abcdefghi")
print(str(did))  # Output: did:example:123456789abcdefghi

Comparison Methods

eq(other)

def __eq__(self, other: object) -> bool

Test equality of two DIDs.

Parameters:

  • other (DID) - Another DID object to compare with

Returns: True if the DIDs' component fields test equal, False otherwise

ne(other)

def __ne__(self, other: object) -> bool

Test inequality of two DIDs.

Parameters:

  • other (DID) - Another DID object to compare with

Returns: True if the DIDs are not equal, False otherwise

String Representation

str()

def __str__(self) -> str

String representation of the DID.

Returns: DID as a string (e.g., "did:example:123456")

repr()

def __repr__(self) -> str

Developer representation of the DID.

Returns: Detailed representation showing class and DID string

Example: Working with DIDs

from keychain.identity.did import DID

# Create DIDs from strings
did1 = DID("did:example:123456789abcdefghi")
did2 = DID("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK")

# Create DID from components
did3 = DID.from_components("web", "example.com")

print(f"DID 1: {did1}")
print(f"DID 2: {did2}")
print(f"DID 3: {did3}")

# Parse DID components
scheme, method, method_id = did1.parse_components()
print(f"Scheme: {scheme}")
print(f"Method: {method}")
print(f"Method-specific ID: {method_id}")

# Check individual components
print(f"DID 1 scheme: {did1.scheme}")
print(f"DID 1 method: {did1.method}")
print(f"DID 1 method-specific ID: {did1.method_specific_id}")

# Compare DIDs
if did1 == did2:
    print("DIDs are equal")
else:
    print("DIDs are different")

# Copy DIDs
copied_did = did1.copy()
if did1 == copied_did:
    print("Original and copy are equal")

# Check if DID is null
empty_did = DID()
if empty_did.is_null():
    print("Empty DID is null")

# Serialize DID
serialized = did1.serialize()
print(f"Serialized: {serialized}")

# Recreate from serialized data
reconstructed = DID(serialized.decode('utf-8'))
if did1 == reconstructed:
    print("Serialization and deserialization successful")

Example: DID Validation and Parsing

from keychain.identity.did import DID

def validate_and_parse_did(did_string: str) -> dict:
    """Validate and parse a DID string."""
    try:
        did = DID(did_string)

        if did.is_null():
            return {
                "valid": False,
                "error": "DID is null or empty"
            }

        # Parse components
        scheme, method, method_id = did.parse_components()

        # Basic validation
        if scheme != "did":
            return {
                "valid": False,
                "error": f"Invalid scheme: {scheme} (expected 'did')"
            }

        if not method:
            return {
                "valid": False,
                "error": "Missing DID method"
            }

        if not method_id:
            return {
                "valid": False,
                "error": "Missing method-specific identifier"
            }

        return {
            "valid": True,
            "scheme": scheme,
            "method": method,
            "method_specific_id": method_id,
            "serialized": did.serialize().decode('utf-8')
        }

    except Exception as e:
        return {
            "valid": False,
            "error": f"Failed to parse DID: {str(e)}"
        }

# Test DID validation
test_dids = [
    "did:example:123456789abcdefghi",
    "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
    "did:web:example.com",
    "not:a:valid:did",
    "",
    "did:method:",
    "did::missing_method"
]

for did_string in test_dids:
    result = validate_and_parse_did(did_string)
    print(f"\nDID: {did_string}")
    print(f"Valid: {result['valid']}")

    if result['valid']:
        print(f"  Scheme: {result['scheme']}")
        print(f"  Method: {result['method']}")
        print(f"  Method-specific ID: {result['method_specific_id']}")
        print(f"  Serialized: {result['serialized']}")
    else:
        print(f"  Error: {result['error']}")

Example: DID Registry and Management

from keychain.identity.did import DID
from typing import Dict, List, Optional

class DIDRegistry:
    """Simple DID registry for managing DIDs."""

    def __init__(self):
        self._dids: Dict[str, DID] = {}
        self._aliases: Dict[str, str] = {}

    def register_did(self, did: DID, alias: Optional[str] = None) -> str:
        """Register a DID in the registry."""
        did_string = str(did)
        self._dids[did_string] = did.copy()

        if alias:
            self._aliases[alias] = did_string

        return did_string

    def lookup_did(self, identifier: str) -> Optional[DID]:
        """Look up a DID by string or alias."""
        # Check if it's an alias
        if identifier in self._aliases:
            identifier = self._aliases[identifier]

        # Look up by DID string
        if identifier in self._dids:
            return self._dids[identifier].copy()

        return None

    def list_dids(self, method_filter: Optional[str] = None) -> List[DID]:
        """List all registered DIDs, optionally filtered by method."""
        dids = []

        for did in self._dids.values():
            if method_filter is None or did.method == method_filter:
                dids.append(did.copy())

        return dids

    def get_methods(self) -> List[str]:
        """Get all unique methods in the registry."""
        methods = set()
        for did in self._dids.values():
            methods.add(did.method)
        return sorted(list(methods))

    def remove_did(self, identifier: str) -> bool:
        """Remove a DID from the registry."""
        # Check if it's an alias
        if identifier in self._aliases:
            did_string = self._aliases[identifier]
            del self._aliases[identifier]
            identifier = did_string

        # Remove by DID string
        if identifier in self._dids:
            del self._dids[identifier]
            return True

        return False

# Usage example
def did_registry_example():
    """Demonstrate DID registry usage."""

    registry = DIDRegistry()

    # Create and register some DIDs
    example_did = DID("did:example:123456789abcdefghi")
    key_did = DID("did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK")
    web_did = DID("did:web:example.com")

    # Register DIDs with aliases
    registry.register_did(example_did, "alice")
    registry.register_did(key_did, "bob")
    registry.register_did(web_did, "company")

    print("=== DID Registry Demo ===")

    # List all DIDs
    all_dids = registry.list_dids()
    print(f"Total DIDs registered: {len(all_dids)}")

    # List DIDs by method
    for method in registry.get_methods():
        method_dids = registry.list_dids(method_filter=method)
        print(f"Method '{method}': {len(method_dids)} DIDs")

    # Look up DIDs
    alice_did = registry.lookup_did("alice")
    if alice_did:
        print(f"Alice's DID: {alice_did}")

    # Look up by DID string
    looked_up = registry.lookup_did(str(key_did))
    if looked_up and looked_up == key_did:
        print("DID lookup by string successful")

    # Remove a DID
    if registry.remove_did("company"):
        print("Company DID removed")

    print(f"DIDs remaining: {len(registry.list_dids())}")

if __name__ == "__main__":
    did_registry_example()

Error Handling

DID operations can raise various exceptions:

Example:

from keychain.exceptions import KeychainValidationError

try:
    # Create DID from string
    did = DID("did:example:123456")

    # Access properties
    scheme = did.scheme
    method = did.method
    method_id = did.method_specific_id

    # Serialize DID
    serialized = did.serialize()

    # Compare DIDs
    other_did = DID("did:example:654321")
    are_equal = (did == other_did)

except KeychainValidationError as e:
    print(f"Validation error: {e}")
except Exception as e:
    print(f"Unexpected error: {e}")

See Also