@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.
@@ -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 = keccak256("pvium.vault.implementation.v1");
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)) revert VerifierNotApproved(_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(bytes32 creationCodeHash) public view returns (address) {
114
- return address(uint160(uint256(keccak256(abi.encodePacked(
115
- bytes1(0xff), address(this), IMPLEMENTATION_SALT, creationCodeHash
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(bytes memory creationCode, bool makeDefault)
123
- external onlyOwner returns (address implementation)
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(0, add(creationCode, 32), mload(creationCode), salt)
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) revert InvalidImplementation();
134
- emit ImplementationDeployed(implementation, keccak256(creationCode));
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 (proposedImplementation != implementation || proposedImplementationMakeDefault != makeDefault) {
282
+ if (
283
+ proposedImplementation != implementation ||
284
+ proposedImplementationMakeDefault != makeDefault
285
+ ) {
139
286
  revert ImplementationProposalPending();
140
287
  }
141
288
  return implementation;
142
289
  }
143
- if (isRegisteredImplementation[implementation] && (!makeDefault || baseImplementation == implementation)) {
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 (implementation.code.length == 0 || isRegisteredImplementation[implementation]) revert InvalidImplementation();
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(address implementation) external onlyOwner {
157
- if (!isRegisteredImplementation[implementation] || implementation == baseImplementation) revert InvalidImplementation();
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(address implementation, bool makeDefault) private {
321
+ function _proposeImplementation(
322
+ address implementation,
323
+ bool makeDefault
324
+ ) private {
163
325
  proposedImplementation = implementation;
164
326
  proposedImplementationMakeDefault = makeDefault;
165
- proposedImplementationEta = uint64(block.timestamp) + DEFAULT_VERIFIER_DELAY;
327
+ proposedImplementationEta =
328
+ uint64(block.timestamp) +
329
+ DEFAULT_VERIFIER_DELAY;
166
330
  emit ImplementationProposed(implementation, proposedImplementationEta);
167
- emit ImplementationDefaultProposed(implementation, makeDefault, proposedImplementationEta);
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(abi.encodeWithSignature("factory()"));
172
- if (!ok || result.length != 32 || abi.decode(result, (bytes32)) != bytes32(uint256(uint160(address(this))))) {
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
- if (makeDefault) _checkFactoryBinding(implementation);
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 (implementation == baseImplementation || implementation == initialImplementation || !isRegisteredImplementation[implementation]) revert InvalidImplementation();
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)) revert VerifierNotApproved(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)) revert VerifierNotApproved(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)) revert VerifierNotApproved(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(nsHash, identityHash, minRefundWindow, maxRefundWindow);
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 (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
+ );
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) {