did-manager/examples/07-generate-and-submit-did.php
Namith Jawahar f159e0d060
Add a builtin method to access previous CID
Add a builtin method to access previous CID

Signed-off-by: Chuck Adams <cja987@gmail.com>
2026-01-05 12:03:39 -07:00

388 lines
16 KiB
PHP

<?php
/**
* Example 7: Generate and Submit DID to PLC Directory
*
* This example demonstrates the complete workflow of:
* 1. Generating cryptographic keys
* 2. Creating a PLC operation
* 3. Signing the operation
* 4. Generating the DID
* 5. Submitting it to the PLC directory
*
* @package FairDidManager\Examples
*/
declare(strict_types=1);
use FAIR\DID\Crypto\DidCodec;
use FAIR\DID\PLC\PlcClient;
use FAIR\DID\Sorage\KeyStore;
use FAIR\DID\DIDManager;
require_once __DIR__ . '/../vendor/autoload.php';
echo "=== FAIR CLI: Generate and Submit DID to PLC Directory ===\n\n";
echo "This example demonstrates the complete DID generation and submission workflow.\n";
echo "All components (key generation, signing, CBOR encoding, DID generation, and\n";
echo "submission to plc.directory) are working correctly.\n\n";
echo "See Method 2 for detailed step-by-step demonstration.\n\n";
echo "Note: For local development, SSL verification is disabled. In production,\n";
echo "ensure proper SSL certificate validation.\n\n";
// -----------------------------------------------------------------------------
// Method 1: Using DIDManager (Recommended - High-level API)
// -----------------------------------------------------------------------------
echo "METHOD 1: Using DIDManager (Recommended)\n";
echo str_repeat('=', 70) . "\n\n";
try {
// Initialize storage and PLC client.
$storage_path = __DIR__ . '/temp-storage';
if (!file_exists($storage_path)) {
mkdir($storage_path, 0755, true);
}
$key_store = new KeyStore($storage_path . '/keystore.json');
$plc_client = new PlcClient('https://plc.directory', 30, false); // Disable SSL verification for local dev
// Create DID manager.
$did_manager = new DIDManager($key_store, $plc_client);
// Create and submit DID with handle.
echo "1. Creating DID with handle...\n";
$result = $did_manager->create_did(
handle: 'my-wordpress-plugin.example.com',
service_endpoint: null,
plugin_path: null,
inject_id: false
);
echo "✓ DID Created and Submitted Successfully!\n\n";
echo "DID: {$result['did']}\n";
echo "Handle: {$result['handle']}\n";
echo "Service Endpoint: {$result['serviceEndpoint']}\n";
echo "Rotation Key (Public): " . substr($result['rotationKey'], 0, 50) . "...\n";
echo "Verification Key (Public): " . substr($result['verificationKey'], 0, 50) . "...\n\n";
// Verify the DID was created by resolving it.
echo "2. Verifying DID by resolving from PLC directory...\n";
try {
$resolved = $did_manager->resolve_did($result['did']);
echo "✓ DID Resolved Successfully!\n";
echo "DID Document ID: {$resolved['id']}\n";
echo "Also Known As: " . (empty($resolved['alsoKnownAs']) ? '(none)' : implode(', ', $resolved['alsoKnownAs'])) . "\n";
echo "Verification Methods: " . count($resolved['verificationMethod']) . "\n";
echo "Services: " . count($resolved['service']) . "\n\n";
} catch (\Exception $e) {
echo "Note: Unable to resolve from live directory (expected in local dev): {$e->getMessage()}\n\n";
}
// Update the DID with a service endpoint.
echo "3. Updating DID with service endpoint...\n";
$service = [
'id' => '#fairpm_repo',
'type' => 'FairPackageManagementRepo',
'serviceEndpoint' => "https://fair.git-updater.com/wp-json/fair-beacon/v1/packages/{$result['did']}",
];
// Note: DIDManager currently supports 'handle' and 'service' changes
// For custom services like FairPackageManagementRepo, we need to use the low-level API
// This example shows the intended usage pattern
echo "Note: Custom service endpoints require using the low-level API (see Method 2)\n";
echo "Service to add:\n";
echo " ID: {$service['id']}\n";
echo " Type: {$service['type']}\n";
echo " Endpoint: {$service['serviceEndpoint']}\n\n";
} catch (\Exception $e) {
echo "Note: {$e->getMessage()}\n";
echo "(This is expected in local development environments)\n\n";
}
// -----------------------------------------------------------------------------
// Method 2: Manual Process (Low-level API for fine control)
// -----------------------------------------------------------------------------
echo "\nMETHOD 2: Manual Process (Low-level API)\n";
echo str_repeat('=', 70) . "\n\n";
try {
// Step 1: Generate cryptographic keys.
echo "Step 1: Generating Cryptographic Keys\n";
echo str_repeat('-', 50) . "\n";
$rotation_key = DidCodec::generate_key_pair();
$verification_key = DidCodec::generate_ed25519_key_pair();
echo "✓ Rotation Key Generated (secp256k1)\n";
echo " Public Key: " . substr($rotation_key->encode_public(), 0, 50) . "...\n";
echo "✓ Verification Key Generated (Ed25519)\n";
echo " Public Key: " . substr($verification_key->encode_public(), 0, 50) . "...\n\n";
// Step 2: Create PLC operation.
echo "Step 2: Creating PLC Operation\n";
echo str_repeat('-', 50) . "\n";
$handle = 'manual-plugin.example.com';
$service_endpoint = null;
$operation = DidCodec::create_plc_operation(
$rotation_key,
$verification_key,
$handle,
$service_endpoint
);
echo "✓ PLC Operation Created\n";
echo " Type: {$operation->type}\n";
echo " Rotation Keys: " . count($operation->rotation_keys) . "\n";
echo " Verification Methods: " . count($operation->verification_methods) . "\n";
echo " Handle: " . implode(', ', $operation->also_known_as) . "\n";
echo " Services: " . (empty($operation->services) ? '(none - PLC directory handles all operations)' : json_encode(array_keys($operation->services))) . "\n\n";
// Step 3: Sign the operation.
echo "Step 3: Signing the Operation\n";
echo str_repeat('-', 50) . "\n";
$signed_operation = DidCodec::sign_plc_operation($operation, $rotation_key);
echo "✓ Operation Signed\n";
echo " Signature: " . substr($signed_operation->sig, 0, 60) . "...\n";
echo " Signature Length: " . strlen($signed_operation->sig) . " chars\n\n";
// Step 4: Generate DID from signed operation.
echo "Step 4: Generating DID\n";
echo str_repeat('-', 50) . "\n";
$did = DidCodec::generate_plc_did($signed_operation);
echo "✓ DID Generated\n";
echo " DID: {$did}\n";
echo " CID: {$signed_operation->get_cid()}\n\n";
// Step 5: Submit to PLC directory.
echo "Step 5: Submitting to PLC Directory\n";
echo str_repeat('-', 50) . "\n";
try {
$plc_client = new PlcClient('https://plc.directory', 30, false); // Disable SSL verification for local dev
$response = $plc_client->create_did($did, (array) $signed_operation->jsonSerialize());
echo "✓ DID Submitted Successfully!\n";
echo " Response: " . json_encode($response) . "\n\n";
// Step 6: Verify by resolving.
echo "Step 6: Verifying Submission\n";
echo str_repeat('-', 50) . "\n";
$resolved = $plc_client->resolve_did($did);
echo "✓ DID Verified on PLC Directory!\n";
echo " Document ID: {$resolved['id']}\n";
echo " Context: " . (is_array($resolved['@context']) ? implode(', ', $resolved['@context']) : $resolved['@context']) . "\n\n";
} catch (\Exception $e) {
echo "Note: Unable to submit/verify with live directory: " . substr($e->getMessage(), 0, 60) . "...\n";
echo "(This is expected in local development environments)\n";
echo "✓ However, the DID was successfully generated: {$did}\n";
echo "✓ And the operation was properly signed and ready for submission\n\n";
}
// Step 7: Update the DID with a service endpoint.
echo "Step 7: Updating DID with Service Endpoint\n";
echo str_repeat('-', 50) . "\n";
try {
// First, get the previous CID from the PLC directory
// This is the real-world approach - fetching the current state before updating
echo "Fetching previous CID from PLC directory...\n";
$previous_cid = $plc_client->get_previous_cid($did);
echo "✓ Previous CID Retrieved: {$previous_cid}\n\n";
// For updates with custom services, we need to create PlcOperation directly
// Services are structured as an associative array: key => ['type' => ..., 'endpoint' => ...]
$services = [
'fairpm_repo' => [
'type' => 'FairPackageManagementRepo',
'endpoint' => "https://fair.git-updater.com/wp-json/fair-beacon/v1/packages/{$did}",
],
];
// Generate a unique ID for the verification key (same as original)
$key_id = substr(hash('sha256', $verification_key->encode_public()), 0, 6);
$update_operation = new \FAIR\DID\PLC\PlcOperation(
type: 'plc_operation',
rotation_keys: [$rotation_key],
verification_methods: ['fair_' . $key_id => $verification_key],
also_known_as: ['at://' . $handle],
services: $services,
prev: $previous_cid // Use the CID fetched from PLC directory
);
// Sign the update operation.
$signed_update = DidCodec::sign_plc_operation($update_operation, $rotation_key);
echo "✓ Update Operation Created and Signed\n";
echo " Previous CID: {$previous_cid}\n";
echo " Service: FairPackageManagementRepo\n\n";
// Submit update.
$plc_client = new PlcClient('https://plc.directory', 30, false);
$update_response = $plc_client->update_did($did, (array) $signed_update->jsonSerialize());
echo "✓ DID Updated Successfully!\n";
echo " Response: " . json_encode($update_response) . "\n\n";
// Verify the update.
echo "Step 8: Verifying Update\n";
echo str_repeat('-', 50) . "\n";
$resolved_updated = $plc_client->resolve_did($did);
echo "✓ Updated DID Verified!\n";
echo " Services: " . count($resolved_updated['service']) . "\n";
if (!empty($resolved_updated['service'])) {
foreach ($resolved_updated['service'] as $svc) {
echo " - {$svc['id']}: {$svc['type']}\n";
echo " Endpoint: {$svc['serviceEndpoint']}\n";
}
}
echo "\n";
} catch (\Exception $e) {
echo "Note: Unable to update DID: " . substr($e->getMessage(), 0, 60) . "...\n";
echo "(This is expected in local development environments)\n\n";
}
} catch (\Exception $e) {
echo "✗ Error in manual process: {$e->getMessage()}\n";
echo " Stack trace:\n";
echo " " . str_replace("\n", "\n ", $e->getTraceAsString()) . "\n\n";
}
// -----------------------------------------------------------------------------
// Method 3: Using DIDManager with Plugin Integration
// -----------------------------------------------------------------------------
echo "\nMETHOD 3: With WordPress Plugin Integration\n";
echo str_repeat('=', 70) . "\n\n";
try {
// Initialize components.
$storage_path = __DIR__ . '/temp-storage-plugin';
if (!file_exists($storage_path)) {
mkdir($storage_path, 0755, true);
}
$key_store = new KeyStore($storage_path . '/keystore.json');
$plc_client = new PlcClient('https://plc.directory', 30, false); // Disable SSL verification for local dev
$did_manager = new DIDManager($key_store, $plc_client);
// Simulate a plugin path (in real scenario, this would be an actual plugin file).
$plugin_path = __DIR__ . '/mock-plugin.php';
// Create mock plugin file for demonstration.
if (!file_exists($plugin_path)) {
file_put_contents($plugin_path, "<?php\n/**\n * Plugin Name: My Demo Plugin\n * Version: 1.0.0\n */\n");
}
echo "Creating DID for WordPress plugin...\n";
$result = $did_manager->create_did(
handle: 'my-demo-plugin.wordpress.org',
service_endpoint: null,
plugin_path: $plugin_path,
inject_id: true
);
echo "✓ Plugin DID Created!\n\n";
echo "DID: {$result['did']}\n";
echo "Handle: {$result['handle']}\n";
echo "Service Endpoint: {$result['serviceEndpoint']}\n\n";
// Show what was stored.
echo "Stored in local key store:\n";
$stored = $key_store->get_did($result['did']);
echo " DID: {$stored['did']}\n";
echo " Type: {$stored['type']}\n";
echo " Created At: {$stored['created_at']}\n";
echo " Metadata Keys: " . implode(', ', array_keys($stored['metadata'])) . "\n\n";
// Clean up mock plugin file.
if (file_exists($plugin_path)) {
unlink($plugin_path);
}
// Update the DID with a service endpoint.
echo "Updating plugin DID with service endpoint...\n";
$service = [
'id' => '#fairpm_repo',
'type' => 'FairPackageManagementRepo',
'serviceEndpoint' => "https://fair.git-updater.com/wp-json/fair-beacon/v1/packages/{$result['did']}",
];
echo "Note: Custom service endpoints require using the low-level API (see Method 2)\n";
echo "Service to add:\n";
echo " ID: {$service['id']}\n";
echo " Type: {$service['type']}\n";
echo " Endpoint: {$service['serviceEndpoint']}\n\n";
} catch (\Exception $e) {
echo "Note: {$e->getMessage()}\n";
echo "(This is expected in local development environments)\n\n";
// Clean up mock plugin file even on error.
if (isset($plugin_path) && file_exists($plugin_path)) {
unlink($plugin_path);
}
}
// -----------------------------------------------------------------------------
// Summary and Best Practices
// -----------------------------------------------------------------------------
echo "\n" . str_repeat('=', 70) . "\n";
echo "SUMMARY AND BEST PRACTICES\n";
echo str_repeat('=', 70) . "\n\n";
echo "✓ Successfully Demonstrated:\n";
echo " 1. Cryptographic key generation (secp256k1 & Ed25519)\n";
echo " 2. PLC operation creation with handles and services\n";
echo " 3. CBOR encoding of operations\n";
echo " 4. Cryptographic signing of operations\n";
echo " 5. DID generation from signed operations\n";
echo " 6. Successful submission to live plc.directory service\n";
echo " 7. DID resolution and verification\n\n";
echo "✓ Three methods demonstrated:\n";
echo " 1. DIDManager (Recommended) - High-level, handles storage automatically\n";
echo " 2. Manual Process - Low-level control for custom implementations\n";
echo " 3. Plugin Integration - WordPress plugin-specific workflow\n\n";
echo "✓ Key Points:\n";
echo " • All DID operations (create/update/resolve) go to https://plc.directory\n";
echo " • Always securely store private keys (rotation and verification)\n";
echo " • Use handles (alsoKnownAs) for human-readable identifiers\n";
echo " • Service endpoints are optional - only use for custom repo URLs\n";
echo " • Keep rotation keys secure - they control DID updates\n\n";
echo "✓ Next Steps:\n";
echo " • See example 02 for updating DIDs\n";
echo " • See example 03 for secure key storage options\n";
echo " • See example 05 for generating metadata\n\n";
// Clean up temporary storage.
if (isset($storage_path) && file_exists($storage_path)) {
array_map('unlink', glob("{$storage_path}/*.*"));
rmdir($storage_path);
}
if (isset($storage_path) && file_exists($storage_path . '-plugin')) {
$plugin_storage = $storage_path . '-plugin';
array_map('unlink', glob("{$plugin_storage}/*.*"));
rmdir($plugin_storage);
}
echo "=== Example Complete ===\n";