PrivPSV is a privacy-preserving party selection and valuation framework for vertical federated learning (VFL). It lets one active party select passive parties with informative features and evaluate their contributions with secure multi-party computation.
The implementation uses replicated secret sharing through MP-SPDZ. The C++ client program prepares local inputs and sends shares to MP-SPDZ, while the MP-SPDZ programs run the secure mutual information, feature selection, and valuation logic.
- Ubuntu 20.04 or a compatible Linux environment
- MP-SPDZ under
third_party/MP-SPDZ - Boost
- CMake and a C++ compiler
- Docker, only if running in Docker mode
Check CMakeLists.txt before building and make sure SPDZ_HOME points to your
local MP-SPDZ directory.
From the repository root:
bash build.shThis creates the C++ client executable at:
build/ppdvThe main secure feature selection and valuation program is:
third_party/MP-SPDZ/Programs/Source/client_input_feature_selection.mpcCompile it from the MP-SPDZ directory:
cd third_party/MP-SPDZ
./compile.py -R 64 -l Programs/Source/client_input_feature_selection.mpc -- \
<n_rounds> <sample_num> <active_feature_num> <passive_feature_num> \
<active_nbits> <passive_nbits> <threads> <class_num> <method_id> <k> \
<inv_n> <n1> <n2> <n3>Example for the current 1-active-party/3-passive-party bank setup:
cd third_party/MP-SPDZ
./compile.py -R 64 -l Programs/Source/client_input_feature_selection.mpc -- \
1 2115 3 13 3 3 16 2 4 2 0.00009454 5 4 4The generated program name is based on the file name and compile arguments, for example:
client_input_feature_selection-1-2115-3-13-3-3-16-2-4-2-0.00009454-5-4-4
| Argument | Meaning |
|---|---|
n_rounds |
Number of protocol rounds, usually 1. |
sample_num |
Number of samples held by each party. |
active_feature_num |
Number of active-party features. |
passive_feature_num |
Total passive-party features. |
active_nbits |
Bit length for active-party discretized values. |
passive_nbits |
Bit length for passive-party discretized values. |
threads |
Number of MP-SPDZ threads. |
class_num |
Number of label classes. |
method_id |
Secure computation method. |
k |
Number of top features selected by each passive party. |
inv_n |
Inverse sample count, 1 / sample_num. |
n1, n2, n3 |
Feature count for passive parties. |
Supported method_id values:
| Method | Description |
|---|---|
1 |
Sorting-based mutual information, treating all active features as one variable. |
2 |
Indicator-vector-based mRMR feature selection. |
3 |
Sorting-based mRMR feature selection. |
4 |
Sorting-based feature selection and party valuation. |
Start the three MP-SPDZ parties first. Use the generated program name from the compile step.
For local execution:
cd third_party/MP-SPDZ
./replicated-ring-party.x -I -p 0 client_input_feature_selection-1-2115-3-13-3-3-16-2-4-2-0.00009454-5-4-4
./replicated-ring-party.x -I -p 1 client_input_feature_selection-1-2115-3-13-3-3-16-2-4-2-0.00009454-5-4-4
./replicated-ring-party.x -I -p 2 client_input_feature_selection-1-2115-3-13-3-3-16-2-4-2-0.00009454-5-4-4For remote execution, add the MP-SPDZ host:
./replicated-ring-party.x -I -h <spdz-host> -p <0|1|2> <program-name>Then run the active party and three passive parties from build/, one terminal
per party:
cd build
./ppdv --party-id 0 --party-num 4 --party-type 0 \
--network-file ../data/network/Parties-4.txt \
--log-file ../log/ \
--data-input-file ../data/dataset/rebuttal/bank_scalability/bank_1a3p_1/active_party.csv \
--data-output-file ../data/dataset/rebuttal/bank_scalability/bank_1a3p_1/client0_output.txt \
--mpc-party-num 3 --sample-num 2115 --class-num 2 \
--method-id 4 --selected-feature-num 2
./ppdv --party-id 1 --party-num 4 --party-type 1 \
--network-file ../data/network/Parties-4.txt \
--log-file ../log/ \
--data-input-file ../data/dataset/rebuttal/bank_scalability/bank_1a3p_1/passive_1_processed.csv \
--data-output-file ../data/dataset/rebuttal/bank_scalability/bank_1a3p_1/client1_output.txt \
--mpc-party-num 3 --sample-num 2115 --class-num 2 \
--method-id 4 --selected-feature-num 2
./ppdv --party-id 2 --party-num 4 --party-type 1 \
--network-file ../data/network/Parties-4.txt \
--log-file ../log/ \
--data-input-file ../data/dataset/rebuttal/bank_scalability/bank_1a3p_1/passive_2_processed.csv \
--data-output-file ../data/dataset/rebuttal/bank_scalability/bank_1a3p_1/client2_output.txt \
--mpc-party-num 3 --sample-num 2115 --class-num 2 \
--method-id 4 --selected-feature-num 2
./ppdv --party-id 3 --party-num 4 --party-type 1 \
--network-file ../data/network/Parties-4.txt \
--log-file ../log/ \
--data-input-file ../data/dataset/rebuttal/bank_scalability/bank_1a3p_1/passive_3_processed.csv \
--data-output-file ../data/dataset/rebuttal/bank_scalability/bank_1a3p_1/client3_output.txt \
--mpc-party-num 3 --sample-num 2115 --class-num 2 \
--method-id 4 --selected-feature-num 2party-type 0 is the active party and party-type 1 is a passive party. The
C++ clients perform local preprocessing, connect to each other using
data/network/Parties-4.txt, and send secret-shared inputs to the MP-SPDZ
runtime on port 14000.
Change the MAX_NUM_CLIENTS in Programs/Source/client_input_feature_selection.mpc to 2 (for one active party
and one passive party). Compile it in the same way above by using method_id = 2 or method_id = 3.
The micro-benchmark program is:
third_party/MP-SPDZ/Programs/Source/micro_bench_single_mi.mpcIt generates random inputs inside MP-SPDZ and benchmarks one mutual information
calculation. It does not require the ppdv C++ clients.
Compile from the MP-SPDZ directory:
cd third_party/MP-SPDZ
./compile.py -R 64 -l Programs/Source/micro_bench_single_mi.mpc -- \
<sample_num> <nbits> <threads> <method_id>Arguments:
| Argument | Meaning |
|---|---|
sample_num |
Number of generated samples. |
nbits |
Bit length of each discretized value. The bucket count is 2^nbits. |
threads |
Number of MP-SPDZ threads. |
method_id |
Benchmark method. |
Micro-benchmark methods:
| Method | Description |
|---|---|
1 |
Sorting-based MI with a naive sequential scan. |
2 |
Sorting-based MI with Blelloch scan parallelization. |
3 |
Indicator-vector-based MI. |
Example compile commands:
# Method 1: sorting-based, naive scan
./compile.py -R 64 -l Programs/Source/micro_bench_single_mi.mpc -- 64 3 4 1
# Method 2: sorting-based, Blelloch scan
./compile.py -R 64 -l Programs/Source/micro_bench_single_mi.mpc -- 64 3 4 2
# Method 3: indicator-vector-based
./compile.py -R 64 -l Programs/Source/micro_bench_single_mi.mpc -- 64 3 4 3Run the benchmark with three MP-SPDZ parties:
cd third_party/MP-SPDZ
./replicated-ring-party.x -I -p 0 micro_bench_single_mi-64-3-4-2
./replicated-ring-party.x -I -p 1 micro_bench_single_mi-64-3-4-2
./replicated-ring-party.x -I -p 2 micro_bench_single_mi-64-3-4-2Replace the suffix 64-3-4-2 with the arguments used at compile time. The
program prints timing markers and the final MI result in the MP-SPDZ output.
- Keep
SPDZ_PORT_BASEininclude/common.hconsistent withPORT_NUMin the MP-SPDZ program. The current value is14000. - Keep
SPDZ_FIXED_POINT_PRECISIONininclude/common.hconsistent withFIXED_Fin the MP-SPDZ program. The current value is16. - MP-SPDZ
-lenables flow optimization. It is useful for the current programs, but large datasets can still take a long time to compile.