verifyEvmSignature function
"Did the device sign exactly what I sent, with the key I expected?" keccak digest + public-key recovery; the recovered address must equal the request's. Run it before broadcasting.
Implementation
VerifyResult verifyEvmSignature(VerifyEvmSignatureArgs args) {
if (args.signature.length < 65) {
return failed('signature is shorter than 65 bytes');
}
final reEncoded = args.reEncodedSignData;
if (reEncoded != null && !equalBytes(reEncoded, args.signData)) {
return failed(
'the transaction to broadcast is not the one the device signed');
}
final Uint8List digest;
switch (args.dataType) {
case EvmDataType.transaction:
case EvmDataType.typedTransaction:
digest = keccak256(args.signData);
case EvmDataType.personalMessage:
digest = _personalSignDigest(args.signData);
case EvmDataType.typedData:
return unverifiable(
'EIP-712: the digest is the hash of the structure, computed only on the device',
);
default:
return failed('unknown dataType ${args.dataType}');
}
var vBig = BigInt.zero;
for (final b in args.signature.sublist(64)) {
vBig = (vBig << 8) | BigInt.from(b);
}
final recoveryId = foldRecoveryId(vBig);
if (recoveryId != 0 && recoveryId != 1) {
return failed('implausible recovery value');
}
Uint8List recovered;
try {
final compressed =
Secp256k1.recover(args.signature.sublist(0, 64), digest, recoveryId);
final point = Secp256k1.parsePublicKey(compressed);
final uncompressed = Uint8List.fromList(point.getEncoded(false));
recovered = keccak256(uncompressed.sublist(1)).sublist(12);
} on ArgumentError catch (e) {
return failed('signature could not be checked: ${e.message}');
}
final expected = args.address is Uint8List
? args.address as Uint8List
: hexToBytes(args.address as String);
if (expected.length != 20) {
return failed('expected address must be 20 bytes, got ${expected.length}');
}
if (!equalBytes(recovered, expected)) {
return failed(
'the signature does not belong to this account (recovered 0x${bytesToHex(recovered)})',
);
}
return verified;
}