mirror of
https://gh.wpcy.net/https://github.com/fairpm/did-manager.git
synced 2026-06-19 02:03:36 +08:00
373 lines
15 KiB
PHP
373 lines
15 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 FAIR\DID\Examples
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
use FAIR\DID\Crypto\DidCodec;
|
|
use FAIR\DID\PLC\PlcClient;
|
|
use FAIR\DID\Storage\KeyStore;
|
|
use FAIR\DID\DIDManager;
|
|
|
|
require_once __DIR__ . '/../vendor/autoload.php';
|
|
|
|
echo "=== FAIR DID Example: 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: 'example-package.example.com',
|
|
service_endpoint: null,
|
|
type: 'package'
|
|
);
|
|
|
|
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-package.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, $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, $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 Generic Package Metadata
|
|
// -----------------------------------------------------------------------------
|
|
echo "\nMETHOD 3: DIDManager with Generic Package Metadata\n";
|
|
echo str_repeat('=', 70) . "\n\n";
|
|
|
|
try {
|
|
// Initialize components.
|
|
$storage_path = __DIR__ . '/temp-storage-package';
|
|
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);
|
|
|
|
echo "Creating DID for a generic package record...\n";
|
|
|
|
$result = $did_manager->create_did(
|
|
handle: 'demo-package.example.com',
|
|
service_endpoint: null,
|
|
type: 'package',
|
|
metadata: [
|
|
'packageManager' => 'composer',
|
|
'packageName' => 'fairpm/demo-package',
|
|
'distribution' => 'zip',
|
|
],
|
|
);
|
|
|
|
echo "✓ Package 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['createdAt']}\n";
|
|
echo " Metadata Keys: " . implode(', ', array_keys($stored['metadata'])) . "\n\n";
|
|
|
|
echo "Package-specific metadata stored locally:\n";
|
|
echo " Package Manager: {$stored['metadata']['packageManager']}\n";
|
|
echo " Package Name: {$stored['metadata']['packageName']}\n";
|
|
echo " Distribution: {$stored['metadata']['distribution']}\n\n";
|
|
|
|
echo "Need WordPress plugin or theme parsing?\n";
|
|
echo " Use the fairpm/did-manager-wordpress package for WordPress-specific workflows.\n\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";
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// 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. Generic Package Metadata - store package-specific context in the core manager\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 the did-manager-wordpress package for WordPress parsing and metadata generation\n\n";
|
|
|
|
// Clean up temporary storage.
|
|
if (isset($storage_path) && file_exists($storage_path)) {
|
|
array_map('unlink', glob("{$storage_path}/*.*"));
|
|
rmdir($storage_path);
|
|
}
|
|
|
|
echo "=== Example Complete ===\n";
|