From c5ecd4bc8407a0b755102d07724a2a86f3094cb7 Mon Sep 17 00:00:00 2001 From: jwinterm Date: Sat, 18 Apr 2026 19:07:42 -0400 Subject: [PATCH] security: accept both RFC 8032 and legacy Ed25519 signatures Smirk extension is migrating from SHA256 pre-hash to standard RFC 8032 Ed25519 signatures. Accept both formats during transition: - Try raw message bytes first (new standard) - Fall back to SHA256(message) (legacy, remove after 2026-07-01) --- src/index.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/index.js b/src/index.js index 3946379..a7036cd 100644 --- a/src/index.js +++ b/src/index.js @@ -532,18 +532,24 @@ app.post('/api/auth/smirk/verify', asyncHandler(async (req, res) => { `, [challengeResult.rows[0].id]); // 3. Verify signature using tweetnacl - // The signature should be the challenge signed by the wallet's private key + // Accepts both formats during transition: + // - RFC 8032 standard: signature over raw UTF-8 message bytes (new extension) + // - Legacy: signature over SHA256(message) (old extension, remove after 2026-07-01) let isValidSignature = false; try { const nacl = require('tweetnacl'); - // The Smirk extension signs SHA256(utf8(challenge)), not the raw challenge bytes. - // We must hash the challenge the same way before verifying. - const challengeHash = crypto.createHash('sha256').update(challenge).digest(); const signatureBytes = Buffer.from(signature, 'hex'); const publicKeyBytes = Buffer.from(publicKey, 'hex'); - // tweetnacl.sign.detached.verify(message, signature, publicKey) - isValidSignature = nacl.sign.detached.verify(challengeHash, signatureBytes, publicKeyBytes); + // Try RFC 8032 standard first (raw message bytes) + const challengeBytes = Buffer.from(challenge, 'utf8'); + isValidSignature = nacl.sign.detached.verify(challengeBytes, signatureBytes, publicKeyBytes); + + // Fall back to legacy SHA256 pre-hash (DEPRECATED — remove after 2026-07-01) + if (!isValidSignature) { + const challengeHash = crypto.createHash('sha256').update(challenge).digest(); + isValidSignature = nacl.sign.detached.verify(challengeHash, signatureBytes, publicKeyBytes); + } } catch (verifyError) { console.error('Signature verification error:', verifyError.message); throw new ValidationError('Signature verification failed', {