tangled
alpha
login
or
join now
mary.my.id
/
boat
22
fork
atom
handy online tools for AT Protocol
boat.kelinci.net
atproto
bluesky
atcute
typescript
solidjs
22
fork
atom
overview
issues
pulls
pipelines
feat: crypto info
mary.my.id
3 months ago
d35afbe1
81c2be02
verified
This commit was signed with the committer's
known signature
.
mary.my.id
SSH Key Fingerprint:
SHA256:ZlTP/auFSGpGnaoDg4mCTG1g9OZvXp62jWR4c6H4O3c=
+261
-2
4 changed files
expand all
collapse all
unified
split
src
routes.ts
views
crypto
crypto-generate.tsx
crypto-info.tsx
frontpage.tsx
+4
src/routes.ts
···
22
22
path: '/crypto-generate',
23
23
component: lazy(() => import('./views/crypto/crypto-generate')),
24
24
},
25
25
+
{
26
26
+
path: '/crypto-info',
27
27
+
component: lazy(() => import('./views/crypto/crypto-info')),
28
28
+
},
25
29
26
30
{
27
31
path: '/did-lookup',
+1
-1
src/views/crypto/crypto-generate.tsx
···
51
51
]);
52
52
53
53
const result: KeypairResult = {
54
54
-
type: keypair.type,
54
54
+
type: keypair.type as KeyType,
55
55
publicDidKey,
56
56
privateHex,
57
57
privateMultikey,
+255
src/views/crypto/crypto-info.tsx
···
1
1
+
import { createMemo, createSignal, Match, Show, Switch } from 'solid-js';
2
2
+
3
3
+
import {
4
4
+
type DidKeyString,
5
5
+
P256PrivateKeyExportable,
6
6
+
P256PublicKey,
7
7
+
parseDidKey,
8
8
+
parsePrivateMultikey,
9
9
+
parsePublicMultikey,
10
10
+
Secp256k1PrivateKeyExportable,
11
11
+
Secp256k1PublicKey,
12
12
+
} from '@atcute/crypto';
13
13
+
import { fromBase16 } from '@atcute/multibase';
14
14
+
15
15
+
import { useTitle } from '~/lib/navigation/router';
16
16
+
17
17
+
import Button from '~/components/inputs/button';
18
18
+
import RadioInput from '~/components/inputs/radio-input';
19
19
+
import TextInput from '~/components/inputs/text-input';
20
20
+
import PageHeader from '~/components/page-header';
21
21
+
22
22
+
type KeyType = 'p256' | 'secp256k1';
23
23
+
type KeyFormat = 'did:key' | 'multikey' | 'hex';
24
24
+
25
25
+
interface KeyInfo {
26
26
+
keyType: KeyType;
27
27
+
isPrivate: boolean;
28
28
+
inputFormat: KeyFormat;
29
29
+
publicDidKey: DidKeyString;
30
30
+
publicMultikey: string;
31
31
+
privateHex?: string;
32
32
+
privateMultikey?: string;
33
33
+
}
34
34
+
35
35
+
const DID_KEY_REGEX = /^did:key:z[a-km-zA-HJ-NP-Z1-9]+$/;
36
36
+
const MULTIKEY_REGEX = /^z[a-km-zA-HJ-NP-Z1-9]+$/;
37
37
+
const HEX_REGEX = /^[0-9a-f]+$/;
38
38
+
39
39
+
const CryptoInfoPage = () => {
40
40
+
const [input, setInput] = createSignal('');
41
41
+
const [hexKeyType, setHexKeyType] = createSignal<KeyType>();
42
42
+
const [result, setResult] = createSignal<KeyInfo>();
43
43
+
const [error, setError] = createSignal<string>();
44
44
+
45
45
+
const detectedFormat = createMemo((): KeyFormat | undefined => {
46
46
+
const $input = input().trim();
47
47
+
48
48
+
if (DID_KEY_REGEX.test($input)) {
49
49
+
return 'did:key';
50
50
+
}
51
51
+
if (MULTIKEY_REGEX.test($input)) {
52
52
+
return 'multikey';
53
53
+
}
54
54
+
if (HEX_REGEX.test($input)) {
55
55
+
return 'hex';
56
56
+
}
57
57
+
});
58
58
+
59
59
+
const canSubmit = createMemo(() => {
60
60
+
const format = detectedFormat();
61
61
+
if (!format) {
62
62
+
return false;
63
63
+
}
64
64
+
if (format === 'hex' && !hexKeyType()) {
65
65
+
return false;
66
66
+
}
67
67
+
return true;
68
68
+
});
69
69
+
70
70
+
useTitle(() => `View crypto key info — boat`);
71
71
+
72
72
+
return (
73
73
+
<>
74
74
+
<PageHeader title="View crypto key info" subtitle="Show basic metadata about a public or private key" />
75
75
+
76
76
+
<form
77
77
+
onSubmit={async (ev) => {
78
78
+
ev.preventDefault();
79
79
+
80
80
+
const $input = input().trim();
81
81
+
const format = detectedFormat();
82
82
+
83
83
+
setResult();
84
84
+
setError();
85
85
+
86
86
+
try {
87
87
+
let info: KeyInfo;
88
88
+
89
89
+
if (format === 'did:key') {
90
90
+
const parsed = parseDidKey($input);
91
91
+
const pubKey =
92
92
+
parsed.type === 'p256'
93
93
+
? await P256PublicKey.importRaw(parsed.publicKeyBytes)
94
94
+
: await Secp256k1PublicKey.importRaw(parsed.publicKeyBytes);
95
95
+
96
96
+
info = {
97
97
+
keyType: parsed.type,
98
98
+
isPrivate: false,
99
99
+
inputFormat: 'did:key',
100
100
+
publicDidKey: await pubKey.exportPublicKey('did'),
101
101
+
publicMultikey: await pubKey.exportPublicKey('multikey'),
102
102
+
};
103
103
+
} else if (format === 'multikey') {
104
104
+
// try parsing as private key first
105
105
+
try {
106
106
+
const parsed = parsePrivateMultikey($input);
107
107
+
const privKey =
108
108
+
parsed.type === 'p256'
109
109
+
? await P256PrivateKeyExportable.importRaw(parsed.privateKeyBytes)
110
110
+
: await Secp256k1PrivateKeyExportable.importRaw(parsed.privateKeyBytes);
111
111
+
112
112
+
info = {
113
113
+
keyType: parsed.type,
114
114
+
isPrivate: true,
115
115
+
inputFormat: 'multikey',
116
116
+
publicDidKey: await privKey.exportPublicKey('did'),
117
117
+
publicMultikey: await privKey.exportPublicKey('multikey'),
118
118
+
privateHex: await privKey.exportPrivateKey('rawHex'),
119
119
+
privateMultikey: await privKey.exportPrivateKey('multikey'),
120
120
+
};
121
121
+
} catch {
122
122
+
// try parsing as public key
123
123
+
const parsed = parsePublicMultikey($input);
124
124
+
const pubKey =
125
125
+
parsed.type === 'p256'
126
126
+
? await P256PublicKey.importRaw(parsed.publicKeyBytes)
127
127
+
: await Secp256k1PublicKey.importRaw(parsed.publicKeyBytes);
128
128
+
129
129
+
info = {
130
130
+
keyType: parsed.type,
131
131
+
isPrivate: false,
132
132
+
inputFormat: 'multikey',
133
133
+
publicDidKey: await pubKey.exportPublicKey('did'),
134
134
+
publicMultikey: await pubKey.exportPublicKey('multikey'),
135
135
+
};
136
136
+
}
137
137
+
} else if (format === 'hex') {
138
138
+
const keyType = hexKeyType()!;
139
139
+
const privateKeyBytes = fromBase16($input);
140
140
+
141
141
+
const privKey =
142
142
+
keyType === 'p256'
143
143
+
? await P256PrivateKeyExportable.importRaw(privateKeyBytes)
144
144
+
: await Secp256k1PrivateKeyExportable.importRaw(privateKeyBytes);
145
145
+
146
146
+
info = {
147
147
+
keyType: keyType,
148
148
+
isPrivate: true,
149
149
+
inputFormat: 'hex',
150
150
+
publicDidKey: await privKey.exportPublicKey('did'),
151
151
+
publicMultikey: await privKey.exportPublicKey('multikey'),
152
152
+
privateHex: await privKey.exportPrivateKey('rawHex'),
153
153
+
privateMultikey: await privKey.exportPrivateKey('multikey'),
154
154
+
};
155
155
+
} else {
156
156
+
throw new Error('Unknown key format');
157
157
+
}
158
158
+
159
159
+
setResult(info);
160
160
+
} catch (err) {
161
161
+
console.error(err);
162
162
+
setError(`Failed to parse key: ${err}`);
163
163
+
}
164
164
+
}}
165
165
+
class="flex flex-col gap-4 p-4"
166
166
+
>
167
167
+
<TextInput
168
168
+
label="Public or private key"
169
169
+
blurb="Accepts did:key, multikey, or hex format"
170
170
+
monospace
171
171
+
autocomplete="off"
172
172
+
autocorrect="off"
173
173
+
placeholder="did:key:z... or z... or a5973930f9d348..."
174
174
+
value={input()}
175
175
+
required
176
176
+
onChange={setInput}
177
177
+
/>
178
178
+
179
179
+
<Show when={detectedFormat() === 'hex'}>
180
180
+
<RadioInput
181
181
+
label="This is a..."
182
182
+
value={hexKeyType()}
183
183
+
required
184
184
+
options={[
185
185
+
{ value: 'secp256k1', label: `ES256K (secp256k1) private key` },
186
186
+
{ value: 'p256', label: `ES256 (p256) private key` },
187
187
+
]}
188
188
+
onChange={setHexKeyType}
189
189
+
/>
190
190
+
</Show>
191
191
+
192
192
+
<div>
193
193
+
<Button type="submit" disabled={!canSubmit()}>
194
194
+
Inspect
195
195
+
</Button>
196
196
+
</div>
197
197
+
</form>
198
198
+
199
199
+
<hr class="mx-4 border-gray-300" />
200
200
+
201
201
+
<Switch>
202
202
+
<Match when={error()}>
203
203
+
<div class="p-4 text-red-600">{error()}</div>
204
204
+
</Match>
205
205
+
206
206
+
<Match when={result()} keyed>
207
207
+
{(info) => (
208
208
+
<div class="flex flex-col gap-6 break-words p-4 text-gray-900">
209
209
+
<div>
210
210
+
<p class="font-semibold text-gray-600">Key type</p>
211
211
+
<span>
212
212
+
{/* @once */ info.keyType === 'p256'
213
213
+
? 'ES256 (p256)'
214
214
+
: 'ES256K (secp256k1)'}{' '}
215
215
+
{/* @once */ info.isPrivate ? 'private' : 'public'} key
216
216
+
</span>
217
217
+
</div>
218
218
+
219
219
+
<div>
220
220
+
<p class="font-semibold text-gray-600">Input encoding</p>
221
221
+
<span>{/* @once */ info.inputFormat}</span>
222
222
+
</div>
223
223
+
224
224
+
<div>
225
225
+
<p class="font-semibold text-gray-600">Public key (did:key)</p>
226
226
+
<span class="font-mono">{/* @once */ info.publicDidKey}</span>
227
227
+
</div>
228
228
+
229
229
+
<div>
230
230
+
<p class="font-semibold text-gray-600">Public key (multikey)</p>
231
231
+
<span class="font-mono">{/* @once */ info.publicMultikey}</span>
232
232
+
</div>
233
233
+
234
234
+
<Show when={info.privateHex}>
235
235
+
<div>
236
236
+
<p class="font-semibold text-gray-600">Private key (hex)</p>
237
237
+
<span class="font-mono">{/* @once */ info.privateHex}</span>
238
238
+
</div>
239
239
+
</Show>
240
240
+
241
241
+
<Show when={info.privateMultikey}>
242
242
+
<div>
243
243
+
<p class="font-semibold text-gray-600">Private key (multikey)</p>
244
244
+
<span class="font-mono">{/* @once */ info.privateMultikey}</span>
245
245
+
</div>
246
246
+
</Show>
247
247
+
</div>
248
248
+
)}
249
249
+
</Match>
250
250
+
</Switch>
251
251
+
</>
252
252
+
);
253
253
+
};
254
254
+
255
255
+
export default CryptoInfoPage;
+1
-1
src/views/frontpage.tsx
···
121
121
{
122
122
name: `View crypto key info`,
123
123
description: `Show basic metadata about a public or private key`,
124
124
-
href: null,
124
124
+
href: `/crypto-info`,
125
125
icon: KeyVisualizerIcon,
126
126
},
127
127
],