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