• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vaariance / web3-signers / #10

15 Jan 2026 03:08PM UTC coverage: 94.705% (-0.01%) from 94.715%
#10

push

code-z2
feat: Rework ABI encoding and parsing, remove EIP-7702 dependency, and update the Signer interface with sync and async methods.

194 of 205 new or added lines in 4 files covered. (94.63%)

5 existing lines in 3 files now uncovered.

948 of 1001 relevant lines covered (94.71%)

2.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.73
/lib/src/types/verifier.dart
1
part of '../../web3_signers.dart';
2

3
/// Utility class for verifying signatures according to ERC-1271 or ERC-7739.
4
///
5
/// This class provides static methods to validate signatures for contracts
6
/// (using `isValidSignature`) and standard EOAs (using ECDSA recovery),
7
/// including support for Passkeys (WebAuthn).
8
final class Verifier {
9
  const Verifier._();
×
10

11
  /// Verifies a signature against a smart contract account.
12
  ///
13
  /// Calls the `isValidSignature(bytes32,bytes)` method on the [contractAddress].
14
  ///
15
  /// Parameters:
16
  /// - [hash]: The 32-byte hash of the data that was signed.
17
  /// - [signature]: The signature bytes to verify.
18
  /// - [contractAddress]: The address of the smart contract.
19
  /// - [rpcUrl]: The RPC URL to use for the `eth_call`.
20
  ///
21
  /// Returns a valid response if the contract returns the magic value `0x1626ba7e`.
22
  ///
23
  /// Example:
24
  /// ```dart
25
  /// final isValid = await Verifier.isValidContractSignature(
26
  ///   keccak256(rawPayload),
27
  ///   signature,
28
  ///   contractAddress,
29
  ///   "https://mainnet.infura.io/v3/..."
30
  /// );
31
  /// ```
32
  static Future<IsValidSignatureResponse> isValidContractSignature(
1✔
33
    Bytes hash,
34
    Bytes signatureBytes,
35
    HexString contractAddress,
36
    String rpcUrl,
37
  ) async {
38
    final selector = hexToBytes("1626ba7e");
1✔
39
    final encoded = encodeAbiParameters(
1✔
40
      ['bytes32', 'bytes'],
1✔
41
      [hash, signatureBytes],
1✔
42
    );
43
    final calldata = selector.concat(encoded);
1✔
44

45
    final result = await _rpcRequest(calldata, contractAddress, rpcUrl);
1✔
46

47
    return IsValidSignatureResponse.isValidResult(result);
1✔
48
  }
49

50
  /// Verifies an ECDSA signature for a raw payload.
51
  ///
52
  /// Supports both standard ECDSA signatures and Passkey (WebAuthn) signatures
53
  /// by reconstructing the client data JSON if necessary.
54
  ///
55
  /// Parameters:
56
  /// - [preImage]: The original data that was signed (do not hash first).
57
  /// - [signature]: The signature object containing `r`, `s`, `v`, and optional WebAuthn data.
58
  /// - [signer]: The public key of the signer to verify against.
59
  ///
60
  /// Example:
61
  /// ```dart
62
  /// final isValid = Verifier.isValidECSignature(
63
  ///   rawPayload,
64
  ///   signature,
65
  ///   signerPublicKey
66
  /// );
67
  /// ```
68
  static IsValidSignatureResponse isValidECSignature(
3✔
69
    Bytes preImage,
70
    Signature signature,
71
    PublicKey signer,
72
  ) {
73
    final payload = _getPayload(preImage, signature);
3✔
74
    return _ecRecover(payload, signature, signer);
3✔
75
  }
76

77
  /// Verifies an Ethereum Signed Message (EIP-191).
78
  ///
79
  /// Wraps the message with the standard prefix "\x19Ethereum Signed Message:\n"
80
  /// before verification.
81
  ///
82
  /// Parameters:
83
  /// - [message]: The raw message bytes.
84
  /// - [signature]: The signature to verify.
85
  /// - [signer]: The public key of the expected signer.
86
  ///
87
  /// Example:
88
  /// ```dart
89
  /// final isValid = Verifier.isValidSignedMessage(
90
  ///   utf8.encode("Hello World"),
91
  ///   signature,
92
  ///   signerPublicKey
93
  /// );
94
  /// ```
95
  static IsValidSignatureResponse isValidSignedMessage(
3✔
96
    Bytes message,
97
    Signature signature,
98
    PublicKey signer,
99
  ) {
100
    final prefix = '\u0019Ethereum Signed Message:\n${message.length}';
6✔
101
    final prefixBytes = ascii.encode(prefix);
3✔
102
    final payload = _getPayload(prefixBytes.concat(message), signature);
6✔
103
    return _ecRecover(payload, signature, signer);
3✔
104
  }
105

106
  /// Verifies an EIP-712 Typed Data signature.
107
  ///
108
  /// Hashes the typed data according to the spec before verification.
109
  ///
110
  /// Parameters:
111
  /// - [typedData]: The EIP-712 typed message.
112
  /// - [version]: The EIP-712 version to use for hashing.
113
  /// - [signature]: The signature to verify.
114
  /// - [signer]: The public key of the expected signer.
115
  ///
116
  /// Example:
117
  /// ```dart
118
  /// final isValid = Verifier.isValidSignedTypedData(
119
  ///   TypedMessage.fromJson({...}),
120
  ///   TypedDataVersion.V4,
121
  ///   signature,
122
  ///   signerPublicKey
123
  /// );
124
  /// ```
125
  static IsValidSignatureResponse isValidSignedTypedData(
3✔
126
    TypedMessage typedData,
127
    TypedDataVersion version,
128
    Signature signature,
129
    PublicKey signer,
130
  ) {
131
    final message = hashTypedData(typedData: typedData, version: version);
3✔
132
    final payload = _getPayload(message, signature);
3✔
133
    return _ecRecover(payload, signature, signer);
3✔
134
  }
135

136
  static IsValidSignatureResponse _ecRecover(
4✔
137
    Bytes payload,
138
    Signature signature,
139
    PublicKey signer,
140
  ) {
141
    final params = signature.curve!.curveParams;
8✔
142
    final Q = params.curve.createPoint(signer.x.value, signer.y.value);
24✔
143
    final ecPubKey = ECPublicKey(Q, params);
4✔
144
    final ecSigner = ECDSASigner(signature.curve!.digest);
12✔
145
    ecSigner.init(false, PublicKeyParameter(ecPubKey));
8✔
146

147
    final valid = ecSigner.verifySignature(payload, signature);
4✔
148
    return IsValidSignatureResponse.isValid(valid);
4✔
149
  }
150

151
  static Bytes _getPayload(Bytes message, Signature signature) {
4✔
152
    if (signature.authData == null && signature.clientDataJson == null) {
7✔
153
      return message;
154
    }
155
    final hashBase64 = b64e(message);
1✔
156
    final match = cdjRgExp.firstMatch(signature.clientDataJson!)!;
3✔
157
    final clientDataJSON =
158
        '{"type":"webauthn.get","challenge":"$hashBase64",${match[1]}}';
2✔
159
    final clientHash = sha256Hash(utf8.encode(clientDataJSON));
2✔
160
    return signature.authData!.concat(clientHash);
2✔
161
  }
162

163
  static Future<Uint32> _rpcRequest(
1✔
164
    Bytes calldata,
165
    String contractAddress,
166
    String rpcUrl,
167
  ) async {
168
    final client = HttpClient();
1✔
169
    final String requestBody = json.encode({
2✔
170
      'jsonrpc': '2.0',
171
      'method': 'eth_call',
172
      'params': [
1✔
173
        {'to': contractAddress, 'data': hexlify(calldata)},
2✔
174
        'latest',
175
      ],
176
      'id': 1,
177
    });
178
    try {
179
      final Uri uri = Uri.parse(rpcUrl);
1✔
180
      final HttpClientRequest request = await client.postUrl(uri);
1✔
181
      request.headers.set(HttpHeaders.contentTypeHeader, 'application/json');
2✔
182
      request.add(utf8.encode(requestBody));
2✔
183

184
      final HttpClientResponse response = await request.close();
1✔
185
      final String responseBody = await response.transform(utf8.decoder).join();
3✔
186
      final Dict jsonResponse = json.decode(responseBody);
1✔
187

188
      if (jsonResponse.containsKey('error')) {
1✔
UNCOV
189
        return Uint32.zero;
×
190
      } else if (jsonResponse.containsKey('result')) {
1✔
191
        return Uint32.fromHex(jsonResponse['result']);
2✔
192
      } else {
UNCOV
193
        return Uint32.zero;
×
194
      }
195
    } catch (e) {
196
      return Uint32.zero;
×
197
    } finally {
198
      client.close();
1✔
199
    }
200
  }
201
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc