@coder/mux-md-client 0.1.0-main.14 → 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.cjs CHANGED
@@ -249,7 +249,6 @@ async function createSignatureEnvelope(content, privateKey, publicKey, options)
249
249
  return {
250
250
  sig,
251
251
  publicKey,
252
- email: options?.email,
253
252
  githubUser: options?.githubUser
254
253
  };
255
254
  }
@@ -312,7 +311,7 @@ async function upload(data, fileInfo, options = {}) {
312
311
  data,
313
312
  options.sign.privateKey,
314
313
  options.sign.publicKey,
315
- { email: options.sign.email, githubUser: options.sign.githubUser }
314
+ { githubUser: options.sign.githubUser }
316
315
  );
317
316
  }
318
317
  let plaintext;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/crypto.ts","../src/signing.ts","../src/client.ts"],"sourcesContent":["/**\n * @coder/mux-md-client - Client library for mux.md encrypted file sharing\n *\n * @example\n * ```typescript\n * import { upload, download } from '@coder/mux-md-client';\n *\n * // Upload with optional signing (library handles signature creation)\n * const content = new TextEncoder().encode('# Hello World');\n * const result = await upload(\n * content,\n * { name: 'msg.md', type: 'text/markdown', size: content.length },\n * {\n * sign: {\n * privateKey, // Uint8Array\n * publicKey, // SSH format string, e.g. \"ssh-ed25519 AAAA...\"\n * githubUser: 'username', // optional attribution\n * }\n * }\n * );\n *\n * // Download and verify signature\n * const { data, info, signature } = await download(result.url);\n * if (signature) {\n * // signature.publicKey contains the signer's public key\n * // signature.githubUser contains claimed GitHub username (if provided)\n * }\n * ```\n */\n\n// High-level client operations\nexport {\n buildUrl,\n type DownloadResult,\n deleteFile,\n download,\n getMeta,\n parseUrl,\n type SetExpirationOptions,\n type SetExpirationResult,\n type SignOptions,\n setExpiration,\n type UploadOptions,\n type UploadResult,\n upload,\n} from './client';\n// Low-level crypto (for advanced use)\nexport {\n base64Decode,\n base64Encode,\n base64UrlDecode,\n base64UrlEncode,\n decrypt,\n deriveKey,\n encrypt,\n generateId,\n generateIV,\n generateKey,\n generateMutateKey,\n generateSalt,\n} from './crypto';\n// Signing & verification\nexport {\n computeFingerprint,\n formatFingerprint,\n type KeyType,\n type ParsedPublicKey,\n parsePublicKey,\n verifySignature,\n} from './signing';\n\n// Types\nexport type {\n FileInfo,\n SignatureEnvelope,\n SignedPayload,\n UploadMeta,\n} from './types';\n","/**\n * Cryptographic utilities for mux.md\n *\n * Security parameters:\n * - Key: 80 bits entropy (14 chars base64url)\n * - ID: 30 bits entropy (5 chars base62) - not security critical\n * - Salt: 128 bits (16 bytes) - HKDF salt\n * - IV: 96 bits (12 bytes) for AES-GCM\n */\nconst SALT_BYTES = 16;\nconst IV_BYTES = 12;\nconst KEY_BYTES = 10; // 80 bits\nconst ID_BYTES = 4; // 32 bits, we'll use 30\nconst MUTATE_KEY_BYTES = 16; // 128 bits for mutate key\n\n// Base62 alphabet for URL-safe IDs (no special chars)\nconst BASE62 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\n\n/**\n * Generate a random file ID (30 bits, 5 chars base62)\n */\nexport function generateId(): string {\n const bytes = new Uint8Array(ID_BYTES);\n crypto.getRandomValues(bytes);\n\n // Convert to number (32 bits), then encode as base62\n const num = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];\n\n // Use modulo to get 5 base62 characters (covers ~30 bits)\n let result = '';\n let n = num >>> 0; // Ensure unsigned\n for (let i = 0; i < 5; i++) {\n result = BASE62[n % 62] + result;\n n = Math.floor(n / 62);\n }\n\n return result;\n}\n\n/**\n * Generate encryption key material (80 bits, 14 chars base64url)\n */\nexport function generateKey(): string {\n const bytes = new Uint8Array(KEY_BYTES);\n crypto.getRandomValues(bytes);\n return base64UrlEncode(bytes);\n}\n\n/**\n * Generate mutate key (128 bits, 22 chars base64url)\n * Used for mutation operations: delete, set expiration\n */\nexport function generateMutateKey(): string {\n const bytes = new Uint8Array(MUTATE_KEY_BYTES);\n crypto.getRandomValues(bytes);\n return base64UrlEncode(bytes);\n}\n\n/**\n * Generate random salt for HKDF (128 bits)\n */\nexport function generateSalt(): Uint8Array {\n const salt = new Uint8Array(SALT_BYTES);\n crypto.getRandomValues(salt);\n return salt;\n}\n\n/**\n * Generate random IV for AES-GCM (96 bits)\n */\nexport function generateIV(): Uint8Array {\n const iv = new Uint8Array(IV_BYTES);\n crypto.getRandomValues(iv);\n return iv;\n}\n\n/**\n * Derive AES-256 key from key material using HKDF\n *\n * HKDF is the correct choice for deriving keys from high-entropy\n * random key material. Unlike PBKDF2, it doesn't need iterations\n * since we're not stretching a weak password.\n */\nexport async function deriveKey(\n keyMaterial: string,\n salt: Uint8Array,\n): Promise<CryptoKey> {\n // Import the raw key material\n const rawKey = base64UrlDecode(keyMaterial);\n const baseKey = await crypto.subtle.importKey(\n 'raw',\n rawKey.buffer as ArrayBuffer,\n 'HKDF',\n false,\n ['deriveKey'],\n );\n\n // Derive AES-256 key using HKDF\n return crypto.subtle.deriveKey(\n {\n name: 'HKDF',\n salt: salt.buffer as ArrayBuffer,\n info: new Uint8Array(0), // No additional context needed\n hash: 'SHA-256',\n },\n baseKey,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt', 'decrypt'],\n );\n}\n\n/**\n * Encrypt data using AES-256-GCM\n */\nexport async function encrypt(\n data: Uint8Array,\n key: CryptoKey,\n iv: Uint8Array,\n): Promise<Uint8Array> {\n const ciphertext = await crypto.subtle.encrypt(\n { name: 'AES-GCM', iv: iv.buffer as ArrayBuffer },\n key,\n data.buffer as ArrayBuffer,\n );\n return new Uint8Array(ciphertext);\n}\n\n/**\n * Decrypt data using AES-256-GCM\n */\nexport async function decrypt(\n ciphertext: Uint8Array,\n key: CryptoKey,\n iv: Uint8Array,\n): Promise<Uint8Array> {\n const plaintext = await crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: iv.buffer as ArrayBuffer },\n key,\n ciphertext.buffer as ArrayBuffer,\n );\n return new Uint8Array(plaintext);\n}\n\n/**\n * Base64url encode (URL-safe, no padding)\n */\nexport function base64UrlEncode(data: Uint8Array): string {\n const base64 = btoa(String.fromCharCode(...data));\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/**\n * Base64url decode\n */\nexport function base64UrlDecode(str: string): Uint8Array {\n // Restore standard base64\n let base64 = str.replace(/-/g, '+').replace(/_/g, '/');\n // Add padding if needed\n while (base64.length % 4) {\n base64 += '=';\n }\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\n/**\n * Standard base64 encode\n */\nexport function base64Encode(data: Uint8Array): string {\n return btoa(String.fromCharCode(...data));\n}\n\n/**\n * Standard base64 decode\n */\nexport function base64Decode(str: string): Uint8Array {\n const binary = atob(str);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n","/**\n * Signing and verification utilities for mux.md\n *\n * Supports Ed25519 and ECDSA (P-256, P-384, P-521) keys in SSH format.\n */\n\nimport { p256, p384, p521 } from '@noble/curves/nist.js';\nimport * as ed from '@noble/ed25519';\nimport { sha512 } from '@noble/hashes/sha2.js';\nimport type { SignatureEnvelope } from './types';\n\n// Configure @noble/ed25519 to use sha512 from @noble/hashes\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ed.etc as any).sha512Sync = (...m: Uint8Array[]) =>\n sha512(ed.etc.concatBytes(...m));\n\n// ----- Types -----\n\n/** Supported key types */\nexport type KeyType = 'ed25519' | 'ecdsa-p256' | 'ecdsa-p384' | 'ecdsa-p521';\n\n/** Parsed public key with type and raw bytes */\nexport interface ParsedPublicKey {\n type: KeyType;\n keyBytes: Uint8Array;\n}\n\n// SSH key type identifiers\nconst SSH_KEY_TYPES: Record<string, KeyType> = {\n 'ssh-ed25519': 'ed25519',\n 'ecdsa-sha2-nistp256': 'ecdsa-p256',\n 'ecdsa-sha2-nistp384': 'ecdsa-p384',\n 'ecdsa-sha2-nistp521': 'ecdsa-p521',\n};\n\n// ----- SSH Key Parsing -----\n\n/**\n * Read a length-prefixed string from SSH wire format\n */\nfunction readSSHString(\n data: Uint8Array,\n offset: number,\n): { value: Uint8Array; nextOffset: number } {\n const view = new DataView(data.buffer, data.byteOffset);\n const len = view.getUint32(offset);\n const value = data.slice(offset + 4, offset + 4 + len);\n return { value, nextOffset: offset + 4 + len };\n}\n\n/**\n * Decode standard base64 string to Uint8Array\n */\nfunction base64Decode(str: string): Uint8Array {\n // Handle base64url as well\n let base64 = str.replace(/-/g, '+').replace(/_/g, '/');\n while (base64.length % 4) {\n base64 += '=';\n }\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\n/**\n * Parse an SSH public key and extract the key bytes and type.\n * Supports formats:\n * - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... [comment]\n * - ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY... [comment]\n * - ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQ... [comment]\n * - ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjE... [comment]\n * - Raw base64 (32 bytes when decoded = Ed25519)\n */\nexport function parsePublicKey(keyString: string): ParsedPublicKey {\n const trimmed = keyString.trim();\n\n // Check for SSH format by looking for known key type prefixes\n for (const [sshType, keyType] of Object.entries(SSH_KEY_TYPES)) {\n if (trimmed.startsWith(`${sshType} `)) {\n const parts = trimmed.split(' ');\n if (parts.length < 2) {\n throw new Error('Invalid SSH key format');\n }\n const keyData = base64Decode(parts[1]);\n\n // SSH key format: [4 byte len][type string][4 byte len][key data...]\n // For ECDSA: [4 byte len][type][4 byte len][curve name][4 byte len][point]\n const { value: typeBytes, nextOffset: afterType } = readSSHString(\n keyData,\n 0,\n );\n const typeStr = new TextDecoder().decode(typeBytes);\n\n if (typeStr !== sshType) {\n throw new Error(\n `Key type mismatch: expected ${sshType}, got ${typeStr}`,\n );\n }\n\n if (keyType === 'ed25519') {\n // Ed25519: [type][32 byte key]\n const { value: rawKey } = readSSHString(keyData, afterType);\n if (rawKey.length !== 32) {\n throw new Error('Invalid Ed25519 key length');\n }\n return { type: 'ed25519', keyBytes: rawKey };\n }\n // ECDSA: [type][curve name][point]\n const { nextOffset: afterCurve } = readSSHString(keyData, afterType);\n const { value: point } = readSSHString(keyData, afterCurve);\n return { type: keyType, keyBytes: point };\n }\n }\n\n // Try raw base64\n const decoded = base64Decode(trimmed);\n if (decoded.length === 32) {\n return { type: 'ed25519', keyBytes: decoded };\n }\n\n throw new Error('Unsupported public key format');\n}\n\n// ----- Signing -----\n\n/**\n * Sign a message with Ed25519 private key.\n * @param message - The message bytes to sign\n * @param privateKey - 32-byte Ed25519 private key\n * @returns Base64-encoded signature (64 bytes)\n */\nexport async function signEd25519(\n message: Uint8Array,\n privateKey: Uint8Array,\n): Promise<string> {\n const sig = await ed.signAsync(message, privateKey);\n return btoa(String.fromCharCode(...sig));\n}\n\n/**\n * Sign a message with ECDSA private key (P-256/384/521).\n * @param message - The message bytes to sign (will be hashed)\n * @param privateKey - ECDSA private key bytes\n * @param curve - Which curve to use\n * @returns Base64-encoded signature\n */\nexport function signECDSA(\n message: Uint8Array,\n privateKey: Uint8Array,\n curve: 'p256' | 'p384' | 'p521',\n): string {\n const curves = { p256, p384, p521 };\n // In @noble/curves v2, sign() returns a Uint8Array directly (compact r||s format)\n const sigBytes = curves[curve].sign(message, privateKey, { prehash: true });\n return btoa(String.fromCharCode(...sigBytes));\n}\n\n/**\n * Helper: Create a SignatureEnvelope from content + private key.\n * This is the high-level API for signing before upload.\n *\n * @param content - The content bytes to sign\n * @param privateKey - Private key bytes (32 bytes for Ed25519, variable for ECDSA)\n * @param publicKey - SSH format public key string (e.g., \"ssh-ed25519 AAAA...\")\n * @param options - Optional email or GitHub username for attribution\n * @returns SignatureEnvelope ready for upload\n */\nexport async function createSignatureEnvelope(\n content: Uint8Array,\n privateKey: Uint8Array,\n publicKey: string,\n options?: { email?: string; githubUser?: string },\n): Promise<SignatureEnvelope> {\n const parsed = parsePublicKey(publicKey);\n let sig: string;\n\n if (parsed.type === 'ed25519') {\n sig = await signEd25519(content, privateKey);\n } else {\n const curve = parsed.type.replace('ecdsa-', '') as 'p256' | 'p384' | 'p521';\n sig = signECDSA(content, privateKey, curve);\n }\n\n return {\n sig,\n publicKey,\n email: options?.email,\n githubUser: options?.githubUser,\n };\n}\n\n// ----- Verification -----\n\n/**\n * Verify a signature using the appropriate algorithm based on key type.\n * For Ed25519: signature is raw 64 bytes\n * For ECDSA: signature is DER-encoded or raw r||s format\n *\n * @param parsedKey - Parsed public key (from parsePublicKey)\n * @param message - Original message bytes\n * @param signature - Signature bytes (not base64)\n * @returns true if signature is valid\n */\nexport async function verifySignature(\n parsedKey: ParsedPublicKey,\n message: Uint8Array,\n signature: Uint8Array,\n): Promise<boolean> {\n try {\n switch (parsedKey.type) {\n case 'ed25519':\n return await ed.verifyAsync(signature, message, parsedKey.keyBytes);\n\n case 'ecdsa-p256':\n return p256.verify(signature, message, parsedKey.keyBytes, {\n prehash: true,\n });\n\n case 'ecdsa-p384':\n return p384.verify(signature, message, parsedKey.keyBytes, {\n prehash: true,\n });\n\n case 'ecdsa-p521':\n return p521.verify(signature, message, parsedKey.keyBytes, {\n prehash: true,\n });\n\n default:\n return false;\n }\n } catch {\n return false;\n }\n}\n\n// ----- Fingerprint -----\n\n/**\n * Compute SHA256 fingerprint of a public key (matches ssh-keygen -l format)\n * @param publicKey - Raw public key bytes\n * @returns Fingerprint string like \"SHA256:abc123...\"\n */\nexport async function computeFingerprint(\n publicKey: Uint8Array,\n): Promise<string> {\n const hash = await crypto.subtle.digest(\n 'SHA-256',\n publicKey.buffer as ArrayBuffer,\n );\n const hashArray = new Uint8Array(hash);\n const base64 = btoa(String.fromCharCode(...hashArray));\n return `SHA256:${base64.replace(/=+$/, '')}`;\n}\n\n/**\n * Format a fingerprint for nice display.\n * Converts base64 fingerprint to uppercase hex groups like \"DEAD BEEF 1234 5678\"\n */\nexport function formatFingerprint(fingerprint: string): string {\n // Remove \"SHA256:\" prefix if present\n const base64Part = fingerprint.startsWith('SHA256:')\n ? fingerprint.slice(7)\n : fingerprint;\n\n // Decode base64 to bytes, then to hex\n try {\n const binary = atob(base64Part);\n const hex = Array.from(binary)\n .map((c) => c.charCodeAt(0).toString(16).padStart(2, '0'))\n .join('')\n .toUpperCase();\n\n // Take first 16 chars (8 bytes) and format as \"DEAD BEEF 1234 5678\"\n const short = hex.slice(0, 16);\n return short.match(/.{4}/g)?.join(' ') || short;\n } catch {\n // Fallback: just show first part of the fingerprint\n return fingerprint.slice(0, 16).toUpperCase();\n }\n}\n","/**\n * mux.md Client Library\n *\n * Reference implementation for encrypting, uploading, downloading, and decrypting files.\n * Works in both Node.js (with webcrypto) and browser environments.\n */\n\nimport {\n base64Decode,\n base64Encode,\n decrypt,\n deriveKey,\n encrypt,\n generateIV,\n generateKey,\n generateSalt,\n} from './crypto';\nimport { createSignatureEnvelope } from './signing';\nimport type {\n FileInfo,\n SignatureEnvelope,\n SignedPayload,\n UploadMeta,\n} from './types';\n\n// Internal types (not exported from package)\ninterface UploadResponse {\n id: string;\n url: string;\n mutateKey: string;\n expiresAt?: number;\n}\n\ninterface MetaResponse {\n salt: string;\n iv: string;\n encryptedMeta: string;\n size: number;\n}\n\nconst DEFAULT_BASE_URL = 'https://mux.md';\n\n/** Options for signing content during upload */\nexport interface SignOptions {\n /** Private key bytes (32 bytes for Ed25519, variable for ECDSA) */\n privateKey: Uint8Array;\n /** SSH format public key string (e.g., \"ssh-ed25519 AAAA...\") */\n publicKey: string;\n /** Optional email for attribution */\n email?: string;\n /** Optional GitHub username for attribution */\n githubUser?: string;\n}\n\nexport interface UploadOptions {\n /** Base URL of the mux.md service */\n baseUrl?: string;\n /** Expiration time (unix timestamp ms, ISO date string, or Date object) */\n expiresAt?: number | string | Date;\n /**\n * Sign the content with the provided credentials.\n * The library handles creating the signature envelope internally.\n */\n sign?: SignOptions;\n}\n\nexport interface UploadResult {\n /** Full URL with encryption key in fragment */\n url: string;\n /** File ID (without key) */\n id: string;\n /** Encryption key (base64url) */\n key: string;\n /** Mutate key (base64url) - store this to mutate (delete, change expiration) the file later */\n mutateKey: string;\n /** Expiration timestamp (ms), if set */\n expiresAt?: number;\n}\n\nexport interface DownloadResult {\n /** Decrypted file content */\n data: Uint8Array;\n /** Original file info (name, type, size) */\n info: FileInfo;\n /** Decrypted signature envelope (if present) */\n signature?: SignatureEnvelope;\n}\n\n/**\n * Encrypt and upload a file to mux.md\n *\n * @param data - File contents as Uint8Array\n * @param fileInfo - Original file metadata (name, type, size)\n * @param options - Upload options (including optional signature)\n * @returns Upload result with URL containing encryption key\n */\nexport async function upload(\n data: Uint8Array,\n fileInfo: FileInfo,\n options: UploadOptions = {},\n): Promise<UploadResult> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Generate encryption parameters\n const keyMaterial = generateKey();\n const salt = generateSalt();\n const iv = generateIV();\n const cryptoKey = await deriveKey(keyMaterial, salt);\n\n // Create signature envelope if signing credentials provided\n let signatureEnvelope: SignatureEnvelope | undefined;\n if (options.sign) {\n signatureEnvelope = await createSignatureEnvelope(\n data,\n options.sign.privateKey,\n options.sign.publicKey,\n { email: options.sign.email, githubUser: options.sign.githubUser },\n );\n }\n\n // Build plaintext - either signed (JSON) or raw content\n let plaintext: Uint8Array;\n if (signatureEnvelope) {\n // Signed format: JSON with content + signature\n const signed: SignedPayload = {\n content: new TextDecoder().decode(data),\n sig: signatureEnvelope,\n };\n plaintext = new TextEncoder().encode(JSON.stringify(signed));\n } else {\n // Unsigned: raw content bytes\n plaintext = data;\n }\n\n // Single encryption for the payload\n const payload = await encrypt(plaintext, cryptoKey, iv);\n\n // Encrypt file metadata (always the same way)\n const metaJson = JSON.stringify(fileInfo);\n const metaBytes = new TextEncoder().encode(metaJson);\n const metaIv = generateIV();\n const encryptedMeta = await encrypt(metaBytes, cryptoKey, metaIv);\n\n // Prepare upload metadata\n const uploadMeta: UploadMeta = {\n salt: base64Encode(salt),\n iv: base64Encode(iv),\n encryptedMeta: base64Encode(new Uint8Array([...metaIv, ...encryptedMeta])),\n };\n\n // Build headers\n const headers: Record<string, string> = {\n 'Content-Type': 'application/octet-stream',\n 'X-Mux-Meta': btoa(JSON.stringify(uploadMeta)),\n };\n\n // Add expiration header if specified (convert to ISO 8601)\n if (options.expiresAt !== undefined) {\n let expiresDate: Date;\n if (options.expiresAt instanceof Date) {\n expiresDate = options.expiresAt;\n } else if (typeof options.expiresAt === 'string') {\n expiresDate = new Date(options.expiresAt);\n } else {\n expiresDate = new Date(options.expiresAt);\n }\n headers['X-Mux-Expires'] = expiresDate.toISOString();\n }\n\n // Upload to server\n const response = await fetch(`${baseUrl}/`, {\n method: 'POST',\n headers,\n body: payload.buffer as ArrayBuffer,\n });\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Upload failed' }));\n throw new Error((error as { error: string }).error || 'Upload failed');\n }\n\n const result: UploadResponse = await response.json();\n\n return {\n url: `${baseUrl}/${result.id}#${keyMaterial}`,\n id: result.id,\n key: keyMaterial,\n mutateKey: result.mutateKey,\n ...(result.expiresAt && { expiresAt: result.expiresAt }),\n };\n}\n\n/**\n * Download and decrypt a file from mux.md\n *\n * @param url - Full URL with encryption key in fragment, or just the ID\n * @param key - Encryption key (required if url doesn't contain fragment)\n * @param options - Download options\n * @returns Decrypted file data and metadata\n */\nexport async function download(\n url: string,\n key?: string,\n options: { baseUrl?: string } = {},\n): Promise<DownloadResult> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Parse URL to extract ID and key\n let id: string;\n let keyMaterial: string;\n\n if (url.includes('#')) {\n // Full URL with fragment\n const urlObj = new URL(url);\n id = urlObj.pathname.slice(1); // Remove leading /\n keyMaterial = urlObj.hash.slice(1); // Remove leading #\n } else if (url.includes('/')) {\n // URL path without fragment\n const parts = url.split('/');\n id = parts[parts.length - 1];\n if (!key) throw new Error('Key required when URL has no fragment');\n keyMaterial = key;\n } else {\n // Just the ID\n id = url;\n if (!key) throw new Error('Key required when only ID is provided');\n keyMaterial = key;\n }\n\n // Download encrypted file\n const response = await fetch(`${baseUrl}/${id}`);\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Download failed' }));\n throw new Error((error as { error: string }).error || 'Download failed');\n }\n\n // Parse metadata header\n const metaHeader = response.headers.get('X-Mux-Meta');\n if (!metaHeader) {\n throw new Error('Missing metadata header');\n }\n\n const uploadMeta: UploadMeta = JSON.parse(atob(metaHeader));\n\n // Get encrypted data\n const encryptedData = new Uint8Array(await response.arrayBuffer());\n\n // Derive decryption key\n const salt = base64Decode(uploadMeta.salt);\n const iv = base64Decode(uploadMeta.iv);\n const cryptoKey = await deriveKey(keyMaterial, salt);\n\n // Decrypt metadata (same for both formats)\n const encryptedMetaWithIv = base64Decode(uploadMeta.encryptedMeta);\n const metaIv = encryptedMetaWithIv.slice(0, 12);\n const encryptedMetaData = encryptedMetaWithIv.slice(12);\n const metaBytes = await decrypt(encryptedMetaData, cryptoKey, metaIv);\n const info: FileInfo = JSON.parse(new TextDecoder().decode(metaBytes));\n\n // Decrypt the payload\n const decrypted = await decrypt(encryptedData, cryptoKey, iv);\n\n // Check if decrypted content is signed (JSON with content + sig fields)\n if (decrypted[0] === 0x7b) {\n // '{' character - might be signed JSON\n try {\n const jsonStr = new TextDecoder().decode(decrypted);\n const parsed = JSON.parse(jsonStr);\n\n if (typeof parsed.content === 'string' && parsed.sig) {\n // Signed format\n const data = new TextEncoder().encode(parsed.content);\n const signature: SignatureEnvelope = parsed.sig;\n return { data, info, signature };\n }\n } catch {\n // Not valid JSON - treat as raw content\n }\n }\n\n // Unsigned content (raw bytes)\n return { data: decrypted, info };\n}\n\n/**\n * Get file metadata without downloading the full file\n *\n * @param url - Full URL or ID\n * @param key - Encryption key (required to decrypt metadata)\n * @param options - Request options\n * @returns Decrypted file info and server metadata\n */\nexport async function getMeta(\n url: string,\n key?: string,\n options: { baseUrl?: string } = {},\n): Promise<{ info: FileInfo; size: number }> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Parse URL to extract ID and key\n let id: string;\n let keyMaterial: string;\n\n if (url.includes('#')) {\n const urlObj = new URL(url);\n id = urlObj.pathname.slice(1);\n keyMaterial = urlObj.hash.slice(1);\n } else if (url.includes('/')) {\n const parts = url.split('/');\n id = parts[parts.length - 1];\n if (!key) throw new Error('Key required when URL has no fragment');\n keyMaterial = key;\n } else {\n id = url;\n if (!key) throw new Error('Key required when only ID is provided');\n keyMaterial = key;\n }\n\n // Fetch metadata\n const response = await fetch(`${baseUrl}/${id}/meta`);\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Request failed' }));\n throw new Error((error as { error: string }).error || 'Request failed');\n }\n\n const meta: MetaResponse = await response.json();\n\n // Derive key and decrypt metadata\n const salt = base64Decode(meta.salt);\n const cryptoKey = await deriveKey(keyMaterial, salt);\n\n const encryptedMetaWithIv = base64Decode(meta.encryptedMeta);\n const metaIv = encryptedMetaWithIv.slice(0, 12);\n const encryptedMetaData = encryptedMetaWithIv.slice(12);\n const metaBytes = await decrypt(encryptedMetaData, cryptoKey, metaIv);\n const info: FileInfo = JSON.parse(new TextDecoder().decode(metaBytes));\n\n return {\n info,\n size: meta.size,\n };\n}\n\n/**\n * Parse a mux.md URL into its components\n */\nexport function parseUrl(url: string): { id: string; key: string } | null {\n try {\n const urlObj = new URL(url);\n if (!urlObj.hash) return null;\n\n const id = urlObj.pathname.slice(1);\n const key = urlObj.hash.slice(1);\n\n if (!id || !key) return null;\n\n return { id, key };\n } catch {\n return null;\n }\n}\n\n/**\n * Build a mux.md URL from components\n */\nexport function buildUrl(\n id: string,\n key: string,\n baseUrl = DEFAULT_BASE_URL,\n): string {\n return `${baseUrl}/${id}#${key}`;\n}\n\n/**\n * Delete a file from mux.md using its mutate key\n *\n * @param id - File ID\n * @param mutateKey - Mutate key returned from upload\n * @param options - Request options\n */\nexport async function deleteFile(\n id: string,\n mutateKey: string,\n options: { baseUrl?: string } = {},\n): Promise<void> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n const response = await fetch(`${baseUrl}/${id}`, {\n method: 'DELETE',\n headers: {\n 'X-Mux-Mutate-Key': mutateKey,\n },\n });\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Delete failed' }));\n throw new Error((error as { error: string }).error || 'Delete failed');\n }\n}\n\nexport interface SetExpirationOptions {\n /** Base URL of the mux.md service */\n baseUrl?: string;\n}\n\nexport interface SetExpirationResult {\n /** Whether the operation succeeded */\n success: boolean;\n /** File ID */\n id: string;\n /** New expiration timestamp (ms), or undefined if expiration was removed */\n expiresAt?: number;\n}\n\n/**\n * Set or remove the expiration of a file using its mutate key\n *\n * @param id - File ID\n * @param mutateKey - Mutate key returned from upload\n * @param expiresAt - New expiration time (unix timestamp ms, ISO date string, Date object, or \"never\" to remove expiration)\n * @param options - Request options\n * @returns Result with new expiration info\n */\nexport async function setExpiration(\n id: string,\n mutateKey: string,\n expiresAt: number | string | Date | 'never',\n options: SetExpirationOptions = {},\n): Promise<SetExpirationResult> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Convert expiration to ISO 8601 string\n let expiresHeader: string;\n if (expiresAt === 'never') {\n expiresHeader = 'never';\n } else if (expiresAt instanceof Date) {\n expiresHeader = expiresAt.toISOString();\n } else if (typeof expiresAt === 'string') {\n expiresHeader = new Date(expiresAt).toISOString();\n } else {\n expiresHeader = new Date(expiresAt).toISOString();\n }\n\n const response = await fetch(`${baseUrl}/${id}`, {\n method: 'PATCH',\n headers: {\n 'X-Mux-Mutate-Key': mutateKey,\n 'X-Mux-Expires': expiresHeader,\n },\n });\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Set expiration failed' }));\n throw new Error(\n (error as { error: string }).error || 'Set expiration failed',\n );\n }\n\n return response.json();\n}\n"],"mappings":"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","names":["base64Decode"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/crypto.ts","../src/signing.ts","../src/client.ts"],"sourcesContent":["/**\n * @coder/mux-md-client - Client library for mux.md encrypted file sharing\n *\n * @example\n * ```typescript\n * import { upload, download } from '@coder/mux-md-client';\n *\n * // Upload with optional signing (library handles signature creation)\n * const content = new TextEncoder().encode('# Hello World');\n * const result = await upload(\n * content,\n * { name: 'msg.md', type: 'text/markdown', size: content.length },\n * {\n * sign: {\n * privateKey, // Uint8Array\n * publicKey, // SSH format string, e.g. \"ssh-ed25519 AAAA...\"\n * githubUser: 'username', // optional attribution\n * }\n * }\n * );\n *\n * // Download and verify signature\n * const { data, info, signature } = await download(result.url);\n * if (signature) {\n * // signature.publicKey contains the signer's public key\n * // signature.githubUser contains claimed GitHub username (if provided)\n * }\n * ```\n */\n\n// High-level client operations\nexport {\n buildUrl,\n type DownloadResult,\n deleteFile,\n download,\n getMeta,\n parseUrl,\n type SetExpirationOptions,\n type SetExpirationResult,\n type SignOptions,\n setExpiration,\n type UploadOptions,\n type UploadResult,\n upload,\n} from './client';\n// Low-level crypto (for advanced use)\nexport {\n base64Decode,\n base64Encode,\n base64UrlDecode,\n base64UrlEncode,\n decrypt,\n deriveKey,\n encrypt,\n generateId,\n generateIV,\n generateKey,\n generateMutateKey,\n generateSalt,\n} from './crypto';\n// Signing & verification\nexport {\n computeFingerprint,\n formatFingerprint,\n type KeyType,\n type ParsedPublicKey,\n parsePublicKey,\n verifySignature,\n} from './signing';\n\n// Types\nexport type {\n FileInfo,\n SignatureEnvelope,\n SignedPayload,\n UploadMeta,\n} from './types';\n","/**\n * Cryptographic utilities for mux.md\n *\n * Security parameters:\n * - Key: 80 bits entropy (14 chars base64url)\n * - ID: 30 bits entropy (5 chars base62) - not security critical\n * - Salt: 128 bits (16 bytes) - HKDF salt\n * - IV: 96 bits (12 bytes) for AES-GCM\n */\nconst SALT_BYTES = 16;\nconst IV_BYTES = 12;\nconst KEY_BYTES = 10; // 80 bits\nconst ID_BYTES = 4; // 32 bits, we'll use 30\nconst MUTATE_KEY_BYTES = 16; // 128 bits for mutate key\n\n// Base62 alphabet for URL-safe IDs (no special chars)\nconst BASE62 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\n\n/**\n * Generate a random file ID (30 bits, 5 chars base62)\n */\nexport function generateId(): string {\n const bytes = new Uint8Array(ID_BYTES);\n crypto.getRandomValues(bytes);\n\n // Convert to number (32 bits), then encode as base62\n const num = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];\n\n // Use modulo to get 5 base62 characters (covers ~30 bits)\n let result = '';\n let n = num >>> 0; // Ensure unsigned\n for (let i = 0; i < 5; i++) {\n result = BASE62[n % 62] + result;\n n = Math.floor(n / 62);\n }\n\n return result;\n}\n\n/**\n * Generate encryption key material (80 bits, 14 chars base64url)\n */\nexport function generateKey(): string {\n const bytes = new Uint8Array(KEY_BYTES);\n crypto.getRandomValues(bytes);\n return base64UrlEncode(bytes);\n}\n\n/**\n * Generate mutate key (128 bits, 22 chars base64url)\n * Used for mutation operations: delete, set expiration\n */\nexport function generateMutateKey(): string {\n const bytes = new Uint8Array(MUTATE_KEY_BYTES);\n crypto.getRandomValues(bytes);\n return base64UrlEncode(bytes);\n}\n\n/**\n * Generate random salt for HKDF (128 bits)\n */\nexport function generateSalt(): Uint8Array {\n const salt = new Uint8Array(SALT_BYTES);\n crypto.getRandomValues(salt);\n return salt;\n}\n\n/**\n * Generate random IV for AES-GCM (96 bits)\n */\nexport function generateIV(): Uint8Array {\n const iv = new Uint8Array(IV_BYTES);\n crypto.getRandomValues(iv);\n return iv;\n}\n\n/**\n * Derive AES-256 key from key material using HKDF\n *\n * HKDF is the correct choice for deriving keys from high-entropy\n * random key material. Unlike PBKDF2, it doesn't need iterations\n * since we're not stretching a weak password.\n */\nexport async function deriveKey(\n keyMaterial: string,\n salt: Uint8Array,\n): Promise<CryptoKey> {\n // Import the raw key material\n const rawKey = base64UrlDecode(keyMaterial);\n const baseKey = await crypto.subtle.importKey(\n 'raw',\n rawKey.buffer as ArrayBuffer,\n 'HKDF',\n false,\n ['deriveKey'],\n );\n\n // Derive AES-256 key using HKDF\n return crypto.subtle.deriveKey(\n {\n name: 'HKDF',\n salt: salt.buffer as ArrayBuffer,\n info: new Uint8Array(0), // No additional context needed\n hash: 'SHA-256',\n },\n baseKey,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt', 'decrypt'],\n );\n}\n\n/**\n * Encrypt data using AES-256-GCM\n */\nexport async function encrypt(\n data: Uint8Array,\n key: CryptoKey,\n iv: Uint8Array,\n): Promise<Uint8Array> {\n const ciphertext = await crypto.subtle.encrypt(\n { name: 'AES-GCM', iv: iv.buffer as ArrayBuffer },\n key,\n data.buffer as ArrayBuffer,\n );\n return new Uint8Array(ciphertext);\n}\n\n/**\n * Decrypt data using AES-256-GCM\n */\nexport async function decrypt(\n ciphertext: Uint8Array,\n key: CryptoKey,\n iv: Uint8Array,\n): Promise<Uint8Array> {\n const plaintext = await crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: iv.buffer as ArrayBuffer },\n key,\n ciphertext.buffer as ArrayBuffer,\n );\n return new Uint8Array(plaintext);\n}\n\n/**\n * Base64url encode (URL-safe, no padding)\n */\nexport function base64UrlEncode(data: Uint8Array): string {\n const base64 = btoa(String.fromCharCode(...data));\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/**\n * Base64url decode\n */\nexport function base64UrlDecode(str: string): Uint8Array {\n // Restore standard base64\n let base64 = str.replace(/-/g, '+').replace(/_/g, '/');\n // Add padding if needed\n while (base64.length % 4) {\n base64 += '=';\n }\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\n/**\n * Standard base64 encode\n */\nexport function base64Encode(data: Uint8Array): string {\n return btoa(String.fromCharCode(...data));\n}\n\n/**\n * Standard base64 decode\n */\nexport function base64Decode(str: string): Uint8Array {\n const binary = atob(str);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n","/**\n * Signing and verification utilities for mux.md\n *\n * Supports Ed25519 and ECDSA (P-256, P-384, P-521) keys in SSH format.\n */\n\nimport { p256, p384, p521 } from '@noble/curves/nist.js';\nimport * as ed from '@noble/ed25519';\nimport { sha512 } from '@noble/hashes/sha2.js';\nimport type { SignatureEnvelope } from './types';\n\n// Configure @noble/ed25519 to use sha512 from @noble/hashes\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ed.etc as any).sha512Sync = (...m: Uint8Array[]) =>\n sha512(ed.etc.concatBytes(...m));\n\n// ----- Types -----\n\n/** Supported key types */\nexport type KeyType = 'ed25519' | 'ecdsa-p256' | 'ecdsa-p384' | 'ecdsa-p521';\n\n/** Parsed public key with type and raw bytes */\nexport interface ParsedPublicKey {\n type: KeyType;\n keyBytes: Uint8Array;\n}\n\n// SSH key type identifiers\nconst SSH_KEY_TYPES: Record<string, KeyType> = {\n 'ssh-ed25519': 'ed25519',\n 'ecdsa-sha2-nistp256': 'ecdsa-p256',\n 'ecdsa-sha2-nistp384': 'ecdsa-p384',\n 'ecdsa-sha2-nistp521': 'ecdsa-p521',\n};\n\n// ----- SSH Key Parsing -----\n\n/**\n * Read a length-prefixed string from SSH wire format\n */\nfunction readSSHString(\n data: Uint8Array,\n offset: number,\n): { value: Uint8Array; nextOffset: number } {\n const view = new DataView(data.buffer, data.byteOffset);\n const len = view.getUint32(offset);\n const value = data.slice(offset + 4, offset + 4 + len);\n return { value, nextOffset: offset + 4 + len };\n}\n\n/**\n * Decode standard base64 string to Uint8Array\n */\nfunction base64Decode(str: string): Uint8Array {\n // Handle base64url as well\n let base64 = str.replace(/-/g, '+').replace(/_/g, '/');\n while (base64.length % 4) {\n base64 += '=';\n }\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\n/**\n * Parse an SSH public key and extract the key bytes and type.\n * Supports formats:\n * - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... [comment]\n * - ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY... [comment]\n * - ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQ... [comment]\n * - ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjE... [comment]\n * - Raw base64 (32 bytes when decoded = Ed25519)\n */\nexport function parsePublicKey(keyString: string): ParsedPublicKey {\n const trimmed = keyString.trim();\n\n // Check for SSH format by looking for known key type prefixes\n for (const [sshType, keyType] of Object.entries(SSH_KEY_TYPES)) {\n if (trimmed.startsWith(`${sshType} `)) {\n const parts = trimmed.split(' ');\n if (parts.length < 2) {\n throw new Error('Invalid SSH key format');\n }\n const keyData = base64Decode(parts[1]);\n\n // SSH key format: [4 byte len][type string][4 byte len][key data...]\n // For ECDSA: [4 byte len][type][4 byte len][curve name][4 byte len][point]\n const { value: typeBytes, nextOffset: afterType } = readSSHString(\n keyData,\n 0,\n );\n const typeStr = new TextDecoder().decode(typeBytes);\n\n if (typeStr !== sshType) {\n throw new Error(\n `Key type mismatch: expected ${sshType}, got ${typeStr}`,\n );\n }\n\n if (keyType === 'ed25519') {\n // Ed25519: [type][32 byte key]\n const { value: rawKey } = readSSHString(keyData, afterType);\n if (rawKey.length !== 32) {\n throw new Error('Invalid Ed25519 key length');\n }\n return { type: 'ed25519', keyBytes: rawKey };\n }\n // ECDSA: [type][curve name][point]\n const { nextOffset: afterCurve } = readSSHString(keyData, afterType);\n const { value: point } = readSSHString(keyData, afterCurve);\n return { type: keyType, keyBytes: point };\n }\n }\n\n // Try raw base64\n const decoded = base64Decode(trimmed);\n if (decoded.length === 32) {\n return { type: 'ed25519', keyBytes: decoded };\n }\n\n throw new Error('Unsupported public key format');\n}\n\n// ----- Signing -----\n\n/**\n * Sign a message with Ed25519 private key.\n * @param message - The message bytes to sign\n * @param privateKey - 32-byte Ed25519 private key\n * @returns Base64-encoded signature (64 bytes)\n */\nexport async function signEd25519(\n message: Uint8Array,\n privateKey: Uint8Array,\n): Promise<string> {\n const sig = await ed.signAsync(message, privateKey);\n return btoa(String.fromCharCode(...sig));\n}\n\n/**\n * Sign a message with ECDSA private key (P-256/384/521).\n * @param message - The message bytes to sign (will be hashed)\n * @param privateKey - ECDSA private key bytes\n * @param curve - Which curve to use\n * @returns Base64-encoded signature\n */\nexport function signECDSA(\n message: Uint8Array,\n privateKey: Uint8Array,\n curve: 'p256' | 'p384' | 'p521',\n): string {\n const curves = { p256, p384, p521 };\n // In @noble/curves v2, sign() returns a Uint8Array directly (compact r||s format)\n const sigBytes = curves[curve].sign(message, privateKey, { prehash: true });\n return btoa(String.fromCharCode(...sigBytes));\n}\n\n/**\n * Helper: Create a SignatureEnvelope from content + private key.\n * This is the high-level API for signing before upload.\n *\n * @param content - The content bytes to sign\n * @param privateKey - Private key bytes (32 bytes for Ed25519, variable for ECDSA)\n * @param publicKey - SSH format public key string (e.g., \"ssh-ed25519 AAAA...\")\n * @param options - Optional GitHub username for attribution\n * @returns SignatureEnvelope ready for upload\n */\nexport async function createSignatureEnvelope(\n content: Uint8Array,\n privateKey: Uint8Array,\n publicKey: string,\n options?: { githubUser?: string },\n): Promise<SignatureEnvelope> {\n const parsed = parsePublicKey(publicKey);\n let sig: string;\n\n if (parsed.type === 'ed25519') {\n sig = await signEd25519(content, privateKey);\n } else {\n const curve = parsed.type.replace('ecdsa-', '') as 'p256' | 'p384' | 'p521';\n sig = signECDSA(content, privateKey, curve);\n }\n\n return {\n sig,\n publicKey,\n githubUser: options?.githubUser,\n };\n}\n\n// ----- Verification -----\n\n/**\n * Verify a signature using the appropriate algorithm based on key type.\n * For Ed25519: signature is raw 64 bytes\n * For ECDSA: signature is DER-encoded or raw r||s format\n *\n * @param parsedKey - Parsed public key (from parsePublicKey)\n * @param message - Original message bytes\n * @param signature - Signature bytes (not base64)\n * @returns true if signature is valid\n */\nexport async function verifySignature(\n parsedKey: ParsedPublicKey,\n message: Uint8Array,\n signature: Uint8Array,\n): Promise<boolean> {\n try {\n switch (parsedKey.type) {\n case 'ed25519':\n return await ed.verifyAsync(signature, message, parsedKey.keyBytes);\n\n case 'ecdsa-p256':\n return p256.verify(signature, message, parsedKey.keyBytes, {\n prehash: true,\n });\n\n case 'ecdsa-p384':\n return p384.verify(signature, message, parsedKey.keyBytes, {\n prehash: true,\n });\n\n case 'ecdsa-p521':\n return p521.verify(signature, message, parsedKey.keyBytes, {\n prehash: true,\n });\n\n default:\n return false;\n }\n } catch {\n return false;\n }\n}\n\n// ----- Fingerprint -----\n\n/**\n * Compute SHA256 fingerprint of a public key (matches ssh-keygen -l format)\n * @param publicKey - Raw public key bytes\n * @returns Fingerprint string like \"SHA256:abc123...\"\n */\nexport async function computeFingerprint(\n publicKey: Uint8Array,\n): Promise<string> {\n const hash = await crypto.subtle.digest(\n 'SHA-256',\n publicKey.buffer as ArrayBuffer,\n );\n const hashArray = new Uint8Array(hash);\n const base64 = btoa(String.fromCharCode(...hashArray));\n return `SHA256:${base64.replace(/=+$/, '')}`;\n}\n\n/**\n * Format a fingerprint for nice display.\n * Converts base64 fingerprint to uppercase hex groups like \"DEAD BEEF 1234 5678\"\n */\nexport function formatFingerprint(fingerprint: string): string {\n // Remove \"SHA256:\" prefix if present\n const base64Part = fingerprint.startsWith('SHA256:')\n ? fingerprint.slice(7)\n : fingerprint;\n\n // Decode base64 to bytes, then to hex\n try {\n const binary = atob(base64Part);\n const hex = Array.from(binary)\n .map((c) => c.charCodeAt(0).toString(16).padStart(2, '0'))\n .join('')\n .toUpperCase();\n\n // Take first 16 chars (8 bytes) and format as \"DEAD BEEF 1234 5678\"\n const short = hex.slice(0, 16);\n return short.match(/.{4}/g)?.join(' ') || short;\n } catch {\n // Fallback: just show first part of the fingerprint\n return fingerprint.slice(0, 16).toUpperCase();\n }\n}\n","/**\n * mux.md Client Library\n *\n * Reference implementation for encrypting, uploading, downloading, and decrypting files.\n * Works in both Node.js (with webcrypto) and browser environments.\n */\n\nimport {\n base64Decode,\n base64Encode,\n decrypt,\n deriveKey,\n encrypt,\n generateIV,\n generateKey,\n generateSalt,\n} from './crypto';\nimport { createSignatureEnvelope } from './signing';\nimport type {\n FileInfo,\n SignatureEnvelope,\n SignedPayload,\n UploadMeta,\n} from './types';\n\n// Internal types (not exported from package)\ninterface UploadResponse {\n id: string;\n url: string;\n mutateKey: string;\n expiresAt?: number;\n}\n\ninterface MetaResponse {\n salt: string;\n iv: string;\n encryptedMeta: string;\n size: number;\n}\n\nconst DEFAULT_BASE_URL = 'https://mux.md';\n\n/** Options for signing content during upload */\nexport interface SignOptions {\n /** Private key bytes (32 bytes for Ed25519, variable for ECDSA) */\n privateKey: Uint8Array;\n /** SSH format public key string (e.g., \"ssh-ed25519 AAAA...\") */\n publicKey: string;\n /** Optional GitHub username for attribution */\n githubUser?: string;\n}\n\nexport interface UploadOptions {\n /** Base URL of the mux.md service */\n baseUrl?: string;\n /** Expiration time (unix timestamp ms, ISO date string, or Date object) */\n expiresAt?: number | string | Date;\n /**\n * Sign the content with the provided credentials.\n * The library handles creating the signature envelope internally.\n */\n sign?: SignOptions;\n}\n\nexport interface UploadResult {\n /** Full URL with encryption key in fragment */\n url: string;\n /** File ID (without key) */\n id: string;\n /** Encryption key (base64url) */\n key: string;\n /** Mutate key (base64url) - store this to mutate (delete, change expiration) the file later */\n mutateKey: string;\n /** Expiration timestamp (ms), if set */\n expiresAt?: number;\n}\n\nexport interface DownloadResult {\n /** Decrypted file content */\n data: Uint8Array;\n /** Original file info (name, type, size) */\n info: FileInfo;\n /** Decrypted signature envelope (if present) */\n signature?: SignatureEnvelope;\n}\n\n/**\n * Encrypt and upload a file to mux.md\n *\n * @param data - File contents as Uint8Array\n * @param fileInfo - Original file metadata (name, type, size)\n * @param options - Upload options (including optional signature)\n * @returns Upload result with URL containing encryption key\n */\nexport async function upload(\n data: Uint8Array,\n fileInfo: FileInfo,\n options: UploadOptions = {},\n): Promise<UploadResult> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Generate encryption parameters\n const keyMaterial = generateKey();\n const salt = generateSalt();\n const iv = generateIV();\n const cryptoKey = await deriveKey(keyMaterial, salt);\n\n // Create signature envelope if signing credentials provided\n let signatureEnvelope: SignatureEnvelope | undefined;\n if (options.sign) {\n signatureEnvelope = await createSignatureEnvelope(\n data,\n options.sign.privateKey,\n options.sign.publicKey,\n { githubUser: options.sign.githubUser },\n );\n }\n\n // Build plaintext - either signed (JSON) or raw content\n let plaintext: Uint8Array;\n if (signatureEnvelope) {\n // Signed format: JSON with content + signature\n const signed: SignedPayload = {\n content: new TextDecoder().decode(data),\n sig: signatureEnvelope,\n };\n plaintext = new TextEncoder().encode(JSON.stringify(signed));\n } else {\n // Unsigned: raw content bytes\n plaintext = data;\n }\n\n // Single encryption for the payload\n const payload = await encrypt(plaintext, cryptoKey, iv);\n\n // Encrypt file metadata (always the same way)\n const metaJson = JSON.stringify(fileInfo);\n const metaBytes = new TextEncoder().encode(metaJson);\n const metaIv = generateIV();\n const encryptedMeta = await encrypt(metaBytes, cryptoKey, metaIv);\n\n // Prepare upload metadata\n const uploadMeta: UploadMeta = {\n salt: base64Encode(salt),\n iv: base64Encode(iv),\n encryptedMeta: base64Encode(new Uint8Array([...metaIv, ...encryptedMeta])),\n };\n\n // Build headers\n const headers: Record<string, string> = {\n 'Content-Type': 'application/octet-stream',\n 'X-Mux-Meta': btoa(JSON.stringify(uploadMeta)),\n };\n\n // Add expiration header if specified (convert to ISO 8601)\n if (options.expiresAt !== undefined) {\n let expiresDate: Date;\n if (options.expiresAt instanceof Date) {\n expiresDate = options.expiresAt;\n } else if (typeof options.expiresAt === 'string') {\n expiresDate = new Date(options.expiresAt);\n } else {\n expiresDate = new Date(options.expiresAt);\n }\n headers['X-Mux-Expires'] = expiresDate.toISOString();\n }\n\n // Upload to server\n const response = await fetch(`${baseUrl}/`, {\n method: 'POST',\n headers,\n body: payload.buffer as ArrayBuffer,\n });\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Upload failed' }));\n throw new Error((error as { error: string }).error || 'Upload failed');\n }\n\n const result: UploadResponse = await response.json();\n\n return {\n url: `${baseUrl}/${result.id}#${keyMaterial}`,\n id: result.id,\n key: keyMaterial,\n mutateKey: result.mutateKey,\n ...(result.expiresAt && { expiresAt: result.expiresAt }),\n };\n}\n\n/**\n * Download and decrypt a file from mux.md\n *\n * @param url - Full URL with encryption key in fragment, or just the ID\n * @param key - Encryption key (required if url doesn't contain fragment)\n * @param options - Download options\n * @returns Decrypted file data and metadata\n */\nexport async function download(\n url: string,\n key?: string,\n options: { baseUrl?: string } = {},\n): Promise<DownloadResult> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Parse URL to extract ID and key\n let id: string;\n let keyMaterial: string;\n\n if (url.includes('#')) {\n // Full URL with fragment\n const urlObj = new URL(url);\n id = urlObj.pathname.slice(1); // Remove leading /\n keyMaterial = urlObj.hash.slice(1); // Remove leading #\n } else if (url.includes('/')) {\n // URL path without fragment\n const parts = url.split('/');\n id = parts[parts.length - 1];\n if (!key) throw new Error('Key required when URL has no fragment');\n keyMaterial = key;\n } else {\n // Just the ID\n id = url;\n if (!key) throw new Error('Key required when only ID is provided');\n keyMaterial = key;\n }\n\n // Download encrypted file\n const response = await fetch(`${baseUrl}/${id}`);\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Download failed' }));\n throw new Error((error as { error: string }).error || 'Download failed');\n }\n\n // Parse metadata header\n const metaHeader = response.headers.get('X-Mux-Meta');\n if (!metaHeader) {\n throw new Error('Missing metadata header');\n }\n\n const uploadMeta: UploadMeta = JSON.parse(atob(metaHeader));\n\n // Get encrypted data\n const encryptedData = new Uint8Array(await response.arrayBuffer());\n\n // Derive decryption key\n const salt = base64Decode(uploadMeta.salt);\n const iv = base64Decode(uploadMeta.iv);\n const cryptoKey = await deriveKey(keyMaterial, salt);\n\n // Decrypt metadata (same for both formats)\n const encryptedMetaWithIv = base64Decode(uploadMeta.encryptedMeta);\n const metaIv = encryptedMetaWithIv.slice(0, 12);\n const encryptedMetaData = encryptedMetaWithIv.slice(12);\n const metaBytes = await decrypt(encryptedMetaData, cryptoKey, metaIv);\n const info: FileInfo = JSON.parse(new TextDecoder().decode(metaBytes));\n\n // Decrypt the payload\n const decrypted = await decrypt(encryptedData, cryptoKey, iv);\n\n // Check if decrypted content is signed (JSON with content + sig fields)\n if (decrypted[0] === 0x7b) {\n // '{' character - might be signed JSON\n try {\n const jsonStr = new TextDecoder().decode(decrypted);\n const parsed = JSON.parse(jsonStr);\n\n if (typeof parsed.content === 'string' && parsed.sig) {\n // Signed format\n const data = new TextEncoder().encode(parsed.content);\n const signature: SignatureEnvelope = parsed.sig;\n return { data, info, signature };\n }\n } catch {\n // Not valid JSON - treat as raw content\n }\n }\n\n // Unsigned content (raw bytes)\n return { data: decrypted, info };\n}\n\n/**\n * Get file metadata without downloading the full file\n *\n * @param url - Full URL or ID\n * @param key - Encryption key (required to decrypt metadata)\n * @param options - Request options\n * @returns Decrypted file info and server metadata\n */\nexport async function getMeta(\n url: string,\n key?: string,\n options: { baseUrl?: string } = {},\n): Promise<{ info: FileInfo; size: number }> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Parse URL to extract ID and key\n let id: string;\n let keyMaterial: string;\n\n if (url.includes('#')) {\n const urlObj = new URL(url);\n id = urlObj.pathname.slice(1);\n keyMaterial = urlObj.hash.slice(1);\n } else if (url.includes('/')) {\n const parts = url.split('/');\n id = parts[parts.length - 1];\n if (!key) throw new Error('Key required when URL has no fragment');\n keyMaterial = key;\n } else {\n id = url;\n if (!key) throw new Error('Key required when only ID is provided');\n keyMaterial = key;\n }\n\n // Fetch metadata\n const response = await fetch(`${baseUrl}/${id}/meta`);\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Request failed' }));\n throw new Error((error as { error: string }).error || 'Request failed');\n }\n\n const meta: MetaResponse = await response.json();\n\n // Derive key and decrypt metadata\n const salt = base64Decode(meta.salt);\n const cryptoKey = await deriveKey(keyMaterial, salt);\n\n const encryptedMetaWithIv = base64Decode(meta.encryptedMeta);\n const metaIv = encryptedMetaWithIv.slice(0, 12);\n const encryptedMetaData = encryptedMetaWithIv.slice(12);\n const metaBytes = await decrypt(encryptedMetaData, cryptoKey, metaIv);\n const info: FileInfo = JSON.parse(new TextDecoder().decode(metaBytes));\n\n return {\n info,\n size: meta.size,\n };\n}\n\n/**\n * Parse a mux.md URL into its components\n */\nexport function parseUrl(url: string): { id: string; key: string } | null {\n try {\n const urlObj = new URL(url);\n if (!urlObj.hash) return null;\n\n const id = urlObj.pathname.slice(1);\n const key = urlObj.hash.slice(1);\n\n if (!id || !key) return null;\n\n return { id, key };\n } catch {\n return null;\n }\n}\n\n/**\n * Build a mux.md URL from components\n */\nexport function buildUrl(\n id: string,\n key: string,\n baseUrl = DEFAULT_BASE_URL,\n): string {\n return `${baseUrl}/${id}#${key}`;\n}\n\n/**\n * Delete a file from mux.md using its mutate key\n *\n * @param id - File ID\n * @param mutateKey - Mutate key returned from upload\n * @param options - Request options\n */\nexport async function deleteFile(\n id: string,\n mutateKey: string,\n options: { baseUrl?: string } = {},\n): Promise<void> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n const response = await fetch(`${baseUrl}/${id}`, {\n method: 'DELETE',\n headers: {\n 'X-Mux-Mutate-Key': mutateKey,\n },\n });\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Delete failed' }));\n throw new Error((error as { error: string }).error || 'Delete failed');\n }\n}\n\nexport interface SetExpirationOptions {\n /** Base URL of the mux.md service */\n baseUrl?: string;\n}\n\nexport interface SetExpirationResult {\n /** Whether the operation succeeded */\n success: boolean;\n /** File ID */\n id: string;\n /** New expiration timestamp (ms), or undefined if expiration was removed */\n expiresAt?: number;\n}\n\n/**\n * Set or remove the expiration of a file using its mutate key\n *\n * @param id - File ID\n * @param mutateKey - Mutate key returned from upload\n * @param expiresAt - New expiration time (unix timestamp ms, ISO date string, Date object, or \"never\" to remove expiration)\n * @param options - Request options\n * @returns Result with new expiration info\n */\nexport async function setExpiration(\n id: string,\n mutateKey: string,\n expiresAt: number | string | Date | 'never',\n options: SetExpirationOptions = {},\n): Promise<SetExpirationResult> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Convert expiration to ISO 8601 string\n let expiresHeader: string;\n if (expiresAt === 'never') {\n expiresHeader = 'never';\n } else if (expiresAt instanceof Date) {\n expiresHeader = expiresAt.toISOString();\n } else if (typeof expiresAt === 'string') {\n expiresHeader = new Date(expiresAt).toISOString();\n } else {\n expiresHeader = new Date(expiresAt).toISOString();\n }\n\n const response = await fetch(`${baseUrl}/${id}`, {\n method: 'PATCH',\n headers: {\n 'X-Mux-Mutate-Key': mutateKey,\n 'X-Mux-Expires': expiresHeader,\n },\n });\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Set expiration failed' }));\n throw new Error(\n (error as { error: string }).error || 'Set expiration failed',\n );\n }\n\n return response.json();\n}\n"],"mappings":"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","names":["base64Decode"]}
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).
@@ -51,8 +50,6 @@ interface SignOptions {
51
50
  privateKey: Uint8Array;
52
51
  /** SSH format public key string (e.g., "ssh-ed25519 AAAA...") */
53
52
  publicKey: string;
54
- /** Optional email for attribution */
55
- email?: string;
56
53
  /** Optional GitHub username for attribution */
57
54
  githubUser?: string;
58
55
  }
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).
@@ -51,8 +50,6 @@ interface SignOptions {
51
50
  privateKey: Uint8Array;
52
51
  /** SSH format public key string (e.g., "ssh-ed25519 AAAA...") */
53
52
  publicKey: string;
54
- /** Optional email for attribution */
55
- email?: string;
56
53
  /** Optional GitHub username for attribution */
57
54
  githubUser?: string;
58
55
  }
