/**
* ANProto Client-Side Signing
*
* Lightweight ed25519 signing for browser using noble/ed25519
* Vendored to avoid external dependencies
*/
// Import noble/ed25519 from CDN
// In production, vendor this file locally
const ed25519Module = await (async () => {
try {
// Try to use @noble/ed25519 if available
if (typeof window.nobleEd25519 !== 'undefined') {
return window.nobleEd25519;
}
// Otherwise, load from CDN
const module = await import('https://cdn.jsdelivr.net/npm/@noble/ed25519@2.0.0/+esm');
return module;
} catch (err) {
console.error('[ANProto] Failed to load ed25519:', err);
throw new Error('ed25519 library not available');
}
})();
export const ANProtoClient = {
/**
* Sign a challenge using ed25519
* @param {string} challenge - Challenge string
* @param {string} secretKeyB64 - Base64 secret key (88 chars)
* @returns {Promise<string>} Base64 signature (88 chars)
*/
async sign(challenge, secretKeyB64) {
try {
// Convert inputs
const challengeBytes = new TextEncoder().encode(challenge);
const secretKeyBytes = base64ToBytes(secretKeyB64);
// Validate secret key size (should be 64 bytes)
if (secretKeyBytes.length !== 64) {
throw new Error(\`Invalid secret key length: \${secretKeyBytes.length} (expected 64)\`);
}
// Extract signing key (first 32 bytes of 64-byte secret key for ed25519)
const signingKey = secretKeyBytes.slice(0, 32);
// Sign using noble/ed25519
const signature = await ed25519Module.sign(challengeBytes, signingKey);
// Validate signature size
if (signature.length !== 64) {
throw new Error(\`Invalid signature length: \${signature.length}\`);
}
// Convert to base64 (should be 88 chars)
return bytesToBase64(signature);
} catch (err) {
console.error('[ANProto] Signing error:', err);
throw err;
}
},
/**
* Get public key from keypair
* @param {string} keypair - 132 char base64 keypair
* @returns {string} 44 char base64 public key
*/
getPublicKey(keypair) {
return keypair.substring(0, 44);
}
};
/**
* Convert base64 to Uint8Array
*/
function base64ToBytes(base64) {
const binary = atob(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes;
}
/**
* Convert Uint8Array to base64
*/
function bytesToBase64(bytes) {
const binary = String.fromCharCode(...bytes);
return btoa(binary);
}