28 views
# Adding Privacy Preserving Computations capabilities to MCPs :::success **By [Silence Laboratories](https://www.silencelaboratories.com/)** ::: ## 1. System Overview This system implements a privacy-preserving computation using the Private Set Intersection (PSI) protocol between two entities: - **Entity 1**: Holds specific information it wants to check - **Entity 2**: Maintains a list of sensitive items/information Entity 1 wants to determine if the information it holds is present in Entity 2's list. The key privacy requirement is that neither entity should reveal their data directly to the other: - Entity 1 must not disclose its sensitive information to Entity 2 - Entity 2 must not share its complete list with Entity 1 This is a classic case of Private Set Intersection, where two parties can find the intersection of their data sets without revealing the actual data to each other. In this implementation, Entity 1 is hosted via an MCP (Model Context Protocol) server and accessible through a client interface via Nanda. This allows AI models to securely interact with the system while preserving the privacy guarantees of the PSC protocol. ## 2. Prerequisites - Python 3.7 or higher - Flask framework - A server or virtual machine with x86 architecture - Basic knowledge of terminal/command line ## 3. Download and Setup ### 3.1 Download the Binary Library The system requires a pre-compiled binary library to perform the cryptographic operations for the PSC protocol. 1. Download the pre-compiled binary: ```bash wget https://db-app-2.s3.us-west-2.amazonaws.com/libsanction_check_lib_1.so ``` 2. Place it in your project directory: ```bash mkdir -p ~/entity2_server mv libsanction_check_lib_single_thread_x86.so ~/entity2_server/ cd ~/entity2_server ``` **Note**: This binary is compiled for x86 architecture. Make sure your system is compatible. ### 3.2 Create the Items JSON File Create a file called `items.json` to store your list: ```bash nano items.json ``` Add the following content (you can modify the list as needed): ```json { "sanctioned_items": [ "Entity001,,", "Entity002,,", "Entity003,,", "Person001,,", "Person002,,", "Person003,,", "Organization001,,", "Organization002,,", "Organization003,,", "Ship001,," ] } ``` **Note:** When editing this file directly, each entity must end with `,,` for the system to work properly. However, when using the API to add entities, you should NOT include these commas. Save and exit (in nano: Ctrl+O, Enter, Ctrl+X). ### 3.3 Set Up Python Environment It's recommended to use a virtual environment: ```bash python -m venv venv source venv/bin/activate # On Windows: venv\Scripts\activate ``` Install the required packages: ```bash pip install flask requests ``` ## 4. Write the Flask Application Create a new file called `entity2_server.py`: ```bash nano entity2_server.py ``` Copy the following code into the file: ```python from flask import Flask, request, jsonify import ctypes from ctypes import c_char_p, c_size_t, c_uint64, POINTER, byref, c_int import base64 import os import json app = Flask(__name__) # Load the binary library lib_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'libsanction_check_lib_single_thread_x86.so') lib = ctypes.CDLL(lib_path) # Define argument and return types for library functions lib.generate_hash.argtypes = [c_char_p, c_size_t, c_char_p] lib.generate_hash.restype = POINTER(ctypes.c_uint8) lib.process_psc_msg1.argtypes = [ POINTER(ctypes.c_uint8 * 32), # session ID POINTER(ctypes.c_uint8), # big_y_bytes pointer c_size_t, # number of entries in big_y_bytes POINTER(ctypes.c_uint8), # msg1 pointer c_size_t, # msg1 pointer length c_uint64, # RNG seed POINTER(POINTER(ctypes.c_uint8)), # output_msg2_ptr POINTER(c_size_t) # output msg2 length ] lib.process_psc_msg1.restype = ctypes.c_int # Path to items JSON file ITEMS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'items.json') # Load items from JSON file def load_items(): try: if os.path.exists(ITEMS_FILE): with open(ITEMS_FILE, 'r') as file: data = json.load(file) return data.get('sanctioned_items', []) else: # Create default items file if it doesn't exist default_items = { "sanctioned_items": [ "Entity001,,", "Entity002,,", "Entity003,,", "Person001,,", "Person002,,", "Person003,,", "Organization001,,", "Organization002,,", "Organization003,,", "Ship001,," ] } with open(ITEMS_FILE, 'w') as file: json.dump(default_items, file, indent=2) return default_items.get('sanctioned_items', []) except Exception as e: print(f"Error loading items: {str(e)}") return [] # Initialize items list ITEMS_LIST = load_items() # Save items to JSON file def save_items(): try: with open(ITEMS_FILE, 'w') as file: json.dump({"sanctioned_items": ITEMS_LIST}, file, indent=2) return True except Exception as e: print(f"Error saving items: {str(e)}") return False # Format item entry to ensure it ends with ",," def format_item_entry(entry): # Strip any trailing commas first entry = entry.rstrip(',') # Then add the required ",," suffix return f"{entry},," # Helper functions def generate_hash(input_data, num_entries: int, type_flag: str) -> bytes: input_data_c = c_char_p(input_data.encode('utf-8')) type_flag_c = c_char_p(type_flag.encode('utf-8')) hash_ptr = lib.generate_hash(input_data_c, num_entries, type_flag_c) if not hash_ptr: raise ValueError("Failed to generate hash bytes") hash_length = 32 * num_entries # 32 bytes per item hash_bytes = bytes(hash_ptr[i] for i in range(hash_length)) return hash_bytes def process_psc_msg1(session_id: bytes, big_y_hashes: list, msg1: bytes, seed: int) -> bytes: if len(session_id) != 32: raise ValueError("Session ID must be 32 bytes long") # Convert session_id to ctypes array session_id_array = (ctypes.c_uint8 * 32).from_buffer_copy(session_id) # Convert msg1 to ctypes array msg1_array = (ctypes.c_uint8 * len(msg1)).from_buffer_copy(msg1) msg1_len = len(msg1) # Convert big_y_hashes to a single ctypes array big_y_bytes = (ctypes.c_uint8 * (32 * len(big_y_hashes)))() for i, item_hash in enumerate(big_y_hashes): for j in range(32): big_y_bytes[i * 32 + j] = item_hash[j] # Prepare output parameters output_msg2_ptr = ctypes.POINTER(ctypes.c_uint8)() output_msg2_len = c_size_t(0) # Call the library function result = lib.process_psc_msg1( byref(session_id_array), big_y_bytes, len(big_y_hashes), msg1_array, msg1_len, seed, ctypes.byref(output_msg2_ptr), ctypes.byref(output_msg2_len) ) # Check result and handle error codes if result == 0: # Return the actual message bytes msg2 = bytes(ctypes.cast(output_msg2_ptr, ctypes.POINTER(ctypes.c_uint8 * output_msg2_len.value)).contents) return msg2 elif result == -1: raise ValueError("Null pointer error in process_psc_msg1") elif result == -2: raise ValueError("Deserialization error for msg1 in process_psc_msg1") elif result == -3: raise ValueError("Invalid number of entries for big_y_bytes in process_psc_msg1") elif result == -4: raise ValueError("Error in psc_process_msg1 execution") elif result == -5: raise ValueError("Serialization error for msg2 in process_psc_msg1") else: raise ValueError("Unknown error in process_psc_msg1") @app.route('/', methods=['GET']) def root(): return jsonify({ "status": "Active", "service": "Privacy-Preserving Item Check API", "version": "1.0" }) # API Routes @app.route('/api/update_items_list', methods=['POST']) def update_items_list(): try: data = request.json raw_items_list = data.get('items_list', []) if not isinstance(raw_items_list, list): return jsonify({"error": "Items list must be an array"}), 400 # Check for entries with double commas if any(entry.endswith(',,') for entry in raw_items_list): return jsonify({"error": "Items should not include double commas, they will be added automatically"}), 400 # Format each entry to ensure it has the required ",," suffix formatted_items_list = [format_item_entry(entry) for entry in raw_items_list] global ITEMS_LIST ITEMS_LIST = formatted_items_list # Save updated list to file save_items() return jsonify({ "success": True, "message": f"Items list updated with {len(ITEMS_LIST)} entries" }) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/process_msg1', methods=['POST']) def handle_process_msg1(): try: data = request.json session_id_b64 = data.get('session_id') msg1_b64 = data.get('msg1') seed = data.get('seed') if not all([session_id_b64, msg1_b64, seed]): return jsonify({"error": "Missing required parameters"}), 400 # Decode base64 data session_id = base64.b64decode(session_id_b64) msg1 = base64.b64decode(msg1_b64) # Generate hash for the items list items_csv = ';'.join(ITEMS_LIST) items_hash = generate_hash(items_csv, len(ITEMS_LIST), "Sanction") # Split the hash into chunks for each item big_y_hashes = [items_hash[i:i+32] for i in range(0, len(items_hash), 32)] # Process the message msg2 = process_psc_msg1(session_id, big_y_hashes, msg1, seed) # Return the result return jsonify({ "msg2": base64.b64encode(msg2).decode('utf-8') }) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/get_items_info', methods=['GET']) def get_items_info(): # Strip the ",," from the displayed entries display_items = [item.rstrip(',,') for item in ITEMS_LIST] return jsonify({ "count": len(ITEMS_LIST), "sample": display_items[:2] if len(display_items) > 2 else display_items }) @app.route('/api/get_all_items', methods=['GET']) def get_all_items(): # Strip the ",," from the displayed entries display_items = [item.rstrip(',,') for item in ITEMS_LIST] return jsonify({ "count": len(ITEMS_LIST), "items": display_items }) @app.route('/api/add_item', methods=['POST']) def add_item(): try: data = request.json # Get the item data as a string raw_entry = data.get('item_entry') if not raw_entry: return jsonify({"error": "Item entry is required"}), 400 # Check if the entry already has double commas if raw_entry.endswith(',,'): return jsonify({"error": "Item entry should not include double commas, they will be added automatically"}), 400 # Format the entry to add the required ",," suffix item_entry = format_item_entry(raw_entry) # Add to the global list global ITEMS_LIST # Check if it already exists (comparing normalized versions) normalized_entries = [item.rstrip(',,') for item in ITEMS_LIST] if raw_entry.rstrip(',') in normalized_entries: return jsonify({"success": False, "message": "This item already exists in the list"}), 409 ITEMS_LIST.append(item_entry) # Save updated list to file save_items() return jsonify({ "success": True, "message": f"Item '{raw_entry}' added successfully", "current_count": len(ITEMS_LIST) }) except Exception as e: return jsonify({"error": str(e)}), 500 if __name__ == '__main__': # Ensure the items are loaded at startup ITEMS_LIST = load_items() app.run(host='0.0.0.0', port=5001) ``` Save and exit (in nano: Ctrl+O, Enter, Ctrl+X). ## 5. Deploy the Flask Application ### 5.1 Running in Development Mode For testing and development, you can run the Flask app directly: ```bash python entity2_server.py ``` This will start the server on port 5001, and it will be accessible at http://127.0.0.1:5001/ ### 5.2 Production Deployment For production environments, it's recommended to use a WSGI server like Gunicorn: ```bash pip install gunicorn ``` Run with Gunicorn: ```bash gunicorn -w 4 -b 0.0.0.0:5001 entity2_server:app ``` For a more robust setup, you can create a systemd service: ```bash sudo nano /etc/systemd/system/entity2-server.service ``` Add the following content (adjust paths as needed): ``` [Unit] Description=Privacy-Preserving Item Check API After=network.target [Service] User=yourusername WorkingDirectory=/home/yourusername/entity2_server ExecStart=/home/yourusername/entity2_server/venv/bin/gunicorn -w 4 -b 0.0.0.0:5001 entity2_server:app Restart=always [Install] WantedBy=multi-user.target ``` Start and enable the service: ```bash sudo systemctl daemon-reload sudo systemctl start entity2-server sudo systemctl enable entity2-server ``` ## 6. API Reference The Entity 2 server provides the following API endpoints: ### 6.1 Server Status Check **Endpoint**: `/` **Method**: GET **Description**: Verify the server status. **Example**: ```bash curl -X GET http://localhost:5001/ ``` **Response**: ```json { "status": "Active", "service": "Privacy-Preserving Item Check API", "version": "1.0" } ``` ### 6.2 Update Items List **Endpoint**: `/api/update_items_list` **Method**: POST **Description**: Updates the entire items list. **Important**: Do not include double commas in the entries; they will be added automatically. **Example**: ```bash curl -X POST http://localhost:5001/api/update_items_list \ -H "Content-Type: application/json" \ -d '{ "items_list": [ "Entity001", "Person001", "Ship001" ] }' ``` **Response**: ```json { "success": true, "message": "Items list updated with 3 entries" } ``` ### 6.3 Add Single Item **Endpoint**: `/api/add_item` **Method**: POST **Description**: Adds a single entry to the items list. **Important**: Do not include double commas in the entry; they will be added automatically. **Example**: ```bash curl -X POST http://localhost:5001/api/add_item \ -H "Content-Type: application/json" \ -d '{ "item_entry": "Organization004" }' ``` **Response**: ```json { "success": true, "message": "Item 'Organization004' added successfully", "current_count": 11 } ``` ### 6.4 Get Items List Summary **Endpoint**: `/api/get_items_info` **Method**: GET **Description**: Gets summary information about the items list. **Example**: ```bash curl -X GET http://localhost:5001/api/get_items_info ``` **Response**: ```json { "count": 10, "sample": [ "Entity001", "Entity002" ] } ``` ### 6.5 Get All Items **Endpoint**: `/api/get_all_items` **Method**: GET **Description**: Gets the complete list of all items. **Example**: ```bash curl -X GET http://localhost:5001/api/get_all_items ``` **Response**: ```json { "count": 10, "items": [ "Entity001", "Entity002", "Entity003", "Person001", "Person002", "Person003", "Organization001", "Organization002", "Organization003", "Ship001" ] } ``` ### 6.6 Process PSC Message (Internal API) **Endpoint**: `/api/process_msg1` **Method**: POST **Description**: Processes PSC message 1 from Entity 1. This is primarily used internally by the Entity 1 server as part of the PSC protocol. **Example**: ```bash curl -X POST http://localhost:5001/api/process_msg1 \ -H "Content-Type: application/json" \ -d '{ "session_id": "base64_encoded_session_id", "msg1": "base64_encoded_msg1", "seed": 1234567890 }' ``` **Response**: ```json { "msg2": "base64_encoded_msg2" } ``` ## 7. Assumptions and Technical Notes - The system internally requires items to end with double commas (`,,`) for the PSC protocol. - When using the API, you should never include these commas; the system adds them automatically. - When editing the JSON file directly, you must include the double commas. - The system will display items without the double commas in API responses for better readability.