PersonaDID Class

Overview

The PersonaDID class combines two keychain DIDs since personas track two keychains - one for encryption and one for signature operations. This provides a unified identifier that encompasses both cryptographic capabilities of a persona.

Package: keychain.identity.persona_did

from keychain.identity.persona_did import PersonaDID

Class Definition

class PersonaDID(DID):
    """Persona Decentralized Identifier implementation."""

Constructor

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

Initialize a PersonaDID object from a DID string, keychain DIDs, or C pointer.

Parameters:

  • did_string (str, optional) - PersonaDID string to parse (W3C DID format)

  • encr_keychain_did (KeychainDID, optional) - The encryption keychain DID

  • sign_keychain_did (KeychainDID, optional) - The signature keychain DID

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

Example:

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

# Create from PersonaDID string
persona_did = PersonaDID("did:kcn-persona:alice.company.com")

# Create from keychain DIDs
encrypt_did = KeychainDID.from_keychain_components(
    KeychainType.ENCRYPT, "btc_regtest1", "encrypt_abc123"
)
sign_did = KeychainDID.from_keychain_components(
    KeychainType.SIGN, "btc_regtest1", "sign_xyz789"
)
persona_did = PersonaDID(
    encr_keychain_did=encrypt_did,
    sign_keychain_did=sign_did
)

# Create empty PersonaDID
empty_did = PersonaDID()

Properties

Keychain DID Properties

encr_keychain_did

@property
def encr_keychain_did(self) -> KeychainDID

Get the encryption keychain DID.

Returns: The encryption KeychainDID component

sign_keychain_did

@property
def sign_keychain_did(self) -> KeychainDID

Get the signature keychain DID.

Returns: The signature KeychainDID component

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

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

Create a deep copy of this PersonaDID.

Returns: New PersonaDID object that is a deep copy

serialize()

def serialize(self) -> bytes

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

Returns: The PersonaDID serialized as UTF-8 encoded bytes

Example:

persona_did = PersonaDID("did:kcn-persona:alice.company.com")
serialized = persona_did.serialize()
# Returns: b"did:kcn-persona:alice.company.com"

data_type()

def data_type(self) -> DataType

Get the data type.

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

is_null()

def is_null(self) -> bool

Check if this is a null PersonaDID.

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

parse_keychain_dids()

def parse_keychain_dids(self) -> Tuple[KeychainDID, KeychainDID]

Parse the PersonaDID into its keychain DID components.

Returns: A tuple of (encryption_keychain_did, signature_keychain_did)

Example:

persona_did = PersonaDID(encr_keychain_did=encrypt_did, sign_keychain_did=sign_did)
encr_did, sign_did = persona_did.parse_keychain_dids()
print(f"Encryption DID: {encr_did}")
print(f"Signature DID: {sign_did}")

parse_components()

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

Parse the PersonaDID into its DID component parts.

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

Class Methods

from_keychain_dids(encr_keychain_did, sign_keychain_did)

@classmethod
def from_keychain_dids(
    cls, encr_keychain_did: KeychainDID, sign_keychain_did: KeychainDID
) -> "PersonaDID"

Create a PersonaDID from two keychain DIDs.

Parameters:

  • encr_keychain_did (KeychainDID) - The encryption keychain DID

  • sign_keychain_did (KeychainDID) - The signature keychain DID

Returns: New PersonaDID object constructed from the keychain DIDs

Example:

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

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

persona_did = PersonaDID.from_keychain_dids(encrypt_did, sign_did)
print(str(persona_did))

Comparison Methods

eq(other)

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

Test equality of two PersonaDIDs.

Parameters:

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

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

ne(other)

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

Test inequality of two PersonaDIDs.

Parameters:

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

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

String Representation

str()

def __str__(self) -> str

String representation of the PersonaDID.

Returns: PersonaDID as a string (e.g., "did:kcn-persona:alice.company.com")

repr()

def __repr__(self) -> str

Developer representation of the PersonaDID.

Returns: Detailed representation showing class and PersonaDID string

Example: Working with PersonaDIDs

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

# Create keychain DIDs for encryption and signing
encrypt_did = KeychainDID.from_keychain_components(
    KeychainType.ENCRYPT,
    "btc_regtest1",
    "alice_encrypt_key"
)

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

# Create PersonaDID from keychain DIDs
persona_did = PersonaDID.from_keychain_dids(encrypt_did, sign_did)

