fix: sign and verify for evm wallets (#3599)
What changed, and why it matters
This commit fixes how Cake Wallet's EVM (Ethereum-compatible) wallets sign and verify messages. It switches message encoding from ASCII to UTF-8, removes a third-party signature utility, and adds stricter checks on the address and signature format. The change appears to be a bug fix for incorrect or unreliable signature verification, which could previously fail or behave unexpectedly for non-ASCII messages or malformed inputs.
Review the manual ECDSA recovery logic for correctness, ensure the v-value normalization handles all valid EVM signature forms, verify that removing eth_sig_util does not lose security checks, and add unit tests covering UTF-8 messages, malformed signatures, and address casing edge cases.
Security signals we found
Encoding change from ASCII to UTF-8 for signed messages
Manual signature verification replacing third-party library call
Added input validation for address and signature length
Case-insensitive address comparison with 0x stripping
Signature recovery v-value normalization (adds 27 if below 27)
Evidence from the diff
The patch modifies cw_evm/lib/evm_chain_wallet.dart. signMessage now UTF-8-encodes the message before signing with signPersonalMessage, replacing ASCII encoding. verifyMessage now rejects null/empty addresses, enforces a 65-byte signature length, manually reconstructs the Ethereum signed-message prefix using UTF-8 message length, recovers the public key with ecRecover/keccak256, derives the Ethereum address, and compares it case-insensitively after stripping the 0x prefix. The eth_sig_util dependency import is removed.
Changed components
cw_evm/lib/evm_chain_wallet.dartEVM chain wallet signMessage methodEVM chain wallet verifyMessage methodInspect captured patch +31 / −10
diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart
index c9e07c4a..d66aa6fd 100644
--- a/cw_evm/lib/evm_chain_wallet.dart
+++ b/cw_evm/lib/evm_chain_wallet.dart
@@ -41,7 +41,6 @@ import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:web3dart/crypto.dart';
import 'package:web3dart/web3dart.dart';
-import 'package:eth_sig_util/eth_sig_util.dart';
import 'contract/erc20.dart';
import 'evm_chain_transaction_info.dart';
@@ -1564,20 +1563,42 @@ abstract class EVMChainWalletBase
}
@override
- Future<String> signMessage(String message, {String? address}) async {
- return bytesToHex(await _evmChainPrivateKey.signPersonalMessage(ascii.encode(message)));
- }
+ Future<String> signMessage(String message, {String? address}) async =>
+ bytesToHex(await _evmChainPrivateKey.signPersonalMessage(utf8.encode(message)));
@override
Future<bool> verifyMessage(String message, String signature, {String? address}) async {
- if (address == null) {
+ if (address == null || address.isEmpty) {
+ return false;
+ }
+
+ try {
+ final signatureBytes = hexToBytes(signature.trim().toLowerCase());
+ if (signatureBytes.length != 65) {
+ return false;
+ }
+
+ final messageBytes = utf8.encode(message);
+ final prefixedMessage = Uint8List.fromList(
+ ascii.encode("\x19Ethereum Signed Message:\n${messageBytes.length}") + messageBytes,
+ );
+ final v = signatureBytes[64] < 27 ? signatureBytes[64] + 27 : signatureBytes[64];
+ final publicKey = ecRecover(
+ keccak256(prefixedMessage),
+ MsgSignature(
+ bytesToUnsignedInt(signatureBytes.sublist(0, 32)),
+ bytesToUnsignedInt(signatureBytes.sublist(32, 64)),
+ v,
+ ),
+ );
+
+ final paddedPublicKey = Uint8List(64)..setRange(64 - publicKey.length, 64, publicKey);
+ final recoveredAddress = EthereumAddress.fromPublicKey(paddedPublicKey);
+ return recoveredAddress.hexNo0x == strip0x(address.trim().toLowerCase());
+ } catch (e) {
+ printV("Failed to verify EVM message signature: $e");
return false;
}
- final recoveredAddress = EthSigUtil.recoverPersonalSignature(
- message: ascii.encode(message),
- signature: signature,
- );
- return recoveredAddress.toUpperCase() == address.toUpperCase();
}
Web3Client? getWeb3Client() => _client.getWeb3Client();
Why this scored 35/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.