/** * DID Generator Script * @author Matthew Raymer * * This script generates and registers Decentralized Identifiers (DIDs) with admin authorization. * It supports the creation of Ethereum-based DIDs (did:ethr) and handles the complete * registration flow with the endorser.ch API. * * Features: * - Ethereum keypair generation using ethers.js * - JWT creation and signing using did-jwt * - DID registration with admin authorization * - Detailed error handling and logging * - Command-line interface for admin DID input * * Dependencies: * did-jwt: For JWT creation and signing * ethers: For Ethereum account operations * node-fetch: For API communication * commander: For CLI argument parsing * dotenv: For environment variable loading * * Usage: * npm run generate-did -- [options] * * Options: * -a, --admin-did Override default admin DID * --api-url Override default API endpoint * * Environment: * Default Admin DID: did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F * Default API URL: https://test-api.endorser.ch/api/v2/claim */ /// // Add at the top of your file to ignore dom types import * as didJwt from 'did-jwt'; import { ethers } from 'ethers'; import fetch from 'node-fetch'; import { program } from 'commander'; import * as dotenv from 'dotenv'; import { config } from 'dotenv'; // Load environment variables config(); const admin_keypair = { did: process.env.ADMIN_DID || 'did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F', privateKey: process.env.ADMIN_PRIVATE_KEY || '2b6472c026ec2aa2c4235c994a63868fc9212d18b58f6cbfe861b52e71330f5b' }; // Default values from environment const DEFAULT_ADMIN_DID = admin_keypair.did; const DEFAULT_API_URL = process.env.ENDORSER_API_URL || 'https://test-api.endorser.ch/api/v2/claim'; /** * Result interface for DID creation process */ interface DIDCreationResult { did: string; // The generated DID privateKey: string; // Private key without 0x prefix publicKey: string; // Public key with 0x prefix isValid: boolean; // Validation status jwt: string; // Signed JWT for registration } /** * Result interface for DID registration attempt */ interface RegistrationResult { success: boolean; // Registration success status error?: string; // Optional error message response?: any; // Optional API response data } /** * Creates and validates a new DID with admin authorization * * Workflow: * 1. Generates new Ethereum keypair using ethers.js * 2. Creates DID from public key in did:ethr format * 3. Constructs registration claim with: * - Admin DID as the agent (authorizer) * - New DID as the participant (being registered) * 4. Signs claim as JWT using admin's private key * * @param adminDid - Administrator DID for authorization * @returns Promise - Generated DID details and JWT */ async function createAndValidateDID(adminDid: string): Promise { console.log('Using admin DID:', adminDid); // Generate new DID keypair console.log('Generating new keypair...'); const wallet = ethers.Wallet.createRandom(); const did = `did:ethr:${wallet.address}`; const privateKey = wallet.privateKey.slice(2); const publicKey = wallet.publicKey; // Create registration claim with admin as agent const registerClaim = { "@context": "https://schema.org", "@type": "RegisterAction", agent: { did: adminDid }, participant: { did: did }, object: "endorser.ch" }; const vcPayload = { iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 300, sub: "RegisterAction", vc: { "@context": ["https://www.w3.org/2018/credentials/v1"], type: ["VerifiableCredential"], credentialSubject: registerClaim } }; console.log('\nGenerated DID Details:'); console.log('----------------------'); console.log('DID:', did); console.log('Admin DID:', adminDid); console.log('Address:', wallet.address); console.log('Private Key:', wallet.privateKey); console.log('Public Key:', wallet.publicKey); console.log('\nDebug Details:'); console.log('-------------'); console.log('Private Key (hex):', privateKey); // Should be without 0x console.log('Public Key (hex):', publicKey); // Should be with 0x console.log('Header:', { typ: "JWT", alg: "ES256K" }); console.log('Payload:', vcPayload); // Create and sign JWT with admin's key console.log('\nCreating JWT...'); const signer = didJwt.SimpleSigner(admin_keypair.privateKey); // Use admin's private key const jwt = await didJwt.createJWT(vcPayload, { issuer: admin_keypair.did, // Admin DID as issuer signer: signer }); console.log('\nJWT Parts:'); const [header, payload, signature] = jwt.split('.'); console.log('Header (base64):', header); console.log('Payload (base64):', payload); console.log('Signature (base64):', signature); return { did, privateKey, publicKey, isValid: true, jwt }; } /** * Registers a DID with the endorser.ch API * * Workflow: * 1. Submits JWT to API endpoint * 2. Processes response (success/error) * 3. Handles error cases: * - Network errors * - API errors (400, 401, etc) * - Malformed responses * * @param jwt - Signed JWT for registration * @returns Promise - Registration result with response/error */ async function registerDID(jwt: string): Promise { try { const response = await fetch(DEFAULT_API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ jwtEncoded: jwt }), }); console.log(`\nServer Response Status: ${response.status}`); const responseText = await response.text(); console.log(`Server Response Body: ${responseText}`); if (response.ok) { return { success: true, response: JSON.parse(responseText) }; } else { try { const errorJson = JSON.parse(responseText); const errorMsg = errorJson.error?.message || 'Unknown error'; return { success: false, error: `Registration failed (${response.status}): ${errorMsg}`, response: errorJson }; } catch { return { success: false, error: `Registration failed (${response.status}): ${responseText}`, response: responseText }; } } } catch (e) { return { success: false, error: `Request failed: ${e}` }; } } // Command line handling program .name('did-generator') .description('Generate and register a new DID') .option('-a, --admin-did ', 'Admin DID', DEFAULT_ADMIN_DID) .option('--api-url ', 'Override API URL', DEFAULT_API_URL) .parse(); const options = program.opts(); const adminDid = options.adminDid; // Use the option instead of args const apiUrl = options.apiUrl; console.log('Starting DID Generation...\n'); console.log('Using admin DID:', adminDid); // Log the admin DID being used (async () => { try { const result = await createAndValidateDID(adminDid); console.log('\nSuccessfully generated DID with admin authorization!'); console.log('Registration JWT:', result.jwt.substring(0, 50) + '...'); console.log('\nAttempting registration...'); const registrationResult = await registerDID(result.jwt); if (registrationResult.success) { console.log('Registration successful!'); console.log('Response:', JSON.stringify(registrationResult.response, null, 2)); } else { console.log('Registration failed!'); console.log('Error:', registrationResult.error); if (registrationResult.response) { console.log('Full response:', JSON.stringify(registrationResult.response, null, 2)); } } } catch (error) { console.error('\nError:', error); process.exit(1); } })();