You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
176 lines
4.7 KiB
176 lines
4.7 KiB
1 week ago
|
#!/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 "$@"
|