forked from jsnbuchanan/crowd-funder-for-time-pwa
- Bash implementation of DID creation-registration - Move admin credentials to .env file for better security - Add .env.example with default values - Add dotenv support to TypeScript, Python and Bash implementations - Update dependencies to include dotenv packages - Fix JWT signature format in Bash implementation - Add DER signature parsing for ES256K in Bash script The admin DID and private key can now be configured via environment variables, with fallback to default values if not set. This allows for easier testing and deployment across different environments.
250 lines
7.9 KiB
TypeScript
250 lines
7.9 KiB
TypeScript
/**
|
|
* 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);
|
|
}
|
|
})(); |