···
2
+
# Derive public key from existing PDS_ROTATION_KEY and create did.json
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.
8
+
# Usage: ./scripts/derive-did-from-key.sh
12
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
13
+
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
14
+
OUTPUT_DIR="$PROJECT_DIR/static/.well-known"
22
+
log() { echo -e "${GREEN}[DERIVE]${NC} $1"; }
23
+
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
24
+
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
26
+
# Check for required tools
27
+
if ! command -v openssl &> /dev/null; then
28
+
error "openssl is required but not installed"
31
+
if ! command -v python3 &> /dev/null; then
32
+
error "python3 is required for base58 encoding"
35
+
# Check for base58 library
36
+
if ! 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"
41
+
# Load environment to get the existing key
42
+
if [ -f "$PROJECT_DIR/.env.prod" ]; then
43
+
source "$PROJECT_DIR/.env.prod"
44
+
elif [ -f "$PROJECT_DIR/.env" ]; then
45
+
source "$PROJECT_DIR/.env"
47
+
error "No .env.prod or .env file found"
50
+
if [ -z "$PDS_ROTATION_KEY" ]; then
51
+
error "PDS_ROTATION_KEY not found in environment"
54
+
# Validate key format (should be 64 hex chars)
55
+
if [[ ! "$PDS_ROTATION_KEY" =~ ^[0-9a-fA-F]{64}$ ]]; then
56
+
error "PDS_ROTATION_KEY is not a valid 64-character hex string"
59
+
log "Deriving public key from existing PDS_ROTATION_KEY..."
61
+
# Create a temporary PEM file from the hex private key
62
+
TEMP_DIR=$(mktemp -d)
63
+
PRIVATE_KEY_HEX="$PDS_ROTATION_KEY"
65
+
# Convert hex private key to PEM format
66
+
# secp256k1 curve OID: 1.3.132.0.10
67
+
python3 > "$TEMP_DIR/private.pem" << EOF
70
+
# Private key in hex
71
+
priv_hex = "$PRIVATE_KEY_HEX"
72
+
priv_bytes = binascii.unhexlify(priv_hex)
75
+
oid = bytes([0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x0a])
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
81
+
# EC PARAMETERS for secp256k1
83
+
0x30, 0x07, # SEQUENCE, 7 bytes
84
+
0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x0a # OID for secp256k1
87
+
# EC PRIVATE KEY structure
88
+
# SEQUENCE { version, privateKey, [0] parameters }
89
+
inner = bytes([0x02, 0x01, 0x01]) # version = 1
90
+
inner += bytes([0x04, 0x20]) + priv_bytes # OCTET STRING with 32-byte key
91
+
inner += bytes([0xa0, 0x07]) + bytes([0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x0a]) # [0] OID
94
+
key_der = bytes([0x30, len(inner)]) + inner
98
+
key_b64 = base64.b64encode(key_der).decode('ascii')
101
+
print("-----BEGIN EC PRIVATE KEY-----")
102
+
for i in range(0, len(key_b64), 64):
103
+
print(key_b64[i:i+64])
104
+
print("-----END EC PRIVATE KEY-----")
107
+
# Extract the compressed public key
108
+
PUBLIC_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')
114
+
if [ -z "$PUBLIC_KEY_HEX" ] || [ ${#PUBLIC_KEY_HEX} -ne 66 ]; then
115
+
error "Failed to derive public key. Got: $PUBLIC_KEY_HEX"
118
+
log "Derived public key: ${PUBLIC_KEY_HEX:0:8}...${PUBLIC_KEY_HEX: -8}"
120
+
# Encode public key as multibase with multicodec
121
+
PUBLIC_KEY_MULTIBASE=$(python3 << EOF
124
+
# Compressed public key bytes
125
+
pub_hex = "$PUBLIC_KEY_HEX"
126
+
pub_bytes = bytes.fromhex(pub_hex)
128
+
# Prepend multicodec 0xe7 for secp256k1-pub
129
+
# 0xe7 as varint is just 0xe7 (single byte, < 128)
130
+
multicodec = bytes([0xe7, 0x01]) # 0xe701 for secp256k1-pub compressed
131
+
key_with_codec = multicodec + pub_bytes
134
+
encoded = base58.b58encode(key_with_codec).decode('ascii')
136
+
# Add 'z' prefix for multibase
137
+
print('z' + encoded)
141
+
log "Public key multibase: $PUBLIC_KEY_MULTIBASE"
143
+
# Generate the did.json file
144
+
log "Generating did.json..."
146
+
mkdir -p "$OUTPUT_DIR"
148
+
cat > "$OUTPUT_DIR/did.json" << EOF
150
+
"id": "did:web:coves.social",
151
+
"alsoKnownAs": ["at://coves.social"],
152
+
"verificationMethod": [
154
+
"id": "did:web:coves.social#atproto",
155
+
"type": "Multikey",
156
+
"controller": "did:web:coves.social",
157
+
"publicKeyMultibase": "$PUBLIC_KEY_MULTIBASE"
162
+
"id": "#atproto_pds",
163
+
"type": "AtprotoPersonalDataServer",
164
+
"serviceEndpoint": "https://coves.me"
170
+
log "Created: $OUTPUT_DIR/did.json"
172
+
echo "============================================"
173
+
echo " DID Document Generated Successfully!"
174
+
echo "============================================"
176
+
echo "Public key multibase: $PUBLIC_KEY_MULTIBASE"
179
+
echo " 1. Copy this file to your production server:"
180
+
echo " scp $OUTPUT_DIR/did.json user@server:/opt/coves/static/.well-known/"
182
+
echo " 2. Or if running on production, restart Caddy:"
183
+
echo " docker compose -f docker-compose.prod.yml restart caddy"
185
+
echo " 3. Verify it's accessible:"
186
+
echo " curl https://coves.social/.well-known/did.json"