print(f"Persona DID: {persona_did}")

# Parse PersonaDID components
print("\n=== Persona DID Analysis ===")
print(f"Scheme: {persona_did.scheme}")
print(f"Method: {persona_did.method}")
print(f"Method-specific ID: {persona_did.method_specific_id}")

# Access keychain DIDs
encr_did = persona_did.encr_keychain_did
sign_did = persona_did.sign_keychain_did

print(f"\nEncryption DID: {encr_did}")
print(f"  Type: {encr_did.keychain_type_str}")
print(f"  Network: {encr_did.network}")
print(f"  Network ID: {encr_did.network_specific_id}")

print(f"\nSignature DID: {sign_did}")
print(f"  Type: {sign_did.keychain_type_str}")
print(f"  Network: {sign_did.network}")
print(f"  Network ID: {sign_did.network_specific_id}")

# Parse keychain DIDs
encr_did_parsed, sign_did_parsed = persona_did.parse_keychain_dids()
print(f"\nParsed encryption DID: {encr_did_parsed}")
print(f"Parsed signature DID: {sign_did_parsed}")

# Verify equality
if encr_did == encr_did_parsed and sign_did == sign_did_parsed:
    print("✓ Keychain DID parsing successful")

# Create from string and verify round-trip
did_string = str(persona_did)
reconstructed = PersonaDID(did_string)

if persona_did == reconstructed:
    print("✓ String serialization/deserialization successful")

# Copy PersonaDID
copied_did = persona_did.copy()
if persona_did == copied_did:
    print("✓ Copy operation successful")

Example: Persona Identity Management

from keychain.identity.persona_did import PersonaDID
from keychain.identity.keychain_did import KeychainDID
from keychain.constants import KeychainType
from typing import Dict, List, Optional

class PersonaIdentityManager:
    """Manage persona identities and their associated DIDs."""

    def __init__(self):
        self._personas: Dict[str, PersonaDID] = {}
        self._by_network: Dict[str, List[str]] = {}

    def register_persona(self, name: str, persona_did: PersonaDID) -> None:
        """Register a persona identity."""
        self._personas[name] = persona_did.copy()

        # Index by network
        encr_did = persona_did.encr_keychain_did
        network = encr_did.network

        if network not in self._by_network:
            self._by_network[network] = []

        if name not in self._by_network[network]:
            self._by_network[network].append(name)

    def get_persona_did(self, name: str) -> Optional[PersonaDID]:
        """Get a persona DID by name."""
        if name in self._personas:
            return self._personas[name].copy()
        return None

    def get_personas_for_network(self, network: str) -> List[str]:
        """Get all persona names for a specific network."""
        return self._by_network.get(network, []).copy()

    def create_persona_from_components(
        self,
        name: str,
        network: str,
        base_id: str
    ) -> PersonaDID:
        """Create a new persona DID from components."""
        # Create keychain DIDs
        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"
        )

        # Create persona DID
        persona_did = PersonaDID.from_keychain_dids(encrypt_did, sign_did)

        # Register it
        self.register_persona(name, persona_did)

        return persona_did

    def get_persona_network_info(self, name: str) -> Optional[Dict]:
        """Get network information for a persona."""
        persona_did = self.get_persona_did(name)
        if not persona_did:
            return None

        encr_did = persona_did.encr_keychain_did
        sign_did = persona_did.sign_keychain_did

        return {
            "name": name,
            "persona_did": str(persona_did),
            "network": encr_did.network,
            "encryption_did": str(encr_did),
            "signature_did": str(sign_did),
            "encryption_network_id": encr_did.network_specific_id,
            "signature_network_id": sign_did.network_specific_id
        }

    def list_personas(self) -> List[str]:
        """List all registered persona names."""
        return list(self._personas.keys())

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

    def export_persona_info(self) -> Dict:
        """Export information about all personas."""
        export_data = {
            "personas": {},
            "by_network": self._by_network.copy(),
            "total_personas": len(self._personas)
        }

        for name, persona_did in self._personas.items():
            info = self.get_persona_network_info(name)
            export_data["personas"][name] = info

        return export_data

