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