Skip to content

Commit 18e7e3c

Browse files
committed
git commit -m "refactor: cleanup signature verification debug code
Remove debug logs and simplify verification logic while maintaining core functionality and MetaMask compatibility fallback"
1 parent 03a6bd1 commit 18e7e3c

File tree

2 files changed

+46
-306
lines changed

2 files changed

+46
-306
lines changed

templates/chain-template/pages/api/verify-signature.ts

Lines changed: 43 additions & 258 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)