// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; {{IMPORTS}} /** * @title {{CONTRACT_NAME}} * @notice Private preference matching - find matches without revealing preferences * @dev Uses FHE bitwise operations for privacy-preserving compatibility checks * * FHE Operations Used: * - and: Check if both parties have matching preferences * - or: Combine preference flags * - xor: Find differences in preferences * - not: Invert preference bits * - shl/shr: Bit manipulation for preference encoding * - eq: Exact preference match * - gt/gte: Threshold-based matching * - select: Conditional reveal */ contract {{CONTRACT_NAME}} is {{INHERITS}} { // ============ Errors ============ error ProfileAlreadyExists(); error ProfileNotFound(); error PreferencesNotSet(); error CannotMatchSelf(); error MatchAlreadyChecked(); error NotAMatch(); error NotAuthorized(); {{ERRORS}} // ============ Events ============ event ProfileRegistered(address indexed user); event PreferencesSet(address indexed user); event MatchFound(address indexed user1, address indexed user2); event MatchRevealed(address indexed user1, address indexed user2); {{EVENTS}} // ============ Structs ============ struct Profile { [[PREF_TYPE]] attributes; // Encrypted: what I am (bit flags) [[PREF_TYPE]] preferences; // Encrypted: what I'm looking for bool registered; bool preferencesSet; } struct MatchResult { ebool isMatch; bool revealed; bool accepted1; bool accepted2; } // ============ State Variables ============ mapping(address => Profile) public _profiles; mapping(bytes32 => MatchResult) public _matches; address[] public registeredUsers; uint256 public minMatchScore; // Minimum compatibility score (0-100) {{STATE_VARIABLES}} // ============ Modifiers ============ modifier hasProfile() { if (!_profiles[msg.sender].registered) revert ProfileNotFound(); _; } modifier hasPreferences() { if (!_profiles[msg.sender].preferencesSet) revert PreferencesNotSet(); _; } {{MODIFIERS}} // ============ Constructor ============ constructor(uint256 _minMatchScore{{CONSTRUCTOR_PARAMS}}) { require(_minMatchScore <= 100, "Score must be <= 100"); minMatchScore = _minMatchScore; {{CONSTRUCTOR_BODY}} } // ============ External Functions ============ /** * @notice Register a new profile with encrypted attributes * @param encryptedAttributes Your attributes as encrypted bitfield * Bits represent: interests, location, age range, etc. */ function registerProfile([[EXTERNAL_PREF_TYPE]] encryptedAttributes, bytes calldata inputProof) external { if (_profiles[msg.sender].registered) revert ProfileAlreadyExists(); [[PREF_TYPE]] attributes = FHE.fromExternal(encryptedAttributes, inputProof); _profiles[msg.sender] = Profile({ attributes: attributes, preferences: FHE.asEuint64(0), registered: true, preferencesSet: false }); FHE.allowThis(attributes); FHE.allow(attributes, msg.sender); registeredUsers.push(msg.sender); emit ProfileRegistered(msg.sender); } /** * @notice Set what you're looking for (preferences) * @param encryptedPreferences Preferences as encrypted bitfield * Must match format of attributes (same bit positions) */ function setPreferences([[EXTERNAL_PREF_TYPE]] encryptedPreferences, bytes calldata inputProof) external hasProfile { [[PREF_TYPE]] preferences = FHE.fromExternal(encryptedPreferences, inputProof); _profiles[msg.sender].preferences = preferences; _profiles[msg.sender].preferencesSet = true; FHE.allowThis(preferences); FHE.allow(preferences, msg.sender); emit PreferencesSet(msg.sender); } /** * @notice Check if two users are compatible * @dev Uses FHE bitwise AND to check preference overlap * @param other The other user to check compatibility with */ function checkMatch(address other) external hasProfile hasPreferences returns (bytes32) { if (other == msg.sender) revert CannotMatchSelf(); if (!_profiles[other].registered) revert ProfileNotFound(); if (!_profiles[other].preferencesSet) revert PreferencesNotSet(); bytes32 matchId = _getMatchId(msg.sender, other); if (_matches[matchId].revealed) revert MatchAlreadyChecked(); Profile storage myProfile = _profiles[msg.sender]; Profile storage theirProfile = _profiles[other]; // Check mutual compatibility using bitwise AND // My preferences AND their attributes = do they have what I want? [[PREF_TYPE]] iLikeThem = FHE.and(myProfile.preferences, theirProfile.attributes); // Their preferences AND my attributes = do I have what they want? [[PREF_TYPE]] theyLikeMe = FHE.and(theirProfile.preferences, myProfile.attributes); // Mutual match: both must have overlap // XOR the results and check if there's significant overlap [[PREF_TYPE]] mutualMatch = FHE.and(iLikeThem, theyLikeMe); // Check if mutual match is non-zero (using gt with 0) ebool isMatch = FHE.gt(mutualMatch, FHE.asEuint64(0)); _matches[matchId] = MatchResult({ isMatch: isMatch, revealed: false, accepted1: false, accepted2: false }); FHE.allowThis(isMatch); return matchId; } /** * @notice Reveal match result (both parties must agree) * @param matchId The match to reveal */ function revealMatch(bytes32 matchId) external { MatchResult storage result = _matches[matchId]; (address user1, address user2) = _getUsersFromMatchId(matchId); if (msg.sender != user1 && msg.sender != user2) revert NotAuthorized(); if (result.revealed) revert MatchAlreadyChecked(); // Record acceptance if (msg.sender == user1) { result.accepted1 = true; } else { result.accepted2 = true; } // If both accepted, reveal if (result.accepted1 && result.accepted2) { result.revealed = true; // Allow both users to see the result FHE.allow(result.isMatch, user1); FHE.allow(result.isMatch, user2); emit MatchRevealed(user1, user2); } } /** * @notice Update your attributes * @param encryptedAttributes New attributes */ function updateAttributes([[EXTERNAL_PREF_TYPE]] encryptedAttributes, bytes calldata inputProof) external hasProfile { [[PREF_TYPE]] attributes = FHE.fromExternal(encryptedAttributes, inputProof); _profiles[msg.sender].attributes = attributes; FHE.allowThis(attributes); FHE.allow(attributes, msg.sender); } /** * @notice Get compatibility score between two preference sets * @dev Uses popcount-like operation to count matching bits * @param user1 First user * @param user2 Second user */ function getCompatibilityScore(address user1, address user2) external view returns (bytes32) { // Returns matchId to check score return _getMatchId(user1, user2); } {{EXTERNAL_FUNCTIONS}} // ============ View Functions ============ /** * @notice Check if address has a profile */ function hasRegisteredProfile(address user) external view returns (bool) { return _profiles[user].registered; } /** * @notice Check if user has set preferences */ function hasSetPreferences(address user) external view returns (bool) { return _profiles[user].preferencesSet; } /** * @notice Get total registered users */ function getRegisteredUserCount() external view returns (uint256) { return registeredUsers.length; } /** * @notice Check match status */ function getMatchStatus(bytes32 matchId) external view returns ( bool revealed, bool accepted1, bool accepted2 ) { MatchResult storage result = _matches[matchId]; return (result.revealed, result.accepted1, result.accepted2); } {{VIEW_FUNCTIONS}} // ============ Internal Functions ============ function _getMatchId(address user1, address user2) internal pure returns (bytes32) { // Consistent ordering for match ID if (user1 < user2) { return keccak256(abi.encodePacked(user1, user2)); } else { return keccak256(abi.encodePacked(user2, user1)); } } function _getUsersFromMatchId(bytes32 matchId) internal view returns (address, address) { // This is a simplified version - in production you'd store this for (uint i = 0; i < registeredUsers.length; i++) { for (uint j = i + 1; j < registeredUsers.length; j++) { if (_getMatchId(registeredUsers[i], registeredUsers[j]) == matchId) { return (registeredUsers[i], registeredUsers[j]); } } } revert("Match not found"); } {{INTERNAL_FUNCTIONS}} }