You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
250 lines
7.9 KiB
250 lines
7.9 KiB
/**
|
|
* 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 <did> Override default admin DID
|
|
* --api-url <url> Override default API endpoint
|
|
*
|
|
* Environment:
|
|
* Default Admin DID: did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F
|
|
* Default API URL: https://test-api.endorser.ch/api/v2/claim
|
|
*/
|
|
|
|
/// <reference types="node" />
|
|
// 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<DIDCreationResult> - Generated DID details and JWT
|
|
*/
|
|
async function createAndValidateDID(adminDid: string): Promise<DIDCreationResult> {
|
|
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<RegistrationResult> - Registration result with response/error
|
|
*/
|
|
async function registerDID(jwt: string): Promise<RegistrationResult> {
|
|
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 <did>', 'Admin DID', DEFAULT_ADMIN_DID)
|
|
.option('--api-url <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);
|
|
}
|
|
})();
|