DID Functions

#include <keychain/keychain_headers.h>

Overview

DID functions implement basic parsing and manipulation of W3C Decentralized Identifiers (DIDs). DIDs are a standardized way to identify entities in a decentralized manner according to the W3C DID specification. A DID consists of three components: a scheme (always "did"), a method name, and a method-specific identifier.

The format is: did:<method>:<method-specific-id>

Object Lifecycle Functions

kc_did_construct_from_serialized_string()

int kc_did_construct_from_serialized_string(
    kc_did_t** out_did_ptr,
    const char* serialized_string,
    const unsigned int serialized_string_size
)

Deserializes a DID from a string representation according to W3C DID specification.

Parameters:

  • out_did_ptr - Pointer to DID pointer (output, caller must free)

  • serialized_string - DID string as defined in W3C DID specification

  • serialized_string_size - Number of bytes in the serialized string

Returns: Error code (0 = success)

Example:

kc_did_t* did = NULL;
const char* did_string = "did:example:123456789abcdefghi";
int result = kc_did_construct_from_serialized_string(&did,
    did_string, strlen(did_string));
if (result == 0) {
    // Use DID...
    kc_did_destruct(&did);
}

kc_did_construct_from_copy()

int kc_did_construct_from_copy(
    kc_did_t** out_did_ptr,
    const kc_did_t* rhs_did_ptr
)

Creates a deep copy of a DID object.

Parameters:

  • out_did_ptr - Pointer to new DID pointer (output, caller must free)

  • rhs_did_ptr - DID object to copy

Returns: Error code (0 = success)

Example:

kc_did_t* original_did = NULL;
kc_did_t* copied_did = NULL;

// ... create original_did ...

int result = kc_did_construct_from_copy(&copied_did, original_did);
if (result == 0) {
    // Use copied DID...
    kc_did_destruct(&copied_did);
}

kc_did_destruct()

int kc_did_destruct(kc_did_t** did_ptr)

Destructs a DID object and frees its resources.

Parameters:

  • did_ptr - Pointer to DID pointer (set to NULL on success)

Returns: Error code (0 = success)

Component Access Functions

kc_did_scheme()

int kc_did_scheme(
    const kc_did_t* did_ptr,
    char** out_scheme,
    unsigned int* out_size
)

Gets the DID scheme (always "did" for valid DIDs).

Parameters:

  • did_ptr - DID object to query

  • out_scheme - Pointer to scheme string pointer (output, caller must free)

  • out_size - Pointer to scheme string size (output)

Returns: Error code (0 = success)

Example:

char* scheme = NULL;
unsigned int scheme_size = 0;
int result = kc_did_scheme(did, &scheme, &scheme_size);
if (result == 0) {
    printf("DID scheme: %.*s\n", scheme_size, scheme);
    kc_common_delete_buffer(&scheme);
}

kc_did_method()

int kc_did_method(
    const kc_did_t* did_ptr,
    char** out_method,
    unsigned int* out_size
)

Gets the DID method name.

Parameters:

  • did_ptr - DID object to query

  • out_method - Pointer to method string pointer (output, caller must free)

  • out_size - Pointer to method string size (output)

Returns: Error code (0 = success)

Example:

char* method = NULL;
unsigned int method_size = 0;
int result = kc_did_method(did, &method, &method_size);
if (result == 0) {
    printf("DID method: %.*s\n", method_size, method);
    kc_common_delete_buffer(&method);
}

kc_did_id()

int kc_did_id(
    const kc_did_t* did_ptr,
    char** out_id,
    unsigned int* out_size
)

Gets the method-specific identifier.

Parameters:

  • did_ptr - DID object to query

  • out_id - Pointer to ID string pointer (output, caller must free)

  • out_size - Pointer to ID string size (output)

Returns: Error code (0 = success)

Example:

char* id = NULL;
unsigned int id_size = 0;
int result = kc_did_id(did, &id, &id_size);
if (result == 0) {
    printf("Method-specific ID: %.*s\n", id_size, id);
    kc_common_delete_buffer(&id);
}

Serialization Functions

kc_did_serialize()

int kc_did_serialize(
    const kc_did_t* did_ptr,
    char** out_serialized_str,
    unsigned int* out_size
)

Serializes the DID to a string as defined in the W3C specification.

Parameters:

  • did_ptr - DID object to serialize

  • out_serialized_str - Pointer to serialized string pointer (output, caller must free)

  • out_size - Pointer to string size (output)

Returns: Error code (0 = success)

Example:

char* serialized = NULL;
unsigned int serialized_size = 0;
int result = kc_did_serialize(did, &serialized, &serialized_size);
if (result == 0) {
    printf("Serialized DID: %.*s\n", serialized_size, serialized);
    kc_common_delete_buffer(&serialized);
}

Metadata Functions

kc_did_data_type()

int kc_did_data_type(
    const kc_did_t* did_ptr,
    tkc_data_type* out_data_type
)

Gets the data type value indicating this is a DID.

Parameters:

  • did_ptr - DID object to query

  • out_data_type - Pointer to data type value (output)

