handy online tools for AT Protocol boat.kelinci.net
atproto bluesky atcute typescript solidjs

feat: crypto generate page

mary.my.id d6ea4792 b29abae2

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