a collection of lightweight TypeScript packages for AT Protocol, the protocol powering Bluesky
atproto bluesky typescript npm
101
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(crypto): try compressed point import

Mary 72987d7b e2f07b3b

+58 -60
+5
.changeset/puny-mugs-bake.md
··· 1 + --- 2 + '@atcute/crypto': patch 3 + --- 4 + 5 + try compressed point import
+53 -60
packages/utilities/crypto/lib/keypairs/p256.ts
··· 12 12 compressPoint, 13 13 deriveEcPublicKeyFromPrivateKey, 14 14 isSignatureNormalized, 15 + isUncompressedPoint, 15 16 normalizeSignature, 16 17 toMultikey, 17 18 } from '../utils.ts'; ··· 26 27 hash: 'SHA-256', 27 28 } as const; 28 29 29 - // TODO(2026-12-09): set this to true, importing compressed EC keys should be widely available by then 30 - // Firefox added support in 2025-10-29, Firefox 146 scheduled release is 2025-12-09 31 - // WebKit added support in 2022-09-10 but only in SPKI format (why???) 32 - const SUPPORTS_COMPRESSED_EC_KEYS = false; 30 + // importing raw compressed EC points is supported on Node, Bun, Deno, Chrome, 31 + // and Firefox 132+. WebKit does not support it (as of 2026-03-18). 32 + // `undefined` means we haven't probed yet — the first import will try compressed 33 + // and set this based on whether it succeeds. 34 + let IS_COMPRESSED_POINT_SUPPORTED: boolean | undefined; 35 + 36 + const importPublicKey = async ( 37 + publicKeyBytes: Uint8Array, 38 + extractable: boolean, 39 + usages: KeyUsage[], 40 + ): Promise<CryptoKey> => { 41 + if (IS_COMPRESSED_POINT_SUPPORTED === true || isUncompressedPoint(publicKeyBytes)) { 42 + return crypto.subtle.importKey('raw', publicKeyBytes as BufferSource, ECDSA_ALG, extractable, usages); 43 + } 44 + 45 + if (IS_COMPRESSED_POINT_SUPPORTED === false) { 46 + return crypto.subtle.importKey( 47 + 'raw', 48 + uncompressP256Point(publicKeyBytes), 49 + ECDSA_ALG, 50 + extractable, 51 + usages, 52 + ); 53 + } 54 + 55 + try { 56 + const key = await crypto.subtle.importKey( 57 + 'raw', 58 + publicKeyBytes as BufferSource, 59 + ECDSA_ALG, 60 + extractable, 61 + usages, 62 + ); 63 + 64 + IS_COMPRESSED_POINT_SUPPORTED = true; 65 + return key; 66 + } catch { 67 + const key = await crypto.subtle.importKey( 68 + 'raw', 69 + uncompressP256Point(publicKeyBytes), 70 + ECDSA_ALG, 71 + extractable, 72 + usages, 73 + ); 74 + 75 + IS_COMPRESSED_POINT_SUPPORTED = false; 76 + return key; 77 + } 78 + }; 33 79 34 80 const ASN1_ALGORITHM_IDENTIFIER = Uint8Array.from([ 35 81 ...[/* SEQ */ 0x30, /* len */ 0x13], // AlgorithmIdentifier ··· 57 103 /**********/ ...[/* OCT_STR */ 0x04, /* len: 32 */ 0x20 /* ... */], 58 104 ]); 59 105 60 - const SPKI_PREFIX = Uint8Array.from([ 61 - ...[/* SEQ */ 0x30, /* len */ 0x39], // SubjectPublicKeyInfo 62 - /**/ ...ASN1_ALGORITHM_IDENTIFIER, // AlgorithmIdentifier 63 - /**/ ...[/* BIT_STR */ 0x03, /* len: 33 */ 0x22, 0x00 /* ... */], // PublicKey 64 - ]); 65 - 66 106 export class P256PublicKey implements PublicKey { 67 107 readonly type = 'p256'; 68 108 readonly jwtAlg = 'ES256'; ··· 76 116 } 77 117 78 118 static async importRaw(publicKeyBytes: Uint8Array): Promise<P256PublicKey> { 79 - let imported: CryptoKey; 80 - 81 - if (!SUPPORTS_COMPRESSED_EC_KEYS) { 82 - imported = await crypto.subtle.importKey('raw', uncompressP256Point(publicKeyBytes), ECDSA_ALG, true, [ 83 - 'verify', 84 - ]); 85 - } else { 86 - imported = await crypto.subtle.importKey( 87 - 'spki', 88 - concat([SPKI_PREFIX, publicKeyBytes]), 89 - ECDSA_ALG, 90 - true, 91 - ['verify'], 92 - ); 93 - } 94 - 119 + const imported = await importPublicKey(publicKeyBytes, true, ['verify']); 95 120 return new P256PublicKey(imported); 96 121 } 97 122 ··· 177 202 let publicKey: CryptoKey; 178 203 179 204 if (publicKeyBytes) { 180 - if (!SUPPORTS_COMPRESSED_EC_KEYS) { 181 - publicKey = await crypto.subtle.importKey( 182 - 'raw', 183 - uncompressP256Point(publicKeyBytes), 184 - ECDSA_ALG, 185 - true, 186 - ['verify'], 187 - ); 188 - } else { 189 - publicKey = await crypto.subtle.importKey( 190 - 'spki', 191 - concat([SPKI_PREFIX, publicKeyBytes]), 192 - ECDSA_ALG, 193 - true, 194 - ['verify'], 195 - ); 196 - } 205 + publicKey = await importPublicKey(publicKeyBytes, true, ['verify']); 197 206 } else { 198 207 publicKey = await deriveEcPublicKeyFromPrivateKey(privateKey, ['verify']); 199 208 } ··· 264 273 let publicKey: CryptoKey; 265 274 266 275 if (publicKeyBytes) { 267 - if (!SUPPORTS_COMPRESSED_EC_KEYS) { 268 - publicKey = await crypto.subtle.importKey( 269 - 'raw', 270 - uncompressP256Point(publicKeyBytes), 271 - ECDSA_ALG, 272 - true, 273 - ['verify'], 274 - ); 275 - } else { 276 - publicKey = await crypto.subtle.importKey( 277 - 'spki', 278 - concat([SPKI_PREFIX, publicKeyBytes]), 279 - ECDSA_ALG, 280 - true, 281 - ['verify'], 282 - ); 283 - } 276 + publicKey = await importPublicKey(publicKeyBytes, true, ['verify']); 284 277 } else { 285 278 publicKey = await deriveEcPublicKeyFromPrivateKey(privateKey, ['verify']); 286 279 }