# 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.