KeychainDID Class

Overview

The KeychainDID class extends the basic DID functionality with keychain-specific metadata such as the public-key infrastructure network ID and the keychain type. This provides network-aware identification for keychain entities.

Package: keychain.identity.keychain_did

from keychain.identity.keychain_did import KeychainDID

Class Definition

class KeychainDID(DID):
    """Keychain-specific Decentralized Identifier implementation."""

Constructor

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

Initialize a KeychainDID object from a DID string, components, or C pointer.

Parameters:

  • did_string (str, optional) - KeychainDID string to parse (e.g., "did:kcn-keychain:encrypt:btc_regtest1:id")

  • keychain_type (KeychainType, optional) - The keychain type (encrypt or sign)

  • network (str, optional) - The network identifier (e.g., "btc_regtest1")

  • network_specific_id (str, optional) - The network-specific identifier

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

Example:

from keychain.constants import KeychainType

# Create from KeychainDID string
keychain_did = KeychainDID("did:kcn-keychain:encrypt:btc_regtest1:abc123")

# Create from components
keychain_did = KeychainDID(
    keychain_type=KeychainType.ENCRYPT,
    network="btc_regtest1",
    network_specific_id="abc123"
)

# Create empty KeychainDID
empty_did = KeychainDID()

Properties

Keychain-Specific Properties

keychain_type

@property
def keychain_type(self) -> KeychainType

Get the keychain type.

Returns: KeychainType enumeration value (ENCRYPT or SIGN)

keychain_type_str

@property
def keychain_type_str(self) -> str

Get the keychain type as a string.

Returns: The keychain type as a string ("encrypt" or "sign")

network

@property
def network(self) -> str

Get the network identifier.

Returns: The network identifier (e.g., "btc_regtest1")

network_specific_id

@property
def network_specific_id(self) -> str

Get the network-specific identifier.

Returns: The network-specific identifier

Inherited DID 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 (usually "kcn-keychain")

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) -> "KeychainDID"

Create a deep copy of this KeychainDID.

Returns: New KeychainDID object that is a deep copy

serialize()

def serialize(self) -> bytes

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

Returns: The KeychainDID serialized as UTF-8 encoded bytes

Example:

keychain_did = KeychainDID(
    keychain_type=KeychainType.ENCRYPT,
    network="btc_regtest1",
    network_specific_id="abc123"
)
serialized = keychain_did.serialize()
# Returns: b"did:kcn-keychain:encrypt:btc_regtest1:abc123"

data_type()

def data_type(self) -> DataType

Get the data type.

Returns: DataType.DID (keychain DIDs use the same data type as regular DIDs)

is_null()

def is_null(self) -> bool

Check if this is a null KeychainDID.

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

parse_keychain_components()

def parse_keychain_components(self) -> Tuple[KeychainType, str, str]

Parse the KeychainDID into its keychain-specific component parts.

Returns: A tuple of (keychain_type, network, network_specific_id)

Example:

keychain_did = KeychainDID("did:kcn-keychain:sign:btc_regtest1:xyz789")
kc_type, network, net_id = keychain_did.parse_keychain_components()
print(f"Type: {kc_type}, Network: {network}, ID: {net_id}")

parse_components()

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

Parse the KeychainDID into its DID component parts.

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

Class Methods

from_keychain_components(keychain_type, network, network_specific_id)

@classmethod
def from_keychain_components(
    cls, keychain_type: KeychainType, network: str, network_specific_id: str
) -> "KeychainDID"

Create a KeychainDID from its component parts.

Parameters:

  • keychain_type (KeychainType) - The keychain type (encrypt or sign)

  • network (str) - The network identifier

  • network_specific_id (str) - The network-specific identifier

Returns: New KeychainDID object constructed from the components

Example:

from keychain.constants import KeychainType

