@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.
@@ -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 P2IDVaults with CREATE2, salted by identity hash, and points them at the
10
- /// current policy (which verifiers are allowed, what capped fee applies) and default verifier. A vault takes no constructor arguments
11
- /// (the factory calls `initialize` right after `new`), so its creation code is a constant
12
- /// and an identity's vault address is
13
- /// `keccak256(0xff ‖ factory ‖ identityHash ‖ keccak256(creationCode))`, computable
14
- /// anywhere from two constants. Funds sent to that address before deployment are swept
15
- /// by the owner after deployment (see P2IDVault: bare transfers are untracked and irrevocable).
16
- /// @dev Everything expected to evolve is in the policy (IP2IDPolicy), so this factory and the vault
17
- /// code, which fix every P2ID address, never have to change. Both move only through a
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 Proposes and activates policy / default-verifier changes (a Pvium multisig). Two-step transfer.
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 The policy every vault consults. Changes only via the timelock below.
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)) revert VerifierNotApproved(_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 Switch every vault to the proposed policy once the delay has elapsed. The new policy
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)) revert VerifierNotApproved(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)) revert VerifierNotApproved(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 Make the proposed verifier the default once the delay has elapsed. The policy must
148
- /// still allow it (a revocation in the meantime cancels it in effect).
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)) revert VerifierNotApproved(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 P2IDVault creation code (no constructor arguments), for offline derivation.
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(P2IDVault).creationCode);
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
- P2IDVault v = new P2IDVault{salt: identityHash}();
214
- v.initialize(nsHash, identityHash, minRefundWindow, maxRefundWindow);
215
- vault = address(v);
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 (vault, P2IDVault(payable(vault)).fundFor{value: msg.value}(msg.sender, verifier, token, amount, constraint, refundWindow, ref));
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
- // Collect from the payer (credit what actually arrived, for fee-on-transfer tokens),
278
- // then let the vault pull exactly that and record the payer as funder.
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 IP2IDVerifier backed by the Pvium ZK circuit. Verifies an identity proof against the
10
- /// registered Privy signing key (via PviumIdentity) and, when a constraint commitment is
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), so a
17
- /// signature is valid on one chain and one verifier only. The identity side is immutable
18
- /// (a new key set or circuit is a new deployment the vault factory registers alongside).
19
- /// The signer set is the owner's: signers only gate constrained deposits, and never choose
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 Manages the signer set; address(0) makes the set given here permanent.
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 Register or revoke a constraint signer. Revoking one makes its signatures fail from
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 if the chain id changes (chain fork).
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
- // Reverts (InvalidProof / UnknownSigner / …) unless the proof is valid under the registered key.
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 Attester changes never affect which wallet a proof resolves to, so only the identity
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 Suggested commitment for a screening attestation: binds the policy to the payee, with
131
- /// a random salt so each deposit has its own commitment (a funder can use one only once).
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
  }