fix: consolidate deep link testing scripts
- Remove redundant test-deeplinks.sh script - Update run-deeplink-tests.sh with improved timeout - Remove unused secp256k1-sign script - Remove deprecated new_flow scripts - Clean up test script directory Technical Changes: - Increase default timeout from 5 to 10 seconds - Remove Python/TypeScript DID generation scripts - Remove redundant deep link test script - Remove unused crypto signing script This simplifies the test script directory by removing deprecated scripts and consolidating deep link testing into a single, more reliable script.
This commit is contained in:
502
test-scripts/generate_data.ts
Normal file
502
test-scripts/generate_data.ts
Normal file
@@ -0,0 +1,502 @@
|
||||
/**
|
||||
* DID Creation and Registration Flow
|
||||
* @author Matthew Raymer
|
||||
*
|
||||
* This module implements the creation and registration of Decentralized Identifiers (DIDs)
|
||||
* with the endorser.ch service. It matches the Python implementation in new_flow.py.
|
||||
*/
|
||||
|
||||
import { HDNode } from '@ethersproject/hdnode';
|
||||
import { Wallet } from '@ethersproject/wallet';
|
||||
import { createJWT, ES256KSigner, SimpleSigner } from 'did-jwt';
|
||||
import axios from 'axios';
|
||||
import { mkdir, writeFile } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
|
||||
// Constants
|
||||
const DEFAULT_ROOT_DERIVATION_PATH = "m/84737769'/0'/0'/0'";
|
||||
const API_SERVER = "https://test-api.endorser.ch";
|
||||
const ENDORSER_DID = "did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F";
|
||||
const ENDORSER_PRIVATE_KEY = "2b6472c026ec2aa2c4235c994a63868fc9212d18b58f6cbfe861b52e71330f5b";
|
||||
|
||||
interface IKey {
|
||||
id: string;
|
||||
type: string;
|
||||
controller: string;
|
||||
ethereumAddress: string;
|
||||
publicKeyHex: string;
|
||||
privateKeyHex: string;
|
||||
}
|
||||
|
||||
interface IIdentifier {
|
||||
did: string;
|
||||
keys: IKey[];
|
||||
services: any[];
|
||||
}
|
||||
|
||||
// Create .generated directory if it doesn't exist
|
||||
const GENERATED_DIR = '.generated';
|
||||
|
||||
// Initialize directory creation
|
||||
async function initializeDirectories() {
|
||||
try {
|
||||
await mkdir(GENERATED_DIR, { recursive: true });
|
||||
} catch (err) {
|
||||
console.error('Error creating .generated directory:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new mnemonic seed phrase
|
||||
*/
|
||||
function generateMnemonic(): string {
|
||||
return Wallet.createRandom().mnemonic.phrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive Ethereum address and keys from a mnemonic phrase
|
||||
*/
|
||||
async function deriveAddress(
|
||||
mnemonic: string,
|
||||
derivationPath: string = DEFAULT_ROOT_DERIVATION_PATH
|
||||
): Promise<[string, string, string, string]> {
|
||||
mnemonic = mnemonic.trim().toLowerCase();
|
||||
const hdnode: HDNode = HDNode.fromMnemonic(mnemonic);
|
||||
const rootNode: HDNode = hdnode.derivePath(derivationPath);
|
||||
|
||||
console.log("\nKey Derivation:");
|
||||
console.log(` Mnemonic (first 4 words): ${mnemonic.split(' ').slice(0,4).join(' ')}...`);
|
||||
console.log(` Derivation Path: ${derivationPath}`);
|
||||
|
||||
const privateHex = rootNode.privateKey.substring(2); // remove '0x'
|
||||
const publicHex = rootNode.publicKey.substring(2); // remove '0x'
|
||||
const address = rootNode.address;
|
||||
|
||||
console.log(` Address: ${address}`);
|
||||
console.log(` Private Key: ${privateHex.substring(0,8)}...`);
|
||||
console.log(` Public Key: ${publicHex.substring(0,8)}...`);
|
||||
|
||||
// Save derivation data
|
||||
const derivationData = {
|
||||
mnemonic,
|
||||
derivationPath,
|
||||
address,
|
||||
privateKey: privateHex,
|
||||
publicKey: publicHex
|
||||
};
|
||||
try {
|
||||
await mkdir(GENERATED_DIR, { recursive: true });
|
||||
await writeFile(
|
||||
join(GENERATED_DIR, 'key_derivation.json'),
|
||||
JSON.stringify(derivationData, null, 2)
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('Error saving derivation data:', err);
|
||||
}
|
||||
|
||||
return [address, privateHex, publicHex, derivationPath];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new DID identifier with associated keys
|
||||
*/
|
||||
function newIdentifier(
|
||||
address: string,
|
||||
publicKeyHex: string,
|
||||
privateKeyHex: string,
|
||||
derivationPath: string
|
||||
): IIdentifier {
|
||||
const did = `did:ethr:${address}`;
|
||||
const keyId = `${did}#keys-1`;
|
||||
|
||||
const identifier: IIdentifier = {
|
||||
did,
|
||||
keys: [{
|
||||
id: keyId,
|
||||
type: "Secp256k1VerificationKey2018",
|
||||
controller: did,
|
||||
ethereumAddress: address,
|
||||
publicKeyHex,
|
||||
privateKeyHex
|
||||
}],
|
||||
services: []
|
||||
};
|
||||
return identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a new DID account or load existing one
|
||||
*/
|
||||
async function initializeAccount(): Promise<IIdentifier> {
|
||||
const mnemonic = generateMnemonic();
|
||||
const [address, privateHex, publicHex, derivationPath] = await deriveAddress(mnemonic);
|
||||
const identity = newIdentifier(address, publicHex, privateHex, derivationPath);
|
||||
|
||||
// Format and display account data
|
||||
const accountData = {
|
||||
did: identity.did,
|
||||
identity,
|
||||
mnemonic,
|
||||
derivation_path: derivationPath
|
||||
};
|
||||
console.log("\nAccount initialized:");
|
||||
console.log(JSON.stringify(accountData, null, 2));
|
||||
console.log();
|
||||
|
||||
// Save account data
|
||||
await writeFile(
|
||||
join(GENERATED_DIR, 'account_init.json'),
|
||||
JSON.stringify(accountData, null, 2)
|
||||
);
|
||||
|
||||
return identity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a signed JWT for DID registration
|
||||
*/
|
||||
async function createEndorserJwt(
|
||||
did: string,
|
||||
privateKeyHex: string,
|
||||
payload: any,
|
||||
subDid?: string,
|
||||
expiresIn: number = 3600
|
||||
): Promise<string> {
|
||||
const signer = await SimpleSigner(privateKeyHex);
|
||||
|
||||
const jwt = await createJWT(
|
||||
payload,
|
||||
{
|
||||
issuer: did,
|
||||
signer,
|
||||
expiresIn,
|
||||
...(subDid && { subject: subDid })
|
||||
}
|
||||
);
|
||||
|
||||
return jwt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a DID with the endorser service
|
||||
*/
|
||||
async function register(
|
||||
activeDid: string,
|
||||
privateKeyHex: string,
|
||||
apiServer: string = API_SERVER
|
||||
): Promise<{
|
||||
success?: boolean;
|
||||
handleId?: string;
|
||||
registrationId?: number;
|
||||
claimId?: string;
|
||||
error?: string;
|
||||
details?: any;
|
||||
}> {
|
||||
console.log("\nEndorser DID:", ENDORSER_DID);
|
||||
console.log("Active DID:", activeDid);
|
||||
|
||||
const vcPayload = {
|
||||
vc: {
|
||||
"@context": ["https://www.w3.org/2018/credentials/v1"],
|
||||
type: ["VerifiableCredential"],
|
||||
credentialSubject: {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "RegisterAction",
|
||||
"agent": {
|
||||
"identifier": ENDORSER_DID
|
||||
},
|
||||
"participant": {
|
||||
"identifier": activeDid
|
||||
},
|
||||
"object": "endorser.ch",
|
||||
"endTime": Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Debug output
|
||||
console.log("\nRegistration Claim:");
|
||||
console.log(JSON.stringify(vcPayload, null, 2));
|
||||
console.log();
|
||||
|
||||
// Sign with endorser's DID and private key
|
||||
const jwtToken = await createEndorserJwt(
|
||||
ENDORSER_DID,
|
||||
ENDORSER_PRIVATE_KEY,
|
||||
vcPayload,
|
||||
activeDid
|
||||
);
|
||||
|
||||
// Debug output
|
||||
console.log("Generated JWT:");
|
||||
console.log(jwtToken);
|
||||
console.log();
|
||||
|
||||
// Debug JWT parts
|
||||
const [header, payload, signature] = jwtToken.split('.');
|
||||
console.log("\nJWT Header:", JSON.parse(Buffer.from(header, 'base64url').toString()));
|
||||
console.log("JWT Payload:", JSON.parse(Buffer.from(payload, 'base64url').toString()));
|
||||
console.log("JWT Signature length:", Buffer.from(signature, 'base64url').length, "bytes");
|
||||
console.log();
|
||||
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${apiServer}/api/v2/claim`,
|
||||
{ jwtEncoded: jwtToken },
|
||||
{ headers: { 'Content-Type': 'application/json' } }
|
||||
);
|
||||
|
||||
if (response.status === 200 && response.data.success?.handleId) {
|
||||
// Return all success details
|
||||
const registrationData = {
|
||||
activeDid,
|
||||
jwtToken,
|
||||
response: response.data.success
|
||||
};
|
||||
await writeFile(
|
||||
join(GENERATED_DIR, 'registration.json'),
|
||||
JSON.stringify(registrationData, null, 2)
|
||||
);
|
||||
return {
|
||||
success: true,
|
||||
handleId: response.data.success.handleId,
|
||||
registrationId: response.data.success.registrationId,
|
||||
claimId: response.data.success.claimId
|
||||
};
|
||||
}
|
||||
|
||||
// Log full error response
|
||||
console.error("\nAPI Error Response:");
|
||||
console.error(JSON.stringify(response.data, null, 2));
|
||||
console.error();
|
||||
|
||||
// If we have success data but no handleId, it might be a partial success
|
||||
if (response.data.success) {
|
||||
const registrationData = {
|
||||
activeDid,
|
||||
jwtToken,
|
||||
response: response.data.success
|
||||
};
|
||||
await writeFile(
|
||||
join(GENERATED_DIR, 'registration.json'),
|
||||
JSON.stringify(registrationData, null, 2)
|
||||
);
|
||||
return {
|
||||
success: true,
|
||||
...response.data.success
|
||||
};
|
||||
} else {
|
||||
const registrationData = {
|
||||
activeDid,
|
||||
jwtToken,
|
||||
response: response.data
|
||||
};
|
||||
await writeFile(
|
||||
join(GENERATED_DIR, 'registration.json'),
|
||||
JSON.stringify(registrationData, null, 2)
|
||||
);
|
||||
return { error: "Registration failed", details: response.data };
|
||||
}
|
||||
} catch (error: any) {
|
||||
// Log detailed error information
|
||||
console.error("\nAPI Request Error:");
|
||||
if (error.response) {
|
||||
console.error("Status:", error.response.status);
|
||||
console.error("Headers:", error.response.headers);
|
||||
console.error("Data:", JSON.stringify(error.response.data, null, 2));
|
||||
} else if (error.request) {
|
||||
console.error("No response received");
|
||||
console.error(error.request);
|
||||
} else {
|
||||
console.error("Error setting up request:", error.message);
|
||||
}
|
||||
console.error();
|
||||
|
||||
const registrationData = {
|
||||
activeDid,
|
||||
jwtToken,
|
||||
response: { error: `Error submitting claim: ${error.message}` }
|
||||
};
|
||||
await writeFile(
|
||||
join(GENERATED_DIR, 'registration.json'),
|
||||
JSON.stringify(registrationData, null, 2)
|
||||
);
|
||||
return { error: `Error submitting claim: ${error.message}` };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch claim details from the endorser service
|
||||
*/
|
||||
async function fetchClaim(
|
||||
claimId: string,
|
||||
activeDid: string,
|
||||
privateKeyHex: string,
|
||||
apiServer: string = API_SERVER
|
||||
): Promise<any> {
|
||||
// Create authentication JWT
|
||||
const authPayload = {
|
||||
intent: 'claim.read',
|
||||
claimId: claimId
|
||||
};
|
||||
|
||||
// Sign with the active DID (not endorser DID)
|
||||
const authToken = await createEndorserJwt(
|
||||
activeDid,
|
||||
privateKeyHex,
|
||||
authPayload
|
||||
);
|
||||
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`${apiServer}/api/claim/byHandle/${claimId}`,
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const claimData = response.data;
|
||||
|
||||
// Save claim data
|
||||
try {
|
||||
await writeFile(
|
||||
join(GENERATED_DIR, 'claim_details.json'),
|
||||
JSON.stringify({
|
||||
claim_id: claimId,
|
||||
active_did: activeDid,
|
||||
response: claimData
|
||||
}, null, 2)
|
||||
);
|
||||
} catch (err) {
|
||||
console.error('Error saving claim data:', err);
|
||||
}
|
||||
|
||||
return claimData;
|
||||
|
||||
} catch (error: any) {
|
||||
console.error("\nError fetching claim:");
|
||||
if (error.response) {
|
||||
console.error("Status:", error.response.status);
|
||||
console.error("Data:", JSON.stringify(error.response.data, null, 2));
|
||||
} else {
|
||||
console.error(error.message);
|
||||
}
|
||||
// Save error state
|
||||
try {
|
||||
await writeFile(
|
||||
join(GENERATED_DIR, 'claim_details.json'),
|
||||
JSON.stringify({
|
||||
claim_id: claimId,
|
||||
active_did: activeDid,
|
||||
error: error.message || 'Unknown error',
|
||||
response: error.response?.data
|
||||
}, null, 2)
|
||||
);
|
||||
} catch (writeErr) {
|
||||
console.error('Error saving claim error:', writeErr);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate test environment data for deeplink testing
|
||||
*/
|
||||
async function generateTestEnv(
|
||||
identity: IIdentifier,
|
||||
jwtToken: string,
|
||||
apiServer: string = API_SERVER
|
||||
): Promise<void> {
|
||||
// Create test data structure
|
||||
const testEnvData = {
|
||||
CONTACT1_DID: identity.did,
|
||||
CONTACT1_KEY: identity.keys[0].privateKeyHex,
|
||||
CONTACT2_DID: `did:ethr:${Wallet.createRandom().address}`, // Generate random DID for contact 2
|
||||
CONTACT2_KEY: Wallet.createRandom().privateKey.substring(2), // Remove '0x'
|
||||
ISSUER_DID: ENDORSER_DID,
|
||||
ISSUER_KEY: ENDORSER_PRIVATE_KEY,
|
||||
TEST_JWT: jwtToken,
|
||||
API_SERVER: apiServer
|
||||
};
|
||||
|
||||
// Write test environment variables
|
||||
const envContent = Object.entries(testEnvData)
|
||||
.map(([key, value]) => `export ${key}="${value}"`)
|
||||
.join('\n');
|
||||
|
||||
await writeFile(
|
||||
join(GENERATED_DIR, 'test-env.sh'),
|
||||
envContent + '\n',
|
||||
'utf-8'
|
||||
);
|
||||
|
||||
// Write test contacts data
|
||||
const contactsData = {
|
||||
contacts: [
|
||||
{ did: testEnvData.CONTACT1_DID, name: 'Test Contact 1' },
|
||||
{ did: testEnvData.CONTACT2_DID, name: 'Test Contact 2' }
|
||||
]
|
||||
};
|
||||
|
||||
await writeFile(
|
||||
join(GENERATED_DIR, 'contacts.json'),
|
||||
JSON.stringify(contactsData, null, 2),
|
||||
'utf-8'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Main execution flow
|
||||
*/
|
||||
async function main() {
|
||||
// Initialize directories first
|
||||
await initializeDirectories();
|
||||
|
||||
try {
|
||||
// Create a new DID
|
||||
const identity = await initializeAccount();
|
||||
const activeDid = identity.did;
|
||||
const privateKeyHex = identity.keys[0].privateKeyHex;
|
||||
|
||||
// Register the DID
|
||||
const result = await register(activeDid, privateKeyHex);
|
||||
console.log("Registration result:", result);
|
||||
|
||||
// If registration was successful, fetch the claim details
|
||||
if (result.success && result.claimId) {
|
||||
console.log("\nFetching claim details...");
|
||||
const claimDetails = await fetchClaim(
|
||||
result.claimId,
|
||||
activeDid,
|
||||
privateKeyHex
|
||||
);
|
||||
console.log("\nClaim Details:");
|
||||
console.log(JSON.stringify(claimDetails, null, 2));
|
||||
|
||||
// Generate test environment data
|
||||
await generateTestEnv(identity, result.claimId);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("Error:", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Run if called directly
|
||||
if (require.main === module) {
|
||||
main().catch(console.error);
|
||||
}
|
||||
|
||||
export {
|
||||
generateMnemonic,
|
||||
deriveAddress,
|
||||
newIdentifier,
|
||||
initializeAccount,
|
||||
createEndorserJwt,
|
||||
register,
|
||||
fetchClaim,
|
||||
generateTestEnv
|
||||
};
|
||||
Reference in New Issue
Block a user