keychain_did = KeychainDID.from_keychain_components(
    KeychainType.SIGN,
    "btc_regtest1",
    "def456"
)
print(str(keychain_did))
# Output: did:kcn-keychain:sign:btc_regtest1:def456

Comparison Methods

eq(other)

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

Test equality of two KeychainDIDs.

Parameters:

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

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

ne(other)

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

Test inequality of two KeychainDIDs.

Parameters:

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

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

String Representation

str()

def __str__(self) -> str

String representation of the KeychainDID.

Returns: KeychainDID as a string (e.g., "did:kcn-keychain:encrypt:btc_regtest1:abc123")

repr()

def __repr__(self) -> str

Developer representation of the KeychainDID.

Returns: Detailed representation showing class and KeychainDID string

Example: Working with KeychainDIDs

from keychain.identity.keychain_did import KeychainDID
from keychain.constants import KeychainType

# Create KeychainDIDs for different purposes
encrypt_did = KeychainDID.from_keychain_components(
    KeychainType.ENCRYPT,
    "btc_regtest1",
    "encrypt_abc123"
)

sign_did = KeychainDID.from_keychain_components(
    KeychainType.SIGN,
    "btc_regtest1",
    "sign_xyz789"
)

print(f"Encryption DID: {encrypt_did}")
print(f"Signature DID: {sign_did}")

# Parse KeychainDID components
print("\n=== Encryption DID Analysis ===")
print(f"Scheme: {encrypt_did.scheme}")
print(f"Method: {encrypt_did.method}")
print(f"Method-specific ID: {encrypt_did.method_specific_id}")
print(f"Keychain Type: {encrypt_did.keychain_type_str}")
print(f"Network: {encrypt_did.network}")
print(f"Network-specific ID: {encrypt_did.network_specific_id}")

# Parse using dedicated methods
kc_type, network, net_id = encrypt_did.parse_keychain_components()
print(f"\nParsed components:")
print(f"  Keychain Type: {kc_type}")
print(f"  Network: {network}")
print(f"  Network ID: {net_id}")

# Create from string and verify round-trip
did_string = str(encrypt_did)
reconstructed = KeychainDID(did_string)

if encrypt_did == reconstructed:
    print("\nāœ“ String serialization/deserialization successful")

# Compare different KeychainDIDs
if encrypt_did != sign_did:
    print("āœ“ Different KeychainDIDs are not equal")

# Copy KeychainDID
copied_did = encrypt_did.copy()
if encrypt_did == copied_did:
    print("āœ“ Copy operation successful")

Example: Network-Aware DID Management

from keychain.identity.keychain_did import KeychainDID
from keychain.constants import KeychainType
from typing import Dict, List, Tuple

