-
Notifications
You must be signed in to change notification settings - Fork 44
Expand file tree
/
Copy pathState.sol
More file actions
627 lines (564 loc) · 22 KB
/
Copy pathState.sol
File metadata and controls
627 lines (564 loc) · 22 KB
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
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.27;
import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import {IState, MAX_SMT_DEPTH} from "../interfaces/IState.sol";
import {IStateTransitionVerifier} from "../interfaces/IStateTransitionVerifier.sol";
import {SmtLib} from "../lib/SmtLib.sol";
import {PoseidonUnit1L} from "../lib/Poseidon.sol";
import {StateLib} from "../lib/StateLib.sol";
import {GenesisUtils} from "../lib/GenesisUtils.sol";
import {ICrossChainProofValidator} from "../interfaces/ICrossChainProofValidator.sol";
/// @title Set and get states for each identity
contract State is Ownable2StepUpgradeable, IState {
/**
* @dev Version of contract
*/
string public constant VERSION = "2.6.3";
/**
* @dev Global state proof type
*/
bytes32 private constant GLOBAL_STATE_PROOF_TYPE = keccak256(bytes("globalStateProof"));
/**
* @dev State proof type
*/
bytes32 private constant STATE_PROOF_TYPE = keccak256(bytes("stateProof"));
// This empty reserved space is put in place to allow future versions
// of the State contract to inherit from other contracts without a risk of
// breaking the storage layout. This is necessary because the parent contracts in the
// future may introduce some storage variables, which are placed before the State
// contract's storage variables.
// (see https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#storage-gaps)
// slither-disable-next-line shadowing-state
// slither-disable-next-line unused-state
uint256[651] private __gap;
/**
* @dev Verifier address
*/
IStateTransitionVerifier internal verifier;
/**
* @dev State data
*/
StateLib.Data internal _stateData;
/**
* @dev Global Identity State Tree (GIST) data
*/
SmtLib.Data internal _gistData;
/**
* @dev Default Id Type
*/
bytes2 internal _defaultIdType;
/**
* @dev Default Id Type initialized flag
*/
bool internal _defaultIdTypeInitialized;
/// @custom:storage-location erc7201:iden3.storage.StateCrossChain
struct StateCrossChainStorage {
mapping(uint256 id => mapping(uint256 state => uint256 replacedAt)) _idToStateReplacedAt;
mapping(bytes2 idType => mapping(uint256 root => uint256 replacedAt)) _rootToGistRootReplacedAt;
ICrossChainProofValidator _crossChainProofValidator;
}
// keccak256(abi.encode(uint256(keccak256("iden3.storage.StateCrossChain")) - 1))
// & ~bytes32(uint256(0xff));
// solhint-disable-next-line const-name-snakecase
bytes32 private constant StateCrossChainStorageLocation =
0xfe6de916382846695d2555237dc6c0ef6555f4c949d4ba263e03532600778100;
function _getStateCrossChainStorage() private pure returns (StateCrossChainStorage storage $) {
// solhint-disable-next-line no-inline-assembly
assembly {
$.slot := StateCrossChainStorageLocation
}
}
using SmtLib for SmtLib.Data;
using StateLib for StateLib.Data;
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/**
* @dev Initialize the contract
* @param verifierContractAddr Verifier address
* @param defaultIdType default id type for Ethereum-based IDs calculation
* @param owner Owner of the contract with administrative functions
* @param validator Cross chain proof validator contract address
*/
function initialize(
IStateTransitionVerifier verifierContractAddr,
bytes2 defaultIdType,
address owner,
ICrossChainProofValidator validator
) public initializer {
if (!_gistData.initialized) {
_gistData.initialize(MAX_SMT_DEPTH);
}
if (address(verifierContractAddr) == address(0)) {
revert("Verifier contract address should not be zero");
}
verifier = verifierContractAddr;
_setDefaultIdType(defaultIdType);
__Ownable_init(owner);
StateCrossChainStorage storage $ = _getStateCrossChainStorage();
$._crossChainProofValidator = validator;
}
/**
* @dev Set cross chain proof validator contract address
* @param validator Cross chain proof validator contract address
*/
function setCrossChainProofValidator(ICrossChainProofValidator validator) public onlyOwner {
StateCrossChainStorage storage $ = _getStateCrossChainStorage();
$._crossChainProofValidator = validator;
}
/**
* @dev Processes cross chain proofs.
* @param crossChainProofs The cross chain proofs.
*/
function processCrossChainProofs(bytes calldata crossChainProofs) public {
if (crossChainProofs.length == 0) {
return;
}
IState.CrossChainProof[] memory proofs = abi.decode(
crossChainProofs,
(IState.CrossChainProof[])
);
StateCrossChainStorage storage $ = _getStateCrossChainStorage();
for (uint256 i = 0; i < proofs.length; i++) {
if (keccak256(bytes(proofs[i].proofType)) == GLOBAL_STATE_PROOF_TYPE) {
IState.GlobalStateProcessResult memory gsp = $
._crossChainProofValidator
.processGlobalStateProof(proofs[i].proof);
$._rootToGistRootReplacedAt[gsp.idType][gsp.root] = gsp.replacedAtTimestamp;
} else if (keccak256(bytes(proofs[i].proofType)) == STATE_PROOF_TYPE) {
IState.IdentityStateProcessResult memory isu = $
._crossChainProofValidator
.processIdentityStateProof(proofs[i].proof);
$._idToStateReplacedAt[isu.id][isu.state] = isu.replacedAtTimestamp;
} else {
revert("Unknown proof type");
}
}
}
/**
* @dev Set ZKP verifier contract address
* @param newVerifierAddr Verifier contract address
*/
function setVerifier(address newVerifierAddr) external onlyOwner {
verifier = IStateTransitionVerifier(newVerifierAddr);
}
/**
* @dev Get defaultIdType
* @return defaultIdType
*/
function getDefaultIdType() public view returns (bytes2) {
require(_defaultIdTypeInitialized, "Default Id Type is not initialized");
return _defaultIdType;
}
/**
* @dev Set defaultIdType external wrapper (only owner can call)
* @param defaultIdType default id type
*/
function setDefaultIdType(bytes2 defaultIdType) external onlyOwner {
_setDefaultIdType(defaultIdType);
}
/**
* @dev Change the state of an identity (transit to the new state) with ZKP ownership check.
* @param id Identity
* @param oldState Previous identity state
* @param newState New identity state
* @param isOldStateGenesis Is the previous state genesis?
* @param a ZKP proof field
* @param b ZKP proof field
* @param c ZKP proof field
*/
function transitState(
uint256 id,
uint256 oldState,
uint256 newState,
bool isOldStateGenesis,
uint256[2] memory a,
uint256[2][2] memory b,
uint256[2] memory c
) public {
// Check if the id type is supported
getIdTypeIfSupported(id);
uint256[4] memory input = [id, oldState, newState, uint256(isOldStateGenesis ? 1 : 0)];
require(
verifier.verifyProof(a, b, c, input),
"Zero-knowledge proof of state transition is not valid"
);
_transitState(id, oldState, newState, isOldStateGenesis);
}
/**
* @dev Change the state of an identity (transit to the new state) with method-specific id ownership check.
* @param id Identity
* @param oldState Previous identity state
* @param newState New identity state
* @param isOldStateGenesis Is the previous state genesis?
* @param methodId State transition method id
* @param methodParams State transition method-specific params
*/
function transitStateGeneric(
uint256 id,
uint256 oldState,
uint256 newState,
bool isOldStateGenesis,
uint256 methodId,
bytes calldata methodParams
) public {
bytes2 idType = getIdTypeIfSupported(id);
if (methodId == 1) {
uint256 calcId = GenesisUtils.calcIdFromEthAddress(idType, msg.sender);
require(calcId == id, "msg.sender is not owner of the identity");
require(methodParams.length == 0, "methodParams should be empty");
if (isOldStateGenesis) {
require(oldState == 0, "Old state should be zero");
}
_transitState(id, oldState, newState, isOldStateGenesis);
} else {
revert("Unknown state transition method id");
}
}
/**
* @dev Get ZKP verifier contract address
* @return verifier contract address
*/
function getVerifier() external view returns (address) {
return address(verifier);
}
/**
* @dev Get cross chain proof validator contract address
* @return verifier contract address
*/
function getCrossChainProofValidator() external view returns (address) {
StateCrossChainStorage storage $ = _getStateCrossChainStorage();
return address($._crossChainProofValidator);
}
/**
* @dev Check if id type supported
* @param idType id type to check
* @return True if the id type is supported
*/
function isIdTypeSupported(bytes2 idType) public view returns (bool) {
return _stateData.isIdTypeSupported[idType];
}
/**
* @dev Retrieve the last state info for a given identity
* @param id identity
* @return State info of the last committed state
*/
function getStateInfoById(uint256 id) external view returns (IState.StateInfo memory) {
return _stateEntryInfoAdapter(_stateData.getStateInfoById(id));
}
/**
* @dev Retrieve states quantity for a given identity
* @param id identity
* @return States quantity
*/
function getStateInfoHistoryLengthById(uint256 id) external view returns (uint256) {
return _stateData.getStateInfoHistoryLengthById(id);
}
/**
* Retrieve state infos for a given identity
* @param id identity
* @param startIndex start index of the state history
* @param length length of the state history
* @return A list of state infos of the identity
*/
function getStateInfoHistoryById(
uint256 id,
uint256 startIndex,
uint256 length
) external view returns (IState.StateInfo[] memory) {
StateLib.EntryInfo[] memory stateInfos = _stateData.getStateInfoHistoryById(
id,
startIndex,
length
);
IState.StateInfo[] memory result = new IState.StateInfo[](stateInfos.length);
for (uint256 i = 0; i < stateInfos.length; i++) {
result[i] = _stateEntryInfoAdapter(stateInfos[i]);
}
return result;
}
/**
* @dev Retrieve state information by id and state.
* @param id An identity.
* @param state A state.
* @return The state info.
*/
function getStateInfoByIdAndState(
uint256 id,
uint256 state
) external view returns (IState.StateInfo memory) {
return _stateEntryInfoAdapter(_stateData.getStateInfoByIdAndState(id, state));
}
/**
* @dev Retrieve GIST inclusion or non-inclusion proof for a given identity.
* @param id Identity
* @return The GIST inclusion or non-inclusion proof for the identity
*/
function getGISTProof(uint256 id) external view returns (IState.GistProof memory) {
return _smtProofAdapter(_gistData.getProof(PoseidonUnit1L.poseidon([id])));
}
/**
* @dev Retrieve GIST inclusion or non-inclusion proof for a given identity for
* some GIST root in the past.
* @param id Identity
* @param root GIST root
* @return The GIST inclusion or non-inclusion proof for the identity
*/
function getGISTProofByRoot(
uint256 id,
uint256 root
) external view returns (IState.GistProof memory) {
return _smtProofAdapter(_gistData.getProofByRoot(PoseidonUnit1L.poseidon([id]), root));
}
/**
* @dev Retrieve GIST inclusion or non-inclusion proof for a given identity
* for GIST latest snapshot by the block number provided.
* @param id Identity
* @param blockNumber Blockchain block number
* @return The GIST inclusion or non-inclusion proof for the identity
*/
function getGISTProofByBlock(
uint256 id,
uint256 blockNumber
) external view returns (IState.GistProof memory) {
return
_smtProofAdapter(_gistData.getProofByBlock(PoseidonUnit1L.poseidon([id]), blockNumber));
}
/**
* @dev Retrieve GIST inclusion or non-inclusion proof for a given identity
* for GIST latest snapshot by the blockchain timestamp provided.
* @param id Identity
* @param timestamp Blockchain timestamp
* @return The GIST inclusion or non-inclusion proof for the identity
*/
function getGISTProofByTime(
uint256 id,
uint256 timestamp
) external view returns (IState.GistProof memory) {
return _smtProofAdapter(_gistData.getProofByTime(PoseidonUnit1L.poseidon([id]), timestamp));
}
/**
* @dev Retrieve GIST latest root.
* @return The latest GIST root
*/
function getGISTRoot() external view returns (uint256) {
return _gistData.getRoot();
}
/**
* @dev Retrieve the GIST root history.
* @param start Start index in the root history
* @param length Length of the root history
* @return Array of GIST roots infos
*/
function getGISTRootHistory(
uint256 start,
uint256 length
) external view returns (IState.GistRootInfo[] memory) {
SmtLib.RootEntryInfo[] memory rootInfos = _gistData.getRootHistory(start, length);
IState.GistRootInfo[] memory result = new IState.GistRootInfo[](rootInfos.length);
for (uint256 i = 0; i < rootInfos.length; i++) {
result[i] = _smtRootInfoAdapter(rootInfos[i]);
}
return result;
}
/**
* @dev Retrieve the length of the GIST root history.
* @return The GIST root history length
*/
function getGISTRootHistoryLength() external view returns (uint256) {
return _gistData.rootEntries.length;
}
/**
* @dev Retrieve the specific GIST root information.
* @param root GIST root.
* @return The GIST root information.
*/
function getGISTRootInfo(uint256 root) external view returns (IState.GistRootInfo memory) {
return _smtRootInfoAdapter(_gistData.getRootInfo(root));
}
/**
* @dev Retrieve the GIST root information, which is latest by the block provided.
* @param blockNumber Blockchain block number
* @return The GIST root info
*/
function getGISTRootInfoByBlock(
uint256 blockNumber
) external view returns (IState.GistRootInfo memory) {
return _smtRootInfoAdapter(_gistData.getRootInfoByBlock(blockNumber));
}
/**
* @dev Retrieve the GIST root information, which is latest by the blockchain timestamp provided.
* @param timestamp Blockchain timestamp
* @return The GIST root info
*/
function getGISTRootInfoByTime(
uint256 timestamp
) external view returns (IState.GistRootInfo memory) {
return _smtRootInfoAdapter(_gistData.getRootInfoByTime(timestamp));
}
/**
* @dev Check if identity exists.
* @param id Identity
* @return True if the identity exists
*/
function idExists(uint256 id) public view returns (bool) {
return _stateData.idExists(id);
}
/**
* @dev Check if state exists.
* @param id Identity
* @param state State
* @return True if the state exists
*/
function stateExists(uint256 id, uint256 state) public view returns (bool) {
return _stateData.stateExists(id, state);
}
/**
* @dev Retrieve the timestamp when the state was replaced by another state.
* @param id Identity id
* @param state State of the identity
* @return replacedAt The timestamp when the state of the identity was replaced by another state
*/
function getStateReplacedAt(uint256 id, uint256 state) external view returns (uint256) {
if (isIdTypeSupported(GenesisUtils.getIdType(id))) {
if (_stateData.stateExists(id, state)) {
return _stateData.getStateInfoByIdAndState(id, state).replacedAtTimestamp;
} else if (GenesisUtils.isGenesisState(id, state)) {
return 0;
}
revert("State entry not found");
} else {
StateCrossChainStorage storage $ = _getStateCrossChainStorage();
uint256 replacedAt = $._idToStateReplacedAt[id][state];
if (replacedAt != 0) {
return replacedAt;
}
revert("Cross-chain state not found");
}
}
/**
* @dev Retrieve the timestamp when the GIST root was replaced by another root.
* @param idType Id type
* @param root GIST root
* @return replacedAt The timestamp when the GIST root was replaced by another root
*/
function getGistRootReplacedAt(bytes2 idType, uint256 root) external view returns (uint256) {
if (isIdTypeSupported(idType)) {
if (_gistData.rootExists(root)) {
return _gistData.getRootInfo(root).replacedAtTimestamp;
}
revert("GIST root entry not found");
} else {
StateCrossChainStorage storage $ = _getStateCrossChainStorage();
uint256 replacedAt = $._rootToGistRootReplacedAt[idType][root];
if (replacedAt != 0) {
return replacedAt;
}
revert("Cross-chain GIST root not found");
}
}
/**
* @dev Check if the id type is supported and return the id type
* @param id Identity
* trows if id type is not supported
*/
function getIdTypeIfSupported(uint256 id) public view returns (bytes2) {
bytes2 idType = GenesisUtils.getIdType(id);
require(_stateData.isIdTypeSupported[idType], "id type is not supported");
return idType;
}
/**
* @dev Set supported IdType setter
* @param idType id type
* @param supported ability to enable or disable id type support
*/
function setSupportedIdType(bytes2 idType, bool supported) public onlyOwner {
_stateData.isIdTypeSupported[idType] = supported;
}
/**
* @dev Change the state of an identity (transit to the new state) with ZKP ownership check.
* @param id Identity
* @param oldState Previous identity state
* @param newState New identity state
* @param isOldStateGenesis Is the previous state genesis?
*/
function _transitState(
uint256 id,
uint256 oldState,
uint256 newState,
bool isOldStateGenesis
) internal {
require(id != 0, "ID should not be zero");
require(newState != 0, "New state should not be zero");
if (isOldStateGenesis) {
require(!idExists(id), "Old state is genesis but identity already exists");
// Push old state to state entries, with zero timestamp and block
_stateData.addGenesisState(id, oldState);
} else {
require(idExists(id), "Old state is not genesis but identity does not yet exist");
StateLib.EntryInfo memory prevStateInfo = _stateData.getStateInfoById(id);
require(prevStateInfo.state == oldState, "Old state does not match the latest state");
}
// this checks that oldState != newState as well
require(!stateExists(id, newState), "New state already exists");
_stateData.addState(id, newState);
_gistData.addLeaf(PoseidonUnit1L.poseidon([id]), newState);
}
function _smtProofAdapter(
SmtLib.Proof memory proof
) internal pure returns (IState.GistProof memory) {
// slither-disable-next-line uninitialized-local
uint256[MAX_SMT_DEPTH] memory siblings;
for (uint256 i = 0; i < MAX_SMT_DEPTH; i++) {
siblings[i] = proof.siblings[i];
}
IState.GistProof memory result = IState.GistProof({
root: proof.root,
existence: proof.existence,
siblings: siblings,
index: proof.index,
value: proof.value,
auxExistence: proof.auxExistence,
auxIndex: proof.auxIndex,
auxValue: proof.auxValue
});
return result;
}
function _smtRootInfoAdapter(
SmtLib.RootEntryInfo memory rootInfo
) internal pure returns (IState.GistRootInfo memory) {
return
IState.GistRootInfo({
root: rootInfo.root,
replacedByRoot: rootInfo.replacedByRoot,
createdAtTimestamp: rootInfo.createdAtTimestamp,
replacedAtTimestamp: rootInfo.replacedAtTimestamp,
createdAtBlock: rootInfo.createdAtBlock,
replacedAtBlock: rootInfo.replacedAtBlock
});
}
function _stateEntryInfoAdapter(
StateLib.EntryInfo memory sei
) internal pure returns (IState.StateInfo memory) {
return
IState.StateInfo({
id: sei.id,
state: sei.state,
replacedByState: sei.replacedByState,
createdAtTimestamp: sei.createdAtTimestamp,
replacedAtTimestamp: sei.replacedAtTimestamp,
createdAtBlock: sei.createdAtBlock,
replacedAtBlock: sei.replacedAtBlock
});
}
/**
* @dev Set set default id type internal setter
* @param defaultIdType default id type
*/
function _setDefaultIdType(bytes2 defaultIdType) internal {
_defaultIdType = defaultIdType;
_defaultIdTypeInitialized = true;
_stateData.isIdTypeSupported[defaultIdType] = true;
}
}