@pvium/p2id-core 0.1.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/README.md +239 -0
- package/contracts/P2IDVault.sol +736 -0
- package/contracts/PviumIdentity.sol +268 -0
- package/contracts/PviumP2IDPolicy.sol +78 -0
- package/contracts/PviumP2IdVaultFactory.sol +327 -0
- package/contracts/PviumVerifier.sol +165 -0
- package/contracts/PviumZKVerifier.sol +2465 -0
- package/contracts/interfaces/IP2IDPolicy.sol +39 -0
- package/contracts/interfaces/IP2IDVault.sol +112 -0
- package/contracts/interfaces/IP2IDVerifier.sol +44 -0
- package/contracts/interfaces/IP2IdVaultFactory.sol +49 -0
- package/contracts/interfaces/IPviumIdentity.sol +28 -0
- package/contracts/lib/P2IDHash.sol +63 -0
- package/dist/cjs/identity.js +68 -0
- package/dist/cjs/identityNames.js +49 -0
- package/dist/cjs/index.js +24 -0
- package/dist/cjs/p2id.js +91 -0
- package/dist/cjs/p2idConstants.js +16 -0
- package/dist/cjs/package.json +3 -0
- package/dist/esm/identity.d.ts +38 -0
- package/dist/esm/identity.js +61 -0
- package/dist/esm/identityNames.d.ts +43 -0
- package/dist/esm/identityNames.js +44 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/p2id.d.ts +54 -0
- package/dist/esm/p2id.js +82 -0
- package/dist/esm/p2idConstants.d.ts +30 -0
- package/dist/esm/p2idConstants.js +13 -0
- package/dist/esm/package.json +3 -0
- package/dist/identity.d.ts +38 -0
- package/dist/identity.js +61 -0
- package/dist/identityNames.d.ts +43 -0
- package/dist/identityNames.js +44 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/p2id.d.ts +54 -0
- package/dist/p2id.js +82 -0
- package/dist/p2idConstants.d.ts +38 -0
- package/dist/p2idConstants.js +21 -0
- package/package.json +44 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.27;
|
|
3
|
+
|
|
4
|
+
import {IP2IDVerifier} from "./interfaces/IP2IDVerifier.sol";
|
|
5
|
+
import {P2IDHash} from "./lib/P2IDHash.sol";
|
|
6
|
+
import {PviumIdentity} from "./PviumIdentity.sol";
|
|
7
|
+
|
|
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.
|
|
12
|
+
/// @dev proof = abi.encode(bytes zkProof, bytes32[] publicInputs). The wallet returned is the
|
|
13
|
+
/// EVM address the circuit read out of the Privy-signed token (public input 1), cross-checked
|
|
14
|
+
/// here against the proof's walletHash with the same formula. A constraint is satisfied by
|
|
15
|
+
/// 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.
|
|
25
|
+
contract PviumVerifier is IP2IDVerifier {
|
|
26
|
+
bytes32 private constant EIP712_DOMAIN_TYPEHASH =
|
|
27
|
+
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
|
|
28
|
+
bytes32 public constant CONSTRAINT_TYPEHASH = keccak256("Constraint(bytes32 commitment)");
|
|
29
|
+
|
|
30
|
+
PviumIdentity public immutable pviumIdentity;
|
|
31
|
+
bytes32 private immutable _cachedDomainSeparator;
|
|
32
|
+
uint256 private immutable _cachedChainId;
|
|
33
|
+
/// @notice Addresses whose signatures satisfy a constraint commitment.
|
|
34
|
+
mapping(address => bool) public isConstraintSigner;
|
|
35
|
+
/// @notice Number of registered signers; constraints are supported while it is non-zero.
|
|
36
|
+
uint256 public constraintSignerCount;
|
|
37
|
+
address public owner;
|
|
38
|
+
address public pendingOwner;
|
|
39
|
+
|
|
40
|
+
event ConstraintSignerSet(address indexed signer, bool allowed);
|
|
41
|
+
event OwnershipTransferStarted(address indexed from, address indexed to);
|
|
42
|
+
event OwnershipTransferred(address indexed from, address indexed to);
|
|
43
|
+
|
|
44
|
+
error IdentityMismatch();
|
|
45
|
+
error NoEvmWallet();
|
|
46
|
+
error WalletHashMismatch();
|
|
47
|
+
error ConstraintsUnsupported();
|
|
48
|
+
error InvalidConstraintSigner();
|
|
49
|
+
error MalformedSignature();
|
|
50
|
+
error NotOwner();
|
|
51
|
+
error NotPendingOwner();
|
|
52
|
+
error InvalidSigner();
|
|
53
|
+
|
|
54
|
+
/// @param _owner Manages the signer set; address(0) makes the set given here permanent.
|
|
55
|
+
/// @param _signers Initial constraint signers (may be empty: constraints unsupported until one is added).
|
|
56
|
+
constructor(PviumIdentity _pviumIdentity, address _owner, address[] memory _signers) {
|
|
57
|
+
pviumIdentity = _pviumIdentity;
|
|
58
|
+
owner = _owner;
|
|
59
|
+
emit OwnershipTransferred(address(0), _owner);
|
|
60
|
+
for (uint256 i = 0; i < _signers.length; i++) _setSigner(_signers[i], true);
|
|
61
|
+
_cachedChainId = block.chainid;
|
|
62
|
+
_cachedDomainSeparator = _domainSeparator(block.chainid);
|
|
63
|
+
}
|
|
64
|
+
|
|
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.
|
|
67
|
+
function setConstraintSigner(address signer, bool allowed) external onlyOwner {
|
|
68
|
+
_setSigner(signer, allowed);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function transferOwnership(address to) external onlyOwner {
|
|
72
|
+
pendingOwner = to;
|
|
73
|
+
emit OwnershipTransferStarted(owner, to);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function acceptOwnership() external {
|
|
77
|
+
if (msg.sender != pendingOwner) revert NotPendingOwner();
|
|
78
|
+
emit OwnershipTransferred(owner, msg.sender);
|
|
79
|
+
owner = msg.sender;
|
|
80
|
+
pendingOwner = address(0);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// @notice EIP-712 domain separator; recomputed if the chain id changes (chain fork).
|
|
84
|
+
function DOMAIN_SEPARATOR() public view returns (bytes32) {
|
|
85
|
+
return block.chainid == _cachedChainId ? _cachedDomainSeparator : _domainSeparator(block.chainid);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function _domainSeparator(uint256 chainId) private view returns (bytes32) {
|
|
89
|
+
return keccak256(abi.encode(EIP712_DOMAIN_TYPEHASH, keccak256("PviumVerifier"), keccak256("1"), chainId, address(this)));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// @inheritdoc IP2IDVerifier
|
|
93
|
+
function getIdentityWallet(
|
|
94
|
+
bytes32 identityHash,
|
|
95
|
+
bytes calldata proof,
|
|
96
|
+
Constraint calldata constraint
|
|
97
|
+
) external view returns (address wallet, uint64 iat) {
|
|
98
|
+
(bytes memory zkProof, bytes32[] memory publicInputs) = abi.decode(proof, (bytes, bytes32[]));
|
|
99
|
+
|
|
100
|
+
// Reverts (InvalidProof / UnknownSigner / …) unless the proof is valid under the registered key.
|
|
101
|
+
PviumIdentity.Attestation memory a = pviumIdentity.verifyAttestation(zkProof, publicInputs);
|
|
102
|
+
if (a.identityHash != identityHash) revert IdentityMismatch();
|
|
103
|
+
if (a.wallet == address(0)) revert NoEvmWallet();
|
|
104
|
+
if (P2IDHash.walletHash(a.wallet) != a.walletHash) revert WalletHashMismatch();
|
|
105
|
+
|
|
106
|
+
if (constraint.commitment != bytes32(0)) {
|
|
107
|
+
if (constraintSignerCount == 0) revert ConstraintsUnsupported();
|
|
108
|
+
if (!isConstraintSigner[_recover(constraint.commitment, constraint.signature)]) revert InvalidConstraintSigner();
|
|
109
|
+
}
|
|
110
|
+
return (a.wallet, a.iat);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// @inheritdoc IP2IDVerifier
|
|
114
|
+
/// @dev Attester changes never affect which wallet a proof resolves to, so only the identity
|
|
115
|
+
/// contract's key revocations count.
|
|
116
|
+
function revision() external view returns (uint64) {
|
|
117
|
+
return pviumIdentity.keySetRevision();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/// @inheritdoc IP2IDVerifier
|
|
121
|
+
function supportsConstraints() external view returns (bool) {
|
|
122
|
+
return constraintSignerCount != 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// @notice The EIP-712 digest a constraint signer signs for a commitment.
|
|
126
|
+
function constraintDigest(bytes32 commitment) public view returns (bytes32) {
|
|
127
|
+
return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), keccak256(abi.encode(CONSTRAINT_TYPEHASH, commitment))));
|
|
128
|
+
}
|
|
129
|
+
|
|
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).
|
|
132
|
+
function screeningCommitment(bytes32 policyHash, bytes32 identityHash, bytes32 salt) public pure returns (bytes32) {
|
|
133
|
+
return keccak256(abi.encode(policyHash, identityHash, salt));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function _recover(bytes32 commitment, bytes calldata signature) private view returns (address) {
|
|
137
|
+
if (signature.length != 65) revert MalformedSignature();
|
|
138
|
+
bytes32 r;
|
|
139
|
+
bytes32 s;
|
|
140
|
+
uint8 v;
|
|
141
|
+
assembly {
|
|
142
|
+
r := calldataload(signature.offset)
|
|
143
|
+
s := calldataload(add(signature.offset, 0x20))
|
|
144
|
+
v := byte(0, calldataload(add(signature.offset, 0x40)))
|
|
145
|
+
}
|
|
146
|
+
if (v < 27) v += 27;
|
|
147
|
+
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) revert MalformedSignature();
|
|
148
|
+
address signer = ecrecover(constraintDigest(commitment), v, r, s);
|
|
149
|
+
if (signer == address(0)) revert MalformedSignature();
|
|
150
|
+
return signer;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function _setSigner(address signer, bool allowed) private {
|
|
154
|
+
if (signer == address(0)) revert InvalidSigner();
|
|
155
|
+
if (isConstraintSigner[signer] == allowed) return;
|
|
156
|
+
isConstraintSigner[signer] = allowed;
|
|
157
|
+
allowed ? constraintSignerCount++ : constraintSignerCount--;
|
|
158
|
+
emit ConstraintSignerSet(signer, allowed);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
modifier onlyOwner() {
|
|
162
|
+
if (msg.sender != owner || owner == address(0)) revert NotOwner();
|
|
163
|
+
_;
|
|
164
|
+
}
|
|
165
|
+
}
|