class NetworkDIDManager:
    """Manage KeychainDIDs across different networks."""

    def __init__(self):
        self._dids_by_network: Dict[str, List[KeychainDID]] = {}
        self._dids_by_type: Dict[KeychainType, List[KeychainDID]] = {}

    def register_keychain_did(self, keychain_did: KeychainDID) -> None:
        """Register a KeychainDID in the manager."""
        # Group by network
        network = keychain_did.network
        if network not in self._dids_by_network:
            self._dids_by_network[network] = []
        self._dids_by_network[network].append(keychain_did.copy())

        # Group by keychain type
        kc_type = keychain_did.keychain_type
        if kc_type not in self._dids_by_type:
            self._dids_by_type[kc_type] = []
        self._dids_by_type[kc_type].append(keychain_did.copy())

    def get_dids_for_network(self, network: str) -> List[KeychainDID]:
        """Get all KeychainDIDs for a specific network."""
        return self._dids_by_network.get(network, []).copy()

    def get_dids_for_type(self, keychain_type: KeychainType) -> List[KeychainDID]:
        """Get all KeychainDIDs for a specific keychain type."""
        return self._dids_by_type.get(keychain_type, []).copy()

    def get_networks(self) -> List[str]:
        """Get all registered networks."""
        return list(self._dids_by_network.keys())

    def get_keychain_types(self) -> List[KeychainType]:
        """Get all registered keychain types."""
        return list(self._dids_by_type.keys())

    def find_matching_pair(self, network: str) -> Tuple[KeychainDID, KeychainDID]:
        """Find a matching encrypt/sign pair for a network."""
        network_dids = self.get_dids_for_network(network)

        encrypt_did = None
        sign_did = None

        for did in network_dids:
            if did.keychain_type == KeychainType.ENCRYPT:
                encrypt_did = did
            elif did.keychain_type == KeychainType.SIGN:
                sign_did = did

        if encrypt_did and sign_did:
            return (encrypt_did, sign_did)
        else:
            raise ValueError(f"No matching encrypt/sign pair found for network {network}")

    def get_statistics(self) -> Dict:
        """Get statistics about registered DIDs."""
        total_dids = sum(len(dids) for dids in self._dids_by_network.values())

        stats = {
            "total_dids": total_dids,
            "networks": len(self._dids_by_network),
            "keychain_types": len(self._dids_by_type),
            "by_network": {},
            "by_type": {}
        }

        for network, dids in self._dids_by_network.items():
            stats["by_network"][network] = len(dids)

        for kc_type, dids in self._dids_by_type.items():
            stats["by_type"][kc_type.name] = len(dids)

        return stats

# Usage example
def network_did_manager_example():
    """Demonstrate network-aware DID management."""

    manager = NetworkDIDManager()

    # Create KeychainDIDs for different networks
    networks = ["btc_regtest1", "btc_testnet", "btc_mainnet"]

    for i, network in enumerate(networks):
        # Create encrypt and sign DIDs for each network
        encrypt_did = KeychainDID.from_keychain_components(
            KeychainType.ENCRYPT,
            network,
            f"encrypt_id_{i}"
        )

        sign_did = KeychainDID.from_keychain_components(
            KeychainType.SIGN,
            network,
            f"sign_id_{i}"
        )

        manager.register_keychain_did(encrypt_did)
        manager.register_keychain_did(sign_did)

    print("=== Network DID Manager Demo ===")

    # Get statistics
    stats = manager.get_statistics()
    print(f"Total DIDs: {stats['total_dids']}")
    print(f"Networks: {stats['networks']}")
    print(f"Keychain Types: {stats['keychain_types']}")

    print("\nDIDs by network:")
    for network, count in stats["by_network"].items():
        print(f"  {network}: {count} DIDs")

    print("\nDIDs by type:")
    for kc_type, count in stats["by_type"].items():
        print(f"  {kc_type}: {count} DIDs")

    # Find matching pairs
    for network in networks:
        try:
            encrypt_did, sign_did = manager.find_matching_pair(network)
            print(f"\n{network} pair:")
            print(f"  Encrypt: {encrypt_did}")
            print(f"  Sign: {sign_did}")
        except ValueError as e:
            print(f"\n{network}: {e}")

    # Get DIDs for specific network
    regtest_dids = manager.get_dids_for_network("btc_regtest1")
    print(f"\nRegtest DIDs: {len(regtest_dids)}")
    for did in regtest_dids:
        print(f"  {did.keychain_type_str}: {did}")

if __name__ == "__main__":
    network_did_manager_example()

Example: KeychainDID Utilities

from keychain.identity.keychain_did import KeychainDID
from keychain.constants import KeychainType