Returns: Error code (0 = success)

Query Functions

kc_did_equals()

int kc_did_equals(
    const kc_did_t* lhs_did_ptr,
    bool* out_is_equal,
    const kc_did_t* rhs_did_ptr
)

Tests equality of two DID objects.

Parameters:

  • lhs_did_ptr - First DID to compare

  • out_is_equal - Pointer to equality result (output)

  • rhs_did_ptr - Second DID to compare

Returns: Error code (0 = success)

Example: DID Parsing and Analysis

void demonstrate_did_parsing(const char* did_string) {
    kc_did_t* did = NULL;

    printf("Parsing DID: %s\n", did_string);

    // Parse the DID string
    int result = kc_did_construct_from_serialized_string(&did,
        did_string, strlen(did_string));

    if (result != 0) {
        printf("Failed to parse DID: %d\n", result);
        return;
    }

    // Extract and display components
    char* scheme = NULL;
    char* method = NULL;
    char* id = NULL;
    unsigned int scheme_size = 0;
    unsigned int method_size = 0;
    unsigned int id_size = 0;

    kc_did_scheme(did, &scheme, &scheme_size);
    kc_did_method(did, &method, &method_size);
    kc_did_id(did, &id, &id_size);

    printf("DID Components:\n");
    printf("  Scheme: %.*s\n", scheme_size, scheme ? scheme : "Unknown");
    printf("  Method: %.*s\n", method_size, method ? method : "Unknown");
    printf("  Method-specific ID: %.*s\n", id_size, id ? id : "Unknown");

    // Verify round-trip serialization
    char* serialized = NULL;
    unsigned int serialized_size = 0;
    result = kc_did_serialize(did, &serialized, &serialized_size);

    if (result == 0) {
        printf("Serialized: %.*s\n", serialized_size, serialized);

        // Verify it matches original
        if (strlen(did_string) == serialized_size &&
            memcmp(did_string, serialized, serialized_size) == 0) {
            printf("✓ Round-trip serialization successful\n");
        } else {
            printf("✗ Round-trip serialization failed\n");
        }

        kc_common_delete_buffer(&serialized);
    }

    // Cleanup
    if (scheme) kc_common_delete_buffer(&scheme);
    if (method) kc_common_delete_buffer(&method);
    if (id) kc_common_delete_buffer(&id);
    kc_did_destruct(&did);
}

void test_various_did_formats() {
    // Test different DID formats
    const char* test_dids[] = {
        "did:example:123456789abcdefghi",
        "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
        "did:web:example.com",
        "did:ethr:0x3b0BC51Ab9De1e5B7B6E34E5b960285805C41736",
        "did:ion:EiClkZMDxPKqC9c-umQfTkR8vvZ9JPhl_xLDI9Nfk38w5w",
        "did:keychain:mainnet:alice.example.com"
    };

    for (size_t i = 0; i < sizeof(test_dids) / sizeof(test_dids[0]); i++) {
        printf("\n=== Test %zu ===\n", i + 1);
        demonstrate_did_parsing(test_dids[i]);
    }
}

Example: DID Comparison and Validation

bool validate_did_format(const char* did_string) {
    kc_did_t* did = NULL;

    // Try to parse the DID
    int result = kc_did_construct_from_serialized_string(&did,
        did_string, strlen(did_string));

    if (result != 0) {
        printf("Invalid DID format: %s\n", did_string);
        return false;
    }

    // Verify scheme is "did"
    char* scheme = NULL;
    unsigned int scheme_size = 0;
    result = kc_did_scheme(did, &scheme, &scheme_size);

    bool valid_scheme = false;
    if (result == 0 && scheme) {
        if (scheme_size == 3 && memcmp(scheme, "did", 3) == 0) {
            valid_scheme = true;
        }
        kc_common_delete_buffer(&scheme);
    }

    if (!valid_scheme) {
        printf("Invalid DID scheme (must be 'did'): %s\n", did_string);
        kc_did_destruct(&did);
        return false;
    }

    // Verify method is not empty
    char* method = NULL;
    unsigned int method_size = 0;
    result = kc_did_method(did, &method, &method_size);

    bool valid_method = false;
    if (result == 0 && method && method_size > 0) {
        valid_method = true;
    }

    if (method) kc_common_delete_buffer(&method);

    if (!valid_method) {
        printf("Invalid DID method (cannot be empty): %s\n", did_string);
        kc_did_destruct(&did);
        return false;
    }

    // Verify method-specific ID is not empty
    char* id = NULL;
    unsigned int id_size = 0;
    result = kc_did_id(did, &id, &id_size);

    bool valid_id = false;
    if (result == 0 && id && id_size > 0) {
        valid_id = true;
    }

    if (id) kc_common_delete_buffer(&id);

    if (!valid_id) {
        printf("Invalid method-specific ID (cannot be empty): %s\n", did_string);
        kc_did_destruct(&did);
        return false;
    }

    kc_did_destruct(&did);
    printf("✓ Valid DID format: %s\n", did_string);
    return true;
}

