A community based topic aggregation platform built on atproto
at main 5.3 kB view raw
1#!/bin/bash 2# Derive public key from existing PDS_ROTATION_KEY and create did.json 3# 4# This script takes your existing private key and derives the public key from it. 5# Use this if you already have a PDS running with a rotation key but need to 6# create/fix the did.json file. 7# 8# Usage: ./scripts/derive-did-from-key.sh 9 10set -e 11 12SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 13PROJECT_DIR="$(dirname "$SCRIPT_DIR")" 14OUTPUT_DIR="$PROJECT_DIR/static/.well-known" 15 16# Colors 17GREEN='\033[0;32m' 18YELLOW='\033[1;33m' 19RED='\033[0;31m' 20NC='\033[0m' 21 22log() { echo -e "${GREEN}[DERIVE]${NC} $1"; } 23warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } 24error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; } 25 26# Check for required tools 27if ! command -v openssl &> /dev/null; then 28 error "openssl is required but not installed" 29fi 30 31if ! command -v python3 &> /dev/null; then 32 error "python3 is required for base58 encoding" 33fi 34 35# Check for base58 library 36if ! python3 -c "import base58" 2>/dev/null; then 37 warn "Installing base58 Python library..." 38 pip3 install base58 || error "Failed to install base58. Run: pip3 install base58" 39fi 40 41# Load environment to get the existing key 42if [ -f "$PROJECT_DIR/.env.prod" ]; then 43 source "$PROJECT_DIR/.env.prod" 44elif [ -f "$PROJECT_DIR/.env" ]; then 45 source "$PROJECT_DIR/.env" 46else 47 error "No .env.prod or .env file found" 48fi 49 50if [ -z "$PDS_ROTATION_KEY" ]; then 51 error "PDS_ROTATION_KEY not found in environment" 52fi 53 54# Validate key format (should be 64 hex chars) 55if [[ ! "$PDS_ROTATION_KEY" =~ ^[0-9a-fA-F]{64}$ ]]; then 56 error "PDS_ROTATION_KEY is not a valid 64-character hex string" 57fi 58 59log "Deriving public key from existing PDS_ROTATION_KEY..." 60 61# Create a temporary PEM file from the hex private key 62TEMP_DIR=$(mktemp -d) 63PRIVATE_KEY_HEX="$PDS_ROTATION_KEY" 64 65# Convert hex private key to PEM format 66# secp256k1 curve OID: 1.3.132.0.10 67python3 > "$TEMP_DIR/private.pem" << EOF 68import binascii 69 70# Private key in hex 71priv_hex = "$PRIVATE_KEY_HEX" 72priv_bytes = binascii.unhexlify(priv_hex) 73 74# secp256k1 OID 75oid = bytes([0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x0a]) 76 77# Build the EC private key structure 78# SEQUENCE { version INTEGER, privateKey OCTET STRING, [0] OID, [1] publicKey } 79# We'll use a simpler approach: just the private key with curve params 80 81# EC PARAMETERS for secp256k1 82ec_params = bytes([ 83 0x30, 0x07, # SEQUENCE, 7 bytes 84 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x0a # OID for secp256k1 85]) 86 87# EC PRIVATE KEY structure 88# SEQUENCE { version, privateKey, [0] parameters } 89inner = bytes([0x02, 0x01, 0x01]) # version = 1 90inner += bytes([0x04, 0x20]) + priv_bytes # OCTET STRING with 32-byte key 91inner += bytes([0xa0, 0x07]) + bytes([0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x0a]) # [0] OID 92 93# Wrap in SEQUENCE 94key_der = bytes([0x30, len(inner)]) + inner 95 96# Base64 encode 97import base64 98key_b64 = base64.b64encode(key_der).decode('ascii') 99 100# Format as PEM 101print("-----BEGIN EC PRIVATE KEY-----") 102for i in range(0, len(key_b64), 64): 103 print(key_b64[i:i+64]) 104print("-----END EC PRIVATE KEY-----") 105EOF 106 107# Extract the compressed public key 108PUBLIC_KEY_HEX=$(openssl ec -in "$TEMP_DIR/private.pem" -pubout -conv_form compressed -outform DER 2>/dev/null | \ 109 tail -c 33 | xxd -p | tr -d '\n') 110 111# Clean up 112rm -rf "$TEMP_DIR" 113 114if [ -z "$PUBLIC_KEY_HEX" ] || [ ${#PUBLIC_KEY_HEX} -ne 66 ]; then 115 error "Failed to derive public key. Got: $PUBLIC_KEY_HEX" 116fi 117 118log "Derived public key: ${PUBLIC_KEY_HEX:0:8}...${PUBLIC_KEY_HEX: -8}" 119 120# Encode public key as multibase with multicodec 121PUBLIC_KEY_MULTIBASE=$(python3 << EOF 122import base58 123 124# Compressed public key bytes 125pub_hex = "$PUBLIC_KEY_HEX" 126pub_bytes = bytes.fromhex(pub_hex) 127 128# Prepend multicodec 0xe7 for secp256k1-pub 129# 0xe7 as varint is just 0xe7 (single byte, < 128) 130multicodec = bytes([0xe7, 0x01]) # 0xe701 for secp256k1-pub compressed 131key_with_codec = multicodec + pub_bytes 132 133# Base58btc encode 134encoded = base58.b58encode(key_with_codec).decode('ascii') 135 136# Add 'z' prefix for multibase 137print('z' + encoded) 138EOF 139) 140 141log "Public key multibase: $PUBLIC_KEY_MULTIBASE" 142 143# Generate the did.json file 144log "Generating did.json..." 145 146mkdir -p "$OUTPUT_DIR" 147 148cat > "$OUTPUT_DIR/did.json" << EOF 149{ 150 "id": "did:web:coves.social", 151 "alsoKnownAs": ["at://coves.social"], 152 "verificationMethod": [ 153 { 154 "id": "did:web:coves.social#atproto", 155 "type": "Multikey", 156 "controller": "did:web:coves.social", 157 "publicKeyMultibase": "$PUBLIC_KEY_MULTIBASE" 158 } 159 ], 160 "service": [ 161 { 162 "id": "#atproto_pds", 163 "type": "AtprotoPersonalDataServer", 164 "serviceEndpoint": "https://coves.me" 165 } 166 ] 167} 168EOF 169 170log "Created: $OUTPUT_DIR/did.json" 171echo "" 172echo "============================================" 173echo " DID Document Generated Successfully!" 174echo "============================================" 175echo "" 176echo "Public key multibase: $PUBLIC_KEY_MULTIBASE" 177echo "" 178echo "Next steps:" 179echo " 1. Copy this file to your production server:" 180echo " scp $OUTPUT_DIR/did.json user@server:/opt/coves/static/.well-known/" 181echo "" 182echo " 2. Or if running on production, restart Caddy:" 183echo " docker compose -f docker-compose.prod.yml restart caddy" 184echo "" 185echo " 3. Verify it's accessible:" 186echo " curl https://coves.social/.well-known/did.json" 187echo ""