# Usage example
def persona_identity_manager_example():
    """Demonstrate persona identity management."""

    manager = PersonaIdentityManager()

    print("=== Persona Identity Manager Demo ===")

    # Create personas for different networks
    personas = [
        ("alice", "btc_regtest1", "alice_keys"),
        ("bob", "btc_regtest1", "bob_keys"),
        ("charlie", "btc_testnet", "charlie_keys"),
        ("diana", "btc_mainnet", "diana_keys")
    ]

    for name, network, base_id in personas:
        persona_did = manager.create_persona_from_components(name, network, base_id)
        print(f"Created persona '{name}': {persona_did}")

    # List personas by network
    print(f"\n=== Personas by Network ===")
    for network in manager.get_networks():
        network_personas = manager.get_personas_for_network(network)
        print(f"{network}: {', '.join(network_personas)}")

    # Get detailed information for each persona
    print(f"\n=== Persona Details ===")
    for name in manager.list_personas():
        info = manager.get_persona_network_info(name)
        if info:
            print(f"\n{info['name']}:")
            print(f"  Persona DID: {info['persona_did']}")
            print(f"  Network: {info['network']}")
            print(f"  Encryption DID: {info['encryption_did']}")
            print(f"  Signature DID: {info['signature_did']}")

    # Export all persona information
    export_data = manager.export_persona_info()
    print(f"\n=== Export Summary ===")
    print(f"Total personas: {export_data['total_personas']}")
    print(f"Networks: {list(export_data['by_network'].keys())}")

if __name__ == "__main__":
    persona_identity_manager_example()

Example: PersonaDID Validation and Analysis

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

class PersonaDIDAnalyzer:
    """Utility class for PersonaDID analysis and validation."""

    @staticmethod
    def validate_persona_did(did_string: str) -> dict:
        """Validate a PersonaDID string."""
        try:
            persona_did = PersonaDID(did_string)

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

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

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

            # Get keychain DIDs
            try:
                encr_did = persona_did.encr_keychain_did
                sign_did = persona_did.sign_keychain_did

                # Validate keychain DIDs
                if encr_did.keychain_type != KeychainType.ENCRYPT:
                    return {
                        "valid": False,
                        "error": f"Invalid encryption keychain type: {encr_did.keychain_type}"
                    }

                if sign_did.keychain_type != KeychainType.SIGN:
                    return {
                        "valid": False,
                        "error": f"Invalid signature keychain type: {sign_did.keychain_type}"
                    }

                # Check if both DIDs are on the same network
                if encr_did.network != sign_did.network:
                    return {
                        "valid": False,
                        "error": f"Network mismatch: encrypt={encr_did.network}, sign={sign_did.network}"
                    }

                return {
                    "valid": True,
                    "scheme": scheme,
                    "method": method,
                    "method_specific_id": method_id,
                    "network": encr_did.network,
                    "encryption_did": str(encr_did),
                    "signature_did": str(sign_did),
                    "serialized": persona_did.serialize().decode('utf-8')
                }

            except Exception as e:
                return {
                    "valid": False,
                    "error": f"Failed to access keychain DIDs: {str(e)}"
                }

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

    @staticmethod
    def compare_persona_dids(did1: PersonaDID, did2: PersonaDID) -> dict:
        """Compare two PersonaDIDs and provide detailed analysis."""
        analysis = {
            "equal": did1 == did2,
            "same_network": False,
            "same_method": False,
            "differences": []
        }

        # Compare basic DID properties
        if did1.scheme != did2.scheme:
            analysis["differences"].append(f"Different schemes: {did1.scheme} vs {did2.scheme}")

        if did1.method != did2.method:
            analysis["differences"].append(f"Different methods: {did1.method} vs {did2.method}")
        else:
            analysis["same_method"] = True

        if did1.method_specific_id != did2.method_specific_id:
            analysis["differences"].append(f"Different method-specific IDs: {did1.method_specific_id} vs {did2.method_specific_id}")

        # Compare keychain DIDs
        try:
            encr1, sign1 = did1.parse_keychain_dids()
            encr2, sign2 = did2.parse_keychain_dids()

            if encr1.network == encr2.network and sign1.network == sign2.network:
                analysis["same_network"] = True
            else:
                analysis["differences"].append(f"Different networks: {encr1.network} vs {encr2.network}")

            if encr1 != encr2:
                analysis["differences"].append("Different encryption keychain DIDs")

            if sign1 != sign2:
                analysis["differences"].append("Different signature keychain DIDs")

        except Exception as e:
            analysis["differences"].append(f"Error comparing keychain DIDs: {str(e)}")

        return analysis

    @staticmethod
    def extract_persona_info(persona_did: PersonaDID) -> dict:
        """Extract comprehensive information from a PersonaDID."""
        try:
            encr_did, sign_did = persona_did.parse_keychain_dids()

            return {
                "persona_did": str(persona_did),
                "scheme": persona_did.scheme,
                "method": persona_did.method,
                "method_specific_id": persona_did.method_specific_id,
                "network": encr_did.network,
                "encryption": {
                    "did": str(encr_did),
                    "network_id": encr_did.network_specific_id,
                    "type": encr_did.keychain_type_str
                },
                "signature": {
                    "did": str(sign_did),
                    "network_id": sign_did.network_specific_id,
                    "type": sign_did.keychain_type_str
                }
            }

        except Exception as e:
            return {
                "error": f"Failed to extract persona info: {str(e)}"
            }

