@coder/mux-md-client 0.1.0-main.13 → 0.1.0-main.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -26,7 +26,6 @@ interface SignatureEnvelope {
26
26
  sig: string;
27
27
  publicKey: string;
28
28
  githubUser?: string;
29
- email?: string;
30
29
  }
31
30
  /**
32
31
  * Signed content payload (decrypted).
@@ -45,13 +44,25 @@ interface SignedPayload {
45
44
  * Works in both Node.js (with webcrypto) and browser environments.
46
45
  */
47
46
 
47
+ /** Options for signing content during upload */
48
+ interface SignOptions {
49
+ /** Private key bytes (32 bytes for Ed25519, variable for ECDSA) */
50
+ privateKey: Uint8Array;
51
+ /** SSH format public key string (e.g., "ssh-ed25519 AAAA...") */
52
+ publicKey: string;
53
+ /** Optional GitHub username for attribution */
54
+ githubUser?: string;
55
+ }
48
56
  interface UploadOptions {
49
57
  /** Base URL of the mux.md service */
50
58
  baseUrl?: string;
51
59
  /** Expiration time (unix timestamp ms, ISO date string, or Date object) */
52
60
  expiresAt?: number | string | Date;
53
- /** Signature envelope (will be encrypted with content) */
54
- signature?: SignatureEnvelope;
61
+ /**
62
+ * Sign the content with the provided credentials.
63
+ * The library handles creating the signature envelope internally.
64
+ */
65
+ sign?: SignOptions;
55
66
  }
