@pvium/p2id-core 0.1.2 → 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 +320 -214
- package/contracts/PviumIdentity.sol +29 -36
- package/contracts/PviumP2IDPolicy.sol +4 -7
- package/contracts/PviumP2IDVaultProxy.sol +199 -0
- package/contracts/PviumP2IdVaultFactory.sol +346 -34
- package/contracts/PviumVerifier.sol +15 -21
- package/contracts/PviumZKVerifier.sol +137 -111
- package/contracts/interfaces/IP2IDPolicy.sol +5 -21
- package/contracts/interfaces/IP2IDVault.sol +42 -17
- package/contracts/interfaces/IP2IDVaultProxy.sol +42 -0
- package/contracts/interfaces/IP2IDVerifier.sol +12 -13
- package/contracts/interfaces/IP2IdVaultFactory.sol +41 -7
- package/contracts/interfaces/IPviumIdentity.sol +6 -7
- package/contracts/lib/P2IDHash.sol +5 -5
- 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
|
@@ -4,33 +4,151 @@ pragma solidity ^0.8.27;
|
|
|
4
4
|
import {IP2IdVaultFactory} from "./interfaces/IP2IdVaultFactory.sol";
|
|
5
5
|
import {IP2IDPolicy} from "./interfaces/IP2IDPolicy.sol";
|
|
6
6
|
import {P2IDVault} from "./P2IDVault.sol";
|
|
7
|
+
import {PviumP2IDVaultProxy} from "./PviumP2IDVaultProxy.sol";
|
|
8
|
+
import {IP2IDVerifier} from "./interfaces/IP2IDVerifier.sol";
|
|
9
|
+
import {IP2IDVault} from "./interfaces/IP2IDVault.sol";
|
|
7
10
|
|
|
8
11
|
/// @title PviumP2IdVaultFactory
|
|
9
|
-
/// @notice Deploys
|
|
10
|
-
///
|
|
11
|
-
///
|
|
12
|
-
///
|
|
13
|
-
///
|
|
14
|
-
///
|
|
15
|
-
///
|
|
16
|
-
///
|
|
17
|
-
///
|
|
18
|
-
/// timelock (propose, wait, activate): the policy after `policyChangeDelay`, the default
|
|
19
|
-
/// verifier after DEFAULT_VERIFIER_DELAY, a fixed 14 days. Direct transfers follow the default
|
|
20
|
-
/// verifier and have no refund path, so a change to it gets the longer notice: anyone who
|
|
21
|
-
/// objects can sweep first, and the vault guarantees nothing can freeze their claim meanwhile.
|
|
22
|
-
/// What a policy can do is bounded by the vault (fee cap, fees fixed at funding, no fee on
|
|
23
|
-
/// refunds, never redirecting a payout, never blocking claims through the default verifier).
|
|
12
|
+
/// @notice Deploys one PviumP2IDVaultProxy per identity with CREATE2 (salt = identityHash) and
|
|
13
|
+
/// supplies policy and default-verifier settings. The proxy takes no constructor arguments;
|
|
14
|
+
/// its address is the low 160 bits of keccak256(0xff || factory || identityHash || initCodeHash()).
|
|
15
|
+
/// @dev The policy and the default verifier change only through a timelock (propose, wait,
|
|
16
|
+
/// activate): the policy after `policyChangeDelay`, the default verifier after the fixed
|
|
17
|
+
/// DEFAULT_VERIFIER_DELAY. Implementation registration also has a timelock; revocation is
|
|
18
|
+
/// immediate and does not change existing proxies. Vault upgrades preserve their addresses.
|
|
19
|
+
/// Manual registration checks code presence; deterministic deployment also checks factory binding.
|
|
20
|
+
/// Neither path verifies storage compatibility or implementation behavior.
|
|
24
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
|
+
}
|
|
25
131
|
bytes32 public immutable nsHash;
|
|
132
|
+
/// @notice Starting implementation for new proxies, changed only by a delayed default proposal.
|
|
133
|
+
address public baseImplementation;
|
|
134
|
+
/// @notice Original implementation, kept registered as a fallback.
|
|
135
|
+
address public immutable initialImplementation;
|
|
136
|
+
bytes32 public constant IMPLEMENTATION_SALT =
|
|
137
|
+
keccak256("pvium.vault.implementation.v1");
|
|
138
|
+
bool public proposedImplementationMakeDefault;
|
|
139
|
+
/// @notice Target allowlist checked by PviumP2IDVaultProxy.upgradeTo.
|
|
140
|
+
mapping(address implementation => bool) public isRegisteredImplementation;
|
|
141
|
+
address public proposedImplementation;
|
|
142
|
+
/// @notice Earliest time the proposed implementation can be registered; 0 when nothing is proposed.
|
|
143
|
+
uint64 public proposedImplementationEta;
|
|
26
144
|
uint64 public immutable minRefundWindow;
|
|
27
145
|
uint64 public immutable maxRefundWindow;
|
|
28
146
|
|
|
29
|
-
/// @notice
|
|
147
|
+
/// @notice Administrative owner for settings and implementation registration; transferred in two steps.
|
|
30
148
|
address public owner;
|
|
31
149
|
address public pendingOwner;
|
|
32
150
|
|
|
33
|
-
/// @notice
|
|
151
|
+
/// @notice Policy address returned to vaults; changed through proposePolicy/activatePolicy.
|
|
34
152
|
address public policy;
|
|
35
153
|
/// @notice Verifier used when a payer does not choose one. Changes only via the timelock below.
|
|
36
154
|
address public defaultVerifier;
|
|
@@ -67,6 +185,8 @@ contract PviumP2IdVaultFactory is IP2IdVaultFactory {
|
|
|
67
185
|
error TokenBalanceQueryFailed();
|
|
68
186
|
error NothingReceived();
|
|
69
187
|
error UnexpectedValue();
|
|
188
|
+
error InvalidImplementation();
|
|
189
|
+
error ImplementationProposalPending();
|
|
70
190
|
|
|
71
191
|
constructor(
|
|
72
192
|
address _owner,
|
|
@@ -75,13 +195,19 @@ contract PviumP2IdVaultFactory is IP2IdVaultFactory {
|
|
|
75
195
|
address _defaultVerifier,
|
|
76
196
|
uint64 _policyChangeDelay,
|
|
77
197
|
uint64 _minRefundWindow,
|
|
78
|
-
uint64 _maxRefundWindow
|
|
198
|
+
uint64 _maxRefundWindow,
|
|
199
|
+
address _alphaAttester
|
|
79
200
|
) {
|
|
80
201
|
if (_owner == address(0)) revert InvalidOwner();
|
|
81
202
|
if (_minRefundWindow > _maxRefundWindow) revert InvalidRefundWindow();
|
|
82
203
|
if (_policy.code.length == 0) revert InvalidPolicy();
|
|
83
|
-
if (!IP2IDPolicy(_policy).isVerifierAllowed(_defaultVerifier))
|
|
204
|
+
if (!IP2IDPolicy(_policy).isVerifierAllowed(_defaultVerifier))
|
|
205
|
+
revert VerifierNotApproved(_defaultVerifier);
|
|
84
206
|
owner = _owner;
|
|
207
|
+
defaultAttester = _alphaAttester == address(0)
|
|
208
|
+
? _owner
|
|
209
|
+
: _alphaAttester;
|
|
210
|
+
emit DefaultAttesterSet(defaultAttester, alphaEpoch);
|
|
85
211
|
emit OwnershipTransferred(address(0), _owner);
|
|
86
212
|
nsHash = _nsHash;
|
|
87
213
|
policyChangeDelay = _policyChangeDelay;
|
|
@@ -91,6 +217,175 @@ contract PviumP2IdVaultFactory is IP2IdVaultFactory {
|
|
|
91
217
|
emit PolicyActivated(_policy);
|
|
92
218
|
defaultVerifier = _defaultVerifier;
|
|
93
219
|
emit DefaultVerifierActivated(_defaultVerifier);
|
|
220
|
+
address base = address(new P2IDVault(address(this)));
|
|
221
|
+
baseImplementation = base;
|
|
222
|
+
initialImplementation = base;
|
|
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
|
|
227
|
+
emit ImplementationRegistered(base);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ------------------------------------------------------------------ vault implementations (timelocked)
|
|
231
|
+
|
|
232
|
+
/// @notice Predict the CREATE2 address from full creation code, including constructor arguments.
|
|
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
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/// @notice Deploy full creation code with CREATE2 and propose registration in the same transaction.
|
|
254
|
+
/// @dev Reuses existing code. An identical pending proposal keeps its ETA; a different proposal
|
|
255
|
+
/// must first be cancelled. makeDefault affects new proxies only, after registration.
|
|
256
|
+
function deployVaultImplementation(
|
|
257
|
+
bytes memory creationCode,
|
|
258
|
+
bool makeDefault
|
|
259
|
+
) external onlyOwner returns (address implementation) {
|
|
260
|
+
if (creationCode.length == 0) revert InvalidImplementation();
|
|
261
|
+
implementation = implementationFor(keccak256(creationCode));
|
|
262
|
+
if (implementation.code.length == 0) {
|
|
263
|
+
bytes32 salt = IMPLEMENTATION_SALT;
|
|
264
|
+
address deployed;
|
|
265
|
+
assembly {
|
|
266
|
+
deployed := create2(
|
|
267
|
+
0,
|
|
268
|
+
add(creationCode, 32),
|
|
269
|
+
mload(creationCode),
|
|
270
|
+
salt
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
if (deployed != implementation || deployed.code.length == 0)
|
|
274
|
+
revert InvalidImplementation();
|
|
275
|
+
emit ImplementationDeployed(
|
|
276
|
+
implementation,
|
|
277
|
+
keccak256(creationCode)
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
_checkFactoryBinding(implementation);
|
|
281
|
+
if (proposedImplementationEta != 0) {
|
|
282
|
+
if (
|
|
283
|
+
proposedImplementation != implementation ||
|
|
284
|
+
proposedImplementationMakeDefault != makeDefault
|
|
285
|
+
) {
|
|
286
|
+
revert ImplementationProposalPending();
|
|
287
|
+
}
|
|
288
|
+
return implementation;
|
|
289
|
+
}
|
|
290
|
+
if (
|
|
291
|
+
isRegisteredImplementation[implementation] &&
|
|
292
|
+
(!makeDefault || baseImplementation == implementation)
|
|
293
|
+
) {
|
|
294
|
+
return implementation;
|
|
295
|
+
}
|
|
296
|
+
_proposeImplementation(implementation, makeDefault);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/// @notice Propose an opt-in upgrade target. Replaces a pending proposal and resets its delay.
|
|
300
|
+
function proposeImplementation(address implementation) external onlyOwner {
|
|
301
|
+
if (
|
|
302
|
+
implementation.code.length == 0 ||
|
|
303
|
+
isRegisteredImplementation[implementation]
|
|
304
|
+
) revert InvalidImplementation();
|
|
305
|
+
_checkFactoryBinding(implementation); // bound to this factory, however it was deployed
|
|
306
|
+
_proposeImplementation(implementation, false);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/// @notice Propose a registered target as the starting implementation for future proxies.
|
|
310
|
+
function proposeDefaultImplementation(
|
|
311
|
+
address implementation
|
|
312
|
+
) external onlyOwner {
|
|
313
|
+
if (
|
|
314
|
+
!isRegisteredImplementation[implementation] ||
|
|
315
|
+
implementation == baseImplementation
|
|
316
|
+
) revert InvalidImplementation();
|
|
317
|
+
_checkFactoryBinding(implementation);
|
|
318
|
+
_proposeImplementation(implementation, true);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function _proposeImplementation(
|
|
322
|
+
address implementation,
|
|
323
|
+
bool makeDefault
|
|
324
|
+
) private {
|
|
325
|
+
proposedImplementation = implementation;
|
|
326
|
+
proposedImplementationMakeDefault = makeDefault;
|
|
327
|
+
proposedImplementationEta =
|
|
328
|
+
uint64(block.timestamp) +
|
|
329
|
+
DEFAULT_VERIFIER_DELAY;
|
|
330
|
+
emit ImplementationProposed(implementation, proposedImplementationEta);
|
|
331
|
+
emit ImplementationDefaultProposed(
|
|
332
|
+
implementation,
|
|
333
|
+
makeDefault,
|
|
334
|
+
proposedImplementationEta
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function _checkFactoryBinding(address implementation) private view {
|
|
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
|
+
) {
|
|
348
|
+
revert InvalidImplementation();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function cancelImplementationProposal() external onlyOwner {
|
|
353
|
+
if (proposedImplementationEta == 0) revert NothingProposed();
|
|
354
|
+
emit ImplementationProposalCancelled(proposedImplementation);
|
|
355
|
+
delete proposedImplementation;
|
|
356
|
+
delete proposedImplementationEta;
|
|
357
|
+
delete proposedImplementationMakeDefault;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function registerImplementation() external onlyOwner {
|
|
361
|
+
uint64 eta = proposedImplementationEta;
|
|
362
|
+
if (eta == 0) revert NothingProposed();
|
|
363
|
+
if (block.timestamp < eta) revert TimelockNotElapsed(eta);
|
|
364
|
+
address implementation = proposedImplementation;
|
|
365
|
+
bool makeDefault = proposedImplementationMakeDefault;
|
|
366
|
+
if (implementation.code.length == 0) revert InvalidImplementation();
|
|
367
|
+
_checkFactoryBinding(implementation); // re-checked at registration: the code could not change, but the rule is one
|
|
368
|
+
delete proposedImplementation;
|
|
369
|
+
delete proposedImplementationEta;
|
|
370
|
+
delete proposedImplementationMakeDefault;
|
|
371
|
+
isRegisteredImplementation[implementation] = true;
|
|
372
|
+
emit ImplementationRegistered(implementation);
|
|
373
|
+
if (makeDefault) {
|
|
374
|
+
baseImplementation = implementation;
|
|
375
|
+
emit DefaultImplementationActivated(implementation);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/// @notice Remove a registered target immediately, except the current default and original implementation.
|
|
380
|
+
/// This changes the registry, not the implementation stored in existing proxies.
|
|
381
|
+
function revokeImplementation(address implementation) external onlyOwner {
|
|
382
|
+
if (
|
|
383
|
+
implementation == baseImplementation ||
|
|
384
|
+
implementation == initialImplementation ||
|
|
385
|
+
!isRegisteredImplementation[implementation]
|
|
386
|
+
) revert InvalidImplementation();
|
|
387
|
+
isRegisteredImplementation[implementation] = false;
|
|
388
|
+
emit ImplementationRevoked(implementation);
|
|
94
389
|
}
|
|
95
390
|
|
|
96
391
|
// ------------------------------------------------------------------ policy (timelocked)
|
|
@@ -111,14 +406,14 @@ contract PviumP2IdVaultFactory is IP2IdVaultFactory {
|
|
|
111
406
|
delete proposedPolicyEta;
|
|
112
407
|
}
|
|
113
408
|
|
|
114
|
-
/// @notice
|
|
115
|
-
/// must allow the current default verifier, so bare transfers stay claimable.
|
|
409
|
+
/// @notice Set policy after the proposal delay, provided it approves the current default verifier.
|
|
116
410
|
function activatePolicy() external onlyOwner {
|
|
117
411
|
uint64 eta = proposedPolicyEta;
|
|
118
412
|
if (eta == 0) revert NothingProposed();
|
|
119
413
|
if (block.timestamp < eta) revert TimelockNotElapsed(eta);
|
|
120
414
|
address newPolicy = proposedPolicy;
|
|
121
|
-
if (!IP2IDPolicy(newPolicy).isVerifierAllowed(defaultVerifier))
|
|
415
|
+
if (!IP2IDPolicy(newPolicy).isVerifierAllowed(defaultVerifier))
|
|
416
|
+
revert VerifierNotApproved(defaultVerifier);
|
|
122
417
|
delete proposedPolicy;
|
|
123
418
|
delete proposedPolicyEta;
|
|
124
419
|
policy = newPolicy;
|
|
@@ -131,7 +426,8 @@ contract PviumP2IdVaultFactory is IP2IdVaultFactory {
|
|
|
131
426
|
/// after DEFAULT_VERIFIER_DELAY (14 days), via activateDefaultVerifier(). Replaces any
|
|
132
427
|
/// pending proposal.
|
|
133
428
|
function proposeDefaultVerifier(address verifier) external onlyOwner {
|
|
134
|
-
if (!IP2IDPolicy(policy).isVerifierAllowed(verifier))
|
|
429
|
+
if (!IP2IDPolicy(policy).isVerifierAllowed(verifier))
|
|
430
|
+
revert VerifierNotApproved(verifier);
|
|
135
431
|
proposedDefaultVerifier = verifier;
|
|
136
432
|
proposedDefaultEta = uint64(block.timestamp) + DEFAULT_VERIFIER_DELAY;
|
|
137
433
|
emit DefaultVerifierProposed(verifier, proposedDefaultEta);
|
|
@@ -144,14 +440,15 @@ contract PviumP2IdVaultFactory is IP2IdVaultFactory {
|
|
|
144
440
|
delete proposedDefaultEta;
|
|
145
441
|
}
|
|
146
442
|
|
|
147
|
-
/// @notice
|
|
148
|
-
///
|
|
443
|
+
/// @notice Set the proposed default after its delay, provided the current policy approves it.
|
|
444
|
+
/// Failed approval leaves the proposal pending.
|
|
149
445
|
function activateDefaultVerifier() external onlyOwner {
|
|
150
446
|
uint64 eta = proposedDefaultEta;
|
|
151
447
|
if (eta == 0) revert NothingProposed();
|
|
152
448
|
if (block.timestamp < eta) revert TimelockNotElapsed(eta);
|
|
153
449
|
address verifier = proposedDefaultVerifier;
|
|
154
|
-
if (!IP2IDPolicy(policy).isVerifierAllowed(verifier))
|
|
450
|
+
if (!IP2IDPolicy(policy).isVerifierAllowed(verifier))
|
|
451
|
+
revert VerifierNotApproved(verifier);
|
|
155
452
|
delete proposedDefaultVerifier;
|
|
156
453
|
delete proposedDefaultEta;
|
|
157
454
|
defaultVerifier = verifier;
|
|
@@ -201,18 +498,22 @@ contract PviumP2IdVaultFactory is IP2IdVaultFactory {
|
|
|
201
498
|
return vaultFor(identityHash).code.length != 0;
|
|
202
499
|
}
|
|
203
500
|
|
|
204
|
-
/// @notice keccak256 of the
|
|
501
|
+
/// @notice keccak256 of the vault proxy's creation code (no constructor arguments), for offline derivation.
|
|
205
502
|
function initCodeHash() public pure returns (bytes32) {
|
|
206
|
-
return keccak256(type(
|
|
503
|
+
return keccak256(type(PviumP2IDVaultProxy).creationCode);
|
|
207
504
|
}
|
|
208
505
|
|
|
209
506
|
/// @inheritdoc IP2IdVaultFactory
|
|
210
507
|
function deploy(bytes32 identityHash) public returns (address vault) {
|
|
211
508
|
vault = vaultFor(identityHash);
|
|
212
509
|
if (vault.code.length != 0) return vault;
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
510
|
+
vault = address(new PviumP2IDVaultProxy{salt: identityHash}());
|
|
511
|
+
IP2IDVault(vault).initialize(
|
|
512
|
+
nsHash,
|
|
513
|
+
identityHash,
|
|
514
|
+
minRefundWindow,
|
|
515
|
+
maxRefundWindow
|
|
516
|
+
);
|
|
216
517
|
emit VaultDeployed(identityHash, vault);
|
|
217
518
|
}
|
|
218
519
|
|
|
@@ -271,11 +572,22 @@ contract PviumP2IdVaultFactory is IP2IdVaultFactory {
|
|
|
271
572
|
vault = deploy(identityHash);
|
|
272
573
|
if (token == address(0)) {
|
|
273
574
|
// Native coin: forward exactly the value sent; the vault checks it equals `amount`.
|
|
274
|
-
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
|
+
);
|
|
275
587
|
}
|
|
276
588
|
if (msg.value != 0) revert UnexpectedValue();
|
|
277
|
-
//
|
|
278
|
-
//
|
|
589
|
+
// Measure the factory's balance increase, approve that amount to the vault, and record
|
|
590
|
+
// msg.sender as funder. The vault measures its own balance increase during the second transfer.
|
|
279
591
|
uint256 before = _balanceOf(token);
|
|
280
592
|
_callToken(
|
|
281
593
|
token,
|
|
@@ -6,22 +6,16 @@ import {P2IDHash} from "./lib/P2IDHash.sol";
|
|
|
6
6
|
import {PviumIdentity} from "./PviumIdentity.sol";
|
|
7
7
|
|
|
8
8
|
/// @title PviumVerifier
|
|
9
|
-
/// @notice
|
|
10
|
-
///
|
|
11
|
-
/// given, that the registered attestation signer has signed that commitment.
|
|
9
|
+
/// @notice Resolves an EVM wallet from a PviumIdentity attestation and checks an optional
|
|
10
|
+
/// constraint signature against the registered constraint signers.
|
|
12
11
|
/// @dev proof = abi.encode(bytes zkProof, bytes32[] publicInputs). The wallet returned is the
|
|
13
12
|
/// EVM address the circuit read out of the Privy-signed token (public input 1), cross-checked
|
|
14
13
|
/// here against the proof's walletHash with the same formula. A constraint is satisfied by
|
|
15
14
|
/// a registered signer's EIP-712 signature over `Constraint(bytes32 commitment)` in this
|
|
16
|
-
/// contract's domain (name "PviumVerifier", version "1", chain id, this address)
|
|
17
|
-
///
|
|
18
|
-
///
|
|
19
|
-
///
|
|
20
|
-
/// the wallet, so a compromised signer or owner can release screened funds to their rightful
|
|
21
|
-
/// payee early but cannot redirect them. An owner of address(0) freezes the set.
|
|
22
|
-
/// What a commitment means is up to the funder; for a screening attestation it should
|
|
23
|
-
/// commit to the payee as well as the policy, plus a salt (see screeningCommitment),
|
|
24
|
-
/// otherwise one signature releases every deposit under that policy for every payee.
|
|
15
|
+
/// contract's domain (name "PviumVerifier", version "1", chain id, this address).
|
|
16
|
+
/// The PviumIdentity address is immutable; its accepted key set can change separately.
|
|
17
|
+
/// Constraint signatures authorize a commitment, while the attestation supplies the wallet.
|
|
18
|
+
/// This contract does not interpret the commitment's contents or enforce signature single-use.
|
|
25
19
|
contract PviumVerifier is IP2IDVerifier {
|
|
26
20
|
bytes32 private constant EIP712_DOMAIN_TYPEHASH =
|
|
27
21
|
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
|
|
@@ -51,7 +45,7 @@ contract PviumVerifier is IP2IDVerifier {
|
|
|
51
45
|
error NotPendingOwner();
|
|
52
46
|
error InvalidSigner();
|
|
53
47
|
|
|
54
|
-
/// @param _owner
|
|
48
|
+
/// @param _owner Constraint-signer administrator; address(0) disables owner-only calls.
|
|
55
49
|
/// @param _signers Initial constraint signers (may be empty: constraints unsupported until one is added).
|
|
56
50
|
constructor(PviumIdentity _pviumIdentity, address _owner, address[] memory _signers) {
|
|
57
51
|
pviumIdentity = _pviumIdentity;
|
|
@@ -62,8 +56,7 @@ contract PviumVerifier is IP2IDVerifier {
|
|
|
62
56
|
_cachedDomainSeparator = _domainSeparator(block.chainid);
|
|
63
57
|
}
|
|
64
58
|
|
|
65
|
-
/// @notice
|
|
66
|
-
/// now on, including for deposits already funded; those wait for a registered signer.
|
|
59
|
+
/// @notice Set a constraint signer's membership. Signatures are checked against current membership.
|
|
67
60
|
function setConstraintSigner(address signer, bool allowed) external onlyOwner {
|
|
68
61
|
_setSigner(signer, allowed);
|
|
69
62
|
}
|
|
@@ -80,7 +73,7 @@ contract PviumVerifier is IP2IDVerifier {
|
|
|
80
73
|
pendingOwner = address(0);
|
|
81
74
|
}
|
|
82
75
|
|
|
83
|
-
/// @notice EIP-712 domain separator; recomputed
|
|
76
|
+
/// @notice EIP-712 domain separator; recomputed when block.chainid differs from its deployment value.
|
|
84
77
|
function DOMAIN_SEPARATOR() public view returns (bytes32) {
|
|
85
78
|
return block.chainid == _cachedChainId ? _cachedDomainSeparator : _domainSeparator(block.chainid);
|
|
86
79
|
}
|
|
@@ -97,7 +90,7 @@ contract PviumVerifier is IP2IDVerifier {
|
|
|
97
90
|
) external view returns (address wallet, uint64 iat) {
|
|
98
91
|
(bytes memory zkProof, bytes32[] memory publicInputs) = abi.decode(proof, (bytes, bytes32[]));
|
|
99
92
|
|
|
100
|
-
//
|
|
93
|
+
// Verify against PviumIdentity's current accepted signer set.
|
|
101
94
|
PviumIdentity.Attestation memory a = pviumIdentity.verifyAttestation(zkProof, publicInputs);
|
|
102
95
|
if (a.identityHash != identityHash) revert IdentityMismatch();
|
|
103
96
|
if (a.wallet == address(0)) revert NoEvmWallet();
|
|
@@ -110,9 +103,10 @@ contract PviumVerifier is IP2IDVerifier {
|
|
|
110
103
|
return (a.wallet, a.iat);
|
|
111
104
|
}
|
|
112
105
|
|
|
106
|
+
function vkHash() external view returns (bytes32) { return pviumIdentity.vkHash(); }
|
|
107
|
+
|
|
113
108
|
/// @inheritdoc IP2IDVerifier
|
|
114
|
-
/// @dev
|
|
115
|
-
/// contract's key revocations count.
|
|
109
|
+
/// @dev Forward the identity key-set revision; constraint-signer changes do not increment it.
|
|
116
110
|
function revision() external view returns (uint64) {
|
|
117
111
|
return pviumIdentity.keySetRevision();
|
|
118
112
|
}
|
|
@@ -127,8 +121,8 @@ contract PviumVerifier is IP2IDVerifier {
|
|
|
127
121
|
return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), keccak256(abi.encode(CONSTRAINT_TYPEHASH, commitment))));
|
|
128
122
|
}
|
|
129
123
|
|
|
130
|
-
/// @notice
|
|
131
|
-
///
|
|
124
|
+
/// @notice Hash policyHash, identityHash and caller-supplied salt using abi.encode.
|
|
125
|
+
/// Does not generate a salt or enforce uniqueness.
|
|
132
126
|
function screeningCommitment(bytes32 policyHash, bytes32 identityHash, bytes32 salt) public pure returns (bytes32) {
|
|
133
127
|
return keccak256(abi.encode(policyHash, identityHash, salt));
|
|
134
128
|
}
|