forked from jsnbuchanan/crowd-funder-for-time-pwa
- Use proper secp256k1 signing tools - Simplify private key format - Add fallback signing mechanism - Match TypeScript/Python signature format - Fix JWT verification error This fixes the JWT verification by using proper secp256k1 signing tools and matching the signature format of the working implementations.
188 lines
5.4 KiB
Bash
Executable File
188 lines
5.4 KiB
Bash
Executable File
#!/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 "$@" |