class KeychainDIDUtils:
    """Utility functions for KeychainDID operations."""

    @staticmethod
    def validate_keychain_did(did_string: str) -> dict:
        """Validate a KeychainDID string."""
        try:
            keychain_did = KeychainDID(did_string)

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

            # Validate expected method
            if keychain_did.method != "kcn-keychain":
                return {
                    "valid": False,
                    "error": f"Invalid method: {keychain_did.method} (expected 'kcn-keychain')"
                }

            # Parse components
            kc_type, network, net_id = keychain_did.parse_keychain_components()

            # Validate components
            if not network:
                return {
                    "valid": False,
                    "error": "Missing network identifier"
                }

            if not net_id:
                return {
                    "valid": False,
                    "error": "Missing network-specific identifier"
                }

            return {
                "valid": True,
                "keychain_type": kc_type,
                "keychain_type_str": keychain_did.keychain_type_str,
                "network": network,
                "network_specific_id": net_id,
                "scheme": keychain_did.scheme,
                "method": keychain_did.method,
                "serialized": keychain_did.serialize().decode('utf-8')
            }

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

    @staticmethod
    def create_keychain_did_pair(network: str, base_id: str) -> Tuple[KeychainDID, KeychainDID]:
        """Create a matching encrypt/sign KeychainDID pair."""
        encrypt_did = KeychainDID.from_keychain_components(
            KeychainType.ENCRYPT,
            network,
            f"{base_id}_encrypt"
        )

        sign_did = KeychainDID.from_keychain_components(
            KeychainType.SIGN,
            network,
            f"{base_id}_sign"
        )

        return (encrypt_did, sign_did)

    @staticmethod
    def extract_network_info(keychain_did: KeychainDID) -> dict:
        """Extract network information from a KeychainDID."""
        return {
            "network": keychain_did.network,
            "network_specific_id": keychain_did.network_specific_id,
            "keychain_type": keychain_did.keychain_type,
            "keychain_type_str": keychain_did.keychain_type_str,
            "full_did": str(keychain_did)
        }

# Usage example
def keychain_did_utils_example():
    """Demonstrate KeychainDID utility functions."""

    print("=== KeychainDID Utilities Demo ===")

    # Test validation
    test_dids = [
        "did:kcn-keychain:encrypt:btc_regtest1:abc123",
        "did:kcn-keychain:sign:btc_testnet:xyz789",
        "did:example:invalid",
        "did:kcn-keychain:encrypt:",
        ""
    ]

    print("DID Validation Tests:")
    for did_string in test_dids:
        result = KeychainDIDUtils.validate_keychain_did(did_string)
        print(f"\nDID: {did_string}")
        print(f"Valid: {result['valid']}")

        if result['valid']:
            print(f"  Type: {result['keychain_type_str']}")
            print(f"  Network: {result['network']}")
            print(f"  Network ID: {result['network_specific_id']}")
        else:
            print(f"  Error: {result['error']}")

    # Create DID pairs
    print("\n=== DID Pair Creation ===")
    encrypt_did, sign_did = KeychainDIDUtils.create_keychain_did_pair("btc_mainnet", "user123")

    print(f"Encrypt DID: {encrypt_did}")
    print(f"Sign DID: {sign_did}")

    # Extract network info
    print("\n=== Network Information ===")
    for did in [encrypt_did, sign_did]:
        info = KeychainDIDUtils.extract_network_info(did)
        print(f"\n{info['keychain_type_str'].title()} DID:")
        print(f"  Network: {info['network']}")
        print(f"  Network ID: {info['network_specific_id']}")
        print(f"  Full DID: {info['full_did']}")

if __name__ == "__main__":
    keychain_did_utils_example()

Error Handling

KeychainDID operations can raise various exceptions:

Example:

from keychain.exceptions import KeychainValidationError
from keychain.constants import KeychainType

try:
    # Create KeychainDID from string
    keychain_did = KeychainDID("did:kcn-keychain:encrypt:btc_regtest1:abc123")

    # Access properties
    kc_type = keychain_did.keychain_type
    network = keychain_did.network
    net_id = keychain_did.network_specific_id

    # Create from components
    new_did = KeychainDID.from_keychain_components(
        KeychainType.SIGN,
        "btc_testnet",
        "xyz789"
    )

    # Serialize
    serialized = keychain_did.serialize()

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

See Also