feat: Improve DID registration with admin credentials

- Add admin keypair (DID + private key) for proper registration signing
- Remove SQLite database dependency for admin DID lookup
- Update both TypeScript and Python implementations to use admin credentials
- Enhance documentation with detailed usage, options, and security notes
- Fix JWT signing to use admin's key as issuer
- Standardize API URL handling with defaults
- Add command-line options for admin DID and API URL override

Both implementations now successfully register new DIDs using the admin's
credentials to sign the registration claims. The admin acts as both the
agent in the claim and the issuer of the JWT.
This commit is contained in:
Matthew Raymer
2025-03-04 05:59:10 +00:00
parent 6685421ee8
commit 9e6f0ab468
4 changed files with 116 additions and 146 deletions

View File

@@ -17,6 +17,18 @@
* did-jwt: For JWT creation and signing
* ethers: For Ethereum account operations
* node-fetch: For API communication
* commander: For CLI argument parsing
*
* 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" />
@@ -24,12 +36,18 @@
import * as didJwt from 'did-jwt';
import { ethers } from 'ethers';
import fetch from 'node-fetch';
import { Database } from 'sqlite3';
import { homedir } from 'os';
import { join } from 'path';
import { existsSync } from 'fs';
import { program } from 'commander';
const admin_keypair = {
did: 'did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F',
privateKey: '2b6472c026ec2aa2c4235c994a63868fc9212d18b58f6cbfe861b52e71330f5b'
}
// Default admin DID
const DEFAULT_ADMIN_DID = admin_keypair.did;
// Add constant for default API URL
const DEFAULT_API_URL = 'https://test-api.endorser.ch/api/v2/claim';
/**
* Result interface for DID creation process
*/
@@ -54,23 +72,20 @@ interface RegistrationResult {
* Creates and validates a new DID with admin authorization
*
* Workflow:
* 1. Validates admin DID input
* 2. Generates Ethereum keypair
* 3. Creates registration claim
* 4. Signs claim as JWT
* 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
* @throws Error if admin DID is missing
*/
async function createAndValidateDID(adminDid: string): Promise<DIDCreationResult> {
if (!adminDid) {
throw new Error('Admin DID is required for registration');
}
console.log('Using admin DID:', adminDid);
// 1. Generate keypair
// Generate new DID keypair
console.log('Generating new keypair...');
const wallet = ethers.Wallet.createRandom();
const did = `did:ethr:${wallet.address}`;
@@ -115,11 +130,11 @@ async function createAndValidateDID(adminDid: string): Promise<DIDCreationResult
});
console.log('Payload:', vcPayload);
// Create and sign JWT
// Create and sign JWT with admin's key
console.log('\nCreating JWT...');
const signer = didJwt.SimpleSigner(privateKey);
const signer = didJwt.SimpleSigner(admin_keypair.privateKey); // Use admin's private key
const jwt = await didJwt.createJWT(vcPayload, {
issuer: did,
issuer: admin_keypair.did, // Admin DID as issuer
signer: signer
});
@@ -137,15 +152,18 @@ async function createAndValidateDID(adminDid: string): Promise<DIDCreationResult
*
* Workflow:
* 1. Submits JWT to API endpoint
* 2. Processes response
* 3. Handles success/error cases
* 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
* @returns Promise<RegistrationResult> - Registration result with response/error
*/
async function registerDID(jwt: string): Promise<RegistrationResult> {
try {
const response = await fetch('https://api.endorser.ch/api/v2/claim', {
const response = await fetch(DEFAULT_API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -187,52 +205,22 @@ async function registerDID(jwt: string): Promise<RegistrationResult> {
}
}
async function getRootDidFromDb(dbPath?: string): Promise<string> {
return new Promise((resolve, reject) => {
const defaultPath = join(homedir(), '.endorser', 'accounts.db');
const path = dbPath || defaultPath;
if (!existsSync(path)) {
reject(new Error(`Database not found at ${path}`));
return;
}
const db = new Database(path);
db.get(
'SELECT issuer as did FROM registration WHERE type = "RegisterAction" ORDER BY issuanceDate DESC LIMIT 1',
(err, row: { did: string }) => {
db.close();
if (err) reject(err);
else if (!row) reject(new Error('No admin DID found in registration table'));
else resolve(row.did);
}
);
});
}
// Command line handling
program
.option('--admin-did <did>', 'Admin DID (e.g., did:ethr:0x0000...)')
.option('--db-path <path>', 'Path to SQLite database (e.g., ../endorser-ch-test-local.sqlite3)')
.parse(process.argv);
.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 () => {
let adminDid = options.adminDid;
if (!adminDid && options.dbPath) {
try {
adminDid = await getRootDidFromDb(options.dbPath);
console.log(`Found root DID in database: ${adminDid}`);
} catch (e) {
console.error(`Error: ${e.message}`);
console.error('Please provide --admin-did argument');
process.exit(1);
}
}
try {
const result = await createAndValidateDID(adminDid);
console.log('\nSuccessfully generated DID with admin authorization!');