package/dist/index.js CHANGED
@@ -191,7 +191,6 @@ async function createSignatureEnvelope(content, privateKey, publicKey, options)
191
191
  return {
192
192
  sig,
193
193
  publicKey,
194
- email: options?.email,
195
194
  githubUser: options?.githubUser
196
195
  };
197
196
  }
@@ -254,7 +253,7 @@ async function upload(data, fileInfo, options = {}) {
254
253
  data,
255
254
  options.sign.privateKey,
256
255
  options.sign.publicKey,
257
- { email: options.sign.email, githubUser: options.sign.githubUser }
256
+ { githubUser: options.sign.githubUser }
258
257
  );
259
258
  }
260
259
  let plaintext;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/crypto.ts","../src/signing.ts","../src/client.ts"],"sourcesContent":["/**\n * Cryptographic utilities for mux.md\n *\n * Security parameters:\n * - Key: 80 bits entropy (14 chars base64url)\n * - ID: 30 bits entropy (5 chars base62) - not security critical\n * - Salt: 128 bits (16 bytes) - HKDF salt\n * - IV: 96 bits (12 bytes) for AES-GCM\n */\nconst SALT_BYTES = 16;\nconst IV_BYTES = 12;\nconst KEY_BYTES = 10; // 80 bits\nconst ID_BYTES = 4; // 32 bits, we'll use 30\nconst MUTATE_KEY_BYTES = 16; // 128 bits for mutate key\n\n// Base62 alphabet for URL-safe IDs (no special chars)\nconst BASE62 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\n\n/**\n * Generate a random file ID (30 bits, 5 chars base62)\n */\nexport function generateId(): string {\n const bytes = new Uint8Array(ID_BYTES);\n crypto.getRandomValues(bytes);\n\n // Convert to number (32 bits), then encode as base62\n const num = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];\n\n // Use modulo to get 5 base62 characters (covers ~30 bits)\n let result = '';\n let n = num >>> 0; // Ensure unsigned\n for (let i = 0; i < 5; i++) {\n result = BASE62[n % 62] + result;\n n = Math.floor(n / 62);\n }\n\n return result;\n}\n\n/**\n * Generate encryption key material (80 bits, 14 chars base64url)\n */\nexport function generateKey(): string {\n const bytes = new Uint8Array(KEY_BYTES);\n crypto.getRandomValues(bytes);\n return base64UrlEncode(bytes);\n}\n\n/**\n * Generate mutate key (128 bits, 22 chars base64url)\n * Used for mutation operations: delete, set expiration\n */\nexport function generateMutateKey(): string {\n const bytes = new Uint8Array(MUTATE_KEY_BYTES);\n crypto.getRandomValues(bytes);\n return base64UrlEncode(bytes);\n}\n\n/**\n * Generate random salt for HKDF (128 bits)\n */\nexport function generateSalt(): Uint8Array {\n const salt = new Uint8Array(SALT_BYTES);\n crypto.getRandomValues(salt);\n return salt;\n}\n\n/**\n * Generate random IV for AES-GCM (96 bits)\n */\nexport function generateIV(): Uint8Array {\n const iv = new Uint8Array(IV_BYTES);\n crypto.getRandomValues(iv);\n return iv;\n}\n\n/**\n * Derive AES-256 key from key material using HKDF\n *\n * HKDF is the correct choice for deriving keys from high-entropy\n * random key material. Unlike PBKDF2, it doesn't need iterations\n * since we're not stretching a weak password.\n */\nexport async function deriveKey(\n keyMaterial: string,\n salt: Uint8Array,\n): Promise<CryptoKey> {\n // Import the raw key material\n const rawKey = base64UrlDecode(keyMaterial);\n const baseKey = await crypto.subtle.importKey(\n 'raw',\n rawKey.buffer as ArrayBuffer,\n 'HKDF',\n false,\n ['deriveKey'],\n );\n\n // Derive AES-256 key using HKDF\n return crypto.subtle.deriveKey(\n {\n name: 'HKDF',\n salt: salt.buffer as ArrayBuffer,\n info: new Uint8Array(0), // No additional context needed\n hash: 'SHA-256',\n },\n baseKey,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt', 'decrypt'],\n );\n}\n\n/**\n * Encrypt data using AES-256-GCM\n */\nexport async function encrypt(\n data: Uint8Array,\n key: CryptoKey,\n iv: Uint8Array,\n): Promise<Uint8Array> {\n const ciphertext = await crypto.subtle.encrypt(\n { name: 'AES-GCM', iv: iv.buffer as ArrayBuffer },\n key,\n data.buffer as ArrayBuffer,\n );\n return new Uint8Array(ciphertext);\n}\n\n/**\n * Decrypt data using AES-256-GCM\n */\nexport async function decrypt(\n ciphertext: Uint8Array,\n key: CryptoKey,\n iv: Uint8Array,\n): Promise<Uint8Array> {\n const plaintext = await crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: iv.buffer as ArrayBuffer },\n key,\n ciphertext.buffer as ArrayBuffer,\n );\n return new Uint8Array(plaintext);\n}\n\n/**\n * Base64url encode (URL-safe, no padding)\n */\nexport function base64UrlEncode(data: Uint8Array): string {\n const base64 = btoa(String.fromCharCode(...data));\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/**\n * Base64url decode\n */\nexport function base64UrlDecode(str: string): Uint8Array {\n // Restore standard base64\n let base64 = str.replace(/-/g, '+').replace(/_/g, '/');\n // Add padding if needed\n while (base64.length % 4) {\n base64 += '=';\n }\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\n/**\n * Standard base64 encode\n */\nexport function base64Encode(data: Uint8Array): string {\n return btoa(String.fromCharCode(...data));\n}\n\n/**\n * Standard base64 decode\n */\nexport function base64Decode(str: string): Uint8Array {\n const binary = atob(str);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n","/**\n * Signing and verification utilities for mux.md\n *\n * Supports Ed25519 and ECDSA (P-256, P-384, P-521) keys in SSH format.\n */\n\nimport { p256, p384, p521 } from '@noble/curves/nist.js';\nimport * as ed from '@noble/ed25519';\nimport { sha512 } from '@noble/hashes/sha2.js';\nimport type { SignatureEnvelope } from './types';\n\n// Configure @noble/ed25519 to use sha512 from @noble/hashes\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ed.etc as any).sha512Sync = (...m: Uint8Array[]) =>\n sha512(ed.etc.concatBytes(...m));\n\n// ----- Types -----\n\n/** Supported key types */\nexport type KeyType = 'ed25519' | 'ecdsa-p256' | 'ecdsa-p384' | 'ecdsa-p521';\n\n/** Parsed public key with type and raw bytes */\nexport interface ParsedPublicKey {\n type: KeyType;\n keyBytes: Uint8Array;\n}\n\n// SSH key type identifiers\nconst SSH_KEY_TYPES: Record<string, KeyType> = {\n 'ssh-ed25519': 'ed25519',\n 'ecdsa-sha2-nistp256': 'ecdsa-p256',\n 'ecdsa-sha2-nistp384': 'ecdsa-p384',\n 'ecdsa-sha2-nistp521': 'ecdsa-p521',\n};\n\n// ----- SSH Key Parsing -----\n\n/**\n * Read a length-prefixed string from SSH wire format\n */\nfunction readSSHString(\n data: Uint8Array,\n offset: number,\n): { value: Uint8Array; nextOffset: number } {\n const view = new DataView(data.buffer, data.byteOffset);\n const len = view.getUint32(offset);\n const value = data.slice(offset + 4, offset + 4 + len);\n return { value, nextOffset: offset + 4 + len };\n}\n\n/**\n * Decode standard base64 string to Uint8Array\n */\nfunction base64Decode(str: string): Uint8Array {\n // Handle base64url as well\n let base64 = str.replace(/-/g, '+').replace(/_/g, '/');\n while (base64.length % 4) {\n base64 += '=';\n }\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\n/**\n * Parse an SSH public key and extract the key bytes and type.\n * Supports formats:\n * - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... [comment]\n * - ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY... [comment]\n * - ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQ... [comment]\n * - ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjE... [comment]\n * - Raw base64 (32 bytes when decoded = Ed25519)\n */\nexport function parsePublicKey(keyString: string): ParsedPublicKey {\n const trimmed = keyString.trim();\n\n // Check for SSH format by looking for known key type prefixes\n for (const [sshType, keyType] of Object.entries(SSH_KEY_TYPES)) {\n if (trimmed.startsWith(`${sshType} `)) {\n const parts = trimmed.split(' ');\n if (parts.length < 2) {\n throw new Error('Invalid SSH key format');\n }\n const keyData = base64Decode(parts[1]);\n\n // SSH key format: [4 byte len][type string][4 byte len][key data...]\n // For ECDSA: [4 byte len][type][4 byte len][curve name][4 byte len][point]\n const { value: typeBytes, nextOffset: afterType } = readSSHString(\n keyData,\n 0,\n );\n const typeStr = new TextDecoder().decode(typeBytes);\n\n if (typeStr !== sshType) {\n throw new Error(\n `Key type mismatch: expected ${sshType}, got ${typeStr}`,\n );\n }\n\n if (keyType === 'ed25519') {\n // Ed25519: [type][32 byte key]\n const { value: rawKey } = readSSHString(keyData, afterType);\n if (rawKey.length !== 32) {\n throw new Error('Invalid Ed25519 key length');\n }\n return { type: 'ed25519', keyBytes: rawKey };\n }\n // ECDSA: [type][curve name][point]\n const { nextOffset: afterCurve } = readSSHString(keyData, afterType);\n const { value: point } = readSSHString(keyData, afterCurve);\n return { type: keyType, keyBytes: point };\n }\n }\n\n // Try raw base64\n const decoded = base64Decode(trimmed);\n if (decoded.length === 32) {\n return { type: 'ed25519', keyBytes: decoded };\n }\n\n throw new Error('Unsupported public key format');\n}\n\n// ----- Signing -----\n\n/**\n * Sign a message with Ed25519 private key.\n * @param message - The message bytes to sign\n * @param privateKey - 32-byte Ed25519 private key\n * @returns Base64-encoded signature (64 bytes)\n */\nexport async function signEd25519(\n message: Uint8Array,\n privateKey: Uint8Array,\n): Promise<string> {\n const sig = await ed.signAsync(message, privateKey);\n return btoa(String.fromCharCode(...sig));\n}\n\n/**\n * Sign a message with ECDSA private key (P-256/384/521).\n * @param message - The message bytes to sign (will be hashed)\n * @param privateKey - ECDSA private key bytes\n * @param curve - Which curve to use\n * @returns Base64-encoded signature\n */\nexport function signECDSA(\n message: Uint8Array,\n privateKey: Uint8Array,\n curve: 'p256' | 'p384' | 'p521',\n): string {\n const curves = { p256, p384, p521 };\n // In @noble/curves v2, sign() returns a Uint8Array directly (compact r||s format)\n const sigBytes = curves[curve].sign(message, privateKey, { prehash: true });\n return btoa(String.fromCharCode(...sigBytes));\n}\n\n/**\n * Helper: Create a SignatureEnvelope from content + private key.\n * This is the high-level API for signing before upload.\n *\n * @param content - The content bytes to sign\n * @param privateKey - Private key bytes (32 bytes for Ed25519, variable for ECDSA)\n * @param publicKey - SSH format public key string (e.g., \"ssh-ed25519 AAAA...\")\n * @param options - Optional email or GitHub username for attribution\n * @returns SignatureEnvelope ready for upload\n */\nexport async function createSignatureEnvelope(\n content: Uint8Array,\n privateKey: Uint8Array,\n publicKey: string,\n options?: { email?: string; githubUser?: string },\n): Promise<SignatureEnvelope> {\n const parsed = parsePublicKey(publicKey);\n let sig: string;\n\n if (parsed.type === 'ed25519') {\n sig = await signEd25519(content, privateKey);\n } else {\n const curve = parsed.type.replace('ecdsa-', '') as 'p256' | 'p384' | 'p521';\n sig = signECDSA(content, privateKey, curve);\n }\n\n return {\n sig,\n publicKey,\n email: options?.email,\n githubUser: options?.githubUser,\n };\n}\n\n// ----- Verification -----\n\n/**\n * Verify a signature using the appropriate algorithm based on key type.\n * For Ed25519: signature is raw 64 bytes\n * For ECDSA: signature is DER-encoded or raw r||s format\n *\n * @param parsedKey - Parsed public key (from parsePublicKey)\n * @param message - Original message bytes\n * @param signature - Signature bytes (not base64)\n * @returns true if signature is valid\n */\nexport async function verifySignature(\n parsedKey: ParsedPublicKey,\n message: Uint8Array,\n signature: Uint8Array,\n): Promise<boolean> {\n try {\n switch (parsedKey.type) {\n case 'ed25519':\n return await ed.verifyAsync(signature, message, parsedKey.keyBytes);\n\n case 'ecdsa-p256':\n return p256.verify(signature, message, parsedKey.keyBytes, {\n prehash: true,\n });\n\n case 'ecdsa-p384':\n return p384.verify(signature, message, parsedKey.keyBytes, {\n prehash: true,\n });\n\n case 'ecdsa-p521':\n return p521.verify(signature, message, parsedKey.keyBytes, {\n prehash: true,\n });\n\n default:\n return false;\n }\n } catch {\n return false;\n }\n}\n\n// ----- Fingerprint -----\n\n/**\n * Compute SHA256 fingerprint of a public key (matches ssh-keygen -l format)\n * @param publicKey - Raw public key bytes\n * @returns Fingerprint string like \"SHA256:abc123...\"\n */\nexport async function computeFingerprint(\n publicKey: Uint8Array,\n): Promise<string> {\n const hash = await crypto.subtle.digest(\n 'SHA-256',\n publicKey.buffer as ArrayBuffer,\n );\n const hashArray = new Uint8Array(hash);\n const base64 = btoa(String.fromCharCode(...hashArray));\n return `SHA256:${base64.replace(/=+$/, '')}`;\n}\n\n/**\n * Format a fingerprint for nice display.\n * Converts base64 fingerprint to uppercase hex groups like \"DEAD BEEF 1234 5678\"\n */\nexport function formatFingerprint(fingerprint: string): string {\n // Remove \"SHA256:\" prefix if present\n const base64Part = fingerprint.startsWith('SHA256:')\n ? fingerprint.slice(7)\n : fingerprint;\n\n // Decode base64 to bytes, then to hex\n try {\n const binary = atob(base64Part);\n const hex = Array.from(binary)\n .map((c) => c.charCodeAt(0).toString(16).padStart(2, '0'))\n .join('')\n .toUpperCase();\n\n // Take first 16 chars (8 bytes) and format as \"DEAD BEEF 1234 5678\"\n const short = hex.slice(0, 16);\n return short.match(/.{4}/g)?.join(' ') || short;\n } catch {\n // Fallback: just show first part of the fingerprint\n return fingerprint.slice(0, 16).toUpperCase();\n }\n}\n","/**\n * mux.md Client Library\n *\n * Reference implementation for encrypting, uploading, downloading, and decrypting files.\n * Works in both Node.js (with webcrypto) and browser environments.\n */\n\nimport {\n base64Decode,\n base64Encode,\n decrypt,\n deriveKey,\n encrypt,\n generateIV,\n generateKey,\n generateSalt,\n} from './crypto';\nimport { createSignatureEnvelope } from './signing';\nimport type {\n FileInfo,\n SignatureEnvelope,\n SignedPayload,\n UploadMeta,\n} from './types';\n\n// Internal types (not exported from package)\ninterface UploadResponse {\n id: string;\n url: string;\n mutateKey: string;\n expiresAt?: number;\n}\n\ninterface MetaResponse {\n salt: string;\n iv: string;\n encryptedMeta: string;\n size: number;\n}\n\nconst DEFAULT_BASE_URL = 'https://mux.md';\n\n/** Options for signing content during upload */\nexport interface SignOptions {\n /** Private key bytes (32 bytes for Ed25519, variable for ECDSA) */\n privateKey: Uint8Array;\n /** SSH format public key string (e.g., \"ssh-ed25519 AAAA...\") */\n publicKey: string;\n /** Optional email for attribution */\n email?: string;\n /** Optional GitHub username for attribution */\n githubUser?: string;\n}\n\nexport interface UploadOptions {\n /** Base URL of the mux.md service */\n baseUrl?: string;\n /** Expiration time (unix timestamp ms, ISO date string, or Date object) */\n expiresAt?: number | string | Date;\n /**\n * Sign the content with the provided credentials.\n * The library handles creating the signature envelope internally.\n */\n sign?: SignOptions;\n}\n\nexport interface UploadResult {\n /** Full URL with encryption key in fragment */\n url: string;\n /** File ID (without key) */\n id: string;\n /** Encryption key (base64url) */\n key: string;\n /** Mutate key (base64url) - store this to mutate (delete, change expiration) the file later */\n mutateKey: string;\n /** Expiration timestamp (ms), if set */\n expiresAt?: number;\n}\n\nexport interface DownloadResult {\n /** Decrypted file content */\n data: Uint8Array;\n /** Original file info (name, type, size) */\n info: FileInfo;\n /** Decrypted signature envelope (if present) */\n signature?: SignatureEnvelope;\n}\n\n/**\n * Encrypt and upload a file to mux.md\n *\n * @param data - File contents as Uint8Array\n * @param fileInfo - Original file metadata (name, type, size)\n * @param options - Upload options (including optional signature)\n * @returns Upload result with URL containing encryption key\n */\nexport async function upload(\n data: Uint8Array,\n fileInfo: FileInfo,\n options: UploadOptions = {},\n): Promise<UploadResult> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Generate encryption parameters\n const keyMaterial = generateKey();\n const salt = generateSalt();\n const iv = generateIV();\n const cryptoKey = await deriveKey(keyMaterial, salt);\n\n // Create signature envelope if signing credentials provided\n let signatureEnvelope: SignatureEnvelope | undefined;\n if (options.sign) {\n signatureEnvelope = await createSignatureEnvelope(\n data,\n options.sign.privateKey,\n options.sign.publicKey,\n { email: options.sign.email, githubUser: options.sign.githubUser },\n );\n }\n\n // Build plaintext - either signed (JSON) or raw content\n let plaintext: Uint8Array;\n if (signatureEnvelope) {\n // Signed format: JSON with content + signature\n const signed: SignedPayload = {\n content: new TextDecoder().decode(data),\n sig: signatureEnvelope,\n };\n plaintext = new TextEncoder().encode(JSON.stringify(signed));\n } else {\n // Unsigned: raw content bytes\n plaintext = data;\n }\n\n // Single encryption for the payload\n const payload = await encrypt(plaintext, cryptoKey, iv);\n\n // Encrypt file metadata (always the same way)\n const metaJson = JSON.stringify(fileInfo);\n const metaBytes = new TextEncoder().encode(metaJson);\n const metaIv = generateIV();\n const encryptedMeta = await encrypt(metaBytes, cryptoKey, metaIv);\n\n // Prepare upload metadata\n const uploadMeta: UploadMeta = {\n salt: base64Encode(salt),\n iv: base64Encode(iv),\n encryptedMeta: base64Encode(new Uint8Array([...metaIv, ...encryptedMeta])),\n };\n\n // Build headers\n const headers: Record<string, string> = {\n 'Content-Type': 'application/octet-stream',\n 'X-Mux-Meta': btoa(JSON.stringify(uploadMeta)),\n };\n\n // Add expiration header if specified (convert to ISO 8601)\n if (options.expiresAt !== undefined) {\n let expiresDate: Date;\n if (options.expiresAt instanceof Date) {\n expiresDate = options.expiresAt;\n } else if (typeof options.expiresAt === 'string') {\n expiresDate = new Date(options.expiresAt);\n } else {\n expiresDate = new Date(options.expiresAt);\n }\n headers['X-Mux-Expires'] = expiresDate.toISOString();\n }\n\n // Upload to server\n const response = await fetch(`${baseUrl}/`, {\n method: 'POST',\n headers,\n body: payload.buffer as ArrayBuffer,\n });\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Upload failed' }));\n throw new Error((error as { error: string }).error || 'Upload failed');\n }\n\n const result: UploadResponse = await response.json();\n\n return {\n url: `${baseUrl}/${result.id}#${keyMaterial}`,\n id: result.id,\n key: keyMaterial,\n mutateKey: result.mutateKey,\n ...(result.expiresAt && { expiresAt: result.expiresAt }),\n };\n}\n\n/**\n * Download and decrypt a file from mux.md\n *\n * @param url - Full URL with encryption key in fragment, or just the ID\n * @param key - Encryption key (required if url doesn't contain fragment)\n * @param options - Download options\n * @returns Decrypted file data and metadata\n */\nexport async function download(\n url: string,\n key?: string,\n options: { baseUrl?: string } = {},\n): Promise<DownloadResult> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Parse URL to extract ID and key\n let id: string;\n let keyMaterial: string;\n\n if (url.includes('#')) {\n // Full URL with fragment\n const urlObj = new URL(url);\n id = urlObj.pathname.slice(1); // Remove leading /\n keyMaterial = urlObj.hash.slice(1); // Remove leading #\n } else if (url.includes('/')) {\n // URL path without fragment\n const parts = url.split('/');\n id = parts[parts.length - 1];\n if (!key) throw new Error('Key required when URL has no fragment');\n keyMaterial = key;\n } else {\n // Just the ID\n id = url;\n if (!key) throw new Error('Key required when only ID is provided');\n keyMaterial = key;\n }\n\n // Download encrypted file\n const response = await fetch(`${baseUrl}/${id}`);\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Download failed' }));\n throw new Error((error as { error: string }).error || 'Download failed');\n }\n\n // Parse metadata header\n const metaHeader = response.headers.get('X-Mux-Meta');\n if (!metaHeader) {\n throw new Error('Missing metadata header');\n }\n\n const uploadMeta: UploadMeta = JSON.parse(atob(metaHeader));\n\n // Get encrypted data\n const encryptedData = new Uint8Array(await response.arrayBuffer());\n\n // Derive decryption key\n const salt = base64Decode(uploadMeta.salt);\n const iv = base64Decode(uploadMeta.iv);\n const cryptoKey = await deriveKey(keyMaterial, salt);\n\n // Decrypt metadata (same for both formats)\n const encryptedMetaWithIv = base64Decode(uploadMeta.encryptedMeta);\n const metaIv = encryptedMetaWithIv.slice(0, 12);\n const encryptedMetaData = encryptedMetaWithIv.slice(12);\n const metaBytes = await decrypt(encryptedMetaData, cryptoKey, metaIv);\n const info: FileInfo = JSON.parse(new TextDecoder().decode(metaBytes));\n\n // Decrypt the payload\n const decrypted = await decrypt(encryptedData, cryptoKey, iv);\n\n // Check if decrypted content is signed (JSON with content + sig fields)\n if (decrypted[0] === 0x7b) {\n // '{' character - might be signed JSON\n try {\n const jsonStr = new TextDecoder().decode(decrypted);\n const parsed = JSON.parse(jsonStr);\n\n if (typeof parsed.content === 'string' && parsed.sig) {\n // Signed format\n const data = new TextEncoder().encode(parsed.content);\n const signature: SignatureEnvelope = parsed.sig;\n return { data, info, signature };\n }\n } catch {\n // Not valid JSON - treat as raw content\n }\n }\n\n // Unsigned content (raw bytes)\n return { data: decrypted, info };\n}\n\n/**\n * Get file metadata without downloading the full file\n *\n * @param url - Full URL or ID\n * @param key - Encryption key (required to decrypt metadata)\n * @param options - Request options\n * @returns Decrypted file info and server metadata\n */\nexport async function getMeta(\n url: string,\n key?: string,\n options: { baseUrl?: string } = {},\n): Promise<{ info: FileInfo; size: number }> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Parse URL to extract ID and key\n let id: string;\n let keyMaterial: string;\n\n if (url.includes('#')) {\n const urlObj = new URL(url);\n id = urlObj.pathname.slice(1);\n keyMaterial = urlObj.hash.slice(1);\n } else if (url.includes('/')) {\n const parts = url.split('/');\n id = parts[parts.length - 1];\n if (!key) throw new Error('Key required when URL has no fragment');\n keyMaterial = key;\n } else {\n id = url;\n if (!key) throw new Error('Key required when only ID is provided');\n keyMaterial = key;\n }\n\n // Fetch metadata\n const response = await fetch(`${baseUrl}/${id}/meta`);\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Request failed' }));\n throw new Error((error as { error: string }).error || 'Request failed');\n }\n\n const meta: MetaResponse = await response.json();\n\n // Derive key and decrypt metadata\n const salt = base64Decode(meta.salt);\n const cryptoKey = await deriveKey(keyMaterial, salt);\n\n const encryptedMetaWithIv = base64Decode(meta.encryptedMeta);\n const metaIv = encryptedMetaWithIv.slice(0, 12);\n const encryptedMetaData = encryptedMetaWithIv.slice(12);\n const metaBytes = await decrypt(encryptedMetaData, cryptoKey, metaIv);\n const info: FileInfo = JSON.parse(new TextDecoder().decode(metaBytes));\n\n return {\n info,\n size: meta.size,\n };\n}\n\n/**\n * Parse a mux.md URL into its components\n */\nexport function parseUrl(url: string): { id: string; key: string } | null {\n try {\n const urlObj = new URL(url);\n if (!urlObj.hash) return null;\n\n const id = urlObj.pathname.slice(1);\n const key = urlObj.hash.slice(1);\n\n if (!id || !key) return null;\n\n return { id, key };\n } catch {\n return null;\n }\n}\n\n/**\n * Build a mux.md URL from components\n */\nexport function buildUrl(\n id: string,\n key: string,\n baseUrl = DEFAULT_BASE_URL,\n): string {\n return `${baseUrl}/${id}#${key}`;\n}\n\n/**\n * Delete a file from mux.md using its mutate key\n *\n * @param id - File ID\n * @param mutateKey - Mutate key returned from upload\n * @param options - Request options\n */\nexport async function deleteFile(\n id: string,\n mutateKey: string,\n options: { baseUrl?: string } = {},\n): Promise<void> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n const response = await fetch(`${baseUrl}/${id}`, {\n method: 'DELETE',\n headers: {\n 'X-Mux-Mutate-Key': mutateKey,\n },\n });\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Delete failed' }));\n throw new Error((error as { error: string }).error || 'Delete failed');\n }\n}\n\nexport interface SetExpirationOptions {\n /** Base URL of the mux.md service */\n baseUrl?: string;\n}\n\nexport interface SetExpirationResult {\n /** Whether the operation succeeded */\n success: boolean;\n /** File ID */\n id: string;\n /** New expiration timestamp (ms), or undefined if expiration was removed */\n expiresAt?: number;\n}\n\n/**\n * Set or remove the expiration of a file using its mutate key\n *\n * @param id - File ID\n * @param mutateKey - Mutate key returned from upload\n * @param expiresAt - New expiration time (unix timestamp ms, ISO date string, Date object, or \"never\" to remove expiration)\n * @param options - Request options\n * @returns Result with new expiration info\n */\nexport async function setExpiration(\n id: string,\n mutateKey: string,\n expiresAt: number | string | Date | 'never',\n options: SetExpirationOptions = {},\n): Promise<SetExpirationResult> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Convert expiration to ISO 8601 string\n let expiresHeader: string;\n if (expiresAt === 'never') {\n expiresHeader = 'never';\n } else if (expiresAt instanceof Date) {\n expiresHeader = expiresAt.toISOString();\n } else if (typeof expiresAt === 'string') {\n expiresHeader = new Date(expiresAt).toISOString();\n } else {\n expiresHeader = new Date(expiresAt).toISOString();\n }\n\n const response = await fetch(`${baseUrl}/${id}`, {\n method: 'PATCH',\n headers: {\n 'X-Mux-Mutate-Key': mutateKey,\n 'X-Mux-Expires': expiresHeader,\n },\n });\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Set expiration failed' }));\n throw new Error(\n (error as { error: string }).error || 'Set expiration failed',\n );\n }\n\n return response.json();\n}\n"],"mappings":"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","names":["base64Decode"]}
