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 generate page
mary.my.id
1 year ago
d6ea4792
b29abae2
verified
This commit was signed with the committer's
known signature
.
mary.my.id
SSH Key Fingerprint:
SHA256:ZlTP/auFSGpGnaoDg4mCTG1g9OZvXp62jWR4c6H4O3c=
+182
6 changed files
expand all
collapse all
unified
split
src
components
ic-icons
baseline-key-visualizer.tsx
baseline-key.tsx
lib
utils
crypto.ts
routes.ts
views
crypto
crypto-generate.tsx
frontpage.tsx
+12
src/components/ic-icons/baseline-key-visualizer.tsx
···
1
1
+
import { createIcon } from './_icon';
2
2
+
3
3
+
const KeyVisualizerIcon = createIcon(() => (
4
4
+
<svg width="1em" height="1em" viewBox="0 0 24 24">
5
5
+
<path
6
6
+
fill="currentColor"
7
7
+
d="M3 21v-2h3v2zm0-4v-2h8v2zm0-4v-2h18v2zm0-4V7h8v2zm0-4V3h3v2zm5 16v-2h3v2zM8 5V3h3v2zm5 16v-2h3v2zm0-4v-2h8v2zm0-8V7h8v2zm0-4V3h3v2zm5 16v-2h3v2zm0-16V3h3v2z"
8
8
+
></path>
9
9
+
</svg>
10
10
+
));
11
11
+
12
12
+
export default KeyVisualizerIcon;
+12
src/components/ic-icons/baseline-key.tsx
···
1
1
+
import { createIcon } from './_icon';
2
2
+
3
3
+
const KeyIcon = createIcon(() => (
4
4
+
<svg width="1em" height="1em" viewBox="0 0 24 24">
5
5
+
<path
6
6
+
fill="currentColor"
7
7
+
d="M21 10h-8.35A5.99 5.99 0 0 0 7 6c-3.31 0-6 2.69-6 6s2.69 6 6 6a5.99 5.99 0 0 0 5.65-4H13l2 2l2-2l2 2l4-4.04zM7 15c-1.65 0-3-1.35-3-3s1.35-3 3-3s3 1.35 3 3s-1.35 3-3 3"
8
8
+
></path>
9
9
+
</svg>
10
10
+
));
11
11
+
12
12
+
export default KeyIcon;
+5
src/lib/utils/crypto.ts
···
1
1
+
export const P256_PRIVATE_PREFIX = Uint8Array.from([0x86, 0x26]);
2
2
+
export const P256_PUBLIC_PREFIX = Uint8Array.from([0x80, 0x24]);
3
3
+
4
4
+
export const K256_PRIVATE_PREFIX = Uint8Array.from([0x81, 0x26]);
5
5
+
export const K256_PUBLIC_PREFIX = Uint8Array.from([0xe7, 0x01]);
+5
src/routes.ts
···
14
14
},
15
15
16
16
{
17
17
+
path: '/crypto-generate',
18
18
+
component: lazy(() => import('./views/crypto/crypto-generate')),
19
19
+
},
20
20
+
21
21
+
{
17
22
path: '/did-lookup',
18
23
component: lazy(() => import('./views/identity/did-lookup')),
19
24
},
+129
src/views/crypto/crypto-generate.tsx
···
1
1
+
import { createSignal, Show } from 'solid-js';
2
2
+
3
3
+
import { P256Keypair, Secp256k1Keypair } from '@atproto/crypto';
4
4
+
import { concat, toString } from 'uint8arrays';
5
5
+
6
6
+
import { useTitle } from '~/lib/navigation/router';
7
7
+
import { K256_PRIVATE_PREFIX, P256_PRIVATE_PREFIX } from '~/lib/utils/crypto';
8
8
+
9
9
+
import Button from '~/components/inputs/button';
10
10
+
import RadioInput from '~/components/inputs/radio-input';
11
11
+
12
12
+
type KeyType = 'nistp256' | 'secp256k1';
13
13
+
14
14
+
interface GeneratedKeypair {
15
15
+
type: KeyType;
16
16
+
publicDidKey: string;
17
17
+
privateBytes: Uint8Array;
18
18
+
privateMultibase: string;
19
19
+
privateHex: string;
20
20
+
}
21
21
+
22
22
+
const CryptoGeneratePage = () => {
23
23
+
const [type, setType] = createSignal<KeyType>('secp256k1');
24
24
+
const [result, setResult] = createSignal<GeneratedKeypair>();
25
25
+
26
26
+
useTitle(() => `Generate secret keys — boat`);
27
27
+
28
28
+
return (
29
29
+
<>
30
30
+
<div class="p-4">
31
31
+
<h1 class="text-lg font-bold text-purple-800">Generate secret keys</h1>
32
32
+
<p class="text-gray-600">Create a new secp256k1/nistp256 keypair</p>
33
33
+
</div>
34
34
+
<hr class="mx-4 border-gray-300" />
35
35
+
36
36
+
<form
37
37
+
onSubmit={async (ev) => {
38
38
+
ev.preventDefault();
39
39
+
40
40
+
const $type = type();
41
41
+
42
42
+
let keypair: P256Keypair | Secp256k1Keypair;
43
43
+
44
44
+
let publicDidKey: string;
45
45
+
46
46
+
let privateBytes: Uint8Array;
47
47
+
let privateMultibase: string;
48
48
+
let privateHex: string;
49
49
+
50
50
+
if ($type === 'nistp256') {
51
51
+
keypair = await P256Keypair.create({ exportable: true });
52
52
+
53
53
+
privateBytes = await keypair.export();
54
54
+
privateMultibase = `z` + toString(concat([P256_PRIVATE_PREFIX, privateBytes]), 'base58btc');
55
55
+
privateHex = toString(privateBytes, 'hex');
56
56
+
57
57
+
publicDidKey = keypair.did();
58
58
+
} else if ($type === 'secp256k1') {
59
59
+
keypair = await Secp256k1Keypair.create({ exportable: true });
60
60
+
61
61
+
privateBytes = await keypair.export();
62
62
+
privateMultibase = `z` + toString(concat([K256_PRIVATE_PREFIX, privateBytes]), 'base58btc');
63
63
+
privateHex = toString(privateBytes, 'hex');
64
64
+
65
65
+
publicDidKey = keypair.did();
66
66
+
} else {
67
67
+
return;
68
68
+
}
69
69
+
70
70
+
const result: GeneratedKeypair = {
71
71
+
type: $type,
72
72
+
publicDidKey,
73
73
+
privateBytes,
74
74
+
privateMultibase,
75
75
+
privateHex,
76
76
+
};
77
77
+
78
78
+
setResult(result);
79
79
+
}}
80
80
+
class="flex flex-col gap-4 p-4"
81
81
+
>
82
82
+
<RadioInput
83
83
+
label="Key type"
84
84
+
name="type"
85
85
+
required
86
86
+
value={type()}
87
87
+
options={[
88
88
+
{ value: 'secp256k1', label: `ES256K (secp256k1) private key` },
89
89
+
{ value: 'nistp256', label: `ES256 (nistp256) private key` },
90
90
+
]}
91
91
+
onChange={setType}
92
92
+
/>
93
93
+
94
94
+
<div>
95
95
+
<Button type="submit">Generate</Button>
96
96
+
</div>
97
97
+
</form>
98
98
+
<hr class="mx-4 border-gray-300" />
99
99
+
100
100
+
<Show when={result()} keyed>
101
101
+
{(keypair) => (
102
102
+
<div class="flex flex-col gap-6 break-words p-4 text-gray-900">
103
103
+
<div>
104
104
+
<p class="font-semibold text-gray-600">Key type</p>
105
105
+
<span>{/* @once */ keypair.type}</span>
106
106
+
</div>
107
107
+
108
108
+
<div>
109
109
+
<p class="font-semibold text-gray-600">Public key (did:key)</p>
110
110
+
<span class="font-mono">{/* @once */ keypair.publicDidKey}</span>
111
111
+
</div>
112
112
+
113
113
+
<div>
114
114
+
<p class="font-semibold text-gray-600">Private key (hex)</p>
115
115
+
<span class="font-mono">{/* @once */ keypair.privateHex}</span>
116
116
+
</div>
117
117
+
118
118
+
<div>
119
119
+
<p class="font-semibold text-gray-600">Private key (multibase)</p>
120
120
+
<span class="font-mono">{/* @once */ keypair.privateMultibase}</span>
121
121
+
</div>
122
122
+
</div>
123
123
+
)}
124
124
+
</Show>
125
125
+
</>
126
126
+
);
127
127
+
};
128
128
+
129
129
+
export default CryptoGeneratePage;
+19
src/views/frontpage.tsx
···
3
3
import { useTitle } from '~/lib/navigation/router';
4
4
5
5
import HistoryIcon from '~/components/ic-icons/baseline-history';
6
6
+
import KeyIcon from '~/components/ic-icons/baseline-key';
7
7
+
import KeyVisualizerIcon from '~/components/ic-icons/baseline-key-visualizer';
6
8
import AdminPanelSettingsOutlinedIcon from '~/components/ic-icons/outline-admin-panel-settings';
7
9
import ArchiveOutlinedIcon from '~/components/ic-icons/outline-archive';
8
10
import BookmarksOutlinedIcon from '~/components/ic-icons/outline-bookmarks';
···
101
103
description: `Move your account data to another server`,
102
104
href: null,
103
105
icon: MoveUpOutlinedIcon,
106
106
+
},
107
107
+
],
108
108
+
},
109
109
+
{
110
110
+
name: `Cryptography`,
111
111
+
items: [
112
112
+
{
113
113
+
name: `Generate secret keys`,
114
114
+
description: `Create a new secp256k1/nistp256 keypair`,
115
115
+
href: `/crypto-generate`,
116
116
+
icon: KeyIcon,
117
117
+
},
118
118
+
{
119
119
+
name: `View crypto key info`,
120
120
+
description: `Show basic metadata about a public or private key`,
121
121
+
href: null,
122
122
+
icon: KeyVisualizerIcon,
104
123
},
105
124
],
106
125
},