Browse Source
- Match Python/TypeScript implementations - Use consistent JWT signing approach - Maintain payload compatibility - Add detailed documentationpull/127/head
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