Browse Source
			
			
			
			
				
		- Match Python/TypeScript implementations - Use consistent JWT signing approach - Maintain payload compatibility - Add detailed documentation
				 1 changed files with 176 additions and 0 deletions
			
			
		@ -0,0 +1,176 @@ | 
				
			|||||
 | 
					#!/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 24-word mnemonic using Python | 
				
			||||
 | 
					        python3 -c " | 
				
			||||
 | 
					from eth_account.hdaccount import generate_mnemonic | 
				
			||||
 | 
					print(generate_mnemonic(language='english')) | 
				
			||||
 | 
					" > mnemonic.txt | 
				
			||||
 | 
					    fi | 
				
			||||
 | 
					     | 
				
			||||
 | 
					    # Read and process mnemonic | 
				
			||||
 | 
					    MNEMONIC=$(cat mnemonic.txt) | 
				
			||||
 | 
					     | 
				
			||||
 | 
					    # Derive address and keys using Python | 
				
			||||
 | 
					    IDENTITY=$(python3 -c " | 
				
			||||
 | 
					from eth_account import Account | 
				
			||||
 | 
					from eth_keys import keys | 
				
			||||
 | 
					import json | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					Account.enable_unaudited_hdwallet_features() | 
				
			||||
 | 
					mnemonic = '$MNEMONIC'.strip() | 
				
			||||
 | 
					account = Account.from_mnemonic(mnemonic) | 
				
			||||
 | 
					address = account.address | 
				
			||||
 | 
					private_key = account.key.hex()[2:] | 
				
			||||
 | 
					pk = keys.PrivateKey(account.key) | 
				
			||||
 | 
					public_key = pk.public_key.to_hex()[2:] | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					identity = { | 
				
			||||
 | 
					    'did': f'did:ethr:{address}', | 
				
			||||
 | 
					    'keys': [{ | 
				
			||||
 | 
					        'id': f'did:ethr:{address}#keys-1', | 
				
			||||
 | 
					        'type': 'Secp256k1VerificationKey2018', | 
				
			||||
 | 
					        'controller': f'did:ethr:{address}', | 
				
			||||
 | 
					        'ethereumAddress': address, | 
				
			||||
 | 
					        'publicKeyHex': public_key, | 
				
			||||
 | 
					        'privateKeyHex': private_key | 
				
			||||
 | 
					    }], | 
				
			||||
 | 
					    'services': [] | 
				
			||||
 | 
					} | 
				
			||||
 | 
					print(json.dumps(identity)) | 
				
			||||
 | 
					") | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					    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" | 
				
			||||
 | 
					     | 
				
			||||
 | 
					    # Sign using Python eth_keys (matching TypeScript ES256K implementation) | 
				
			||||
 | 
					    local signature=$(python3 -c " | 
				
			||||
 | 
					from eth_keys import keys | 
				
			||||
 | 
					import hashlib | 
				
			||||
 | 
					import base64 | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					private_key_bytes = bytes.fromhex('$private_key') | 
				
			||||
 | 
					private_key = keys.PrivateKey(private_key_bytes) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					message_hash = hashlib.sha256('$message'.encode()).digest() | 
				
			||||
 | 
					signature = private_key.sign_msg_hash(message_hash) | 
				
			||||
 | 
					
 | 
				
			||||
 | 
					signature_bytes = signature.r.to_bytes(32, 'big') + signature.s.to_bytes(32, 'big') | 
				
			||||
 | 
					print(base64.urlsafe_b64encode(signature_bytes).decode().rstrip('=')) | 
				
			||||
 | 
					") | 
				
			||||
 | 
					     | 
				
			||||
 | 
					    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 "$@"  | 
				
			||||
					Loading…
					
					
				
		Reference in new issue