@@ -70,18 +70,12 @@ export default function handler(
70
70
// Verify Ethereum personal_sign signature
71
71
isValid = verifyEthereumSignature ( message , signature , signer ) ;
72
72
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
75
75
if ( ! isValid ) {
76
- console . log ( '\n=== ATTEMPTING RECOVERY-BASED VERIFICATION ===' ) ;
77
76
const recoveredAddress = recoverAddressFromSignature ( message , signature ) ;
78
77
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 ) ;
85
79
isValid = true ;
86
80
}
87
81
}
@@ -126,293 +120,84 @@ function verifyEthereumSignature(message: string, signature: string, expectedAdd
126
120
const keccak256 = require ( 'keccak256' ) ;
127
121
const secp256k1 = require ( 'secp256k1' ) ;
128
122
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
-
172
123
// Remove 0x prefix if present
173
124
const sigHex = signature . startsWith ( '0x' ) ? signature . slice ( 2 ) : signature ;
174
125
175
126
if ( sigHex . length !== 130 ) { // 65 bytes * 2 hex chars per byte
176
- console . log ( '❌ Invalid signature length:' , sigHex . length ) ;
177
127
return false ;
178
128
}
179
129
180
130
// 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
-
350
131
const r = Buffer . from ( sigHex . slice ( 0 , 64 ) , 'hex' ) ;
351
132
const s = Buffer . from ( sigHex . slice ( 64 , 128 ) , 'hex' ) ;
352
133
const v = parseInt ( sigHex . slice ( 128 , 130 ) , 16 ) ;
353
134
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
360
136
const actualMessage = message . replace ( / \\ n / g, '\n' ) ;
361
- console . log ( 'Converted message:' , JSON . stringify ( actualMessage ) ) ;
362
-
363
137
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
368
138
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
+ ] ) ;
370
143
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 ) ;
373
146
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 = [ ] ;
376
149
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
+ }
379
154
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 ) {
382
168
try {
383
- console . log ( `\nTrying recovery ID: ${ recId } ` ) ;
384
-
385
- // @ts -ignore
169
+ // Create signature for secp256k1
386
170
const rBytes = Uint8Array . from ( r ) ;
387
- // @ts -ignore
388
171
const sBytes = Uint8Array . from ( s ) ;
389
172
const sig = new Uint8Array ( 64 ) ;
390
173
sig . set ( rBytes , 0 ) ;
391
174
sig . set ( sBytes , 32 ) ;
392
175
176
+ // Convert message hash to Uint8Array
393
177
const hashBytes = Uint8Array . from ( messageHash ) ;
178
+
179
+ // Recover public key
394
180
const publicKey = secp256k1 . ecdsaRecover ( sig , recId , hashBytes ) ;
181
+
182
+ // Convert public key to address (skip first byte which is 0x04)
395
183
const publicKeyBytes = Buffer . from ( publicKey . slice ( 1 ) ) ;
396
184
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' ) ;
402
186
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 ( ) ) {
405
189
return true ;
406
190
}
407
191
} catch ( e ) {
408
- console . log ( `Recovery ID ${ recId } failed:` , ( e as Error ) . message ) ;
192
+ // Continue with next recovery ID
193
+ continue ;
409
194
}
410
195
}
411
196
412
- console . log ( '=== QUICK VERIFY FAILED ===' ) ;
413
197
return false ;
414
- } catch ( e ) {
415
- console . log ( 'Quick verify error:' , e ) ;
198
+
199
+ } catch ( error ) {
200
+ console . error ( 'Error verifying Ethereum signature:' , error ) ;
416
201
return false ;
417
202
}
418
203
}
0 commit comments