#!/bin/bash # DID Creation and Registration Flow # @author Matthew Raymer # # This script implements the creation and registration of Decentralized Identifiers (DIDs) # with the endorser.ch service. It matches the Python and TypeScript implementations. # # Flow: # 1. Generate or load mnemonic seed phrase # 2. Derive Ethereum keys and address # 3. Create DID identifier # 4. Initialize account # 5. Create signed JWT # 6. Register DID with endorser service # Constants API_SERVER=${ENDORSER_API_URL:-"https://test-api.endorser.ch"} ENDORSER_DID="did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F" ENDORSER_PRIVATE_KEY="2b6472c026ec2aa2c4235c994a63868fc9212d18b58f6cbfe861b52e71330f5b" # Create temporary directory for operations TMPDIR=$(mktemp -d) trap 'rm -rf "$TMPDIR"' EXIT initialize_account() { # Generate or load mnemonic if [ ! -f "mnemonic.txt" ]; then # Generate entropy and convert to hex openssl rand -hex 32 > mnemonic.txt fi # Read entropy ENTROPY=$(cat mnemonic.txt) # Create temporary directory for key operations TMPDIR=$(mktemp -d) trap 'rm -rf "$TMPDIR"' EXIT # Generate secp256k1 private key openssl ecparam -name secp256k1 -genkey -noout -out "$TMPDIR/private.pem" # Extract private key in hex format PRIVATE_KEY=$(openssl ec -in "$TMPDIR/private.pem" -text -noout 2>/dev/null | grep priv -A 3 | tail -n +2 | tr -d '\n[:space:]:' | cut -c3-) # Generate public key and address PUBLIC_KEY=$(openssl ec -in "$TMPDIR/private.pem" -pubout -outform DER 2>/dev/null | tail -c 65 | xxd -p -c 65) # Generate Ethereum address (last 20 bytes of keccak256 of public key) ADDRESS=$(echo -n "$PUBLIC_KEY" | xxd -r -p | openssl dgst -sha3-256 -binary | tail -c 20 | xxd -p) # Create identity JSON IDENTITY=$(cat <<EOF { "did": "did:ethr:0x${ADDRESS}", "keys": [{ "id": "did:ethr:0x${ADDRESS}#keys-1", "type": "Secp256k1VerificationKey2018", "controller": "did:ethr:0x${ADDRESS}", "ethereumAddress": "0x${ADDRESS}", "publicKeyHex": "${PUBLIC_KEY}", "privateKeyHex": "${PRIVATE_KEY}" }], "services": [] } EOF ) echo "Account initialized:" echo "$IDENTITY" | jq . echo # Export for other functions export IDENTITY } create_endorser_jwt() { local did="$1" local private_key="$2" local payload="$3" local sub_did="$4" # Create JWT header and payload local now=$(date +%s) local exp=$((now + 3600)) local header='{"typ":"JWT","alg":"ES256K"}' local jwt_payload=$(echo "$payload" | jq --arg iss "$did" \ --arg iat "$now" \ --arg exp "$exp" \ '. + {iss: $iss, iat: ($iat|tonumber), exp: ($exp|tonumber)}') # Base64URL encode header and payload local header_b64=$(echo -n "$header" | base64 -w 0 | tr '/+' '_-' | tr -d '=') local payload_b64=$(echo -n "$jwt_payload" | base64 -w 0 | tr '/+' '_-' | tr -d '=') local message="$header_b64.$payload_b64" # Create temporary directory local TMPDIR=$(mktemp -d) trap 'rm -rf "$TMPDIR"' EXIT # Create private key in SEC1 format ( echo -n "$private_key" # Private key bytes ) | xxd -r -p > "$TMPDIR/private.key" # Hash the message echo -n "$message" | openssl dgst -sha256 -binary -out "$TMPDIR/message.hash" # Sign using bitcoin-cli (or similar tool that handles secp256k1 correctly) if command -v bitcoin-cli &> /dev/null; then # Use bitcoin-cli if available signature=$(bitcoin-cli signmessagewithprivkey \ "$(cat "$TMPDIR/private.key" | xxd -p -c 64)" \ "$(cat "$TMPDIR/message.hash" | xxd -p -c 32)") else # Fallback to custom secp256k1 signing signature=$(secp256k1-sign "$TMPDIR/private.key" "$TMPDIR/message.hash") fi echo "$message.$signature" } register() { local active_did="$1" echo "Endorser DID: $ENDORSER_DID" echo "Active DID: $active_did" # Create registration claim local vc_payload=$(cat <<EOF { "vc": { "@context": ["https://www.w3.org/2018/credentials/v1"], "type": ["VerifiableCredential"], "credentialSubject": { "@context": "https://schema.org", "@type": "RegisterAction", "agent": { "identifier": "$ENDORSER_DID" }, "participant": { "identifier": "$active_did" }, "object": "endorser.ch", "endTime": $(( $(date +%s) + 7*24*60*60 )) } } } EOF ) echo "Registration Claim:" echo "$vc_payload" | jq . echo # Create and sign JWT local jwt_token=$(create_endorser_jwt "$ENDORSER_DID" "$ENDORSER_PRIVATE_KEY" "$vc_payload" "$active_did") echo "Generated JWT:" echo "$jwt_token" echo # Submit registration local response=$(curl -s -X POST "$API_SERVER/api/v2/claim" \ -H "Content-Type: application/json" \ -d "{\"jwtEncoded\": \"$jwt_token\"}") echo "Registration response:" echo "$response" | jq . } main() { initialize_account # Extract DID and private key from identity local active_did=$(echo "$IDENTITY" | jq -r .did) local private_key=$(echo "$IDENTITY" | jq -r .keys[0].privateKeyHex) # Register DID register "$active_did" } main "$@"