@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.
Files changed (41) hide show
  1. package/README.md +239 -0
  2. package/contracts/P2IDVault.sol +736 -0
  3. package/contracts/PviumIdentity.sol +268 -0
  4. package/contracts/PviumP2IDPolicy.sol +78 -0
  5. package/contracts/PviumP2IdVaultFactory.sol +327 -0
  6. package/contracts/PviumVerifier.sol +165 -0
  7. package/contracts/PviumZKVerifier.sol +2465 -0
  8. package/contracts/interfaces/IP2IDPolicy.sol +39 -0
  9. package/contracts/interfaces/IP2IDVault.sol +112 -0
  10. package/contracts/interfaces/IP2IDVerifier.sol +44 -0
  11. package/contracts/interfaces/IP2IdVaultFactory.sol +49 -0
  12. package/contracts/interfaces/IPviumIdentity.sol +28 -0
  13. package/contracts/lib/P2IDHash.sol +63 -0
  14. package/dist/cjs/identity.js +68 -0
  15. package/dist/cjs/identityNames.js +49 -0
  16. package/dist/cjs/index.js +24 -0
  17. package/dist/cjs/p2id.js +91 -0
  18. package/dist/cjs/p2idConstants.js +16 -0
  19. package/dist/cjs/package.json +3 -0
  20. package/dist/esm/identity.d.ts +38 -0
  21. package/dist/esm/identity.js +61 -0
  22. package/dist/esm/identityNames.d.ts +43 -0
  23. package/dist/esm/identityNames.js +44 -0
  24. package/dist/esm/index.d.ts +5 -0
  25. package/dist/esm/index.js +5 -0
  26. package/dist/esm/p2id.d.ts +54 -0
  27. package/dist/esm/p2id.js +82 -0
  28. package/dist/esm/p2idConstants.d.ts +30 -0
  29. package/dist/esm/p2idConstants.js +13 -0
  30. package/dist/esm/package.json +3 -0
  31. package/dist/identity.d.ts +38 -0
  32. package/dist/identity.js +61 -0
  33. package/dist/identityNames.d.ts +43 -0
  34. package/dist/identityNames.js +44 -0
  35. package/dist/index.d.ts +5 -0
  36. package/dist/index.js +5 -0
  37. package/dist/p2id.d.ts +54 -0
  38. package/dist/p2id.js +82 -0
  39. package/dist/p2idConstants.d.ts +38 -0
  40. package/dist/p2idConstants.js +21 -0
  41. package/package.json +44 -0