56
67
  interface UploadResult {
57
68
  /** Full URL with encryption key in fragment */
@@ -151,81 +162,6 @@ interface SetExpirationResult {
151
162
  */
152
163
  declare function setExpiration(id: string, mutateKey: string, expiresAt: number | string | Date | 'never', options?: SetExpirationOptions): Promise<SetExpirationResult>;
153
164
 
154
- /**
155
- * Signing and verification utilities for mux.md
156
- *
157
- * Supports Ed25519 and ECDSA (P-256, P-384, P-521) keys in SSH format.
158
- */
159
-
160
- /** Supported key types */
161
- type KeyType = 'ed25519' | 'ecdsa-p256' | 'ecdsa-p384' | 'ecdsa-p521';
162
- /** Parsed public key with type and raw bytes */
163
- interface ParsedPublicKey {
164
- type: KeyType;
165
- keyBytes: Uint8Array;
166
- }
167
- /**
168
- * Parse an SSH public key and extract the key bytes and type.
169
- * Supports formats:
170
- * - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... [comment]
171
- * - ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY... [comment]
172
- * - ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQ... [comment]
173
- * - ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjE... [comment]
174
- * - Raw base64 (32 bytes when decoded = Ed25519)
175
- */
176
- declare function parsePublicKey(keyString: string): ParsedPublicKey;
177
- /**
178
- * Sign a message with Ed25519 private key.
179
- * @param message - The message bytes to sign
180
- * @param privateKey - 32-byte Ed25519 private key
181
- * @returns Base64-encoded signature (64 bytes)
182
- */
183
- declare function signEd25519(message: Uint8Array, privateKey: Uint8Array): Promise<string>;
184
- /**
185
- * Sign a message with ECDSA private key (P-256/384/521).
186
- * @param message - The message bytes to sign (will be hashed)
187
- * @param privateKey - ECDSA private key bytes
188
- * @param curve - Which curve to use
189
- * @returns Base64-encoded signature
190
- */
191
- declare function signECDSA(message: Uint8Array, privateKey: Uint8Array, curve: 'p256' | 'p384' | 'p521'): string;
192
- /**
193
- * Helper: Create a SignatureEnvelope from content + private key.
194
- * This is the high-level API for signing before upload.
195
- *
196
- * @param content - The content bytes to sign
197
- * @param privateKey - Private key bytes (32 bytes for Ed25519, variable for ECDSA)
198
- * @param publicKey - SSH format public key string (e.g., "ssh-ed25519 AAAA...")
199
- * @param options - Optional email or GitHub username for attribution
200
- * @returns SignatureEnvelope ready for upload
201
- */
202
- declare function createSignatureEnvelope(content: Uint8Array, privateKey: Uint8Array, publicKey: string, options?: {
203
- email?: string;
204
- githubUser?: string;
205
- }): Promise<SignatureEnvelope>;
206
- /**
207
- * Verify a signature using the appropriate algorithm based on key type.
208
- * For Ed25519: signature is raw 64 bytes
209
- * For ECDSA: signature is DER-encoded or raw r||s format
210
- *
211
- * @param parsedKey - Parsed public key (from parsePublicKey)
212
- * @param message - Original message bytes
213
- * @param signature - Signature bytes (not base64)
214
- * @returns true if signature is valid
215
- */
216
- declare function verifySignature(parsedKey: ParsedPublicKey, message: Uint8Array, signature: Uint8Array): Promise<boolean>;
217
- /**
218
- * Compute SHA256 fingerprint of a public key (matches ssh-keygen -l format)
219
- * @param publicKey - Raw public key bytes
220
- * @returns Fingerprint string like "SHA256:abc123..."
221
- */
222
- declare function computeFingerprint(publicKey: Uint8Array): Promise<string>;
223
- /**
224
- * Format a fingerprint for nice display.
225
- * Converts base64 fingerprint to uppercase hex groups like "DEAD BEEF 1234 5678"
226
- */
227
- declare function formatFingerprint(fingerprint: string): string;
228
-
229
165
  /**
230
166
  * Generate a random file ID (30 bits, 5 chars base62)
231
167
  */
@@ -280,4 +216,50 @@ declare function base64Encode(data: Uint8Array): string;
280
216
  */
281
217
  declare function base64Decode(str: string): Uint8Array;
282
218
 
283
- export { type DownloadResult, type FileInfo, type KeyType, type ParsedPublicKey, type SetExpirationOptions, type SetExpirationResult, type SignatureEnvelope, type SignedPayload, type UploadMeta, type UploadOptions, type UploadResult, base64Decode, base64Encode, base64UrlDecode, base64UrlEncode, buildUrl, computeFingerprint, createSignatureEnvelope, decrypt, deleteFile, deriveKey, download, encrypt, formatFingerprint, generateIV, generateId, generateKey, generateMutateKey, generateSalt, getMeta, parsePublicKey, parseUrl, setExpiration, signECDSA, signEd25519, upload, verifySignature };
219
+ /**
220
+ * Signing and verification utilities for mux.md
221
+ *
222
+ * Supports Ed25519 and ECDSA (P-256, P-384, P-521) keys in SSH format.
223
+ */
224
+
225
+ /** Supported key types */
226
+ type KeyType = 'ed25519' | 'ecdsa-p256' | 'ecdsa-p384' | 'ecdsa-p521';
227
+ /** Parsed public key with type and raw bytes */
228
+ interface ParsedPublicKey {
229
+ type: KeyType;
230
+ keyBytes: Uint8Array;
231
+ }
232
+ /**
233
+ * Parse an SSH public key and extract the key bytes and type.
234
+ * Supports formats:
235
+ * - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... [comment]
236
+ * - ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY... [comment]
237
+ * - ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQ... [comment]
238
+ * - ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjE... [comment]
239
+ * - Raw base64 (32 bytes when decoded = Ed25519)
240
+ */
241
+ declare function parsePublicKey(keyString: string): ParsedPublicKey;
242
+ /**
243
+ * Verify a signature using the appropriate algorithm based on key type.
244
+ * For Ed25519: signature is raw 64 bytes
245
+ * For ECDSA: signature is DER-encoded or raw r||s format
246
+ *
247
+ * @param parsedKey - Parsed public key (from parsePublicKey)
248
+ * @param message - Original message bytes
249
+ * @param signature - Signature bytes (not base64)
250
+ * @returns true if signature is valid
251
+ */
252
+ declare function verifySignature(parsedKey: ParsedPublicKey, message: Uint8Array, signature: Uint8Array): Promise<boolean>;
253
+ /**
254
+ * Compute SHA256 fingerprint of a public key (matches ssh-keygen -l format)
255
+ * @param publicKey - Raw public key bytes
256
+ * @returns Fingerprint string like "SHA256:abc123..."
257
+ */
258
+ declare function computeFingerprint(publicKey: Uint8Array): Promise<string>;
259
+ /**
260
+ * Format a fingerprint for nice display.
261
+ * Converts base64 fingerprint to uppercase hex groups like "DEAD BEEF 1234 5678"
262
+ */
263
+ declare function formatFingerprint(fingerprint: string): string;
264
+
265
+ export { type DownloadResult, type FileInfo, type KeyType, type ParsedPublicKey, type SetExpirationOptions, type SetExpirationResult, type SignOptions, type SignatureEnvelope, type SignedPayload, type UploadMeta, type UploadOptions, type UploadResult, base64Decode, base64Encode, base64UrlDecode, base64UrlEncode, buildUrl, computeFingerprint, decrypt, deleteFile, deriveKey, download, encrypt, formatFingerprint, generateIV, generateId, generateKey, generateMutateKey, generateSalt, getMeta, parsePublicKey, parseUrl, setExpiration, upload, verifySignature };
package/dist/index.d.ts CHANGED
@@ -26,7 +26,6 @@ interface SignatureEnvelope {
26
26
  sig: string;
27
27
  publicKey: string;
28
28
  githubUser?: string;
29
- email?: string;
30
29
  }
31
30
  /**
32
31
  * Signed content payload (decrypted).
@@ -45,13 +44,25 @@ interface SignedPayload {
45
44
  * Works in both Node.js (with webcrypto) and browser environments.
46
45
  */
47
46
 
47
+ /** Options for signing content during upload */
48
+ interface SignOptions {
49
+ /** Private key bytes (32 bytes for Ed25519, variable for ECDSA) */
50
+ privateKey: Uint8Array;
51
+ /** SSH format public key string (e.g., "ssh-ed25519 AAAA...") */
52
+ publicKey: string;
53
+ /** Optional GitHub username for attribution */
54
+ githubUser?: string;
55
+ }
48
56
  interface UploadOptions {
49
57
  /** Base URL of the mux.md service */
50
58
  baseUrl?: string;
51
59
  /** Expiration time (unix timestamp ms, ISO date string, or Date object) */
52
60
  expiresAt?: number | string | Date;
53
- /** Signature envelope (will be encrypted with content) */
54
- signature?: SignatureEnvelope;
61
+ /**
62
+ * Sign the content with the provided credentials.
63
+ * The library handles creating the signature envelope internally.
64
+ */
65
+ sign?: SignOptions;
55
66
  }
56
67
  interface UploadResult {
57
68
  /** Full URL with encryption key in fragment */
@@ -151,81 +162,6 @@ interface SetExpirationResult {
151
162
  */
152
163
  declare function setExpiration(id: string, mutateKey: string, expiresAt: number | string | Date | 'never', options?: SetExpirationOptions): Promise<SetExpirationResult>;
153
164
 
154
- /**
155
- * Signing and verification utilities for mux.md
156
- *
157
- * Supports Ed25519 and ECDSA (P-256, P-384, P-521) keys in SSH format.
158
- */
159
-
160
- /** Supported key types */
161
- type KeyType = 'ed25519' | 'ecdsa-p256' | 'ecdsa-p384' | 'ecdsa-p521';
162
- /** Parsed public key with type and raw bytes */
163
- interface ParsedPublicKey {
164
- type: KeyType;
165
- keyBytes: Uint8Array;
166
- }
167
- /**
168
- * Parse an SSH public key and extract the key bytes and type.
169
- * Supports formats:
170
- * - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... [comment]
171
- * - ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY... [comment]
172
- * - ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQ... [comment]
173
- * - ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjE... [comment]
174
- * - Raw base64 (32 bytes when decoded = Ed25519)
175
- */
176
- declare function parsePublicKey(keyString: string): ParsedPublicKey;
177
- /**
178
- * Sign a message with Ed25519 private key.
179
- * @param message - The message bytes to sign
180
- * @param privateKey - 32-byte Ed25519 private key
181
- * @returns Base64-encoded signature (64 bytes)
182
- */
183
- declare function signEd25519(message: Uint8Array, privateKey: Uint8Array): Promise<string>;
184
- /**
185
- * Sign a message with ECDSA private key (P-256/384/521).
186
- * @param message - The message bytes to sign (will be hashed)
187
- * @param privateKey - ECDSA private key bytes
188
- * @param curve - Which curve to use
189
- * @returns Base64-encoded signature
190
- */
191
- declare function signECDSA(message: Uint8Array, privateKey: Uint8Array, curve: 'p256' | 'p384' | 'p521'): string;
192
- /**
193
- * Helper: Create a SignatureEnvelope from content + private key.
194
- * This is the high-level API for signing before upload.
195
- *
196
- * @param content - The content bytes to sign
197
- * @param privateKey - Private key bytes (32 bytes for Ed25519, variable for ECDSA)
198
- * @param publicKey - SSH format public key string (e.g., "ssh-ed25519 AAAA...")
199
- * @param options - Optional email or GitHub username for attribution
200
- * @returns SignatureEnvelope ready for upload
201
- */
202
- declare function createSignatureEnvelope(content: Uint8Array, privateKey: Uint8Array, publicKey: string, options?: {
203
- email?: string;
204
- githubUser?: string;
205
- }): Promise<SignatureEnvelope>;
206
- /**
207
- * Verify a signature using the appropriate algorithm based on key type.
208
- * For Ed25519: signature is raw 64 bytes
209
- * For ECDSA: signature is DER-encoded or raw r||s format
210
- *
211
- * @param parsedKey - Parsed public key (from parsePublicKey)
212
- * @param message - Original message bytes
213
- * @param signature - Signature bytes (not base64)
214
- * @returns true if signature is valid
215
- */
216
- declare function verifySignature(parsedKey: ParsedPublicKey, message: Uint8Array, signature: Uint8Array): Promise<boolean>;
217
- /**
218
- * Compute SHA256 fingerprint of a public key (matches ssh-keygen -l format)
219
- * @param publicKey - Raw public key bytes
220
- * @returns Fingerprint string like "SHA256:abc123..."
221
- */
222
- declare function computeFingerprint(publicKey: Uint8Array): Promise<string>;
223
- /**
224
- * Format a fingerprint for nice display.
225
- * Converts base64 fingerprint to uppercase hex groups like "DEAD BEEF 1234 5678"
226
- */
227
- declare function formatFingerprint(fingerprint: string): string;
228
-
229
165
  /**
230
166
  * Generate a random file ID (30 bits, 5 chars base62)
231
167
  */
@@ -280,4 +216,50 @@ declare function base64Encode(data: Uint8Array): string;
280
216
  */
281
217
  declare function base64Decode(str: string): Uint8Array;
282
218
 
283
- export { type DownloadResult, type FileInfo, type KeyType, type ParsedPublicKey, type SetExpirationOptions, type SetExpirationResult, type SignatureEnvelope, type SignedPayload, type UploadMeta, type UploadOptions, type UploadResult, base64Decode, base64Encode, base64UrlDecode, base64UrlEncode, buildUrl, computeFingerprint, createSignatureEnvelope, decrypt, deleteFile, deriveKey, download, encrypt, formatFingerprint, generateIV, generateId, generateKey, generateMutateKey, generateSalt, getMeta, parsePublicKey, parseUrl, setExpiration, signECDSA, signEd25519, upload, verifySignature };
219
+ /**
220
+ * Signing and verification utilities for mux.md
221
+ *
222
+ * Supports Ed25519 and ECDSA (P-256, P-384, P-521) keys in SSH format.
223
+ */
224
+
225
+ /** Supported key types */
226
+ type KeyType = 'ed25519' | 'ecdsa-p256' | 'ecdsa-p384' | 'ecdsa-p521';
227
+ /** Parsed public key with type and raw bytes */
228
+ interface ParsedPublicKey {
229
+ type: KeyType;
230
+ keyBytes: Uint8Array;
231
+ }
232
+ /**
233
+ * Parse an SSH public key and extract the key bytes and type.
234
+ * Supports formats:
235
+ * - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... [comment]
236
+ * - ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY... [comment]
237
+ * - ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQ... [comment]
238
+ * - ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjE... [comment]
239
+ * - Raw base64 (32 bytes when decoded = Ed25519)
240
+ */
241
+ declare function parsePublicKey(keyString: string): ParsedPublicKey;
242
+ /**
243
+ * Verify a signature using the appropriate algorithm based on key type.
244
+ * For Ed25519: signature is raw 64 bytes
245
+ * For ECDSA: signature is DER-encoded or raw r||s format
246
+ *
247
+ * @param parsedKey - Parsed public key (from parsePublicKey)
248
+ * @param message - Original message bytes
249
+ * @param signature - Signature bytes (not base64)
250
+ * @returns true if signature is valid
251
+ */
252
+ declare function verifySignature(parsedKey: ParsedPublicKey, message: Uint8Array, signature: Uint8Array): Promise<boolean>;
253
+ /**
254
+ * Compute SHA256 fingerprint of a public key (matches ssh-keygen -l format)
255
+ * @param publicKey - Raw public key bytes
256
+ * @returns Fingerprint string like "SHA256:abc123..."
257
+ */
258
+ declare function computeFingerprint(publicKey: Uint8Array): Promise<string>;
259
+ /**
260
+ * Format a fingerprint for nice display.
261
+ * Converts base64 fingerprint to uppercase hex groups like "DEAD BEEF 1234 5678"
262
+ */
263
+ declare function formatFingerprint(fingerprint: string): string;
264
+
265
+ export { type DownloadResult, type FileInfo, type KeyType, type ParsedPublicKey, type SetExpirationOptions, type SetExpirationResult, type SignOptions, type SignatureEnvelope, type SignedPayload, type UploadMeta, type UploadOptions, type UploadResult, base64Decode, base64Encode, base64UrlDecode, base64UrlEncode, buildUrl, computeFingerprint, decrypt, deleteFile, deriveKey, download, encrypt, formatFingerprint, generateIV, generateId, generateKey, generateMutateKey, generateSalt, getMeta, parsePublicKey, parseUrl, setExpiration, upload, verifySignature };
package/dist/index.js CHANGED
@@ -104,6 +104,141 @@ function base64Decode(str) {
104
104
  return bytes;
105
105
  }
106
106
 
107
+ // src/signing.ts
108
+ import { p256, p384, p521 } from "@noble/curves/nist.js";
109
+ import * as ed from "@noble/ed25519";
110
+ import { sha512 } from "@noble/hashes/sha2.js";
111
+ ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));
112
+ var SSH_KEY_TYPES = {
113
+ "ssh-ed25519": "ed25519",
114
+ "ecdsa-sha2-nistp256": "ecdsa-p256",
115
+ "ecdsa-sha2-nistp384": "ecdsa-p384",
116
+ "ecdsa-sha2-nistp521": "ecdsa-p521"
117
+ };
118
+ function readSSHString(data, offset) {
119
+ const view = new DataView(data.buffer, data.byteOffset);
120
+ const len = view.getUint32(offset);
121
+ const value = data.slice(offset + 4, offset + 4 + len);
122
+ return { value, nextOffset: offset + 4 + len };
123
+ }
124
+ function base64Decode2(str) {
125
+ let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
126
+ while (base64.length % 4) {
127
+ base64 += "=";
128
+ }
129
+ const binary = atob(base64);
130
+ const bytes = new Uint8Array(binary.length);
131
+ for (let i = 0; i < binary.length; i++) {
132
+ bytes[i] = binary.charCodeAt(i);
133
+ }
134
+ return bytes;
135
+ }
136
+ function parsePublicKey(keyString) {
137
+ const trimmed = keyString.trim();
138
+ for (const [sshType, keyType] of Object.entries(SSH_KEY_TYPES)) {
139
+ if (trimmed.startsWith(`${sshType} `)) {
140
+ const parts = trimmed.split(" ");
141
+ if (parts.length < 2) {
142
+ throw new Error("Invalid SSH key format");
143
+ }
144
+ const keyData = base64Decode2(parts[1]);
145
+ const { value: typeBytes, nextOffset: afterType } = readSSHString(
146
+ keyData,
147
+ 0
148
+ );
149
+ const typeStr = new TextDecoder().decode(typeBytes);
150
+ if (typeStr !== sshType) {
151
+ throw new Error(
152
+ `Key type mismatch: expected ${sshType}, got ${typeStr}`
153
+ );
154
+ }
155
+ if (keyType === "ed25519") {
156
+ const { value: rawKey } = readSSHString(keyData, afterType);
157
+ if (rawKey.length !== 32) {
158
+ throw new Error("Invalid Ed25519 key length");
159
+ }
160
+ return { type: "ed25519", keyBytes: rawKey };
161
+ }
162
+ const { nextOffset: afterCurve } = readSSHString(keyData, afterType);
163
+ const { value: point } = readSSHString(keyData, afterCurve);
164
+ return { type: keyType, keyBytes: point };
165
+ }
166
+ }
167
+ const decoded = base64Decode2(trimmed);
168
+ if (decoded.length === 32) {
169
+ return { type: "ed25519", keyBytes: decoded };
170
+ }
171
+ throw new Error("Unsupported public key format");
172
+ }
173
+ async function signEd25519(message, privateKey) {
174
+ const sig = await ed.signAsync(message, privateKey);
175
+ return btoa(String.fromCharCode(...sig));
176
+ }
177
+ function signECDSA(message, privateKey, curve) {
178
+ const curves = { p256, p384, p521 };
179
+ const sigBytes = curves[curve].sign(message, privateKey, { prehash: true });
180
+ return btoa(String.fromCharCode(...sigBytes));
181
+ }
182
+ async function createSignatureEnvelope(content, privateKey, publicKey, options) {
183
+ const parsed = parsePublicKey(publicKey);
184
+ let sig;
185
+ if (parsed.type === "ed25519") {
186
+ sig = await signEd25519(content, privateKey);
187
+ } else {
188
+ const curve = parsed.type.replace("ecdsa-", "");
189
+ sig = signECDSA(content, privateKey, curve);
190
+ }
191
+ return {
192
+ sig,
193
+ publicKey,
194
+ githubUser: options?.githubUser
195
+ };
196
+ }
197
+ async function verifySignature(parsedKey, message, signature) {
198
+ try {
199
+ switch (parsedKey.type) {
200
+ case "ed25519":
201
+ return await ed.verifyAsync(signature, message, parsedKey.keyBytes);
202
+ case "ecdsa-p256":
203
+ return p256.verify(signature, message, parsedKey.keyBytes, {
204
+ prehash: true
205
+ });
206
+ case "ecdsa-p384":
207
+ return p384.verify(signature, message, parsedKey.keyBytes, {
208
+ prehash: true
209
+ });
210
+ case "ecdsa-p521":
211
+ return p521.verify(signature, message, parsedKey.keyBytes, {
212
+ prehash: true
213
+ });
214
+ default:
215
+ return false;
216
+ }
217
+ } catch {
218
+ return false;
219
+ }
220
+ }
221
+ async function computeFingerprint(publicKey) {
222
+ const hash = await crypto.subtle.digest(
223
+ "SHA-256",
224
+ publicKey.buffer
225
+ );
226
+ const hashArray = new Uint8Array(hash);
227
+ const base64 = btoa(String.fromCharCode(...hashArray));
228
+ return `SHA256:${base64.replace(/=+$/, "")}`;
229
+ }
230
+ function formatFingerprint(fingerprint) {
231
+ const base64Part = fingerprint.startsWith("SHA256:") ? fingerprint.slice(7) : fingerprint;
232
+ try {
233
+ const binary = atob(base64Part);
234
+ const hex = Array.from(binary).map((c) => c.charCodeAt(0).toString(16).padStart(2, "0")).join("").toUpperCase();
235
+ const short = hex.slice(0, 16);
236
+ return short.match(/.{4}/g)?.join(" ") || short;
237
+ } catch {
238
+ return fingerprint.slice(0, 16).toUpperCase();
239
+ }
240
+ }
241
+
107
242
  // src/client.ts
108
243
  var DEFAULT_BASE_URL = "https://mux.md";
109
244
  async function upload(data, fileInfo, options = {}) {
@@ -112,11 +247,20 @@ async function upload(data, fileInfo, options = {}) {
112
247
  const salt = generateSalt();
113
248
  const iv = generateIV();
114
249
  const cryptoKey = await deriveKey(keyMaterial, salt);
250
+ let signatureEnvelope;
251
+ if (options.sign) {
252
+ signatureEnvelope = await createSignatureEnvelope(
253
+ data,
254
+ options.sign.privateKey,
255
+ options.sign.publicKey,
256
+ { githubUser: options.sign.githubUser }
257
+ );
258
+ }
115
259
  let plaintext;
116
- if (options.signature) {
260
+ if (signatureEnvelope) {
117
261
  const signed = {
118
262
  content: new TextDecoder().decode(data),
119
- sig: options.signature
263
+ sig: signatureEnvelope
120
264
  };
121
265
  plaintext = new TextEncoder().encode(JSON.stringify(signed));
122
266
  } else {
@@ -308,143 +452,6 @@ async function setExpiration(id, mutateKey, expiresAt, options = {}) {
308
452
  }
309
453
  return response.json();
310
454
  }
311
-
312
- // src/signing.ts
313
- import * as ed from "@noble/ed25519";
314
- import { p256, p384, p521 } from "@noble/curves/nist.js";
315
- import { sha512 } from "@noble/hashes/sha2.js";
316
- ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));
317
- var SSH_KEY_TYPES = {
318
- "ssh-ed25519": "ed25519",
319
- "ecdsa-sha2-nistp256": "ecdsa-p256",
320
- "ecdsa-sha2-nistp384": "ecdsa-p384",
321
- "ecdsa-sha2-nistp521": "ecdsa-p521"
322
- };
323
- function readSSHString(data, offset) {
324
- const view = new DataView(data.buffer, data.byteOffset);
325
- const len = view.getUint32(offset);
326
- const value = data.slice(offset + 4, offset + 4 + len);
327
- return { value, nextOffset: offset + 4 + len };
328
- }
329
- function base64Decode2(str) {
330
- let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
331
- while (base64.length % 4) {
332
- base64 += "=";
333
- }
334
- const binary = atob(base64);
335
- const bytes = new Uint8Array(binary.length);
336
- for (let i = 0; i < binary.length; i++) {
337
- bytes[i] = binary.charCodeAt(i);
338
- }
339
- return bytes;
340
- }
341
- function parsePublicKey(keyString) {
342
- const trimmed = keyString.trim();
343
- for (const [sshType, keyType] of Object.entries(SSH_KEY_TYPES)) {
344
- if (trimmed.startsWith(`${sshType} `)) {
345
- const parts = trimmed.split(" ");
346
- if (parts.length < 2) {
347
- throw new Error("Invalid SSH key format");
348
- }
349
- const keyData = base64Decode2(parts[1]);
350
- const { value: typeBytes, nextOffset: afterType } = readSSHString(
351
- keyData,
352
- 0
353
- );
354
- const typeStr = new TextDecoder().decode(typeBytes);
355
- if (typeStr !== sshType) {
356
- throw new Error(
357
- `Key type mismatch: expected ${sshType}, got ${typeStr}`
358
- );
359
- }
360
- if (keyType === "ed25519") {
361
- const { value: rawKey } = readSSHString(keyData, afterType);
362
- if (rawKey.length !== 32) {
363
- throw new Error("Invalid Ed25519 key length");
364
- }
365
- return { type: "ed25519", keyBytes: rawKey };
366
- }
367
- const { nextOffset: afterCurve } = readSSHString(keyData, afterType);
368
- const { value: point } = readSSHString(keyData, afterCurve);
369
- return { type: keyType, keyBytes: point };
370
- }
371
- }
372
- const decoded = base64Decode2(trimmed);
373
- if (decoded.length === 32) {
374
- return { type: "ed25519", keyBytes: decoded };
375
- }
376
- throw new Error("Unsupported public key format");
377
- }
378
- async function signEd25519(message, privateKey) {
379
- const sig = await ed.signAsync(message, privateKey);
380
- return btoa(String.fromCharCode(...sig));
381
- }
382
- function signECDSA(message, privateKey, curve) {
383
- const curves = { p256, p384, p521 };
384
- const sig = curves[curve].sign(message, privateKey, { prehash: true });
385
- const sigBytes = sig.toCompactRawBytes();
386
- return btoa(String.fromCharCode(...sigBytes));
387
- }
388
- async function createSignatureEnvelope(content, privateKey, publicKey, options) {
389
- const parsed = parsePublicKey(publicKey);
390
- let sig;
391
- if (parsed.type === "ed25519") {
392
- sig = await signEd25519(content, privateKey);
393
- } else {
394
- const curve = parsed.type.replace("ecdsa-", "");
395
- sig = signECDSA(content, privateKey, curve);
396
- }
397
- return {
398
- sig,
399
- publicKey,
400
- email: options?.email,
401
- githubUser: options?.githubUser
402
- };
403
- }
404
- async function verifySignature(parsedKey, message, signature) {
405
- try {
406
- switch (parsedKey.type) {
407
- case "ed25519":
408
- return await ed.verifyAsync(signature, message, parsedKey.keyBytes);
409
- case "ecdsa-p256":
410
- return p256.verify(signature, message, parsedKey.keyBytes, {
411
- prehash: true
412
- });
413
- case "ecdsa-p384":
414
- return p384.verify(signature, message, parsedKey.keyBytes, {
415
- prehash: true
416
- });
417
- case "ecdsa-p521":
418
- return p521.verify(signature, message, parsedKey.keyBytes, {
419
- prehash: true
420
- });
421
- default:
422
- return false;
423
- }
424
- } catch {
425
- return false;
426
- }
427
- }
428
- async function computeFingerprint(publicKey) {
429
- const hash = await crypto.subtle.digest(
430
- "SHA-256",
431
- publicKey.buffer
432
- );
433
- const hashArray = new Uint8Array(hash);
434
- const base64 = btoa(String.fromCharCode(...hashArray));
435
- return `SHA256:${base64.replace(/=+$/, "")}`;
436
- }
437
- function formatFingerprint(fingerprint) {
438
- const base64Part = fingerprint.startsWith("SHA256:") ? fingerprint.slice(7) : fingerprint;
439
- try {
440
- const binary = atob(base64Part);
441
- const hex = Array.from(binary).map((c) => c.charCodeAt(0).toString(16).padStart(2, "0")).join("").toUpperCase();
442
- const short = hex.slice(0, 16);
443
- return short.match(/.{4}/g)?.join(" ") || short;
444
- } catch {
445
- return fingerprint.slice(0, 16).toUpperCase();
446
- }
447
- }
448
455
  export {
449
456
  base64Decode,
450
457
  base64Encode,
@@ -452,7 +459,6 @@ export {
452
459
  base64UrlEncode,
453
460
  buildUrl,
454
461
  computeFingerprint,
455
- createSignatureEnvelope,
456
462
  decrypt,
457
463
  deleteFile,
458
464
  deriveKey,
@@ -468,8 +474,6 @@ export {
468
474
  parsePublicKey,
469
475
  parseUrl,
470
476
  setExpiration,
471
- signECDSA,
472
- signEd25519,
473
477
  upload,
474
478
  verifySignature
475
479
  };