1
+ {"version":3,"sources":["../src/crypto.ts","../src/signing.ts","../src/client.ts"],"sourcesContent":["/**\n * Cryptographic utilities for mux.md\n *\n * Security parameters:\n * - Key: 80 bits entropy (14 chars base64url)\n * - ID: 30 bits entropy (5 chars base62) - not security critical\n * - Salt: 128 bits (16 bytes) - HKDF salt\n * - IV: 96 bits (12 bytes) for AES-GCM\n */\nconst SALT_BYTES = 16;\nconst IV_BYTES = 12;\nconst KEY_BYTES = 10; // 80 bits\nconst ID_BYTES = 4; // 32 bits, we'll use 30\nconst MUTATE_KEY_BYTES = 16; // 128 bits for mutate key\n\n// Base62 alphabet for URL-safe IDs (no special chars)\nconst BASE62 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\n\n/**\n * Generate a random file ID (30 bits, 5 chars base62)\n */\nexport function generateId(): string {\n const bytes = new Uint8Array(ID_BYTES);\n crypto.getRandomValues(bytes);\n\n // Convert to number (32 bits), then encode as base62\n const num = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];\n\n // Use modulo to get 5 base62 characters (covers ~30 bits)\n let result = '';\n let n = num >>> 0; // Ensure unsigned\n for (let i = 0; i < 5; i++) {\n result = BASE62[n % 62] + result;\n n = Math.floor(n / 62);\n }\n\n return result;\n}\n\n/**\n * Generate encryption key material (80 bits, 14 chars base64url)\n */\nexport function generateKey(): string {\n const bytes = new Uint8Array(KEY_BYTES);\n crypto.getRandomValues(bytes);\n return base64UrlEncode(bytes);\n}\n\n/**\n * Generate mutate key (128 bits, 22 chars base64url)\n * Used for mutation operations: delete, set expiration\n */\nexport function generateMutateKey(): string {\n const bytes = new Uint8Array(MUTATE_KEY_BYTES);\n crypto.getRandomValues(bytes);\n return base64UrlEncode(bytes);\n}\n\n/**\n * Generate random salt for HKDF (128 bits)\n */\nexport function generateSalt(): Uint8Array {\n const salt = new Uint8Array(SALT_BYTES);\n crypto.getRandomValues(salt);\n return salt;\n}\n\n/**\n * Generate random IV for AES-GCM (96 bits)\n */\nexport function generateIV(): Uint8Array {\n const iv = new Uint8Array(IV_BYTES);\n crypto.getRandomValues(iv);\n return iv;\n}\n\n/**\n * Derive AES-256 key from key material using HKDF\n *\n * HKDF is the correct choice for deriving keys from high-entropy\n * random key material. Unlike PBKDF2, it doesn't need iterations\n * since we're not stretching a weak password.\n */\nexport async function deriveKey(\n keyMaterial: string,\n salt: Uint8Array,\n): Promise<CryptoKey> {\n // Import the raw key material\n const rawKey = base64UrlDecode(keyMaterial);\n const baseKey = await crypto.subtle.importKey(\n 'raw',\n rawKey.buffer as ArrayBuffer,\n 'HKDF',\n false,\n ['deriveKey'],\n );\n\n // Derive AES-256 key using HKDF\n return crypto.subtle.deriveKey(\n {\n name: 'HKDF',\n salt: salt.buffer as ArrayBuffer,\n info: new Uint8Array(0), // No additional context needed\n hash: 'SHA-256',\n },\n baseKey,\n { name: 'AES-GCM', length: 256 },\n false,\n ['encrypt', 'decrypt'],\n );\n}\n\n/**\n * Encrypt data using AES-256-GCM\n */\nexport async function encrypt(\n data: Uint8Array,\n key: CryptoKey,\n iv: Uint8Array,\n): Promise<Uint8Array> {\n const ciphertext = await crypto.subtle.encrypt(\n { name: 'AES-GCM', iv: iv.buffer as ArrayBuffer },\n key,\n data.buffer as ArrayBuffer,\n );\n return new Uint8Array(ciphertext);\n}\n\n/**\n * Decrypt data using AES-256-GCM\n */\nexport async function decrypt(\n ciphertext: Uint8Array,\n key: CryptoKey,\n iv: Uint8Array,\n): Promise<Uint8Array> {\n const plaintext = await crypto.subtle.decrypt(\n { name: 'AES-GCM', iv: iv.buffer as ArrayBuffer },\n key,\n ciphertext.buffer as ArrayBuffer,\n );\n return new Uint8Array(plaintext);\n}\n\n/**\n * Base64url encode (URL-safe, no padding)\n */\nexport function base64UrlEncode(data: Uint8Array): string {\n const base64 = btoa(String.fromCharCode(...data));\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\n/**\n * Base64url decode\n */\nexport function base64UrlDecode(str: string): Uint8Array {\n // Restore standard base64\n let base64 = str.replace(/-/g, '+').replace(/_/g, '/');\n // Add padding if needed\n while (base64.length % 4) {\n base64 += '=';\n }\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\n/**\n * Standard base64 encode\n */\nexport function base64Encode(data: Uint8Array): string {\n return btoa(String.fromCharCode(...data));\n}\n\n/**\n * Standard base64 decode\n */\nexport function base64Decode(str: string): Uint8Array {\n const binary = atob(str);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n","/**\n * Signing and verification utilities for mux.md\n *\n * Supports Ed25519 and ECDSA (P-256, P-384, P-521) keys in SSH format.\n */\n\nimport { p256, p384, p521 } from '@noble/curves/nist.js';\nimport * as ed from '@noble/ed25519';\nimport { sha512 } from '@noble/hashes/sha2.js';\nimport type { SignatureEnvelope } from './types';\n\n// Configure @noble/ed25519 to use sha512 from @noble/hashes\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ed.etc as any).sha512Sync = (...m: Uint8Array[]) =>\n sha512(ed.etc.concatBytes(...m));\n\n// ----- Types -----\n\n/** Supported key types */\nexport type KeyType = 'ed25519' | 'ecdsa-p256' | 'ecdsa-p384' | 'ecdsa-p521';\n\n/** Parsed public key with type and raw bytes */\nexport interface ParsedPublicKey {\n type: KeyType;\n keyBytes: Uint8Array;\n}\n\n// SSH key type identifiers\nconst SSH_KEY_TYPES: Record<string, KeyType> = {\n 'ssh-ed25519': 'ed25519',\n 'ecdsa-sha2-nistp256': 'ecdsa-p256',\n 'ecdsa-sha2-nistp384': 'ecdsa-p384',\n 'ecdsa-sha2-nistp521': 'ecdsa-p521',\n};\n\n// ----- SSH Key Parsing -----\n\n/**\n * Read a length-prefixed string from SSH wire format\n */\nfunction readSSHString(\n data: Uint8Array,\n offset: number,\n): { value: Uint8Array; nextOffset: number } {\n const view = new DataView(data.buffer, data.byteOffset);\n const len = view.getUint32(offset);\n const value = data.slice(offset + 4, offset + 4 + len);\n return { value, nextOffset: offset + 4 + len };\n}\n\n/**\n * Decode standard base64 string to Uint8Array\n */\nfunction base64Decode(str: string): Uint8Array {\n // Handle base64url as well\n let base64 = str.replace(/-/g, '+').replace(/_/g, '/');\n while (base64.length % 4) {\n base64 += '=';\n }\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\n/**\n * Parse an SSH public key and extract the key bytes and type.\n * Supports formats:\n * - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... [comment]\n * - ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY... [comment]\n * - ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQ... [comment]\n * - ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjE... [comment]\n * - Raw base64 (32 bytes when decoded = Ed25519)\n */\nexport function parsePublicKey(keyString: string): ParsedPublicKey {\n const trimmed = keyString.trim();\n\n // Check for SSH format by looking for known key type prefixes\n for (const [sshType, keyType] of Object.entries(SSH_KEY_TYPES)) {\n if (trimmed.startsWith(`${sshType} `)) {\n const parts = trimmed.split(' ');\n if (parts.length < 2) {\n throw new Error('Invalid SSH key format');\n }\n const keyData = base64Decode(parts[1]);\n\n // SSH key format: [4 byte len][type string][4 byte len][key data...]\n // For ECDSA: [4 byte len][type][4 byte len][curve name][4 byte len][point]\n const { value: typeBytes, nextOffset: afterType } = readSSHString(\n keyData,\n 0,\n );\n const typeStr = new TextDecoder().decode(typeBytes);\n\n if (typeStr !== sshType) {\n throw new Error(\n `Key type mismatch: expected ${sshType}, got ${typeStr}`,\n );\n }\n\n if (keyType === 'ed25519') {\n // Ed25519: [type][32 byte key]\n const { value: rawKey } = readSSHString(keyData, afterType);\n if (rawKey.length !== 32) {\n throw new Error('Invalid Ed25519 key length');\n }\n return { type: 'ed25519', keyBytes: rawKey };\n }\n // ECDSA: [type][curve name][point]\n const { nextOffset: afterCurve } = readSSHString(keyData, afterType);\n const { value: point } = readSSHString(keyData, afterCurve);\n return { type: keyType, keyBytes: point };\n }\n }\n\n // Try raw base64\n const decoded = base64Decode(trimmed);\n if (decoded.length === 32) {\n return { type: 'ed25519', keyBytes: decoded };\n }\n\n throw new Error('Unsupported public key format');\n}\n\n// ----- Signing -----\n\n/**\n * Sign a message with Ed25519 private key.\n * @param message - The message bytes to sign\n * @param privateKey - 32-byte Ed25519 private key\n * @returns Base64-encoded signature (64 bytes)\n */\nexport async function signEd25519(\n message: Uint8Array,\n privateKey: Uint8Array,\n): Promise<string> {\n const sig = await ed.signAsync(message, privateKey);\n return btoa(String.fromCharCode(...sig));\n}\n\n/**\n * Sign a message with ECDSA private key (P-256/384/521).\n * @param message - The message bytes to sign (will be hashed)\n * @param privateKey - ECDSA private key bytes\n * @param curve - Which curve to use\n * @returns Base64-encoded signature\n */\nexport function signECDSA(\n message: Uint8Array,\n privateKey: Uint8Array,\n curve: 'p256' | 'p384' | 'p521',\n): string {\n const curves = { p256, p384, p521 };\n // In @noble/curves v2, sign() returns a Uint8Array directly (compact r||s format)\n const sigBytes = curves[curve].sign(message, privateKey, { prehash: true });\n return btoa(String.fromCharCode(...sigBytes));\n}\n\n/**\n * Helper: Create a SignatureEnvelope from content + private key.\n * This is the high-level API for signing before upload.\n *\n * @param content - The content bytes to sign\n * @param privateKey - Private key bytes (32 bytes for Ed25519, variable for ECDSA)\n * @param publicKey - SSH format public key string (e.g., \"ssh-ed25519 AAAA...\")\n * @param options - Optional GitHub username for attribution\n * @returns SignatureEnvelope ready for upload\n */\nexport async function createSignatureEnvelope(\n content: Uint8Array,\n privateKey: Uint8Array,\n publicKey: string,\n options?: { githubUser?: string },\n): Promise<SignatureEnvelope> {\n const parsed = parsePublicKey(publicKey);\n let sig: string;\n\n if (parsed.type === 'ed25519') {\n sig = await signEd25519(content, privateKey);\n } else {\n const curve = parsed.type.replace('ecdsa-', '') as 'p256' | 'p384' | 'p521';\n sig = signECDSA(content, privateKey, curve);\n }\n\n return {\n sig,\n publicKey,\n githubUser: options?.githubUser,\n };\n}\n\n// ----- Verification -----\n\n/**\n * Verify a signature using the appropriate algorithm based on key type.\n * For Ed25519: signature is raw 64 bytes\n * For ECDSA: signature is DER-encoded or raw r||s format\n *\n * @param parsedKey - Parsed public key (from parsePublicKey)\n * @param message - Original message bytes\n * @param signature - Signature bytes (not base64)\n * @returns true if signature is valid\n */\nexport async function verifySignature(\n parsedKey: ParsedPublicKey,\n message: Uint8Array,\n signature: Uint8Array,\n): Promise<boolean> {\n try {\n switch (parsedKey.type) {\n case 'ed25519':\n return await ed.verifyAsync(signature, message, parsedKey.keyBytes);\n\n case 'ecdsa-p256':\n return p256.verify(signature, message, parsedKey.keyBytes, {\n prehash: true,\n });\n\n case 'ecdsa-p384':\n return p384.verify(signature, message, parsedKey.keyBytes, {\n prehash: true,\n });\n\n case 'ecdsa-p521':\n return p521.verify(signature, message, parsedKey.keyBytes, {\n prehash: true,\n });\n\n default:\n return false;\n }\n } catch {\n return false;\n }\n}\n\n// ----- Fingerprint -----\n\n/**\n * Compute SHA256 fingerprint of a public key (matches ssh-keygen -l format)\n * @param publicKey - Raw public key bytes\n * @returns Fingerprint string like \"SHA256:abc123...\"\n */\nexport async function computeFingerprint(\n publicKey: Uint8Array,\n): Promise<string> {\n const hash = await crypto.subtle.digest(\n 'SHA-256',\n publicKey.buffer as ArrayBuffer,\n );\n const hashArray = new Uint8Array(hash);\n const base64 = btoa(String.fromCharCode(...hashArray));\n return `SHA256:${base64.replace(/=+$/, '')}`;\n}\n\n/**\n * Format a fingerprint for nice display.\n * Converts base64 fingerprint to uppercase hex groups like \"DEAD BEEF 1234 5678\"\n */\nexport function formatFingerprint(fingerprint: string): string {\n // Remove \"SHA256:\" prefix if present\n const base64Part = fingerprint.startsWith('SHA256:')\n ? fingerprint.slice(7)\n : fingerprint;\n\n // Decode base64 to bytes, then to hex\n try {\n const binary = atob(base64Part);\n const hex = Array.from(binary)\n .map((c) => c.charCodeAt(0).toString(16).padStart(2, '0'))\n .join('')\n .toUpperCase();\n\n // Take first 16 chars (8 bytes) and format as \"DEAD BEEF 1234 5678\"\n const short = hex.slice(0, 16);\n return short.match(/.{4}/g)?.join(' ') || short;\n } catch {\n // Fallback: just show first part of the fingerprint\n return fingerprint.slice(0, 16).toUpperCase();\n }\n}\n","/**\n * mux.md Client Library\n *\n * Reference implementation for encrypting, uploading, downloading, and decrypting files.\n * Works in both Node.js (with webcrypto) and browser environments.\n */\n\nimport {\n base64Decode,\n base64Encode,\n decrypt,\n deriveKey,\n encrypt,\n generateIV,\n generateKey,\n generateSalt,\n} from './crypto';\nimport { createSignatureEnvelope } from './signing';\nimport type {\n FileInfo,\n SignatureEnvelope,\n SignedPayload,\n UploadMeta,\n} from './types';\n\n// Internal types (not exported from package)\ninterface UploadResponse {\n id: string;\n url: string;\n mutateKey: string;\n expiresAt?: number;\n}\n\ninterface MetaResponse {\n salt: string;\n iv: string;\n encryptedMeta: string;\n size: number;\n}\n\nconst DEFAULT_BASE_URL = 'https://mux.md';\n\n/** Options for signing content during upload */\nexport interface SignOptions {\n /** Private key bytes (32 bytes for Ed25519, variable for ECDSA) */\n privateKey: Uint8Array;\n /** SSH format public key string (e.g., \"ssh-ed25519 AAAA...\") */\n publicKey: string;\n /** Optional GitHub username for attribution */\n githubUser?: string;\n}\n\nexport interface UploadOptions {\n /** Base URL of the mux.md service */\n baseUrl?: string;\n /** Expiration time (unix timestamp ms, ISO date string, or Date object) */\n expiresAt?: number | string | Date;\n /**\n * Sign the content with the provided credentials.\n * The library handles creating the signature envelope internally.\n */\n sign?: SignOptions;\n}\n\nexport interface UploadResult {\n /** Full URL with encryption key in fragment */\n url: string;\n /** File ID (without key) */\n id: string;\n /** Encryption key (base64url) */\n key: string;\n /** Mutate key (base64url) - store this to mutate (delete, change expiration) the file later */\n mutateKey: string;\n /** Expiration timestamp (ms), if set */\n expiresAt?: number;\n}\n\nexport interface DownloadResult {\n /** Decrypted file content */\n data: Uint8Array;\n /** Original file info (name, type, size) */\n info: FileInfo;\n /** Decrypted signature envelope (if present) */\n signature?: SignatureEnvelope;\n}\n\n/**\n * Encrypt and upload a file to mux.md\n *\n * @param data - File contents as Uint8Array\n * @param fileInfo - Original file metadata (name, type, size)\n * @param options - Upload options (including optional signature)\n * @returns Upload result with URL containing encryption key\n */\nexport async function upload(\n data: Uint8Array,\n fileInfo: FileInfo,\n options: UploadOptions = {},\n): Promise<UploadResult> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Generate encryption parameters\n const keyMaterial = generateKey();\n const salt = generateSalt();\n const iv = generateIV();\n const cryptoKey = await deriveKey(keyMaterial, salt);\n\n // Create signature envelope if signing credentials provided\n let signatureEnvelope: SignatureEnvelope | undefined;\n if (options.sign) {\n signatureEnvelope = await createSignatureEnvelope(\n data,\n options.sign.privateKey,\n options.sign.publicKey,\n { githubUser: options.sign.githubUser },\n );\n }\n\n // Build plaintext - either signed (JSON) or raw content\n let plaintext: Uint8Array;\n if (signatureEnvelope) {\n // Signed format: JSON with content + signature\n const signed: SignedPayload = {\n content: new TextDecoder().decode(data),\n sig: signatureEnvelope,\n };\n plaintext = new TextEncoder().encode(JSON.stringify(signed));\n } else {\n // Unsigned: raw content bytes\n plaintext = data;\n }\n\n // Single encryption for the payload\n const payload = await encrypt(plaintext, cryptoKey, iv);\n\n // Encrypt file metadata (always the same way)\n const metaJson = JSON.stringify(fileInfo);\n const metaBytes = new TextEncoder().encode(metaJson);\n const metaIv = generateIV();\n const encryptedMeta = await encrypt(metaBytes, cryptoKey, metaIv);\n\n // Prepare upload metadata\n const uploadMeta: UploadMeta = {\n salt: base64Encode(salt),\n iv: base64Encode(iv),\n encryptedMeta: base64Encode(new Uint8Array([...metaIv, ...encryptedMeta])),\n };\n\n // Build headers\n const headers: Record<string, string> = {\n 'Content-Type': 'application/octet-stream',\n 'X-Mux-Meta': btoa(JSON.stringify(uploadMeta)),\n };\n\n // Add expiration header if specified (convert to ISO 8601)\n if (options.expiresAt !== undefined) {\n let expiresDate: Date;\n if (options.expiresAt instanceof Date) {\n expiresDate = options.expiresAt;\n } else if (typeof options.expiresAt === 'string') {\n expiresDate = new Date(options.expiresAt);\n } else {\n expiresDate = new Date(options.expiresAt);\n }\n headers['X-Mux-Expires'] = expiresDate.toISOString();\n }\n\n // Upload to server\n const response = await fetch(`${baseUrl}/`, {\n method: 'POST',\n headers,\n body: payload.buffer as ArrayBuffer,\n });\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Upload failed' }));\n throw new Error((error as { error: string }).error || 'Upload failed');\n }\n\n const result: UploadResponse = await response.json();\n\n return {\n url: `${baseUrl}/${result.id}#${keyMaterial}`,\n id: result.id,\n key: keyMaterial,\n mutateKey: result.mutateKey,\n ...(result.expiresAt && { expiresAt: result.expiresAt }),\n };\n}\n\n/**\n * Download and decrypt a file from mux.md\n *\n * @param url - Full URL with encryption key in fragment, or just the ID\n * @param key - Encryption key (required if url doesn't contain fragment)\n * @param options - Download options\n * @returns Decrypted file data and metadata\n */\nexport async function download(\n url: string,\n key?: string,\n options: { baseUrl?: string } = {},\n): Promise<DownloadResult> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Parse URL to extract ID and key\n let id: string;\n let keyMaterial: string;\n\n if (url.includes('#')) {\n // Full URL with fragment\n const urlObj = new URL(url);\n id = urlObj.pathname.slice(1); // Remove leading /\n keyMaterial = urlObj.hash.slice(1); // Remove leading #\n } else if (url.includes('/')) {\n // URL path without fragment\n const parts = url.split('/');\n id = parts[parts.length - 1];\n if (!key) throw new Error('Key required when URL has no fragment');\n keyMaterial = key;\n } else {\n // Just the ID\n id = url;\n if (!key) throw new Error('Key required when only ID is provided');\n keyMaterial = key;\n }\n\n // Download encrypted file\n const response = await fetch(`${baseUrl}/${id}`);\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Download failed' }));\n throw new Error((error as { error: string }).error || 'Download failed');\n }\n\n // Parse metadata header\n const metaHeader = response.headers.get('X-Mux-Meta');\n if (!metaHeader) {\n throw new Error('Missing metadata header');\n }\n\n const uploadMeta: UploadMeta = JSON.parse(atob(metaHeader));\n\n // Get encrypted data\n const encryptedData = new Uint8Array(await response.arrayBuffer());\n\n // Derive decryption key\n const salt = base64Decode(uploadMeta.salt);\n const iv = base64Decode(uploadMeta.iv);\n const cryptoKey = await deriveKey(keyMaterial, salt);\n\n // Decrypt metadata (same for both formats)\n const encryptedMetaWithIv = base64Decode(uploadMeta.encryptedMeta);\n const metaIv = encryptedMetaWithIv.slice(0, 12);\n const encryptedMetaData = encryptedMetaWithIv.slice(12);\n const metaBytes = await decrypt(encryptedMetaData, cryptoKey, metaIv);\n const info: FileInfo = JSON.parse(new TextDecoder().decode(metaBytes));\n\n // Decrypt the payload\n const decrypted = await decrypt(encryptedData, cryptoKey, iv);\n\n // Check if decrypted content is signed (JSON with content + sig fields)\n if (decrypted[0] === 0x7b) {\n // '{' character - might be signed JSON\n try {\n const jsonStr = new TextDecoder().decode(decrypted);\n const parsed = JSON.parse(jsonStr);\n\n if (typeof parsed.content === 'string' && parsed.sig) {\n // Signed format\n const data = new TextEncoder().encode(parsed.content);\n const signature: SignatureEnvelope = parsed.sig;\n return { data, info, signature };\n }\n } catch {\n // Not valid JSON - treat as raw content\n }\n }\n\n // Unsigned content (raw bytes)\n return { data: decrypted, info };\n}\n\n/**\n * Get file metadata without downloading the full file\n *\n * @param url - Full URL or ID\n * @param key - Encryption key (required to decrypt metadata)\n * @param options - Request options\n * @returns Decrypted file info and server metadata\n */\nexport async function getMeta(\n url: string,\n key?: string,\n options: { baseUrl?: string } = {},\n): Promise<{ info: FileInfo; size: number }> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Parse URL to extract ID and key\n let id: string;\n let keyMaterial: string;\n\n if (url.includes('#')) {\n const urlObj = new URL(url);\n id = urlObj.pathname.slice(1);\n keyMaterial = urlObj.hash.slice(1);\n } else if (url.includes('/')) {\n const parts = url.split('/');\n id = parts[parts.length - 1];\n if (!key) throw new Error('Key required when URL has no fragment');\n keyMaterial = key;\n } else {\n id = url;\n if (!key) throw new Error('Key required when only ID is provided');\n keyMaterial = key;\n }\n\n // Fetch metadata\n const response = await fetch(`${baseUrl}/${id}/meta`);\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Request failed' }));\n throw new Error((error as { error: string }).error || 'Request failed');\n }\n\n const meta: MetaResponse = await response.json();\n\n // Derive key and decrypt metadata\n const salt = base64Decode(meta.salt);\n const cryptoKey = await deriveKey(keyMaterial, salt);\n\n const encryptedMetaWithIv = base64Decode(meta.encryptedMeta);\n const metaIv = encryptedMetaWithIv.slice(0, 12);\n const encryptedMetaData = encryptedMetaWithIv.slice(12);\n const metaBytes = await decrypt(encryptedMetaData, cryptoKey, metaIv);\n const info: FileInfo = JSON.parse(new TextDecoder().decode(metaBytes));\n\n return {\n info,\n size: meta.size,\n };\n}\n\n/**\n * Parse a mux.md URL into its components\n */\nexport function parseUrl(url: string): { id: string; key: string } | null {\n try {\n const urlObj = new URL(url);\n if (!urlObj.hash) return null;\n\n const id = urlObj.pathname.slice(1);\n const key = urlObj.hash.slice(1);\n\n if (!id || !key) return null;\n\n return { id, key };\n } catch {\n return null;\n }\n}\n\n/**\n * Build a mux.md URL from components\n */\nexport function buildUrl(\n id: string,\n key: string,\n baseUrl = DEFAULT_BASE_URL,\n): string {\n return `${baseUrl}/${id}#${key}`;\n}\n\n/**\n * Delete a file from mux.md using its mutate key\n *\n * @param id - File ID\n * @param mutateKey - Mutate key returned from upload\n * @param options - Request options\n */\nexport async function deleteFile(\n id: string,\n mutateKey: string,\n options: { baseUrl?: string } = {},\n): Promise<void> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n const response = await fetch(`${baseUrl}/${id}`, {\n method: 'DELETE',\n headers: {\n 'X-Mux-Mutate-Key': mutateKey,\n },\n });\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Delete failed' }));\n throw new Error((error as { error: string }).error || 'Delete failed');\n }\n}\n\nexport interface SetExpirationOptions {\n /** Base URL of the mux.md service */\n baseUrl?: string;\n}\n\nexport interface SetExpirationResult {\n /** Whether the operation succeeded */\n success: boolean;\n /** File ID */\n id: string;\n /** New expiration timestamp (ms), or undefined if expiration was removed */\n expiresAt?: number;\n}\n\n/**\n * Set or remove the expiration of a file using its mutate key\n *\n * @param id - File ID\n * @param mutateKey - Mutate key returned from upload\n * @param expiresAt - New expiration time (unix timestamp ms, ISO date string, Date object, or \"never\" to remove expiration)\n * @param options - Request options\n * @returns Result with new expiration info\n */\nexport async function setExpiration(\n id: string,\n mutateKey: string,\n expiresAt: number | string | Date | 'never',\n options: SetExpirationOptions = {},\n): Promise<SetExpirationResult> {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n\n // Convert expiration to ISO 8601 string\n let expiresHeader: string;\n if (expiresAt === 'never') {\n expiresHeader = 'never';\n } else if (expiresAt instanceof Date) {\n expiresHeader = expiresAt.toISOString();\n } else if (typeof expiresAt === 'string') {\n expiresHeader = new Date(expiresAt).toISOString();\n } else {\n expiresHeader = new Date(expiresAt).toISOString();\n }\n\n const response = await fetch(`${baseUrl}/${id}`, {\n method: 'PATCH',\n headers: {\n 'X-Mux-Mutate-Key': mutateKey,\n 'X-Mux-Expires': expiresHeader,\n },\n });\n\n if (!response.ok) {\n const error = await response\n .json()\n .catch(() => ({ error: 'Set expiration failed' }));\n throw new Error(\n (error as { error: string }).error || 'Set expiration failed',\n );\n }\n\n return response.json();\n}\n"],"mappings":"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","names":["base64Decode"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coder/mux-md-client",
3
- "version": "0.1.0-main.14",
3
+ "version": "0.1.0-main.15",
4
4
  "description": "Client library for mux.md encrypted file sharing with signature support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",