@@ -0,0 +1,39 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.27;
3
+
4
+ /// @title IP2IDPolicy
5
+ /// @notice Everything about P2ID vaults that is expected to evolve lives behind this interface,
6
+ /// so it can change without changing vault bytecode (and therefore without moving any
7
+ /// P2ID address). The vault factory points at one policy and can replace it only through
8
+ /// a timelock; every vault consults the factory's current policy on each call.
9
+ ///
10
+ /// What a policy decides, and the limits the vault enforces on it:
11
+ /// - which verifiers deposits may be funded under and claimed through. Gating is the one
12
+ /// thing a policy can use to stop claims (by disallowing a verifier), which freezes
13
+ /// claims under it but never moves funds; refunds never consult the policy.
14
+ /// - the protocol fee on payouts, in basis points. The vault caps it at its own
15
+ /// MAX_FEE_BPS, fixes a deposit's rate when the deposit is made, never charges it on
16
+ /// refunds, and treats a failing quote as no fee.
17
+ /// - how accrued fees are distributed. Fees accrue in each vault per (verifier, token);
18
+ /// anyone can have a vault hand them to the policy (withdrawFees), which pulls them and
19
+ /// splits them however it chooses, e.g. between a verifier's operator and the protocol.
20
+ ///
21
+ /// Launch policy: an owner-managed verifier allowlist and no fee. Permissionless,
22
+ /// stake-based verifier registration and fees are later policies.
23
+ interface IP2IDPolicy {
24
+ /// @notice Whether deposits may be funded under, and claimed through, `verifier`.
25
+ function isVerifierAllowed(address verifier) external view returns (bool);
26
+
27
+ /// @notice Fee on paying out `token` claimed through `verifier`, in basis points. Quoted when a
28
+ /// deposit is made (and fixed for it), or at sweep time for untracked funds.
29
+ function feeBps(address verifier, address token) external view returns (uint16);
30
+
31
+ /// @notice Called by a vault holding fees earned through `verifier`. For an ERC-20 the vault
32
+ /// first approves this policy for exactly `amount`: pull the tokens (transferFrom the
33
+ /// caller) and distribute them; whatever is not pulled stays accrued in the vault, and
34
+ /// the allowance is reset afterwards. Pulling from msg.sender makes the amount
35
+ /// self-verifying: a caller that is not a vault can only give away its own tokens.
36
+ /// For the native coin (token == address(0)) the vault sends `amount` as msg.value
37
+ /// instead, and all of it counts as distributed.
38
+ function distributeFee(address verifier, address token, uint256 amount) external payable;
39
+ }
@@ -0,0 +1,112 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.27;
3
+
4
+ import {IP2IDVerifier} from "./IP2IDVerifier.sol";
5
+
6
+ /// @title IP2IDVault
7
+ /// @notice A vault holding ERC-20 tokens and the native coin for one identity until its owner
8
+ /// proves it and sweeps. The native coin is the token address(0) in every function.
9
+ /// Every deposit names the verifier whose proofs can release it; the factory's policy
10
+ /// decides which verifiers are allowed and what (capped) fee applies.
11
+ interface IP2IDVault {
12
+ /// @dev Field order packs into 5 storage slots:
13
+ /// [funder, fundedAt, consumed] [token, refundWindow] [verifier, feeBps] [amount] [constraint].
14
+ struct Deposit {
15
+ address funder;
16
+ uint64 fundedAt;
17
+ bool consumed;
18
+ address token;
19
+ uint64 refundWindow;
20
+ address verifier;
21
+ /// Fee rate fixed when the deposit was made (<= MAX_FEE_BPS); charged only on a claim.
22
+ uint16 feeBps;
23
+ uint128 amount;
24
+ bytes32 constraint;
25
+ }
26
+
27
+ // ------------------------------------------------------------------ events
28
+ // Every deposit is Funded once and then either Refunded (by its funder) or Claimed by a sweep.
29
+ // Sweeps also emit one aggregate event with the net amount paid and the fee.
30
+
31
+ /// @notice A deposit was recorded via fund()/fundWith()/fundFor().
32
+ /// @param ref Opaque application reference; zero means absent. Emitted only, not stored or enforced as unique.
33
+ event Funded(uint256 indexed depositId, address indexed funder, address indexed token, uint256 amount, address verifier, bytes32 constraint, uint64 refundWindow, uint16 feeBps, bytes32 ref);
34
+ /// @notice An unconsumed deposit was returned to its funder after its refund window (never charged a fee).
35
+ event Refunded(uint256 indexed depositId, address indexed funder, address indexed token, uint256 amount);
36
+ /// @notice A deposit was paid out by a sweep: `amount` gross, of which `fee` accrued as a fee.
37
+ event Claimed(uint256 indexed depositId, address indexed to, uint256 amount, uint256 fee);
38
+ /// @notice A proof newer than any seen under `verifier` set that verifier's owner wallet; older proofs are now refused.
39
+ event OwnerRefreshed(address indexed verifier, address indexed owner, uint64 iat);
40
+ /// @notice Default-bucket funds under `verifier` were paid to its owner wallet (`amount` net of `fee`).
41
+ event Swept(address indexed verifier, address indexed token, uint256 amount, uint256 fee, address indexed to, uint256 depositsConsumed);
42
+ /// @notice Funds from a constrained bucket were paid to the wallet the proof resolved to (`amount` net of `fee`).
43
+ event SweptBucket(address indexed verifier, bytes32 indexed constraint, address indexed token, uint256 amount, uint256 fee, address to, uint256 depositsConsumed);
44
+ /// @notice A fee accrued for `verifier`; it stays in the vault until withdrawFees() hands it to the policy.
45
+ event FeeAccrued(address indexed verifier, address indexed token, uint256 amount);
46
+ /// @notice The policy pulled `amount` of accrued fees to distribute.
47
+ event FeesDistributed(address indexed verifier, address indexed token, uint256 amount, address policy);
48
+
49
+ // setup (factory only, once)
50
+ function initialize(bytes32 nsHash, bytes32 saltCommitment, uint64 minRefundWindow, uint64 maxRefundWindow) external;
51
+
52
+ // funding
53
+ /// @notice Fund under the factory's default verifier. A non-zero `constraint` can be used once per funder.
54
+ /// @param ref Opaque application reference emitted in Funded; bytes32(0) for none. Does not affect claims or refunds.
55
+ function fund(address token, uint256 amount, bytes32 constraint, uint64 refundWindow, bytes32 ref) external payable returns (uint256 depositId);
56
+ /// @notice Fund under any verifier the factory's policy allows.
57
+ /// @param ref Opaque application reference emitted in Funded; bytes32(0) for none.
58
+ function fundWith(address verifier, address token, uint256 amount, bytes32 constraint, uint64 refundWindow, bytes32 ref) external payable returns (uint256 depositId);
59
+ /// @notice Factory-only: record a deposit owned by `funder`; tokens are pulled from the factory.
60
+ /// @param ref Opaque application reference emitted in Funded; bytes32(0) for none.
61
+ function fundFor(address funder, address verifier, address token, uint256 amount, bytes32 constraint, uint64 refundWindow, bytes32 ref) external payable returns (uint256 depositId);
62
+ function refund(uint256 depositId) external;
63
+
64
+ // proofs
65
+ /// @notice Present a proof under `verifier` without claiming: sets that verifier's owner wallet
66
+ /// if the proof is newer than anything seen under it, and retires older proofs.
67
+ function refreshProof(address verifier, bytes calldata proof) external;
68
+ function refreshProofAndSweep(address verifier, bytes calldata proof, address token, uint256 depositCountLimit) external returns (uint256 amount, uint256 consumed);
69
+
70
+ // claiming (amounts returned are net of fees)
71
+ /// @notice Sweep `verifier`'s default bucket to its owner; the default verifier's sweep also takes untracked funds.
72
+ function sweep(address verifier, address token, uint256 depositCountLimit) external returns (uint256 amount, uint256 consumed);
73
+ function sweepUntracked(address token) external returns (uint256 amount);
74
+ function sweepDeposits(address verifier, address token, uint256[] calldata depositIds) external returns (uint256 amount);
75
+ function sweepBucket(address verifier, IP2IDVerifier.Constraint calldata constraint, address token, bytes calldata proof, uint256 depositCountLimit) external returns (uint256 amount, uint256 consumed);
76
+ function sweepBucketDeposits(address verifier, IP2IDVerifier.Constraint calldata constraint, address token, uint256[] calldata depositIds, bytes calldata proof) external returns (uint256 amount);
77
+
78
+ // fees
79
+ /// @notice Hand the fees earned through `verifier` in `token` to the current policy to distribute. Anyone may call it.
80
+ function withdrawFees(address verifier, address token) external returns (uint256 amount);
81
+
82
+ // views
83
+ /// @notice P2ID vault interface version implemented by this contract.
84
+ function p2idVersion() external pure returns (string memory);
85
+ function MAX_FEE_BPS() external view returns (uint16);
86
+ /// @notice address(0): the token address standing for the native coin.
87
+ function NATIVE() external view returns (address);
88
+ /// @notice keccak256 of the address scheme this vault was issued under, e.g. keccak256("pvium.vault.v1").
89
+ function nsHash() external view returns (bytes32);
90
+ function factory() external view returns (address);
91
+ function policy() external view returns (address);
92
+ function defaultVerifier() external view returns (address);
93
+ function owner(address verifier) external view returns (address);
94
+ function latestProofIat(address verifier) external view returns (uint64);
95
+ /// @notice Freshness floor for direct transfers, carried across default-verifier changes.
96
+ function untrackedProofIat() external view returns (uint64);
97
+ function saltCommitment() external view returns (bytes32);
98
+ function depositCount() external view returns (uint256);
99
+ function bucketDepositIds(address verifier, bytes32 constraint, address token) external view returns (uint256[] memory);
100
+ function bucketDepositCount(address verifier, bytes32 constraint, address token) external view returns (uint256);
101
+ /// @notice Whether `funder` has funded under `constraint`, and which deposit. A funder can use a
102
+ /// constraint once (refunded or claimed, it stays used), so a signature over it releases
103
+ /// at most one of their deposits. Funders must make constraints unique, e.g. by salting.
104
+ function constraintDeposit(bytes32 constraint, address funder) external view returns (bool used, uint256 depositId);
105
+ function bucketTotal(address verifier, bytes32 constraint, address token) external view returns (uint256);
106
+ function trackedTotal(address token) external view returns (uint256);
107
+ function feesOwed(address verifier, address token) external view returns (uint256);
108
+ function feesOwedTotal(address token) external view returns (uint256);
109
+ /// @notice Gross amount `sweep(verifier, token, 0)` would release now, before fees.
110
+ function sweepable(address verifier, address token) external view returns (uint256);
111
+ function untrackedBalance(address token) external view returns (uint256);
112
+ }
@@ -0,0 +1,44 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.27;
3
+
4
+ /// @title IP2IDVerifier
5
+ /// @notice Use-case-agnostic identity verification: given a proof, return the wallet the proven
6
+ /// identity resolves to. Optionally also verify a funding constraint.
7
+ interface IP2IDVerifier {
8
+ /// @notice A funding constraint and the evidence that it is satisfied.
9
+ /// @param commitment bytes32(0) = no constraint; otherwise the commitment a deposit was funded with.
10
+ /// @param signature Evidence for `commitment`; for PviumVerifier, the registered signer's
11
+ /// EIP-712 signature over Constraint(bytes32 commitment) in the verifier's
12
+ /// domain. Empty when commitment is zero.
13
+ struct Constraint {
14
+ bytes32 commitment;
15
+ bytes signature;
16
+ }
17
+
18
+ /// @param identityHash The identity the caller expects the proof to be for (e.g. the vault's
19
+ /// commitment). Proofs are public once used on-chain, so the caller MUST
20
+ /// pin the identity rather than trust whatever the submitted proof proves.
21
+ /// @param proof Opaque identity proof; the implementation defines the encoding.
22
+ /// @param constraint Skipped when `constraint.commitment == bytes32(0)`.
23
+ /// @return wallet Wallet associated with the proven identity (never address(0)).
24
+ /// @return iat When the underlying attestation was issued (unix seconds).
25
+ /// @dev MUST revert (never return address(0)) if the proof is invalid, is for a different
26
+ /// identity than `identityHash`, or the constraint is not satisfied.
27
+ function getIdentityWallet(
28
+ bytes32 identityHash,
29
+ bytes calldata proof,
30
+ Constraint calldata constraint
31
+ ) external view returns (address wallet, uint64 iat);
32
+
33
+ /// @notice Whether this verifier can ever satisfy a non-zero constraint. Vaults refuse
34
+ /// constrained deposits under a verifier that returns false (or does not implement
35
+ /// this), so a payer cannot lock funds behind a condition nobody can meet.
36
+ function supportsConstraints() external view returns (bool);
37
+
38
+ /// @notice Changes (increments) whenever proofs this verifier accepted before may no longer be
39
+ /// trusted, e.g. a signing key was revoked. A vault caches the wallet a proof resolved
40
+ /// to and pays it without re-verifying; it discards that cache when the revision moved,
41
+ /// so revoking a key also revokes what was proven under it. Return a constant if
42
+ /// nothing this verifier trusts can ever be revoked.
43
+ function revision() external view returns (uint64);
44
+ }
@@ -0,0 +1,49 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.27;
3
+
4
+ /// @title IP2IdVaultFactory
5
+ /// @notice Deploys one P2IDVault per identity at an address anyone can derive offline from the
6
+ /// identity hash, and points every vault at the current policy and default verifier.
7
+ interface IP2IdVaultFactory {
8
+ event VaultDeployed(bytes32 indexed identityHash, address indexed vault);
9
+
10
+ /// @notice P2ID factory interface version implemented by this contract.
11
+ function p2idVersion() external pure returns (string memory);
12
+
13
+ // ---- what vaults consult on every call; both change only through a timelock ----
14
+ /// @notice The IP2IDPolicy deciding which verifiers are allowed and what (capped) fee applies.
15
+ function policy() external view returns (address);
16
+ /// @notice Verifier used when a payer does not choose one, and the one bare transfers are claimed through.
17
+ function defaultVerifier() external view returns (address);
18
+
19
+ // ---- vaults ----
20
+ /// @notice The vault address for `identityHash`, deployed or not:
21
+ /// `keccak256(0xff ‖ factory ‖ identityHash ‖ keccak256(P2IDVault creationCode))`.
22
+ function vaultFor(bytes32 identityHash) external view returns (address);
23
+ function isDeployed(bytes32 identityHash) external view returns (bool);
24
+ /// @notice Deploy the vault for `identityHash`; returns the existing one if already deployed.
25
+ function deploy(bytes32 identityHash) external returns (address vault);
26
+ /// @notice Deploy if needed, then fund under the default verifier on the caller's behalf
27
+ /// (caller keeps the refund right). ERC-20: approve this factory once to pay any
28
+ /// identity. Native coin: token = address(0), send `amount` as msg.value.
29
+ /// @param ref Opaque application reference forwarded to the vault's Funded event; bytes32(0) for none.
30
+ function fund(
31
+ bytes32 identityHash,
32
+ address token,
33
+ uint256 amount,
34
+ bytes32 constraint,
35
+ uint64 refundWindow,
36
+ bytes32 ref
37
+ ) external payable returns (address vault, uint256 depositId);
38
+ /// @notice Same, under a chosen approved verifier.
39
+ /// @param ref Opaque application reference forwarded to the vault's Funded event; bytes32(0) for none.
40
+ function fundWith(
41
+ bytes32 identityHash,
42
+ address verifier,
43
+ address token,
44
+ uint256 amount,
45
+ bytes32 constraint,
46
+ uint64 refundWindow,
47
+ bytes32 ref
48
+ ) external payable returns (address vault, uint256 depositId);
49
+ }
@@ -0,0 +1,28 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.27;
3
+
4
+ /// @title IPviumIdentity
5
+ /// @notice Verify a Pvium attestation on chain: "identity X and wallet W belong to the same
6
+ /// Privy user". Every function reverts unless the proof is valid, was made from a token
7
+ /// signed by one of the accepted keys, and binds exactly the values given. The return value is
8
+ /// when Privy issued that token (unix seconds); freshness policy is the caller's.
9
+ interface IPviumIdentity {
10
+ /// @notice Circuit version this deployment verifies. Attestations state theirs; use the matching deployment.
11
+ function circuitVersion() external view returns (uint16);
12
+ /// @notice Whether tokens signed by this P-256 key are accepted. Keys are added with 7 days' public notice.
13
+ function isSignerKey(uint256 x, uint256 y) external view returns (bool);
14
+
15
+ /// @notice Verify that the proof binds exactly this identity and this wallet.
16
+ /// @dev Both are passed as hashes (P2IDHash on chain, or the SDK's identityHash), so the raw
17
+ /// identity never appears in calldata.
18
+ /// @param identityType Identity type id: 0 email, 3 twitter, 5 github, … (see P2IDHash)
19
+ /// @param identityHash P2IDHash.identityHash(identityType, value)
20
+ /// @param walletHash P2IDHash.walletHash(wallet): an address, or a string for a non-EVM wallet
21
+ function verifyIdentity(
22
+ bytes calldata proof,
23
+ bytes32[] calldata publicInputs,
24
+ uint8 identityType,
25
+ bytes32 identityHash,
26
+ bytes32 walletHash
27
+ ) external view returns (uint64 issuedAt);
28
+ }
@@ -0,0 +1,63 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.27;
3
+
4
+ /// @title P2IDHash
5
+ /// @notice The identity commitment exactly as circuit/src/main.nr computes it:
6
+ /// sha256("p2id.identity.v1" || identityType || normalize(value)).
7
+ /// `normalize` ASCII-lowercases every type except phone and wallet, and lowercases
8
+ /// `0x…` (EVM, hex) wallet addresses; base58 (Solana) addresses are untouched.
9
+ library P2IDHash {
10
+ bytes internal constant PREFIX = "p2id.identity.v1";
11
+ uint8 internal constant PHONE = 1;
12
+ uint8 internal constant WALLET = 12;
13
+ uint8 internal constant NUM_TYPES = 13;
14
+
15
+ error ValueLength(uint256 length);
16
+ error UnknownIdentityType(uint8 identityType);
17
+
18
+ /// @notice Commitment for an identity value, e.g. (0, "you@example.com").
19
+ function identityHash(uint8 identityType, bytes memory value) internal pure returns (bytes32) {
20
+ if (identityType >= NUM_TYPES) revert UnknownIdentityType(identityType);
21
+ if (value.length == 0 || value.length > 128) revert ValueLength(value.length);
22
+ return sha256(abi.encodePacked(PREFIX, identityType, normalize(identityType, value)));
23
+ }
24
+
25
+ /// @notice Commitment for an EVM wallet, hashed as its lowercase `0x…` hex string.
26
+ function walletHash(address wallet) internal pure returns (bytes32) {
27
+ return sha256(abi.encodePacked(PREFIX, WALLET, toLowerHex(wallet)));
28
+ }
29
+
30
+ /// @notice Commitment for a wallet on any chain, given as Privy stores it (e.g. base58).
31
+ function walletHash(string memory wallet) internal pure returns (bytes32) {
32
+ return identityHash(WALLET, bytes(wallet));
33
+ }
34
+
35
+ function normalize(uint8 identityType, bytes memory value) internal pure returns (bytes memory) {
36
+ bool lower;
37
+ if (identityType == WALLET) {
38
+ lower = value.length >= 2 && value[0] == "0" && value[1] == "x";
39
+ } else {
40
+ lower = identityType != PHONE;
41
+ }
42
+ if (!lower) return value;
43
+ bytes memory out = new bytes(value.length);
44
+ for (uint256 i = 0; i < value.length; i++) {
45
+ uint8 c = uint8(value[i]);
46
+ out[i] = (c >= 65 && c <= 90) ? bytes1(c + 32) : value[i];
47
+ }
48
+ return out;
49
+ }
50
+
51
+ /// @dev "0x" + 40 lowercase hex chars, the form the circuit hashes an EVM address in.
52
+ function toLowerHex(address a) internal pure returns (bytes memory out) {
53
+ bytes16 digits = "0123456789abcdef";
54
+ out = new bytes(42);
55
+ out[0] = "0";
56
+ out[1] = "x";
57
+ uint160 v = uint160(a);
58
+ for (uint256 i = 41; i > 1; i--) {
59
+ out[i] = digits[v & 0xf];
60
+ v >>= 4;
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HASH_PREFIX = exports.IdentityType = void 0;
4
+ exports.isCaseInsensitive = isCaseInsensitive;
5
+ exports.normalizeIdentityValue = normalizeIdentityValue;
6
+ exports.identityHash = identityHash;
7
+ exports.toHex = toHex;
8
+ const sha256_1 = require("@noble/hashes/sha256");
9
+ /**
10
+ * Identity types. The numeric value is the protocol's type id (it is what gets hashed), so it
11
+ * must match circuit/src/identity.nr and never change.
12
+ */
13
+ var IdentityType;
14
+ (function (IdentityType) {
15
+ IdentityType[IdentityType["Email"] = 0] = "Email";
16
+ IdentityType[IdentityType["Phone"] = 1] = "Phone";
17
+ IdentityType[IdentityType["Google"] = 2] = "Google";
18
+ IdentityType[IdentityType["Twitter"] = 3] = "Twitter";
19
+ /** Alias of `Twitter`: the platform's current name. Same id, same addresses. */
20
+ IdentityType[IdentityType["X"] = 3] = "X";
21
+ IdentityType[IdentityType["Discord"] = 4] = "Discord";
22
+ IdentityType[IdentityType["Github"] = 5] = "Github";
23
+ IdentityType[IdentityType["Linkedin"] = 6] = "Linkedin";
24
+ IdentityType[IdentityType["Apple"] = 7] = "Apple";
25
+ IdentityType[IdentityType["Telegram"] = 8] = "Telegram";
26
+ IdentityType[IdentityType["Tiktok"] = 9] = "Tiktok";
27
+ IdentityType[IdentityType["Instagram"] = 10] = "Instagram";
28
+ IdentityType[IdentityType["Farcaster"] = 11] = "Farcaster";
29
+ IdentityType[IdentityType["Wallet"] = 12] = "Wallet";
30
+ })(IdentityType || (exports.IdentityType = IdentityType = {}));
31
+ /** Domain-separation prefix baked into the circuit's identity hash. */
32
+ exports.HASH_PREFIX = 'p2id.identity.v1';
33
+ /** Types whose value is ASCII-lowercased before hashing: everything except phone numbers and
34
+ * wallet addresses (matches CASE_INSENSITIVE in circuit/src/identity.nr). */
35
+ function isCaseInsensitive(type) {
36
+ return type !== IdentityType.Wallet && type !== IdentityType.Phone;
37
+ }
38
+ /**
39
+ * Apply the circuit's normalisation to an identity value: ASCII-lowercase for case-insensitive
40
+ * types and for EVM (`0x…`) wallet addresses; base58 (Solana) addresses are left untouched.
41
+ */
42
+ function normalizeIdentityValue(type, value) {
43
+ const lower = isCaseInsensitive(type) || (type === IdentityType.Wallet && value.startsWith('0x'));
44
+ return lower ? value.replace(/[A-Z]/g, (c) => c.toLowerCase()) : value;
45
+ }
46
+ /**
47
+ * `sha256(HASH_PREFIX || type || normalize(value))`, exactly as the circuit computes it.
48
+ * This is the routing salt a payer uses to address an identity, and the value a proof's
49
+ * `identityHash` output is compared against.
50
+ */
51
+ function identityHash(type, value, domain = exports.HASH_PREFIX) {
52
+ const enc = new TextEncoder();
53
+ const prefix = enc.encode(domain);
54
+ const body = enc.encode(normalizeIdentityValue(type, value));
55
+ if (body.length < 1 || body.length > 128)
56
+ throw new Error('identity value must be 1..128 bytes');
57
+ const preimage = new Uint8Array(prefix.length + 1 + body.length);
58
+ preimage.set(prefix, 0);
59
+ preimage[prefix.length] = type;
60
+ preimage.set(body, prefix.length + 1);
61
+ return toHex((0, sha256_1.sha256)(preimage));
62
+ }
63
+ function toHex(bytes) {
64
+ let s = '';
65
+ for (const b of bytes)
66
+ s += b.toString(16).padStart(2, '0');
67
+ return `0x${s}`;
68
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IDENTITY_TYPE_BY_NAME = exports.P2ID_TYPE_NAMES = void 0;
4
+ exports.identityTypeName = identityTypeName;
5
+ exports.resolveIdentityType = resolveIdentityType;
6
+ const identity_js_1 = require("./identity.js");
7
+ /** P2ID type names (the URI names in P2ID.md). */
8
+ exports.P2ID_TYPE_NAMES = {
9
+ email: identity_js_1.IdentityType.Email,
10
+ phone: identity_js_1.IdentityType.Phone,
11
+ google: identity_js_1.IdentityType.Google,
12
+ x: identity_js_1.IdentityType.X,
13
+ discord: identity_js_1.IdentityType.Discord,
14
+ github: identity_js_1.IdentityType.Github,
15
+ linkedin: identity_js_1.IdentityType.Linkedin,
16
+ apple: identity_js_1.IdentityType.Apple,
17
+ telegram: identity_js_1.IdentityType.Telegram,
18
+ tiktok: identity_js_1.IdentityType.Tiktok,
19
+ instagram: identity_js_1.IdentityType.Instagram,
20
+ farcaster: identity_js_1.IdentityType.Farcaster,
21
+ wallet: identity_js_1.IdentityType.Wallet,
22
+ };
23
+ /**
24
+ * Every accepted name: the canonical P2ID names plus `twitter` (an alias of `x`). P2ID core is
25
+ * agnostic of any identity provider — it does not know Privy (or any other verifier's) account
26
+ * types. Callers map their provider's account types to these names themselves.
27
+ */
28
+ exports.IDENTITY_TYPE_BY_NAME = {
29
+ ...exports.P2ID_TYPE_NAMES,
30
+ twitter: identity_js_1.IdentityType.Twitter,
31
+ };
32
+ /** The P2ID name of a type, e.g. IdentityType.Twitter -> "x". */
33
+ function identityTypeName(type) {
34
+ const entry = Object.entries(exports.P2ID_TYPE_NAMES).find(([, id]) => id === type);
35
+ if (!entry)
36
+ throw new Error(`unknown identity type id ${type}`);
37
+ return entry[0];
38
+ }
39
+ function resolveIdentityType(t) {
40
+ if (typeof t === 'number') {
41
+ if (!Object.values(exports.IDENTITY_TYPE_BY_NAME).includes(t))
42
+ throw new Error(`unknown identity type id ${t}`);
43
+ return t;
44
+ }
45
+ const id = exports.IDENTITY_TYPE_BY_NAME[t];
46
+ if (id === undefined)
47
+ throw new Error(`unknown identity type "${t}"`);
48
+ return id;
49
+ }
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.P2ID_SCHEMES = exports.P2ID_SCHEME = exports.checksumAddress = exports.p2idScheme = exports.p2idAddressForHash = exports.p2idAddress = exports.identityHash = exports.resolveIdentityType = exports.identityTypeName = exports.P2ID_TYPE_NAMES = exports.IDENTITY_TYPE_BY_NAME = exports.toHex = exports.normalizeIdentityValue = exports.isCaseInsensitive = exports.HASH_PREFIX = exports.IdentityType = void 0;
4
+ // P2ID protocol core: no proof verification here, and no heavy dependencies, so it is safe in
5
+ // browsers. Verifying attestations lives in @pvium/p2id-verifier.
6
+ var identity_js_1 = require("./identity.js");
7
+ Object.defineProperty(exports, "IdentityType", { enumerable: true, get: function () { return identity_js_1.IdentityType; } });
8
+ Object.defineProperty(exports, "HASH_PREFIX", { enumerable: true, get: function () { return identity_js_1.HASH_PREFIX; } });
9
+ Object.defineProperty(exports, "isCaseInsensitive", { enumerable: true, get: function () { return identity_js_1.isCaseInsensitive; } });
10
+ Object.defineProperty(exports, "normalizeIdentityValue", { enumerable: true, get: function () { return identity_js_1.normalizeIdentityValue; } });
11
+ Object.defineProperty(exports, "toHex", { enumerable: true, get: function () { return identity_js_1.toHex; } });
12
+ var identityNames_js_1 = require("./identityNames.js");
13
+ Object.defineProperty(exports, "IDENTITY_TYPE_BY_NAME", { enumerable: true, get: function () { return identityNames_js_1.IDENTITY_TYPE_BY_NAME; } });
14
+ Object.defineProperty(exports, "P2ID_TYPE_NAMES", { enumerable: true, get: function () { return identityNames_js_1.P2ID_TYPE_NAMES; } });
15
+ Object.defineProperty(exports, "identityTypeName", { enumerable: true, get: function () { return identityNames_js_1.identityTypeName; } });
16
+ Object.defineProperty(exports, "resolveIdentityType", { enumerable: true, get: function () { return identityNames_js_1.resolveIdentityType; } });
17
+ var p2id_js_1 = require("./p2id.js");
18
+ Object.defineProperty(exports, "identityHash", { enumerable: true, get: function () { return p2id_js_1.identityHash; } });
19
+ Object.defineProperty(exports, "p2idAddress", { enumerable: true, get: function () { return p2id_js_1.p2idAddress; } });
20
+ Object.defineProperty(exports, "p2idAddressForHash", { enumerable: true, get: function () { return p2id_js_1.p2idAddressForHash; } });
21
+ Object.defineProperty(exports, "p2idScheme", { enumerable: true, get: function () { return p2id_js_1.p2idScheme; } });
22
+ Object.defineProperty(exports, "checksumAddress", { enumerable: true, get: function () { return p2id_js_1.checksumAddress; } });
23
+ Object.defineProperty(exports, "P2ID_SCHEME", { enumerable: true, get: function () { return p2id_js_1.P2ID_SCHEME; } });
24
+ Object.defineProperty(exports, "P2ID_SCHEMES", { enumerable: true, get: function () { return p2id_js_1.P2ID_SCHEMES; } });
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.P2ID_SCHEMES = exports.P2ID_SCHEME = void 0;
4
+ exports.p2idScheme = p2idScheme;
5
+ exports.identityHash = identityHash;
6
+ exports.p2idAddress = p2idAddress;
7
+ exports.p2idAddressForHash = p2idAddressForHash;
8
+ exports.checksumAddress = checksumAddress;
9
+ const sha3_1 = require("@noble/hashes/sha3");
10
+ const identity_js_1 = require("./identity.js");
11
+ const identityNames_js_1 = require("./identityNames.js");
12
+ const p2idConstants_js_1 = require("./p2idConstants.js");
13
+ Object.defineProperty(exports, "P2ID_SCHEME", { enumerable: true, get: function () { return p2idConstants_js_1.P2ID_SCHEME; } });
14
+ Object.defineProperty(exports, "P2ID_SCHEMES", { enumerable: true, get: function () { return p2idConstants_js_1.P2ID_SCHEMES; } });
15
+ /**
16
+ * P2ID address derivation.
17
+ *
18
+ * identityHash = sha256(identityDomain ‖ byte(typeId) ‖ normalize(value))
19
+ * p2id = keccak256(0xff ‖ factory ‖ identityHash ‖ vaultInitCodeHash)[12..]
20
+ *
21
+ * `normalize` ASCII-lowercases every type except phone and wallet, and lowercases `0x…` wallet
22
+ * addresses. A *scheme* (`pvium.vault.v1`, or a later `pvium.vault.vN`) fixes the three constants: the identity domain, the
23
+ * vault creation-code hash and the factory. The factory is deployed through the deterministic
24
+ * deployment proxy, so it is at the same address on every EVM chain: a P2ID address is
25
+ * chain-agnostic, like any wallet address. (The vault contract still has to be deployed on each
26
+ * chain where it is claimed; anyone can do that, and funds sent before then are claimable once
27
+ * it is.)
28
+ *
29
+ * Each Pvium environment is its own stack: `production` (mainnets, the production Privy app) and
30
+ * `sandbox` (testnets, the sandbox Privy app) have different factories, so the same identity has
31
+ * a different address in each. Production is the default.
32
+ *
33
+ * Schemes are history. A change to the vault bytecode moves every address, so it ships as the
34
+ * next scheme and becomes `P2ID_SCHEME`; earlier schemes stay in `P2ID_SCHEMES` so addresses
35
+ * people were already given can be derived, and claimed, forever.
36
+ */
37
+ /** Constants of a scheme; defaults to the current one. */
38
+ function p2idScheme(name = p2idConstants_js_1.P2ID_SCHEME) {
39
+ const s = p2idConstants_js_1.P2ID_SCHEMES[name];
40
+ if (!s)
41
+ throw new Error(`unknown P2ID scheme "${name}" (known: ${Object.keys(p2idConstants_js_1.P2ID_SCHEMES).join(', ')})`);
42
+ return s;
43
+ }
44
+ /** The identity commitment: the CREATE2 salt of the identity's vault and the value proofs bind. */
45
+ function identityHash(type, value, scheme = p2idConstants_js_1.P2ID_SCHEME) {
46
+ return (0, identity_js_1.identityHash)((0, identityNames_js_1.resolveIdentityType)(type), value, p2idScheme(scheme).identityDomain);
47
+ }
48
+ /** The P2ID address for an identity: where to pay it, on any EVM chain, deployed or not. Checksummed. */
49
+ function p2idAddress(input) {
50
+ const salt = identityHash(input.identityType, input.identityValue, input.scheme);
51
+ return p2idAddressForHash(salt, { scheme: input.scheme, environment: input.environment, factory: input.factory });
52
+ }
53
+ /** Same, from an identity hash you already have (e.g. from an attestation's claim). */
54
+ function p2idAddressForHash(identityHash, opts = {}) {
55
+ const scheme = p2idScheme(opts.scheme);
56
+ const environment = opts.environment ?? 'production';
57
+ if (environment !== 'production' && environment !== 'sandbox')
58
+ throw new Error(`unknown environment "${environment}"`);
59
+ const factory = opts.factory ?? scheme.factories[environment];
60
+ if (!factory) {
61
+ throw new Error(`scheme ${opts.scheme ?? p2idConstants_js_1.P2ID_SCHEME} has no ${environment} factory address in this release yet; pass \`factory\` explicitly`);
62
+ }
63
+ if (!/^0x[0-9a-fA-F]{40}$/.test(factory))
64
+ throw new Error(`bad factory address ${factory}`);
65
+ const preimage = new Uint8Array(1 + 20 + 32 + 32);
66
+ preimage[0] = 0xff;
67
+ preimage.set(hexToBytes(factory, 20), 1);
68
+ preimage.set(hexToBytes(identityHash, 32), 21);
69
+ preimage.set(hexToBytes(scheme.vaultInitCodeHash, 32), 53);
70
+ return checksumAddress((0, identity_js_1.toHex)((0, sha3_1.keccak_256)(preimage).subarray(12)));
71
+ }
72
+ /** EIP-55 checksum. */
73
+ function checksumAddress(address) {
74
+ const lower = address.slice(2).toLowerCase();
75
+ const hash = (0, sha3_1.keccak_256)(new TextEncoder().encode(lower));
76
+ let out = '0x';
77
+ for (let i = 0; i < 40; i++) {
78
+ const nibble = (hash[i >> 1] >> (i % 2 === 0 ? 4 : 0)) & 0xf;
79
+ out += nibble >= 8 ? lower[i].toUpperCase() : lower[i];
80
+ }
81
+ return out;
82
+ }
83
+ function hexToBytes(hex, length) {
84
+ const h = hex.startsWith('0x') ? hex.slice(2) : hex;
85
+ if (h.length !== length * 2 || !/^[0-9a-fA-F]*$/.test(h))
86
+ throw new Error(`expected ${length}-byte hex, got ${hex}`);
87
+ const out = new Uint8Array(length);
88
+ for (let i = 0; i < length; i++)
89
+ out[i] = parseInt(h.slice(i * 2, i * 2 + 2), 16);
90
+ return out;
91
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.P2ID_SCHEME = exports.P2ID_SCHEMES = void 0;
4
+ /** Every P2ID address scheme this release can derive, keyed by domain. Older schemes stay forever. */
5
+ exports.P2ID_SCHEMES = {
6
+ "pvium.vault.v1": {
7
+ "identityDomain": "p2id.identity.v1",
8
+ "vaultInitCodeHash": "0x59c2f7f1e9725340e25a45b122fae7d1e9cbd9c7b00dba6215ebaa67cfb691ef",
9
+ "factories": {
10
+ "production": null,
11
+ "sandbox": "0xd1DEa4e8911955DAD961B643AFdE6Ab34d9Bd371"
12
+ }
13
+ }
14
+ };
15
+ /** The scheme new payments should use. */
16
+ exports.P2ID_SCHEME = 'pvium.vault.v1';
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Identity types. The numeric value is the protocol's type id (it is what gets hashed), so it
3
+ * must match circuit/src/identity.nr and never change.
4
+ */
5
+ export declare enum IdentityType {
6
+ Email = 0,
7
+ Phone = 1,
8
+ Google = 2,
9
+ Twitter = 3,
10
+ /** Alias of `Twitter`: the platform's current name. Same id, same addresses. */
11
+ X = 3,
12
+ Discord = 4,
13
+ Github = 5,
14
+ Linkedin = 6,
15
+ Apple = 7,
16
+ Telegram = 8,
17
+ Tiktok = 9,
18
+ Instagram = 10,
19
+ Farcaster = 11,
20
+ Wallet = 12
21
+ }
22
+ /** Domain-separation prefix baked into the circuit's identity hash. */
23
+ export declare const HASH_PREFIX = "p2id.identity.v1";
24
+ /** Types whose value is ASCII-lowercased before hashing: everything except phone numbers and
25
+ * wallet addresses (matches CASE_INSENSITIVE in circuit/src/identity.nr). */
26
+ export declare function isCaseInsensitive(type: IdentityType): boolean;
27
+ /**
28
+ * Apply the circuit's normalisation to an identity value: ASCII-lowercase for case-insensitive
29
+ * types and for EVM (`0x…`) wallet addresses; base58 (Solana) addresses are left untouched.
30
+ */
31
+ export declare function normalizeIdentityValue(type: IdentityType, value: string): string;
32
+ /**
33
+ * `sha256(HASH_PREFIX || type || normalize(value))`, exactly as the circuit computes it.
34
+ * This is the routing salt a payer uses to address an identity, and the value a proof's
35
+ * `identityHash` output is compared against.
36
+ */
37
+ export declare function identityHash(type: IdentityType, value: string, domain?: string): `0x${string}`;
38
+ export declare function toHex(bytes: Uint8Array): `0x${string}`;