void compare_dids(const char* did1_str, const char* did2_str) {
    kc_did_t* did1 = NULL;
    kc_did_t* did2 = NULL;

    // Parse both DIDs
    int result1 = kc_did_construct_from_serialized_string(&did1,
        did1_str, strlen(did1_str));
    int result2 = kc_did_construct_from_serialized_string(&did2,
        did2_str, strlen(did2_str));

    if (result1 != 0 || result2 != 0) {
        printf("Failed to parse one or both DIDs\n");
        if (did1) kc_did_destruct(&did1);
        if (did2) kc_did_destruct(&did2);
        return;
    }

    // Compare for equality
    bool are_equal = false;
    int result = kc_did_equals(did1, &are_equal, did2);

    if (result == 0) {
        printf("Comparing DIDs:\n");
        printf("  DID 1: %s\n", did1_str);
        printf("  DID 2: %s\n", did2_str);
        printf("  Equal: %s\n", are_equal ? "Yes" : "No");
    } else {
        printf("Failed to compare DIDs: %d\n", result);
    }

    kc_did_destruct(&did1);
    kc_did_destruct(&did2);
}

void demonstrate_did_validation() {
    // Test valid DIDs
    const char* valid_dids[] = {
        "did:example:123",
        "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH",
        "did:web:example.com"
    };

    printf("Testing valid DIDs:\n");
    for (size_t i = 0; i < sizeof(valid_dids) / sizeof(valid_dids[0]); i++) {
        validate_did_format(valid_dids[i]);
    }

    // Test invalid DIDs
    const char* invalid_dids[] = {
        "notadid:example:123",
        "did::123",
        "did:example:",
        "",
        "did"
    };

    printf("\nTesting invalid DIDs:\n");
    for (size_t i = 0; i < sizeof(invalid_dids) / sizeof(invalid_dids[0]); i++) {
        validate_did_format(invalid_dids[i]);
    }

    // Test DID comparison
    printf("\nTesting DID comparison:\n");
    compare_dids("did:example:123", "did:example:123");
    compare_dids("did:example:123", "did:example:456");
    compare_dids("did:key:abc", "did:web:abc");
}

Example: DID Utility Functions

// Extract method from DID string without full parsing
char* extract_did_method_quick(const char* did_string) {
    if (!did_string) return NULL;

    // Check for "did:" prefix
    if (strlen(did_string) < 4 || memcmp(did_string, "did:", 4) != 0) {
        return NULL;
    }

    // Find the method part (between first and second colon)
    const char* method_start = did_string + 4;
    const char* method_end = strchr(method_start, ':');

    if (!method_end) return NULL;

    size_t method_length = method_end - method_start;
    if (method_length == 0) return NULL;

    char* method = malloc(method_length + 1);
    if (method) {
        memcpy(method, method_start, method_length);
        method[method_length] = '\0';
    }

    return method;
}

bool is_keychain_did(const char* did_string) {
    char* method = extract_did_method_quick(did_string);
    if (!method) return false;

    bool is_keychain = (strcmp(method, "keychain") == 0);
    free(method);
    return is_keychain;
}

void categorize_did_by_method(const char* did_string) {
    kc_did_t* did = NULL;
    int result = kc_did_construct_from_serialized_string(&did,
        did_string, strlen(did_string));

    if (result != 0) {
        printf("Invalid DID: %s\n", did_string);
        return;
    }

    char* method = NULL;
    unsigned int method_size = 0;
    result = kc_did_method(did, &method, &method_size);

    if (result == 0 && method) {
        printf("DID: %s\n", did_string);
        printf("Method: %.*s\n", method_size, method);

        // Categorize by method
        if (method_size == 3 && memcmp(method, "key", 3) == 0) {
            printf("Category: Cryptographic key-based DID\n");
        } else if (method_size == 3 && memcmp(method, "web", 3) == 0) {
            printf("Category: Web-based DID\n");
        } else if (method_size == 4 && memcmp(method, "ethr", 4) == 0) {
            printf("Category: Ethereum-based DID\n");
        } else if (method_size == 3 && memcmp(method, "ion", 3) == 0) {
            printf("Category: ION (Bitcoin-based) DID\n");
        } else if (method_size == 8 && memcmp(method, "keychain", 8) == 0) {
            printf("Category: Keychain-specific DID\n");
        } else {
            printf("Category: Other method\n");
        }

        kc_common_delete_buffer(&method);
    }

    kc_did_destruct(&did);
}

Memory Management

  • Use kc_did_destruct() to free DID objects

  • Use kc_common_delete_buffer() for string outputs (scheme, method, ID, serialized data)

  • Always check return codes before using output parameters

  • DIDs are immutable once created - create new DIDs for different identifiers

Best Practices

  • Always validate DID format before using in critical operations

  • Use appropriate DID methods for your use case

  • Store DIDs in their canonical string form for persistence

  • Compare DIDs using the equals function rather than string comparison

  • Cache parsed DID objects if you need to access components multiple times

See Also