@pvium/p2id-core 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/contracts/P2IDVault.sol +202 -75
- package/contracts/PviumIdentity.sol +6 -0
- package/contracts/PviumP2IDVaultProxy.sol +30 -0
- package/contracts/PviumP2IdVaultFactory.sol +228 -30
- package/contracts/PviumVerifier.sol +2 -0
- package/contracts/PviumZKVerifier.sol +137 -111
- package/contracts/interfaces/IP2IDVault.sol +18 -0
- package/contracts/interfaces/IP2IDVaultProxy.sol +4 -0
- package/contracts/interfaces/IP2IDVerifier.sol +3 -0
- package/contracts/interfaces/IP2IdVaultFactory.sol +7 -0
- package/contracts/interfaces/IPviumIdentity.sol +1 -0
- package/dist/cjs/p2idConstants.js +2 -2
- package/dist/esm/p2idConstants.d.ts +2 -2
- package/dist/esm/p2idConstants.js +2 -2
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ import {IP2IdVaultFactory} from "./interfaces/IP2IdVaultFactory.sol";
|
|
|
5
5
|
import {IP2IDPolicy} from "./interfaces/IP2IDPolicy.sol";
|
|
6
6
|
import {P2IDVault} from "./P2IDVault.sol";
|
|
7
7
|
import {PviumP2IDVaultProxy} from "./PviumP2IDVaultProxy.sol";
|
|
8
|
+
import {IP2IDVerifier} from "./interfaces/IP2IDVerifier.sol";
|
|
8
9
|
import {IP2IDVault} from "./interfaces/IP2IDVault.sol";
|
|
9
10
|
|
|
10
11
|
/// @title PviumP2IdVaultFactory
|
|
@@ -18,12 +19,122 @@ import {IP2IDVault} from "./interfaces/IP2IDVault.sol";
|
|
|
18
19
|
/// Manual registration checks code presence; deterministic deployment also checks factory binding.
|
|
19
20
|
/// Neither path verifies storage compatibility or implementation behavior.
|
|
20
21
|
contract PviumP2IdVaultFactory is IP2IdVaultFactory {
|
|
22
|
+
/// @notice Alpha proof-acceptance status by pinned verification-key SHA-256.
|
|
23
|
+
/// @dev Inverted storage: a key is in alpha unless it has been released, so a verification key
|
|
24
|
+
/// nobody has configured (a new circuit) requires attestation from its first proof.
|
|
25
|
+
mapping(bytes32 => bool) public alphaReleased;
|
|
26
|
+
mapping(bytes32 => uint256) public alphaRevision;
|
|
27
|
+
address public defaultAttester;
|
|
28
|
+
uint256 public alphaEpoch;
|
|
29
|
+
bytes32 public constant ALPHA_AUTHORIZATION_TYPEHASH =
|
|
30
|
+
keccak256(
|
|
31
|
+
"AlphaAuthorization(address vault,address caller,bytes32 callHash,uint256 nonce,uint256 deadline,uint256 epoch)"
|
|
32
|
+
);
|
|
33
|
+
event AlphaModeSet(bytes32 indexed vkHash, bool enabled, uint256 epoch);
|
|
34
|
+
event DefaultAttesterSet(address attester, uint256 epoch);
|
|
35
|
+
error InvalidAlphaAuthorization();
|
|
36
|
+
error AlphaAuthorizationExpired();
|
|
37
|
+
error InvalidVkHash();
|
|
38
|
+
|
|
39
|
+
/// @notice Whether proofs under `vkHash` need an alpha attestation. True for every key until
|
|
40
|
+
/// the owner releases it with setAlpha(vkHash, false).
|
|
41
|
+
function isAlpha(bytes32 vkHash) public view returns (bool) {
|
|
42
|
+
return !alphaReleased[vkHash];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function setAlpha(bytes32 vkHash, bool enabled) external onlyOwner {
|
|
46
|
+
if (vkHash == bytes32(0)) revert InvalidVkHash();
|
|
47
|
+
if (enabled && alphaReleased[vkHash]) ++alphaRevision[vkHash]; // back into alpha: voids owners cached while released
|
|
48
|
+
alphaReleased[vkHash] = !enabled;
|
|
49
|
+
++alphaEpoch;
|
|
50
|
+
emit AlphaModeSet(vkHash, enabled, alphaEpoch);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// @notice Replace the alpha attester immediately; invalidates outstanding alpha approvals.
|
|
54
|
+
function setDefaultAttester(address attester) external onlyOwner {
|
|
55
|
+
if (attester == address(0)) revert InvalidAlphaAuthorization();
|
|
56
|
+
defaultAttester = attester;
|
|
57
|
+
++alphaEpoch;
|
|
58
|
+
emit DefaultAttesterSet(attester, alphaEpoch);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function alphaAuthorizationDigest(
|
|
62
|
+
address vault,
|
|
63
|
+
address caller,
|
|
64
|
+
bytes32 callHash,
|
|
65
|
+
uint256 nonce,
|
|
66
|
+
uint256 deadline
|
|
67
|
+
) public view returns (bytes32) {
|
|
68
|
+
bytes32 domain = keccak256(
|
|
69
|
+
abi.encode(
|
|
70
|
+
keccak256(
|
|
71
|
+
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
|
|
72
|
+
),
|
|
73
|
+
keccak256("PviumAlpha"),
|
|
74
|
+
keccak256("1"),
|
|
75
|
+
block.chainid,
|
|
76
|
+
address(this)
|
|
77
|
+
)
|
|
78
|
+
);
|
|
79
|
+
return
|
|
80
|
+
keccak256(
|
|
81
|
+
abi.encodePacked(
|
|
82
|
+
"\x19\x01",
|
|
83
|
+
domain,
|
|
84
|
+
keccak256(
|
|
85
|
+
abi.encode(
|
|
86
|
+
ALPHA_AUTHORIZATION_TYPEHASH,
|
|
87
|
+
vault,
|
|
88
|
+
caller,
|
|
89
|
+
callHash,
|
|
90
|
+
nonce,
|
|
91
|
+
deadline,
|
|
92
|
+
alphaEpoch
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function verifyAlphaAuthorization(
|
|
100
|
+
address vault,
|
|
101
|
+
address caller,
|
|
102
|
+
bytes32 callHash,
|
|
103
|
+
uint256 nonce,
|
|
104
|
+
uint256 deadline,
|
|
105
|
+
bytes calldata signature
|
|
106
|
+
) external view {
|
|
107
|
+
if (block.timestamp > deadline) revert AlphaAuthorizationExpired();
|
|
108
|
+
if (signature.length != 65) revert InvalidAlphaAuthorization();
|
|
109
|
+
bytes32 r;
|
|
110
|
+
bytes32 s;
|
|
111
|
+
uint8 v;
|
|
112
|
+
assembly {
|
|
113
|
+
r := calldataload(signature.offset)
|
|
114
|
+
s := calldataload(add(signature.offset, 32))
|
|
115
|
+
v := byte(0, calldataload(add(signature.offset, 64)))
|
|
116
|
+
}
|
|
117
|
+
if (
|
|
118
|
+
(v != 27 && v != 28) ||
|
|
119
|
+
uint256(s) >
|
|
120
|
+
0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0
|
|
121
|
+
) revert InvalidAlphaAuthorization();
|
|
122
|
+
address signer = ecrecover(
|
|
123
|
+
alphaAuthorizationDigest(vault, caller, callHash, nonce, deadline),
|
|
124
|
+
v,
|
|
125
|
+
r,
|
|
126
|
+
s
|
|
127
|
+
);
|
|
128
|
+
if (signer == address(0) || signer != defaultAttester)
|
|
129
|
+
revert InvalidAlphaAuthorization();
|
|
130
|
+
}
|
|
21
131
|
bytes32 public immutable nsHash;
|
|
22
132
|
/// @notice Starting implementation for new proxies, changed only by a delayed default proposal.
|
|
23
133
|
address public baseImplementation;
|
|
24
134
|
/// @notice Original implementation, kept registered as a fallback.
|
|
25
135
|
address public immutable initialImplementation;
|
|
26
|
-
bytes32 public constant IMPLEMENTATION_SALT =
|
|
136
|
+
bytes32 public constant IMPLEMENTATION_SALT =
|
|
137
|
+
keccak256("pvium.vault.implementation.v1");
|
|
27
138
|
bool public proposedImplementationMakeDefault;
|
|
28
139
|
/// @notice Target allowlist checked by PviumP2IDVaultProxy.upgradeTo.
|
|
29
140
|
mapping(address implementation => bool) public isRegisteredImplementation;
|
|
@@ -84,13 +195,19 @@ contract PviumP2IdVaultFactory is IP2IdVaultFactory {
|
|
|
84
195
|
address _defaultVerifier,
|
|
85
196
|
uint64 _policyChangeDelay,
|
|
86
197
|
uint64 _minRefundWindow,
|
|
87
|
-
uint64 _maxRefundWindow
|
|
198
|
+
uint64 _maxRefundWindow,
|
|
199
|
+
address _alphaAttester
|
|
88
200
|
) {
|
|
89
201
|
if (_owner == address(0)) revert InvalidOwner();
|
|
90
202
|
if (_minRefundWindow > _maxRefundWindow) revert InvalidRefundWindow();
|
|
91
203
|
if (_policy.code.length == 0) revert InvalidPolicy();
|
|
92
|
-
if (!IP2IDPolicy(_policy).isVerifierAllowed(_defaultVerifier))
|
|
204
|
+
if (!IP2IDPolicy(_policy).isVerifierAllowed(_defaultVerifier))
|
|
205
|
+
revert VerifierNotApproved(_defaultVerifier);
|
|
93
206
|
owner = _owner;
|
|
207
|
+
defaultAttester = _alphaAttester == address(0)
|
|
208
|
+
? _owner
|
|
209
|
+
: _alphaAttester;
|
|
210
|
+
emit DefaultAttesterSet(defaultAttester, alphaEpoch);
|
|
94
211
|
emit OwnershipTransferred(address(0), _owner);
|
|
95
212
|
nsHash = _nsHash;
|
|
96
213
|
policyChangeDelay = _policyChangeDelay;
|
|
@@ -104,43 +221,76 @@ contract PviumP2IdVaultFactory is IP2IdVaultFactory {
|
|
|
104
221
|
baseImplementation = base;
|
|
105
222
|
initialImplementation = base;
|
|
106
223
|
isRegisteredImplementation[base] = true;
|
|
224
|
+
bytes32 initialVkHash = IP2IDVerifier(_defaultVerifier).vkHash();
|
|
225
|
+
if (initialVkHash == bytes32(0)) revert InvalidVkHash();
|
|
226
|
+
alphaRevision[initialVkHash] = 1; // in alpha by default, like every key
|
|
107
227
|
emit ImplementationRegistered(base);
|
|
108
228
|
}
|
|
109
229
|
|
|
110
230
|
// ------------------------------------------------------------------ vault implementations (timelocked)
|
|
111
231
|
|
|
112
232
|
/// @notice Predict the CREATE2 address from full creation code, including constructor arguments.
|
|
113
|
-
function implementationFor(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
233
|
+
function implementationFor(
|
|
234
|
+
bytes32 creationCodeHash
|
|
235
|
+
) public view returns (address) {
|
|
236
|
+
return
|
|
237
|
+
address(
|
|
238
|
+
uint160(
|
|
239
|
+
uint256(
|
|
240
|
+
keccak256(
|
|
241
|
+
abi.encodePacked(
|
|
242
|
+
bytes1(0xff),
|
|
243
|
+
address(this),
|
|
244
|
+
IMPLEMENTATION_SALT,
|
|
245
|
+
creationCodeHash
|
|
246
|
+
)
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
);
|
|
117
251
|
}
|
|
118
252
|
|
|
119
253
|
/// @notice Deploy full creation code with CREATE2 and propose registration in the same transaction.
|
|
120
254
|
/// @dev Reuses existing code. An identical pending proposal keeps its ETA; a different proposal
|
|
121
255
|
/// must first be cancelled. makeDefault affects new proxies only, after registration.
|
|
122
|
-
function deployVaultImplementation(
|
|
123
|
-
|
|
124
|
-
|
|
256
|
+
function deployVaultImplementation(
|
|
257
|
+
bytes memory creationCode,
|
|
258
|
+
bool makeDefault
|
|
259
|
+
) external onlyOwner returns (address implementation) {
|
|
125
260
|
if (creationCode.length == 0) revert InvalidImplementation();
|
|
126
261
|
implementation = implementationFor(keccak256(creationCode));
|
|
127
262
|
if (implementation.code.length == 0) {
|
|
128
263
|
bytes32 salt = IMPLEMENTATION_SALT;
|
|
129
264
|
address deployed;
|
|
130
265
|
assembly {
|
|
131
|
-
deployed := create2(
|
|
266
|
+
deployed := create2(
|
|
267
|
+
0,
|
|
268
|
+
add(creationCode, 32),
|
|
269
|
+
mload(creationCode),
|
|
270
|
+
salt
|
|
271
|
+
)
|
|
132
272
|
}
|
|
133
|
-
if (deployed != implementation || deployed.code.length == 0)
|
|
134
|
-
|
|
273
|
+
if (deployed != implementation || deployed.code.length == 0)
|
|
274
|
+
revert InvalidImplementation();
|
|
275
|
+
emit ImplementationDeployed(
|
|
276
|
+
implementation,
|
|
277
|
+
keccak256(creationCode)
|
|
278
|
+
);
|
|
135
279
|
}
|
|
136
280
|
_checkFactoryBinding(implementation);
|
|
137
281
|
if (proposedImplementationEta != 0) {
|
|
138
|
-
if (
|
|
282
|
+
if (
|
|
283
|
+
proposedImplementation != implementation ||
|
|
284
|
+
proposedImplementationMakeDefault != makeDefault
|
|
285
|
+
) {
|
|
139
286
|
revert ImplementationProposalPending();
|
|
140
287
|
}
|
|
141
288
|
return implementation;
|
|
142
289
|
}
|
|
143
|
-
if (
|
|
290
|
+
if (
|
|
291
|
+
isRegisteredImplementation[implementation] &&
|
|
292
|
+
(!makeDefault || baseImplementation == implementation)
|
|
293
|
+
) {
|
|
144
294
|
return implementation;
|
|
145
295
|
}
|
|
146
296
|
_proposeImplementation(implementation, makeDefault);
|
|
@@ -148,28 +298,53 @@ contract PviumP2IdVaultFactory is IP2IdVaultFactory {
|
|
|
148
298
|
|
|
149
299
|
/// @notice Propose an opt-in upgrade target. Replaces a pending proposal and resets its delay.
|
|
150
300
|
function proposeImplementation(address implementation) external onlyOwner {
|
|
151
|
-
if (
|
|
301
|
+
if (
|
|
302
|
+
implementation.code.length == 0 ||
|
|
303
|
+
isRegisteredImplementation[implementation]
|
|
304
|
+
) revert InvalidImplementation();
|
|
305
|
+
_checkFactoryBinding(implementation); // bound to this factory, however it was deployed
|
|
152
306
|
_proposeImplementation(implementation, false);
|
|
153
307
|
}
|
|
154
308
|
|
|
155
309
|
/// @notice Propose a registered target as the starting implementation for future proxies.
|
|
156
|
-
function proposeDefaultImplementation(
|
|
157
|
-
|
|
310
|
+
function proposeDefaultImplementation(
|
|
311
|
+
address implementation
|
|
312
|
+
) external onlyOwner {
|
|
313
|
+
if (
|
|
314
|
+
!isRegisteredImplementation[implementation] ||
|
|
315
|
+
implementation == baseImplementation
|
|
316
|
+
) revert InvalidImplementation();
|
|
158
317
|
_checkFactoryBinding(implementation);
|
|
159
318
|
_proposeImplementation(implementation, true);
|
|
160
319
|
}
|
|
161
320
|
|
|
162
|
-
function _proposeImplementation(
|
|
321
|
+
function _proposeImplementation(
|
|
322
|
+
address implementation,
|
|
323
|
+
bool makeDefault
|
|
324
|
+
) private {
|
|
163
325
|
proposedImplementation = implementation;
|
|
164
326
|
proposedImplementationMakeDefault = makeDefault;
|
|
165
|
-
proposedImplementationEta =
|
|
327
|
+
proposedImplementationEta =
|
|
328
|
+
uint64(block.timestamp) +
|
|
329
|
+
DEFAULT_VERIFIER_DELAY;
|
|
166
330
|
emit ImplementationProposed(implementation, proposedImplementationEta);
|
|
167
|
-
emit ImplementationDefaultProposed(
|
|
331
|
+
emit ImplementationDefaultProposed(
|
|
332
|
+
implementation,
|
|
333
|
+
makeDefault,
|
|
334
|
+
proposedImplementationEta
|
|
335
|
+
);
|
|
168
336
|
}
|
|
169
337
|
|
|
170
338
|
function _checkFactoryBinding(address implementation) private view {
|
|
171
|
-
(bool ok, bytes memory result) = implementation.staticcall(
|
|
172
|
-
|
|
339
|
+
(bool ok, bytes memory result) = implementation.staticcall(
|
|
340
|
+
abi.encodeWithSignature("factory()")
|
|
341
|
+
);
|
|
342
|
+
if (
|
|
343
|
+
!ok ||
|
|
344
|
+
result.length != 32 ||
|
|
345
|
+
abi.decode(result, (bytes32)) !=
|
|
346
|
+
bytes32(uint256(uint160(address(this))))
|
|
347
|
+
) {
|
|
173
348
|
revert InvalidImplementation();
|
|
174
349
|
}
|
|
175
350
|
}
|
|
@@ -189,7 +364,7 @@ contract PviumP2IdVaultFactory is IP2IdVaultFactory {
|
|
|
189
364
|
address implementation = proposedImplementation;
|
|
190
365
|
bool makeDefault = proposedImplementationMakeDefault;
|
|
191
366
|
if (implementation.code.length == 0) revert InvalidImplementation();
|
|
192
|
-
|
|
367
|
+
_checkFactoryBinding(implementation); // re-checked at registration: the code could not change, but the rule is one
|
|
193
368
|
delete proposedImplementation;
|
|
194
369
|
delete proposedImplementationEta;
|
|
195
370
|
delete proposedImplementationMakeDefault;
|
|
@@ -204,7 +379,11 @@ contract PviumP2IdVaultFactory is IP2IdVaultFactory {
|
|
|
204
379
|
/// @notice Remove a registered target immediately, except the current default and original implementation.
|
|
205
380
|
/// This changes the registry, not the implementation stored in existing proxies.
|
|
206
381
|
function revokeImplementation(address implementation) external onlyOwner {
|
|
207
|
-
if (
|
|
382
|
+
if (
|
|
383
|
+
implementation == baseImplementation ||
|
|
384
|
+
implementation == initialImplementation ||
|
|
385
|
+
!isRegisteredImplementation[implementation]
|
|
386
|
+
) revert InvalidImplementation();
|
|
208
387
|
isRegisteredImplementation[implementation] = false;
|
|
209
388
|
emit ImplementationRevoked(implementation);
|
|
210
389
|
}
|
|
@@ -233,7 +412,8 @@ contract PviumP2IdVaultFactory is IP2IdVaultFactory {
|
|
|
233
412
|
if (eta == 0) revert NothingProposed();
|
|
234
413
|
if (block.timestamp < eta) revert TimelockNotElapsed(eta);
|
|
235
414
|
address newPolicy = proposedPolicy;
|
|
236
|
-
if (!IP2IDPolicy(newPolicy).isVerifierAllowed(defaultVerifier))
|
|
415
|
+
if (!IP2IDPolicy(newPolicy).isVerifierAllowed(defaultVerifier))
|
|
416
|
+
revert VerifierNotApproved(defaultVerifier);
|
|
237
417
|
delete proposedPolicy;
|
|
238
418
|
delete proposedPolicyEta;
|
|
239
419
|
policy = newPolicy;
|
|
@@ -246,7 +426,8 @@ contract PviumP2IdVaultFactory is IP2IdVaultFactory {
|
|
|
246
426
|
/// after DEFAULT_VERIFIER_DELAY (14 days), via activateDefaultVerifier(). Replaces any
|
|
247
427
|
/// pending proposal.
|
|
248
428
|
function proposeDefaultVerifier(address verifier) external onlyOwner {
|
|
249
|
-
if (!IP2IDPolicy(policy).isVerifierAllowed(verifier))
|
|
429
|
+
if (!IP2IDPolicy(policy).isVerifierAllowed(verifier))
|
|
430
|
+
revert VerifierNotApproved(verifier);
|
|
250
431
|
proposedDefaultVerifier = verifier;
|
|
251
432
|
proposedDefaultEta = uint64(block.timestamp) + DEFAULT_VERIFIER_DELAY;
|
|
252
433
|
emit DefaultVerifierProposed(verifier, proposedDefaultEta);
|
|
@@ -266,7 +447,8 @@ contract PviumP2IdVaultFactory is IP2IdVaultFactory {
|
|
|
266
447
|
if (eta == 0) revert NothingProposed();
|
|
267
448
|
if (block.timestamp < eta) revert TimelockNotElapsed(eta);
|
|
268
449
|
address verifier = proposedDefaultVerifier;
|
|
269
|
-
if (!IP2IDPolicy(policy).isVerifierAllowed(verifier))
|
|
450
|
+
if (!IP2IDPolicy(policy).isVerifierAllowed(verifier))
|
|
451
|
+
revert VerifierNotApproved(verifier);
|
|
270
452
|
delete proposedDefaultVerifier;
|
|
271
453
|
delete proposedDefaultEta;
|
|
272
454
|
defaultVerifier = verifier;
|
|
@@ -326,7 +508,12 @@ contract PviumP2IdVaultFactory is IP2IdVaultFactory {
|
|
|
326
508
|
vault = vaultFor(identityHash);
|
|
327
509
|
if (vault.code.length != 0) return vault;
|
|
328
510
|
vault = address(new PviumP2IDVaultProxy{salt: identityHash}());
|
|
329
|
-
IP2IDVault(vault).initialize(
|
|
511
|
+
IP2IDVault(vault).initialize(
|
|
512
|
+
nsHash,
|
|
513
|
+
identityHash,
|
|
514
|
+
minRefundWindow,
|
|
515
|
+
maxRefundWindow
|
|
516
|
+
);
|
|
330
517
|
emit VaultDeployed(identityHash, vault);
|
|
331
518
|
}
|
|
332
519
|
|
|
@@ -385,7 +572,18 @@ contract PviumP2IdVaultFactory is IP2IdVaultFactory {
|
|
|
385
572
|
vault = deploy(identityHash);
|
|
386
573
|
if (token == address(0)) {
|
|
387
574
|
// Native coin: forward exactly the value sent; the vault checks it equals `amount`.
|
|
388
|
-
return (
|
|
575
|
+
return (
|
|
576
|
+
vault,
|
|
577
|
+
P2IDVault(payable(vault)).fundFor{value: msg.value}(
|
|
578
|
+
msg.sender,
|
|
579
|
+
verifier,
|
|
580
|
+
token,
|
|
581
|
+
amount,
|
|
582
|
+
constraint,
|
|
583
|
+
refundWindow,
|
|
584
|
+
ref
|
|
585
|
+
)
|
|
586
|
+
);
|
|
389
587
|
}
|
|
390
588
|
if (msg.value != 0) revert UnexpectedValue();
|
|
391
589
|
// Measure the factory's balance increase, approve that amount to the vault, and record
|
|
@@ -103,6 +103,8 @@ contract PviumVerifier is IP2IDVerifier {
|
|
|
103
103
|
return (a.wallet, a.iat);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
function vkHash() external view returns (bytes32) { return pviumIdentity.vkHash(); }
|
|
107
|
+
|
|
106
108
|
/// @inheritdoc IP2IDVerifier
|
|
107
109
|
/// @dev Forward the identity key-set revision; constraint-signer changes do not increment it.
|
|
108
110
|
function revision() external view returns (uint64) {
|