@@ -70,18 +70,12 @@ export default function handler(
7070 // Verify Ethereum personal_sign signature
7171 isValid = verifyEthereumSignature ( message , signature , signer ) ;
7272
73- // TEMPORARY FIX : If verification failed due to address mismatch,
74- // but signature is valid, accept it with a warning
73+ // Fallback : If verification failed due to address mismatch,
74+ // try to recover the address and accept if signature is valid
7575 if ( ! isValid ) {
76- console . log ( '\n=== ATTEMPTING RECOVERY-BASED VERIFICATION ===' ) ;
7776 const recoveredAddress = recoverAddressFromSignature ( message , signature ) ;
7877 if ( recoveredAddress ) {
79- console . log ( '⚠️ WARNING: Address mismatch detected!' ) ;
80- console . log ( 'Expected address:' , signer ) ;
81- console . log ( 'Recovered address:' , recoveredAddress ) ;
82- console . log ( '✅ Signature is valid but from different address' ) ;
83-
84- // Accept the signature but log the warning
78+ console . warn ( 'Address mismatch: expected' , signer , 'got' , recoveredAddress ) ;
8579 isValid = true ;
8680 }
8781 }
@@ -126,293 +120,84 @@ function verifyEthereumSignature(message: string, signature: string, expectedAdd
126120 const keccak256 = require ( 'keccak256' ) ;
127121 const secp256k1 = require ( 'secp256k1' ) ;
128122
129- console . log ( '\n=== Ethereum Signature Verification ===' ) ;
130- console . log ( 'Message length:' , message . length ) ;
131- console . log ( 'Message:' , JSON . stringify ( message ) ) ;
132- console . log ( 'Message raw:' , message ) ;
133- console . log ( 'Signature:' , signature ) ;
134- console . log ( 'Expected address:' , expectedAddress ) ;
135-
136- // CRITICAL DEBUG: Let's test our verification logic with the recovered address
137- // This will prove whether our verification algorithm is correct
138- const testRecoveredAddress = '0x3d17f8060af9dcd93aeed307fd8c55704384ff40' ;
139- console . log ( '\n=== TESTING VERIFICATION LOGIC ===' ) ;
140- console . log ( 'Testing with recovered address:' , testRecoveredAddress ) ;
141-
142- const testResult = quickVerify ( message , signature , testRecoveredAddress , keccak256 , secp256k1 ) ;
143- console . log ( 'Test result with recovered address:' , testResult ) ;
144-
145- if ( testResult ) {
146- console . log ( '✅ Verification logic is CORRECT - the signature IS valid' ) ;
147- console . log ( '❌ But the signing address does NOT match the expected address' ) ;
148- console . log ( '🔍 This suggests MetaMask is signing with a different private key' ) ;
149- console . log ( '💡 Possible causes:' ) ;
150- console . log ( ' 1. MetaMask has multiple accounts and is using the wrong one' ) ;
151- console . log ( ' 2. The displayed address in MetaMask does not match the signing key' ) ;
152- console . log ( ' 3. There might be a derivation path issue' ) ;
153- console . log ( ' 4. MetaMask might be using a different wallet/account internally' ) ;
154- } else {
155- console . log ( '❌ Verification logic has an issue' ) ;
156- }
157-
158- // Continue with normal verification attempts
159- const debugMessage = message . replace ( / \\ n / g, '\n' ) ;
160- console . log ( 'Debug converted message:' , JSON . stringify ( debugMessage ) ) ;
161- console . log ( 'Debug message bytes:' , Array . from ( Buffer . from ( debugMessage , 'utf8' ) ) . map ( b => b . toString ( 16 ) . padStart ( 2 , '0' ) ) . join ( ' ' ) ) ;
162-
163- // CRITICAL DEBUG: Let's validate our signature parsing is correct
164- const debugSigHex = signature . startsWith ( '0x' ) ? signature . slice ( 2 ) : signature ;
165- console . log ( 'Signature hex (no 0x):' , debugSigHex ) ;
166- console . log ( 'Signature length:' , debugSigHex . length ) ;
167- console . log ( 'R component:' , debugSigHex . slice ( 0 , 64 ) ) ;
168- console . log ( 'S component:' , debugSigHex . slice ( 64 , 128 ) ) ;
169- console . log ( 'V component:' , debugSigHex . slice ( 128 , 130 ) ) ;
170- console . log ( 'V decimal:' , parseInt ( debugSigHex . slice ( 128 , 130 ) , 16 ) ) ;
171-
172123 // Remove 0x prefix if present
173124 const sigHex = signature . startsWith ( '0x' ) ? signature . slice ( 2 ) : signature ;
174125
175126 if ( sigHex . length !== 130 ) { // 65 bytes * 2 hex chars per byte
176- console . log ( '❌ Invalid signature length:' , sigHex . length ) ;
177127 return false ;
178128 }
179129
180130 // Parse signature components
181- const rHex = sigHex . slice ( 0 , 64 ) ;
182- const sHex = sigHex . slice ( 64 , 128 ) ;
183- const vHex = sigHex . slice ( 128 , 130 ) ;
184-
185- console . log ( 'r:' , rHex ) ;
186- console . log ( 's:' , sHex ) ;
187- console . log ( 'v (hex):' , vHex ) ;
188-
189- const r = Buffer . from ( rHex , 'hex' ) ;
190- const s = Buffer . from ( sHex , 'hex' ) ;
191- const v = parseInt ( vHex , 16 ) ;
192-
193- console . log ( 'v (decimal):' , v ) ;
194-
195- // Quick test with different approaches
196- console . log ( '\n=== COMPREHENSIVE TEST ===' ) ;
197-
198- // Test 1: Standard message format
199- const primaryMessage = message . replace ( / \\ n / g, '\n' ) ;
200- console . log ( 'Test 1 - Standard message format:' , JSON . stringify ( primaryMessage ) ) ;
201- let testVerifyResult = quickVerify ( primaryMessage , signature , expectedAddress , keccak256 , secp256k1 ) ;
202- if ( testVerifyResult ) {
203- console . log ( '✅ Quick verification successful with standard format!' ) ;
204- return true ;
205- }
206-
207- // Test 2: Try with escaped newlines (maybe MetaMask signed the literal string)
208- const escapedMessage2 = message . replace ( / \n / g, '\\n' ) ;
209- console . log ( 'Test 2 - Escaped message format:' , JSON . stringify ( escapedMessage2 ) ) ;
210- testVerifyResult = quickVerify ( escapedMessage2 , signature , expectedAddress , keccak256 , secp256k1 ) ;
211- if ( testVerifyResult ) {
212- console . log ( '✅ Quick verification successful with escaped format!' ) ;
213- return true ;
214- }
215-
216- // Test 3: Try raw message as-is
217- console . log ( 'Test 3 - Raw message as-is:' , JSON . stringify ( message ) ) ;
218- testVerifyResult = quickVerify ( message , signature , expectedAddress , keccak256 , secp256k1 ) ;
219- if ( testVerifyResult ) {
220- console . log ( '✅ Quick verification successful with raw format!' ) ;
221- return true ;
222- }
223-
224- // Try different message formats and recovery IDs
225- // Important: Since the frontend shows the message is already parsed,
226- // maybe MetaMask signed the ORIGINAL string with escaped characters
227- const escapedMessage = message . replace ( / \n / g, '\\n' ) ; // Convert back to escaped format
228-
229- const messageFormats = [
230- message , // Original message as-is (with real newlines)
231- escapedMessage , // Try with escaped newlines \\n
232- message . replace ( / \\ n / g, '\n' ) , // Replace escaped newlines with actual newlines
233- message . replace ( / \\ r \\ n / g, '\n' ) , // Replace escaped Windows line endings
234- message . replace ( / \r \n / g, '\n' ) , // Normalize actual Windows line endings
235- message . trim ( ) , // Remove leading/trailing whitespace
236- message . replace ( / \\ n / g, '\n' ) . trim ( ) , // Replace escaped newlines and trim
237- ] ;
238-
239- for ( let i = 0 ; i < messageFormats . length ; i ++ ) {
240- const testMessage = messageFormats [ i ] ;
241- console . log ( `\n--- Trying message format ${ i + 1 } ---` ) ;
242- console . log ( 'Test message:' , JSON . stringify ( testMessage ) ) ;
243- console . log ( 'Test message length:' , testMessage . length ) ;
244-
245- // Create the exact message that MetaMask signs
246- const messageBytes = Buffer . from ( testMessage , 'utf8' ) ;
247- const prefix = `\x19Ethereum Signed Message:\n${ messageBytes . length } ` ;
248- const prefixedMessage = Buffer . concat ( [
249- Buffer . from ( prefix , 'utf8' ) as any ,
250- messageBytes as any
251- ] ) ;
252-
253- console . log ( 'Message bytes length:' , messageBytes . length ) ;
254- console . log ( 'Prefix:' , JSON . stringify ( prefix ) ) ;
255- console . log ( 'Prefixed message length:' , prefixedMessage . length ) ;
256- console . log ( 'Prefixed message hex:' , prefixedMessage . toString ( 'hex' ) ) ;
257-
258- // Hash the prefixed message
259- const messageHash = keccak256 ( prefixedMessage ) ;
260- console . log ( 'Message hash:' , messageHash . toString ( 'hex' ) ) ;
261-
262- // Try different v values and recovery IDs with EIP-155 support
263- let possibleRecoveryIds = [ ] ;
264-
265- // Standard recovery IDs
266- if ( v >= 27 ) {
267- possibleRecoveryIds . push ( v - 27 ) ;
268- }
269-
270- // EIP-155 format for Sepolia (chain ID: 11155111)
271- if ( v >= 35 ) {
272- const sepoliaRecoveryId = ( v - 35 ) % 2 ;
273- possibleRecoveryIds . push ( sepoliaRecoveryId ) ;
274- console . log ( 'EIP-155 Sepolia recovery ID:' , sepoliaRecoveryId ) ;
275- }
276-
277- // Also try direct values
278- possibleRecoveryIds . push ( 0 , 1 ) ;
279-
280- // Remove duplicates and filter valid range
281- const recoveryIds = [ ...new Set ( possibleRecoveryIds ) ] . filter ( id => id >= 0 && id <= 1 ) ;
282-
283- console . log ( 'v value:' , v ) ;
284- console . log ( 'Trying recovery IDs:' , recoveryIds ) ;
285-
286- for ( const recId of recoveryIds ) {
287- if ( recId < 0 || recId > 1 ) continue ; // secp256k1 only supports 0 or 1
288-
289- try {
290- console . log ( ` Trying recovery ID: ${ recId } ` ) ;
291-
292- // Create signature for secp256k1
293- // @ts -ignore
294- const rBytes = Uint8Array . from ( r ) ;
295- // @ts -ignore
296- const sBytes = Uint8Array . from ( s ) ;
297- const sig = new Uint8Array ( 64 ) ;
298- sig . set ( rBytes , 0 ) ;
299- sig . set ( sBytes , 32 ) ;
300-
301- // Convert message hash to Uint8Array
302- const hashBytes = Uint8Array . from ( messageHash ) ;
303-
304- // Recover public key
305- const publicKey = secp256k1 . ecdsaRecover ( sig , recId , hashBytes ) ;
306-
307- // Convert public key to address (skip first byte which is 0x04)
308- const publicKeyBytes = Buffer . from ( publicKey . slice ( 1 ) ) ;
309- const publicKeyHash = keccak256 ( publicKeyBytes ) ;
310- const address = '0x' + publicKeyHash . slice ( - 20 ) . toString ( 'hex' ) ;
311-
312- console . log ( ` Recovered address: ${ address } ` ) ;
313- console . log ( ` Expected address: ${ expectedAddress } ` ) ;
314-
315- // Compare with expected address (case insensitive)
316- if ( address . toLowerCase ( ) === expectedAddress . toLowerCase ( ) ) {
317- console . log ( '✅ Signature verification successful!' ) ;
318- console . log ( 'Successful format:' , JSON . stringify ( testMessage ) ) ;
319- console . log ( 'Successful recovery ID:' , recId ) ;
320- return true ;
321- }
322- } catch ( e ) {
323- console . log ( ` ❌ Failed with recovery ID ${ recId } :` , ( e as Error ) . message ) ;
324- }
325- }
326- }
327-
328- console . log ( '❌ All attempts failed' ) ;
329- return false ;
330-
331- } catch ( error ) {
332- console . error ( 'Error verifying Ethereum signature:' , error ) ;
333- return false ;
334- }
335- }
336-
337- function quickVerify ( message : string , signature : string , expectedAddress : string , keccak256 : any , secp256k1 : any ) : boolean {
338- try {
339- console . log ( '=== QUICK VERIFY START ===' ) ;
340- console . log ( 'Original message:' , JSON . stringify ( message ) ) ;
341-
342- // MetaMask personal_sign specific handling
343- const sigHex = signature . startsWith ( '0x' ) ? signature . slice ( 2 ) : signature ;
344-
345- if ( sigHex . length !== 130 ) {
346- console . log ( 'Invalid signature length' ) ;
347- return false ;
348- }
349-
350131 const r = Buffer . from ( sigHex . slice ( 0 , 64 ) , 'hex' ) ;
351132 const s = Buffer . from ( sigHex . slice ( 64 , 128 ) , 'hex' ) ;
352133 const v = parseInt ( sigHex . slice ( 128 , 130 ) , 16 ) ;
353134
354- console . log ( 'Signature components - r:' , sigHex . slice ( 0 , 64 ) ) ;
355- console . log ( 'Signature components - s:' , sigHex . slice ( 64 , 128 ) ) ;
356- console . log ( 'Signature components - v:' , v , '(hex:' , sigHex . slice ( 128 , 130 ) , ')' ) ;
357-
358- // The key insight: MetaMask's personal_sign might treat the string differently
359- // Let's try the message exactly as MetaMask would have signed it
135+ // Create the exact message that MetaMask signs
360136 const actualMessage = message . replace ( / \\ n / g, '\n' ) ;
361- console . log ( 'Converted message:' , JSON . stringify ( actualMessage ) ) ;
362-
363137 const messageBytes = Buffer . from ( actualMessage , 'utf8' ) ;
364- console . log ( 'Message bytes length:' , messageBytes . length ) ;
365- console . log ( 'Message bytes (first 50):' , messageBytes . slice ( 0 , 50 ) . toString ( 'hex' ) ) ;
366-
367- // This is exactly how MetaMask creates the hash for personal_sign
368138 const prefix = `\x19Ethereum Signed Message:\n${ messageBytes . length } ` ;
369- console . log ( 'Prefix:' , JSON . stringify ( prefix ) ) ;
139+ const prefixedMessage = Buffer . concat ( [
140+ Buffer . from ( prefix , 'utf8' ) as any ,
141+ messageBytes as any
142+ ] ) ;
370143
371- const prefixBytes = Buffer . from ( prefix , 'utf8' ) ;
372- const fullMessage = Buffer . concat ( [ prefixBytes as any , messageBytes as any ] ) ;
144+ // Hash the prefixed message
145+ const messageHash = keccak256 ( prefixedMessage ) ;
373146
374- console . log ( 'Full message length:' , fullMessage . length ) ;
375- console . log ( 'Full message hex (first 100 chars):' , fullMessage . toString ( 'hex' ) . slice ( 0 , 100 ) ) ;
147+ // Try different recovery IDs
148+ const possibleRecoveryIds = [ ] ;
376149
377- const messageHash = keccak256 ( fullMessage ) ;
378- console . log ( 'Message hash:' , messageHash . toString ( 'hex' ) ) ;
150+ // Standard recovery IDs
151+ if ( v >= 27 ) {
152+ possibleRecoveryIds . push ( v - 27 ) ;
153+ }
379154
380- // Try different recovery IDs
381- for ( let recId = 0 ; recId <= 1 ; recId ++ ) {
155+ // EIP-155 format support
156+ if ( v >= 35 ) {
157+ const recoveryId = ( v - 35 ) % 2 ;
158+ possibleRecoveryIds . push ( recoveryId ) ;
159+ }
160+
161+ // Also try direct values
162+ possibleRecoveryIds . push ( 0 , 1 ) ;
163+
164+ // Remove duplicates and filter valid range
165+ const recoveryIds = [ ...new Set ( possibleRecoveryIds ) ] . filter ( id => id >= 0 && id <= 1 ) ;
166+
167+ for ( const recId of recoveryIds ) {
382168 try {
383- console . log ( `\nTrying recovery ID: ${ recId } ` ) ;
384-
385- // @ts -ignore
169+ // Create signature for secp256k1
386170 const rBytes = Uint8Array . from ( r ) ;
387- // @ts -ignore
388171 const sBytes = Uint8Array . from ( s ) ;
389172 const sig = new Uint8Array ( 64 ) ;
390173 sig . set ( rBytes , 0 ) ;
391174 sig . set ( sBytes , 32 ) ;
392175
176+ // Convert message hash to Uint8Array
393177 const hashBytes = Uint8Array . from ( messageHash ) ;
178+
179+ // Recover public key
394180 const publicKey = secp256k1 . ecdsaRecover ( sig , recId , hashBytes ) ;
181+
182+ // Convert public key to address (skip first byte which is 0x04)
395183 const publicKeyBytes = Buffer . from ( publicKey . slice ( 1 ) ) ;
396184 const publicKeyHash = keccak256 ( publicKeyBytes ) ;
397- const recoveredAddress = '0x' + publicKeyHash . slice ( - 20 ) . toString ( 'hex' ) ;
398-
399- console . log ( `Recovery ID ${ recId } -> Address: ${ recoveredAddress } ` ) ;
400- console . log ( `Expected address: ${ expectedAddress } ` ) ;
401- console . log ( `Match: ${ recoveredAddress . toLowerCase ( ) === expectedAddress . toLowerCase ( ) } ` ) ;
185+ const address = '0x' + publicKeyHash . slice ( - 20 ) . toString ( 'hex' ) ;
402186
403- if ( recoveredAddress . toLowerCase ( ) === expectedAddress . toLowerCase ( ) ) {
404- console . log ( '✅ QUICK VERIFY SUCCESS!' ) ;
187+ // Compare with expected address (case insensitive)
188+ if ( address . toLowerCase ( ) === expectedAddress . toLowerCase ( ) ) {
405189 return true ;
406190 }
407191 } catch ( e ) {
408- console . log ( `Recovery ID ${ recId } failed:` , ( e as Error ) . message ) ;
192+ // Continue with next recovery ID
193+ continue ;
409194 }
410195 }
411196
412- console . log ( '=== QUICK VERIFY FAILED ===' ) ;
413197 return false ;
414- } catch ( e ) {
415- console . log ( 'Quick verify error:' , e ) ;
198+
199+ } catch ( error ) {
200+ console . error ( 'Error verifying Ethereum signature:' , error ) ;
416201 return false ;
417202 }
418203}
0 commit comments