-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathClaimVerifier.sol
429 lines (349 loc) · 23.5 KB
/
ClaimVerifier.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.1;
import "./IERC725.sol";
import "./IERC735.sol";
import "./../dependencies/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";
import "./../dependencies/jsmnSol/contracts/JsmnSolLib.sol";
import "./Commons.sol";
import "./IdentityContract.sol";
import "./IdentityContractLib.sol";
import "./ClaimCommons.sol";
/**
* This library contains functionality that concerns the verification of claims.
*/
library ClaimVerifier {
// Constants ERC-735
uint256 constant public ECDSA_SCHEME = 1;
uint256 constant public PREFIXED_ECDSA_SCHEME = 2;
// JSON parsing constants.
uint256 constant MAX_NUMBER_OF_JSON_FIELDS = 20;
/**
* Iff _requiredValidAt is not zero, only claims that are not expired at that time and
* are already valid at that time are considered. If it is set to zero, no expiration
* or starting date check is performed.
*/
function verifyClaim(IdentityContract marketAuthority, address _subject, uint256 _claimId, uint64 _requiredValidAt, bool allowFutureValidity) public view returns(bool __valid) {
(uint256 topic, uint256 scheme, address issuer, bytes memory signature, bytes memory data, ) = IdentityContract(_subject).getClaim(_claimId);
ClaimCommons.ClaimType claimType = ClaimCommons.topic2ClaimType(topic);
if(_requiredValidAt != 0) {
uint64 currentTime = marketAuthority.getBalancePeriod(_requiredValidAt);
if(getExpiryDate(data) < currentTime || ((!allowFutureValidity) && getStartDate(data) > currentTime))
return false;
}
if(claimType == ClaimCommons.ClaimType.IsBalanceAuthority
|| claimType == ClaimCommons.ClaimType.IsMeteringAuthority
|| claimType == ClaimCommons.ClaimType.IsPhysicalAssetAuthority
|| claimType == ClaimCommons.ClaimType.IdentityContractFactoryClaim
|| claimType == ClaimCommons.ClaimType.EnergyTokenContractClaim
|| claimType == ClaimCommons.ClaimType.MarketRulesClaim
|| claimType == ClaimCommons.ClaimType.RealWorldPlantIdClaim) {
return verifySignature(_subject, topic, scheme, issuer, signature, data);
}
if(claimType == ClaimCommons.ClaimType.MeteringClaim
|| claimType == ClaimCommons.ClaimType.BalanceClaim
|| claimType == ClaimCommons.ClaimType.ExistenceClaim
|| claimType == ClaimCommons.ClaimType.InstallationDateClaim
|| claimType == ClaimCommons.ClaimType.MaxPowerGenerationClaim
|| claimType == ClaimCommons.ClaimType.MaxPowerConsumptionClaim
|| claimType == ClaimCommons.ClaimType.GenerationTypeClaim
|| claimType == ClaimCommons.ClaimType.LocationClaim
|| claimType == ClaimCommons.ClaimType.AcceptedDistributorClaim) {
return verifySignature(_subject, topic, scheme, issuer, signature, data) && (getClaimOfType(marketAuthority, address(uint160(issuer)), "", ClaimCommons.getHigherLevelClaim(claimType), _requiredValidAt) != 0);
}
revert("Claim verification failed because the claim type was not recognized.");
}
function verifyClaim(IdentityContract marketAuthority, address _subject, uint256 _claimId) public view returns(bool __valid) {
return verifyClaim(marketAuthority, _subject, _claimId, uint64(block.timestamp), false);
}
/**
* This method does not verify that the given claim exists in the contract. It merely checks whether it is a valid claim.
*
* Use this method before adding claims to make sure that only valid claims are added.
*/
function validateClaim(IdentityContract marketAuthority, ClaimCommons.ClaimType _claimType, address _subject, uint256 _topic, uint256 _scheme, address _issuer, bytes memory _signature, bytes memory _data) public view returns(bool) {
if(ClaimCommons.claimType2Topic(_claimType) != _topic)
return false;
if(_claimType == ClaimCommons.ClaimType.RealWorldPlantIdClaim) {
if(_issuer != _subject)
return false;
}
if(_claimType == ClaimCommons.ClaimType.IsBalanceAuthority
|| _claimType == ClaimCommons.ClaimType.IsMeteringAuthority
|| _claimType == ClaimCommons.ClaimType.IsPhysicalAssetAuthority
|| _claimType == ClaimCommons.ClaimType.IdentityContractFactoryClaim
|| _claimType == ClaimCommons.ClaimType.EnergyTokenContractClaim
|| _claimType == ClaimCommons.ClaimType.MarketRulesClaim) {
if(_issuer != address(marketAuthority))
return false;
}
return verifySignature(_subject, _topic, _scheme, _issuer, _signature, _data);
}
/**
* Returns the claim ID of a claim of the stated type. Only valid claims are considered.
*
* Iff _requiredValidAt is not zero, only claims that are not expired at that time and are already valid at that time are considered. If it is set to zero, no expiration or startig date check is performed.
*/
function getClaimOfType(IdentityContract marketAuthority, address _subject, string memory _realWorldPlantId, ClaimCommons.ClaimType _claimType, uint64 _requiredValidAt) public view returns (uint256 __claimId) {
uint256 topic = ClaimCommons.claimType2Topic(_claimType);
uint256[] memory claimIds = IdentityContract(_subject).getClaimIdsByTopic(topic);
bytes32 realWorldPlantIdHash = keccak256(abi.encodePacked(_realWorldPlantId));
for(uint64 i = 0; i < claimIds.length; i++) {
// Checking the claim's type is important because a malicious IDC can return claims of a different topic via getClaimIdsByTopic().
(uint256 cTopic, , , , bytes memory data,) = IdentityContract(_subject).getClaim(claimIds[i]);
if(cTopic != topic)
continue;
if(_claimType == ClaimCommons.ClaimType.MeteringClaim
|| _claimType == ClaimCommons.ClaimType.BalanceClaim
|| _claimType == ClaimCommons.ClaimType.ExistenceClaim
|| _claimType == ClaimCommons.ClaimType.InstallationDateClaim
|| _claimType == ClaimCommons.ClaimType.MaxPowerGenerationClaim
|| _claimType == ClaimCommons.ClaimType.MaxPowerConsumptionClaim
|| _claimType == ClaimCommons.ClaimType.GenerationTypeClaim
|| _claimType == ClaimCommons.ClaimType.LocationClaim) {
if(keccak256(abi.encodePacked(getRealWorldPlantId(data))) != realWorldPlantIdHash)
continue;
}
if(!verifyClaim(marketAuthority, _subject, claimIds[i], _requiredValidAt, false))
continue;
return claimIds[i];
}
return 0;
}
function getClaimOfType(IdentityContract marketAuthority, address _subject, string memory _realWorldPlantId, ClaimCommons.ClaimType _claimType) public view returns (uint256 __claimId) {
return getClaimOfType(marketAuthority, _subject, _realWorldPlantId, _claimType, marketAuthority.getBalancePeriod(block.timestamp));
}
function getClaimOfTypeByIssuer(IdentityContract marketAuthority, address _subject, ClaimCommons.ClaimType _claimType, address _issuer, uint64 _requiredValidAt) public view returns (uint256 __claimId) {
uint256 topic = ClaimCommons.claimType2Topic(_claimType);
uint256 claimId = IdentityContractLib.getClaimId(_issuer, topic);
(uint256 cTopic, , , , ,) = IdentityContract(_subject).getClaim(claimId);
if(cTopic != topic)
return 0;
if(!verifyClaim(marketAuthority, _subject, claimId, _requiredValidAt, false))
return 0;
return claimId;
}
function getClaimOfTypeByIssuer(IdentityContract marketAuthority, address _subject, ClaimCommons.ClaimType _claimType, address _issuer) public view returns (uint256 __claimId) {
return getClaimOfTypeByIssuer(marketAuthority, _subject, _claimType, _issuer, marketAuthority.getBalancePeriod(block.timestamp));
}
function getClaimOfTypeWithMatchingField(IdentityContract marketAuthority, address _subject, string memory _realWorldPlantId, ClaimCommons.ClaimType _claimType, string memory _fieldName, string memory _fieldContent, uint64 _requiredValidAt) public view returns (uint256 __claimId) {
uint256 topic = ClaimCommons.claimType2Topic(_claimType);
uint256[] memory claimIds = IdentityContract(_subject).getClaimIdsByTopic(topic);
bytes32 realWorldPlantIdHash = keccak256(abi.encodePacked(_realWorldPlantId));
for(uint64 i = 0; i < claimIds.length; i++) {
(uint256 cTopic, , , , bytes memory cData,) = IdentityContract(_subject).getClaim(claimIds[i]);
if(cTopic != topic)
continue;
if(_claimType == ClaimCommons.ClaimType.MeteringClaim
|| _claimType == ClaimCommons.ClaimType.BalanceClaim
|| _claimType == ClaimCommons.ClaimType.ExistenceClaim
|| _claimType == ClaimCommons.ClaimType.InstallationDateClaim
|| _claimType == ClaimCommons.ClaimType.MaxPowerGenerationClaim
|| _claimType == ClaimCommons.ClaimType.GenerationTypeClaim
|| _claimType == ClaimCommons.ClaimType.LocationClaim
|| _claimType == ClaimCommons.ClaimType.AcceptedDistributorClaim) {
if(keccak256(abi.encodePacked(getRealWorldPlantId(cData))) != realWorldPlantIdHash)
continue;
}
if(getClaimOfTypeWithMatchingField_temporalValidityCheck(marketAuthority, _requiredValidAt, cData))
continue;
if(!verifyClaim(marketAuthority, _subject, claimIds[i]))
continue;
// Separate function call to avoid stack too deep error.
if(doesMatchingFieldExist(_fieldName, _fieldContent, cData)) {
return claimIds[i];
}
}
return 0;
}
function getClaimOfTypeWithMatchingField_temporalValidityCheck(IdentityContract marketAuthority, uint64 _requiredValidAt, bytes memory cData) internal view returns(bool) {
return (_requiredValidAt > 0 && getExpiryDate(cData) < marketAuthority.getBalancePeriod(_requiredValidAt));
}
function getClaimOfTypeWithGeqField(IdentityContract marketAuthority, address _subject, string memory _realWorldPlantId, ClaimCommons.ClaimType _claimType, string memory _fieldName, string memory _fieldContent, uint64 _requiredValidAt) public view returns (uint256 __claimId) {
uint256 topic = ClaimCommons.claimType2Topic(_claimType);
uint256[] memory claimIds = IdentityContract(_subject).getClaimIdsByTopic(topic);
bytes32 realWorldPlantIdHash = keccak256(abi.encodePacked(_realWorldPlantId));
for(uint64 i = 0; i < claimIds.length; i++) {
(uint256 cTopic, , , , bytes memory cData,) = IdentityContract(_subject).getClaim(claimIds[i]);
if(cTopic != topic)
continue;
if(_claimType == ClaimCommons.ClaimType.MeteringClaim
|| _claimType == ClaimCommons.ClaimType.BalanceClaim
|| _claimType == ClaimCommons.ClaimType.ExistenceClaim
|| _claimType == ClaimCommons.ClaimType.InstallationDateClaim
|| _claimType == ClaimCommons.ClaimType.MaxPowerGenerationClaim
|| _claimType == ClaimCommons.ClaimType.GenerationTypeClaim
|| _claimType == ClaimCommons.ClaimType.LocationClaim
|| _claimType == ClaimCommons.ClaimType.AcceptedDistributorClaim) {
if(keccak256(abi.encodePacked(getRealWorldPlantId(cData))) != realWorldPlantIdHash)
continue;
}
if(getClaimOfTypeWithMatchingField_temporalValidityCheck(marketAuthority, _requiredValidAt, cData))
continue;
if(!verifyClaim(marketAuthority, _subject, claimIds[i]))
continue;
// Separate function call to avoid stack too deep error.
if(doesGeqFieldExist(_fieldName, _fieldContent, cData)) {
return claimIds[i];
}
}
return 0;
}
function getClaimOfTypeWithLeqField(IdentityContract marketAuthority, address _subject, string memory _realWorldPlantId, ClaimCommons.ClaimType _claimType, string memory _fieldName, string memory _fieldContent, uint64 _requiredValidAt) public view returns (uint256 __claimId) {
uint256 topic = ClaimCommons.claimType2Topic(_claimType);
uint256[] memory claimIds = IdentityContract(_subject).getClaimIdsByTopic(topic);
bytes32 realWorldPlantIdHash = keccak256(abi.encodePacked(_realWorldPlantId));
for(uint64 i = 0; i < claimIds.length; i++) {
(uint256 cTopic, , , , bytes memory cData,) = IdentityContract(_subject).getClaim(claimIds[i]);
if(cTopic != topic)
continue;
if(_claimType == ClaimCommons.ClaimType.MeteringClaim
|| _claimType == ClaimCommons.ClaimType.BalanceClaim
|| _claimType == ClaimCommons.ClaimType.ExistenceClaim
|| _claimType == ClaimCommons.ClaimType.InstallationDateClaim
|| _claimType == ClaimCommons.ClaimType.MaxPowerGenerationClaim
|| _claimType == ClaimCommons.ClaimType.GenerationTypeClaim
|| _claimType == ClaimCommons.ClaimType.LocationClaim
|| _claimType == ClaimCommons.ClaimType.AcceptedDistributorClaim) {
if(keccak256(abi.encodePacked(getRealWorldPlantId(cData))) != realWorldPlantIdHash)
continue;
}
if(getClaimOfTypeWithMatchingField_temporalValidityCheck(marketAuthority, _requiredValidAt, cData))
continue;
if(!verifyClaim(marketAuthority, _subject, claimIds[i]))
continue;
// Separate function call to avoid stack too deep error.
if(doesLeqFieldExist(_fieldName, _fieldContent, cData)) {
return claimIds[i];
}
}
return 0;
}
function getUint64Field(string memory _fieldName, bytes memory _data) public pure returns(uint64) {
int fieldAsInt = JsmnSolLib.parseInt(getStringField(_fieldName, _data));
require(fieldAsInt >= 0, "fieldAsInt must be greater than or equal to 0.");
require(fieldAsInt < 0x10000000000000000, "fieldAsInt must be less than 0x10000000000000000.");
return uint64(uint256(fieldAsInt));
}
function getUint256Field(string calldata _fieldName, bytes calldata _data) external pure returns(uint256) {
int fieldAsInt = JsmnSolLib.parseInt(getStringField(_fieldName, _data));
require(fieldAsInt >= 0, "fieldAsInt must be greater than or equal to 0.");
return uint256(fieldAsInt);
}
function getInt256Field(string memory _fieldName, bytes memory _data) internal pure returns(int256) {
int fieldAsInt = JsmnSolLib.parseInt(getStringField(_fieldName, _data));
return fieldAsInt;
}
function getStringField(string memory _fieldName, bytes memory _data) public pure returns(string memory) {
string memory json = string(_data);
(uint exitCode, JsmnSolLib.Token[] memory tokens, uint numberOfTokensFound) = JsmnSolLib.parse(json, MAX_NUMBER_OF_JSON_FIELDS);
require(exitCode == 0, "Error in getStringField. Exit code is not 0.");
bytes32 fieldNameHash = keccak256(abi.encodePacked(_fieldName));
for(uint i = 1; i < numberOfTokensFound; i += 2) {
JsmnSolLib.Token memory keyToken = tokens[i];
JsmnSolLib.Token memory valueToken = tokens[i+1];
if(keccak256(abi.encodePacked(JsmnSolLib.getBytes(json, keyToken.start, keyToken.end))) == fieldNameHash) {
return JsmnSolLib.getBytes(json, valueToken.start, valueToken.end);
}
}
revert(string(abi.encodePacked("_fieldName ", _fieldName, " not found.")));
}
function getExpiryDate(bytes memory _data) public pure returns(uint64) {
return getUint64Field("expiryDate", _data);
}
function getRealWorldPlantId(bytes memory _data) public pure returns(string memory) {
return getStringField("realWorldPlantId", _data);
}
function getRealWorldPlantId(IdentityContract _marketAuthority, address _plant) public view returns(string memory) {
uint256 claimId = getClaimOfTypeByIssuer(_marketAuthority, _plant, ClaimCommons.ClaimType.RealWorldPlantIdClaim, _plant);
(, , , , bytes memory data,) = IdentityContract(_plant).getClaim(claimId);
return getRealWorldPlantId(data);
}
function getStartDate(bytes memory _data) public pure returns(uint64) {
return getUint64Field("startDate", _data);
}
function verifySignature(address _subject, uint256 _topic, uint256 _scheme, address _issuer, bytes memory _signature, bytes memory _data) public view returns (bool __valid) {
// Check for currently unsupported signature.
if(_scheme != ECDSA_SCHEME && _scheme != PREFIXED_ECDSA_SCHEME)
return false;
address signer = getSignerAddress(claimAttributes2SigningFormat(_scheme, _subject, _topic, _data), _signature);
if(isContract(_issuer)) {
return signer == IdentityContract(_issuer).owner();
} else {
return signer == _issuer;
}
}
// ########################
// # Modifier functions
// ########################
function f_onlyGenerationPlants(IdentityContract marketAuthority, address _plant, uint64 _balancePeriod) public view {
string memory realWorldPlantId = getRealWorldPlantId(marketAuthority, _plant);
require(getClaimOfType(marketAuthority, _plant, realWorldPlantId, ClaimCommons.ClaimType.BalanceClaim, _balancePeriod) != 0, "Invalid BalanceClaim.");
require(getClaimOfTypeWithMatchingField(marketAuthority, _plant, realWorldPlantId, ClaimCommons.ClaimType.ExistenceClaim, "type", "generation", _balancePeriod) != 0, "Invalid ExistenceClaim.");
require(getClaimOfType(marketAuthority, _plant, realWorldPlantId, ClaimCommons.ClaimType.MaxPowerGenerationClaim, _balancePeriod) != 0, "Invalid MaxPowerGenerationClaim.");
require(getClaimOfType(marketAuthority, _plant, realWorldPlantId, ClaimCommons.ClaimType.MeteringClaim, _balancePeriod) != 0, "Invalid MeteringClaim.");
}
function f_onlyStoragePlants(IdentityContract marketAuthority, address _plant, uint64 _balancePeriod) public view {
string memory realWorldPlantId = getRealWorldPlantId(marketAuthority, _plant);
require(getClaimOfType(marketAuthority, _plant, realWorldPlantId, ClaimCommons.ClaimType.BalanceClaim, _balancePeriod) != 0, "Invalid BalanceClaim.");
require(getClaimOfTypeWithMatchingField(marketAuthority, _plant, realWorldPlantId, ClaimCommons.ClaimType.ExistenceClaim, "type", "storage", _balancePeriod) != 0, "Invalid ExistenceClaim (type storage).");
require(getClaimOfType(marketAuthority, _plant, realWorldPlantId, ClaimCommons.ClaimType.MaxPowerGenerationClaim, _balancePeriod) != 0, "Invalid MaxPowerGenerationClaim.");
require(getClaimOfType(marketAuthority, _plant, realWorldPlantId, ClaimCommons.ClaimType.MaxPowerConsumptionClaim, _balancePeriod) != 0, "Invalid MaxPowerConsumptionClaim.");
require(getClaimOfType(marketAuthority, _plant, realWorldPlantId, ClaimCommons.ClaimType.MeteringClaim, _balancePeriod) != 0, "Invalid MeteringClaim.");
}
function f_onlyGenerationOrStoragePlants(IdentityContract marketAuthority, address _plant, uint64 _balancePeriod) public view {
string memory realWorldPlantId = getRealWorldPlantId(marketAuthority, _plant);
require(getClaimOfType(marketAuthority, _plant, realWorldPlantId, ClaimCommons.ClaimType.BalanceClaim, _balancePeriod) != 0, "Invalid BalanceClaim.");
require(getClaimOfTypeWithMatchingField(marketAuthority, _plant, realWorldPlantId, ClaimCommons.ClaimType.ExistenceClaim, "type", "generation", _balancePeriod) != 0
|| getClaimOfTypeWithMatchingField(marketAuthority, _plant, realWorldPlantId, ClaimCommons.ClaimType.ExistenceClaim, "type", "storage", _balancePeriod) != 0, "Invalid ExistenceClaim.");
require(getClaimOfType(marketAuthority, _plant, realWorldPlantId, ClaimCommons.ClaimType.MaxPowerGenerationClaim, _balancePeriod) != 0, "Invalid MaxPowerGenerationClaim.");
require(getClaimOfType(marketAuthority, _plant, realWorldPlantId, ClaimCommons.ClaimType.MeteringClaim, _balancePeriod) != 0, "Invalid MeteringClaim.");
}
// ########################
// # Internal functions
// ########################
function doesMatchingFieldExist(string memory _fieldName, string memory _fieldContent, bytes memory _data) internal pure returns(bool) {
string memory json = string(_data);
(uint exitCode, JsmnSolLib.Token[] memory tokens, uint numberOfTokensFound) = JsmnSolLib.parse(json, MAX_NUMBER_OF_JSON_FIELDS);
require(exitCode == 0, "Error in doesMatchingFieldExist. Exit code is not 0.");
bytes32 fieldNameHash = keccak256(abi.encodePacked(_fieldName));
bytes32 fieldContentHash = keccak256(abi.encodePacked(_fieldContent));
for(uint i = 1; i < numberOfTokensFound; i += 2) {
JsmnSolLib.Token memory keyToken = tokens[i];
JsmnSolLib.Token memory valueToken = tokens[i+1];
if((keccak256(abi.encodePacked(JsmnSolLib.getBytes(json, keyToken.start, keyToken.end))) == fieldNameHash) &&
(keccak256(abi.encodePacked(JsmnSolLib.getBytes(json, valueToken.start, valueToken.end))) == fieldContentHash)) {
return true;
}
}
return false;
}
function doesGeqFieldExist(string memory _fieldName, string memory _fieldContent, bytes memory _data) internal pure returns(bool) {
return getInt256Field(_fieldName, _data) >= JsmnSolLib.parseInt(_fieldContent);
}
function doesLeqFieldExist(string memory _fieldName, string memory _fieldContent, bytes memory _data) internal pure returns(bool) {
return getInt256Field(_fieldName, _data) <= JsmnSolLib.parseInt(_fieldContent);
}
function claimAttributes2SigningFormat(uint256 _scheme, address _subject, uint256 _topic, bytes memory _data) internal pure returns (bytes32 __claimInSigningFormat) {
if(_scheme == ECDSA_SCHEME)
return keccak256(abi.encodePacked(_subject, _topic, _data));
else
return ECDSA.toEthSignedMessageHash(keccak256(abi.encodePacked(_subject, _topic, _data)));
}
function getSignerAddress(bytes32 _claimInSigningFormat, bytes memory _signature) internal pure returns (address __signer) {
return ECDSA.recover(_claimInSigningFormat, _signature);
}
/**
* Checks whether the address points to a contract by checking whether there is code
* for that address.
*
* Is not 100% reliable as someone could state an address and only later deploy a
* contract to that address.
*
* Source: https://stackoverflow.com/a/40939341
*/
function isContract(address _addr) internal view returns (bool) {
uint size;
assembly { size := extcodesize(_addr) }
return size > 0;
}
}