# Usage example
def persona_did_analyzer_example():
    """Demonstrate PersonaDID analysis utilities."""

    print("=== PersonaDID Analyzer Demo ===")

    # Create test PersonaDIDs
    encrypt_did1 = KeychainDID.from_keychain_components(
        KeychainType.ENCRYPT, "btc_regtest1", "alice_encrypt"
    )
    sign_did1 = KeychainDID.from_keychain_components(
        KeychainType.SIGN, "btc_regtest1", "alice_sign"
    )
    persona_did1 = PersonaDID.from_keychain_dids(encrypt_did1, sign_did1)

    encrypt_did2 = KeychainDID.from_keychain_components(
        KeychainType.ENCRYPT, "btc_testnet", "bob_encrypt"
    )
    sign_did2 = KeychainDID.from_keychain_components(
        KeychainType.SIGN, "btc_testnet", "bob_sign"
    )
    persona_did2 = PersonaDID.from_keychain_dids(encrypt_did2, sign_did2)

    # Validate PersonaDIDs
    print("=== Validation Tests ===")
    for i, persona_did in enumerate([persona_did1, persona_did2], 1):
        result = PersonaDIDAnalyzer.validate_persona_did(str(persona_did))
        print(f"\nPersonaDID {i}: {result['valid']}")
        if result['valid']:
            print(f"  Network: {result['network']}")
            print(f"  Encryption DID: {result['encryption_did']}")
            print(f"  Signature DID: {result['signature_did']}")
        else:
            print(f"  Error: {result['error']}")

    # Compare PersonaDIDs
    print("\n=== Comparison Analysis ===")
    comparison = PersonaDIDAnalyzer.compare_persona_dids(persona_did1, persona_did2)
    print(f"Equal: {comparison['equal']}")
    print(f"Same network: {comparison['same_network']}")
    print(f"Same method: {comparison['same_method']}")
    if comparison['differences']:
        print("Differences:")
        for diff in comparison['differences']:
            print(f"  - {diff}")

    # Extract detailed information
    print("\n=== Detailed Information ===")
    for i, persona_did in enumerate([persona_did1, persona_did2], 1):
        info = PersonaDIDAnalyzer.extract_persona_info(persona_did)
        print(f"\nPersona {i}:")
        if 'error' not in info:
            print(f"  DID: {info['persona_did']}")
            print(f"  Network: {info['network']}")
            print(f"  Encryption: {info['encryption']['did']}")
            print(f"  Signature: {info['signature']['did']}")
        else:
            print(f"  Error: {info['error']}")

if __name__ == "__main__":
    persona_did_analyzer_example()

Error Handling

PersonaDID operations can raise various exceptions:

Example:

from keychain.exceptions import KeychainValidationError
from keychain.identity.persona_did import PersonaDID
from keychain.identity.keychain_did import KeychainDID
from keychain.constants import KeychainType

try:
    # Create PersonaDID from string
    persona_did = PersonaDID("did:kcn-persona:alice.company.com")

    # Create from keychain DIDs
    encrypt_did = KeychainDID.from_keychain_components(
        KeychainType.ENCRYPT, "btc_regtest1", "encrypt_id"
    )
    sign_did = KeychainDID.from_keychain_components(
        KeychainType.SIGN, "btc_regtest1", "sign_id"
    )
    persona_did2 = PersonaDID.from_keychain_dids(encrypt_did, sign_did)

    # Access properties
    encr_did = persona_did2.encr_keychain_did
    sign_did = persona_did2.sign_keychain_did

    # Parse components
    encr_parsed, sign_parsed = persona_did2.parse_keychain_dids()

    # Serialize
    serialized = persona_did.serialize()

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

See Also