From 6c582fb955581d6a781a52e209caad12bd0aaa8d Mon Sep 17 00:00:00 2001 From: 6fj Date: Wed, 22 Nov 2023 16:15:43 +0800 Subject: [PATCH] repo-sync-2023-11-22T16:15:37+0800 --- .bazelrc | 76 + .bazelversion | 1 + .clang-format | 15 + .clang-tidy | 78 + .gitignore | 47 + .markdownlint.yaml | 59 + LEGAL.md | 7 + README.md | 75 + SECURITY.md | 31 + VERSIONING.md | 89 + WORKSPACE | 55 + bazel/BUILD.bazel | 30 + bazel/arrow.BUILD | 168 + bazel/brotli.BUILD | 42 + bazel/bzip2.BUILD | 39 + bazel/curve25519-donna.BUILD | 21 + bazel/double-conversion.BUILD | 31 + bazel/eigen.BUILD | 73 + bazel/emp-ot.BUILD | 39 + bazel/emp-tool.BUILD | 38 + bazel/emp-zk.BUILD | 42 + bazel/hexl.BUILD | 39 + bazel/libdivide.BUILD | 32 + bazel/local_openmp_macos.BUILD | 27 + bazel/lz4.BUILD | 37 + bazel/microsoft_apsi.BUILD | 49 + bazel/microsoft_gsl.BUILD | 34 + bazel/microsoft_kuku.BUILD | 36 + bazel/patches/apsi-gen.patch | 1081 +++ bazel/patches/apsi.patch | 338 + bazel/patches/apsi_bin_bundle.patch | 93 + bazel/patches/emp-ot.patch | 99 + bazel/patches/emp-tool-cmake.patch | 22 + bazel/patches/emp-tool-sse2neon.patch | 6507 +++++++++++++++++ bazel/patches/emp-tool.patch | 162 + bazel/patches/emp-zk.patch | 30 + bazel/patches/grpc.patch | 32 + bazel/patches/seal.patch | 120 + bazel/perfetto.BUILD | 26 + bazel/psi.bzl | 110 + bazel/rapidjson.BUILD | 30 + bazel/repositories.bzl | 423 ++ bazel/seal.BUILD | 63 + bazel/snappy.BUILD | 39 + bazel/sparsehash.BUILD | 24 + bazel/thrift.BUILD | 75 + bazel/xsimd.BUILD | 47 + bazel/zstd.BUILD | 38 + docker/.gitignore | 2 + docker/.nsjail/README.md | 1 + docker/.nsjail/nsjail.cfg | 130 + docker/.nsjail/nsjail_seccomp.policy | 12 + docker/.nsjail/run.sh | 8 + docker/Dockerfile | 38 + docker/README.md | 13 + docker/build.sh | 83 + docker/config_templates.yml | 7 + docker/deploy_templates.yml | 20 + docker/entry.sh | 13 + psi/BUILD.bazel | 22 + psi/pir/BUILD.bazel | 118 + psi/pir/pir.cc | 741 ++ psi/pir/pir.h | 54 + psi/pir/pir.proto | 116 + psi/pir/pir_test.cc | 310 + psi/pir/seal_mpir.cc | 346 + psi/pir/seal_mpir.h | 167 + psi/pir/seal_mpir_test.cc | 180 + psi/pir/seal_pir.cc | 1171 +++ psi/pir/seal_pir.h | 273 + psi/pir/seal_pir_test.cc | 137 + psi/pir/seal_pir_utils.cc | 62 + psi/pir/seal_pir_utils.h | 89 + psi/pir/serializable.proto | 47 + psi/proto/BUILD.bazel | 44 + psi/proto/kuscia.proto | 80 + psi/proto/psi.proto | 391 + psi/psi/BUILD.bazel | 250 + psi/psi/benchmark/BUILD.bazel | 62 + psi/psi/benchmark/mparty_bench.cc | 102 + psi/psi/benchmark/mparty_bench.h | 308 + psi/psi/benchmark/standalone_bench.cc | 45 + psi/psi/benchmark/standalone_bench.h | 378 + psi/psi/bucket.cc | 108 + psi/psi/bucket.h | 44 + psi/psi/bucket_psi.cc | 482 ++ psi/psi/bucket_psi.h | 139 + psi/psi/bucket_psi_test.cc | 659 ++ psi/psi/bucket_ub_psi.cc | 505 ++ psi/psi/bucket_ub_psi.h | 70 + psi/psi/bucket_ub_psi_test.cc | 265 + psi/psi/core/BUILD.bazel | 231 + psi/psi/core/bc22_psi/BUILD.bazel | 110 + psi/psi/core/bc22_psi/bc22_psi.cc | 563 ++ psi/psi/core/bc22_psi/bc22_psi.h | 91 + psi/psi/core/bc22_psi/bc22_psi_bench.cc | 147 + psi/psi/core/bc22_psi/bc22_psi_test.cc | 121 + psi/psi/core/bc22_psi/emp_vole.cc | 146 + psi/psi/core/bc22_psi/emp_vole.h | 92 + psi/psi/core/bc22_psi/emp_vole_test.cc | 112 + .../core/bc22_psi/generalized_cuckoo_hash.cc | 262 + .../core/bc22_psi/generalized_cuckoo_hash.h | 123 + .../bc22_psi/generalized_cuckoo_hash_test.cc | 128 + psi/psi/core/communication.cc | 76 + psi/psi/core/communication.h | 104 + psi/psi/core/cuckoo_index.cc | 150 + psi/psi/core/cuckoo_index.h | 156 + psi/psi/core/cuckoo_index_test.cc | 85 + psi/psi/core/dp_psi/BUILD.bazel | 71 + psi/psi/core/dp_psi/dp_psi.cc | 313 + psi/psi/core/dp_psi/dp_psi.h | 86 + psi/psi/core/dp_psi/dp_psi_bench.cc | 200 + psi/psi/core/dp_psi/dp_psi_payload_bench.cc | 397 + psi/psi/core/dp_psi/dp_psi_test.cc | 143 + psi/psi/core/dp_psi/dp_psi_utils.cc | 172 + psi/psi/core/dp_psi/dp_psi_utils.h | 37 + psi/psi/core/ecdh_3pc_psi.cc | 367 + psi/psi/core/ecdh_3pc_psi.h | 162 + psi/psi/core/ecdh_3pc_psi_bench.cc | 94 + psi/psi/core/ecdh_3pc_psi_test.cc | 247 + psi/psi/core/ecdh_oprf/BUILD.bazel | 84 + psi/psi/core/ecdh_oprf/basic_ecdh_oprf.cc | 422 ++ psi/psi/core/ecdh_oprf/basic_ecdh_oprf.h | 180 + .../core/ecdh_oprf/basic_ecdh_oprf_test.cc | 92 + psi/psi/core/ecdh_oprf/ecdh_oprf.cc | 94 + psi/psi/core/ecdh_oprf/ecdh_oprf.h | 195 + psi/psi/core/ecdh_oprf/ecdh_oprf_selector.cc | 184 + psi/psi/core/ecdh_oprf/ecdh_oprf_selector.h | 37 + psi/psi/core/ecdh_oprf_psi.cc | 638 ++ psi/psi/core/ecdh_oprf_psi.h | 223 + psi/psi/core/ecdh_oprf_psi_test.cc | 330 + psi/psi/core/ecdh_psi.cc | 429 ++ psi/psi/core/ecdh_psi.h | 160 + psi/psi/core/ecdh_psi_bench.cc | 88 + psi/psi/core/ecdh_psi_test.cc | 174 + psi/psi/core/generate_psi.py | 75 + psi/psi/core/kkrt_psi.cc | 417 ++ psi/psi/core/kkrt_psi.h | 93 + psi/psi/core/kkrt_psi_bench.cc | 79 + psi/psi/core/kkrt_psi_test.cc | 132 + psi/psi/core/labeled_psi/BUILD.bazel | 119 + psi/psi/core/labeled_psi/README.md | 94 + psi/psi/core/labeled_psi/apsi_bench.cc | 278 + psi/psi/core/labeled_psi/apsi_label_test.cc | 269 + psi/psi/core/labeled_psi/apsi_test.cc | 256 + psi/psi/core/labeled_psi/kv_test.cc | 362 + psi/psi/core/labeled_psi/package.cc | 63 + psi/psi/core/labeled_psi/package.h | 55 + psi/psi/core/labeled_psi/psi_params.cc | 313 + psi/psi/core/labeled_psi/psi_params.h | 79 + psi/psi/core/labeled_psi/receiver.cc | 572 ++ psi/psi/core/labeled_psi/receiver.h | 136 + psi/psi/core/labeled_psi/sender.cc | 544 ++ psi/psi/core/labeled_psi/sender.h | 71 + psi/psi/core/labeled_psi/sender_db.cc | 198 + psi/psi/core/labeled_psi/sender_db.h | 241 + psi/psi/core/labeled_psi/sender_kvdb.cc | 850 +++ psi/psi/core/labeled_psi/sender_kvdb.h | 164 + psi/psi/core/labeled_psi/sender_memdb.cc | 964 +++ psi/psi/core/labeled_psi/sender_memdb.h | 227 + psi/psi/core/labeled_psi/serializable.proto | 87 + psi/psi/core/labeled_psi/serialize.h | 185 + psi/psi/core/mini_psi.cc | 624 ++ psi/psi/core/mini_psi.h | 47 + psi/psi/core/mini_psi_demo.cc | 190 + psi/psi/core/mini_psi_test.cc | 88 + psi/psi/core/polynomial/BUILD.bazel | 41 + psi/psi/core/polynomial/polynomial.cc | 240 + psi/psi/core/polynomial/polynomial.h | 44 + psi/psi/core/polynomial/polynomial_test.cc | 86 + psi/psi/core/vole_psi/BUILD.bazel | 96 + psi/psi/core/vole_psi/okvs/BUILD.bazel | 177 + psi/psi/core/vole_psi/okvs/aes_crhash.cc | 96 + psi/psi/core/vole_psi/okvs/aes_crhash.h | 44 + psi/psi/core/vole_psi/okvs/aes_crhash_test.cc | 61 + psi/psi/core/vole_psi/okvs/baxos.cc | 681 ++ psi/psi/core/vole_psi/okvs/baxos.h | 162 + psi/psi/core/vole_psi/okvs/baxos_test.cc | 74 + psi/psi/core/vole_psi/okvs/dense_mtx.cc | 134 + psi/psi/core/vole_psi/okvs/dense_mtx.h | 276 + psi/psi/core/vole_psi/okvs/galois128.cc | 214 + psi/psi/core/vole_psi/okvs/galois128.h | 102 + psi/psi/core/vole_psi/okvs/galois128_test.cc | 79 + psi/psi/core/vole_psi/okvs/paxos.cc | 1467 ++++ psi/psi/core/vole_psi/okvs/paxos.h | 226 + psi/psi/core/vole_psi/okvs/paxos_hash.cc | 318 + psi/psi/core/vole_psi/okvs/paxos_hash.h | 195 + psi/psi/core/vole_psi/okvs/paxos_hash_test.cc | 62 + psi/psi/core/vole_psi/okvs/paxos_test.cc | 91 + psi/psi/core/vole_psi/okvs/paxos_utils.cc | 17 + psi/psi/core/vole_psi/okvs/paxos_utils.h | 325 + psi/psi/core/vole_psi/okvs/simple_index.cc | 967 +++ psi/psi/core/vole_psi/okvs/simple_index.h | 27 + psi/psi/core/vole_psi/rr22_oprf.cc | 564 ++ psi/psi/core/vole_psi/rr22_oprf.h | 167 + psi/psi/core/vole_psi/rr22_oprf_test.cc | 160 + psi/psi/core/vole_psi/rr22_psi.cc | 172 + psi/psi/core/vole_psi/rr22_psi.h | 74 + psi/psi/core/vole_psi/rr22_psi_bench.cc | 154 + psi/psi/core/vole_psi/rr22_psi_test.cc | 109 + psi/psi/core/vole_psi/rr22_utils.cc | 191 + psi/psi/core/vole_psi/rr22_utils.h | 30 + psi/psi/core/vole_psi/sparseconfig.h | 46 + psi/psi/cryptor/BUILD.bazel | 152 + psi/psi/cryptor/cryptor_selector.cc | 98 + psi/psi/cryptor/cryptor_selector.h | 25 + psi/psi/cryptor/ecc_cryptor.cc | 110 + psi/psi/cryptor/ecc_cryptor.h | 83 + psi/psi/cryptor/ecc_utils.h | 261 + psi/psi/cryptor/ecc_utils_test.cc | 38 + psi/psi/cryptor/fourq_cryptor.cc | 75 + psi/psi/cryptor/fourq_cryptor.h | 39 + psi/psi/cryptor/fpga_ecc_cryptor.h | 28 + psi/psi/cryptor/ipp_ecc_cryptor.cc | 74 + psi/psi/cryptor/ipp_ecc_cryptor.h | 37 + psi/psi/cryptor/sm2_cryptor.cc | 89 + psi/psi/cryptor/sm2_cryptor.h | 66 + psi/psi/cryptor/sm2_cryptor_test.cc | 104 + psi/psi/cryptor/sodium_curve25519_cryptor.cc | 75 + psi/psi/cryptor/sodium_curve25519_cryptor.h | 62 + psi/psi/demo/BUILD.bazel | 26 + psi/psi/demo/README.md | 46 + .../demo/config/ecdh_receiver_inner_join.json | 41 + .../demo/config/ecdh_receiver_recovery.json | 44 + .../demo/config/ecdh_sender_inner_join.json | 41 + psi/psi/demo/config/ecdh_sender_recovery.json | 44 + .../demo/config/kkrt_receiver_recovery.json | 44 + psi/psi/demo/config/kkrt_sender_recovery.json | 44 + .../demo/config/rr22_receiver_recovery.json | 41 + psi/psi/demo/config/rr22_sender_recovery.json | 41 + psi/psi/demo/data/BUILD.bazel | 24 + psi/psi/demo/data/alice.csv | 1547 ++++ psi/psi/demo/data/bob.csv | 1561 ++++ psi/psi/demo/data/carol.csv | 1471 ++++ psi/psi/demo/data/test_data_generator.py | 116 + psi/psi/demo/psi_demo.cc | 106 + psi/psi/ecdh/BUILD.bazel | 44 + psi/psi/ecdh/common.h | 21 + psi/psi/ecdh/receiver.cc | 145 + psi/psi/ecdh/receiver.h | 48 + psi/psi/ecdh/sender.cc | 146 + psi/psi/ecdh/sender.h | 48 + psi/psi/factory.cc | 68 + psi/psi/factory.h | 28 + psi/psi/interface.cc | 374 + psi/psi/interface.h | 132 + psi/psi/io/BUILD.bazel | 27 + psi/psi/io/io.cc | 89 + psi/psi/io/io.h | 105 + psi/psi/kkrt/BUILD.bazel | 52 + psi/psi/kkrt/common.cc | 35 + psi/psi/kkrt/common.h | 31 + psi/psi/kkrt/receiver.cc | 180 + psi/psi/kkrt/receiver.h | 48 + psi/psi/kkrt/sender.cc | 174 + psi/psi/kkrt/sender.h | 48 + psi/psi/kuscia_adapter.cc | 103 + psi/psi/kuscia_adapter.h | 27 + psi/psi/kuscia_adapter_test.cc | 95 + psi/psi/main.cc | 174 + psi/psi/memory_psi.cc | 105 + psi/psi/memory_psi.h | 47 + psi/psi/memory_psi_test.cc | 209 + psi/psi/operator/BUILD.bazel | 136 + psi/psi/operator/base_operator.cc | 42 + psi/psi/operator/base_operator.h | 43 + psi/psi/operator/bc22_2party_psi.cc | 59 + psi/psi/operator/bc22_2party_psi.h | 43 + psi/psi/operator/dp_2party_psi.cc | 89 + psi/psi/operator/dp_2party_psi.h | 40 + psi/psi/operator/ecdh_3party_psi.cc | 97 + psi/psi/operator/ecdh_3party_psi.h | 115 + psi/psi/operator/factory.h | 80 + psi/psi/operator/kkrt_2party_psi.cc | 73 + psi/psi/operator/kkrt_2party_psi.h | 47 + psi/psi/operator/nparty_psi.cc | 262 + psi/psi/operator/nparty_psi.h | 84 + psi/psi/operator/nparty_psi_test.cc | 149 + psi/psi/operator/rr22_2party_psi.cc | 113 + psi/psi/operator/rr22_2party_psi.h | 46 + psi/psi/prelude.h | 47 + psi/psi/psi.proto | 217 + psi/psi/psi_test.cc | 741 ++ psi/psi/recovery.cc | 252 + psi/psi/recovery.h | 128 + psi/psi/recovery_test.cc | 136 + psi/psi/rr22/BUILD.bazel | 51 + psi/psi/rr22/common.cc | 45 + psi/psi/rr22/common.h | 38 + psi/psi/rr22/receiver.cc | 177 + psi/psi/rr22/receiver.h | 44 + psi/psi/rr22/sender.cc | 165 + psi/psi/rr22/sender.h | 44 + psi/psi/trace_categories.cc | 18 + psi/psi/trace_categories.h | 23 + psi/psi/utils/BUILD.bazel | 314 + psi/psi/utils/arrow_csv_batch_provider.cc | 100 + psi/psi/utils/arrow_csv_batch_provider.h | 60 + .../utils/arrow_csv_batch_provider_test.cc | 76 + psi/psi/utils/batch_provider.cc | 337 + psi/psi/utils/batch_provider.h | 165 + psi/psi/utils/csv_checker.cc | 281 + psi/psi/utils/csv_checker.h | 53 + psi/psi/utils/csv_checker_test.cc | 287 + psi/psi/utils/csv_header_analyzer.h | 129 + psi/psi/utils/csv_header_parser.cc | 69 + psi/psi/utils/csv_header_parser.h | 40 + psi/psi/utils/csv_header_parser_test.cc | 57 + psi/psi/utils/ec_point_store.cc | 297 + psi/psi/utils/ec_point_store.h | 143 + psi/psi/utils/emp_io_adapter.cc | 156 + psi/psi/utils/emp_io_adapter.h | 56 + psi/psi/utils/emp_io_adapter_test.cc | 79 + psi/psi/utils/file.cc | 37 + psi/psi/utils/file.h | 21 + psi/psi/utils/hash_bucket_cache.cc | 99 + psi/psi/utils/hash_bucket_cache.h | 83 + psi/psi/utils/index_store.cc | 160 + psi/psi/utils/index_store.h | 97 + psi/psi/utils/index_store_test.cc | 129 + psi/psi/utils/inner_join.cc | 904 +++ psi/psi/utils/inner_join.h | 52 + psi/psi/utils/inner_join_test.cc | 309 + psi/psi/utils/multiplex_disk_cache.cc | 87 + psi/psi/utils/multiplex_disk_cache.h | 71 + psi/psi/utils/multiplex_disk_cache_test.cc | 85 + psi/psi/utils/progress.cc | 145 + psi/psi/utils/progress.h | 83 + psi/psi/utils/progress_test.cc | 85 + psi/psi/utils/resource.cc | 56 + psi/psi/utils/resource.h | 29 + psi/psi/utils/serializable.proto | 33 + psi/psi/utils/serialize.h | 68 + psi/psi/utils/test_utils.h | 69 + psi/psi/utils/ub_psi_cache.cc | 147 + psi/psi/utils/ub_psi_cache.h | 90 + psi/psi/utils/ub_psi_cache_test.cc | 82 + psi/psi/utils/utils.cc | 285 + psi/psi/utils/utils.h | 101 + psi/version.h | 20 + requirements.txt | 0 341 files changed, 61944 insertions(+) create mode 100644 .bazelrc create mode 100644 .bazelversion create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 .gitignore create mode 100644 .markdownlint.yaml create mode 100644 LEGAL.md create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 VERSIONING.md create mode 100644 WORKSPACE create mode 100644 bazel/BUILD.bazel create mode 100644 bazel/arrow.BUILD create mode 100644 bazel/brotli.BUILD create mode 100644 bazel/bzip2.BUILD create mode 100644 bazel/curve25519-donna.BUILD create mode 100644 bazel/double-conversion.BUILD create mode 100644 bazel/eigen.BUILD create mode 100644 bazel/emp-ot.BUILD create mode 100644 bazel/emp-tool.BUILD create mode 100644 bazel/emp-zk.BUILD create mode 100644 bazel/hexl.BUILD create mode 100644 bazel/libdivide.BUILD create mode 100644 bazel/local_openmp_macos.BUILD create mode 100644 bazel/lz4.BUILD create mode 100644 bazel/microsoft_apsi.BUILD create mode 100644 bazel/microsoft_gsl.BUILD create mode 100644 bazel/microsoft_kuku.BUILD create mode 100644 bazel/patches/apsi-gen.patch create mode 100644 bazel/patches/apsi.patch create mode 100644 bazel/patches/apsi_bin_bundle.patch create mode 100644 bazel/patches/emp-ot.patch create mode 100644 bazel/patches/emp-tool-cmake.patch create mode 100644 bazel/patches/emp-tool-sse2neon.patch create mode 100644 bazel/patches/emp-tool.patch create mode 100644 bazel/patches/emp-zk.patch create mode 100644 bazel/patches/grpc.patch create mode 100644 bazel/patches/seal.patch create mode 100644 bazel/perfetto.BUILD create mode 100644 bazel/psi.bzl create mode 100644 bazel/rapidjson.BUILD create mode 100644 bazel/repositories.bzl create mode 100644 bazel/seal.BUILD create mode 100644 bazel/snappy.BUILD create mode 100644 bazel/sparsehash.BUILD create mode 100644 bazel/thrift.BUILD create mode 100644 bazel/xsimd.BUILD create mode 100644 bazel/zstd.BUILD create mode 100644 docker/.gitignore create mode 100644 docker/.nsjail/README.md create mode 100644 docker/.nsjail/nsjail.cfg create mode 100644 docker/.nsjail/nsjail_seccomp.policy create mode 100644 docker/.nsjail/run.sh create mode 100644 docker/Dockerfile create mode 100644 docker/README.md create mode 100644 docker/build.sh create mode 100644 docker/config_templates.yml create mode 100644 docker/deploy_templates.yml create mode 100644 docker/entry.sh create mode 100644 psi/BUILD.bazel create mode 100644 psi/pir/BUILD.bazel create mode 100644 psi/pir/pir.cc create mode 100644 psi/pir/pir.h create mode 100644 psi/pir/pir.proto create mode 100644 psi/pir/pir_test.cc create mode 100644 psi/pir/seal_mpir.cc create mode 100644 psi/pir/seal_mpir.h create mode 100644 psi/pir/seal_mpir_test.cc create mode 100644 psi/pir/seal_pir.cc create mode 100644 psi/pir/seal_pir.h create mode 100644 psi/pir/seal_pir_test.cc create mode 100644 psi/pir/seal_pir_utils.cc create mode 100644 psi/pir/seal_pir_utils.h create mode 100644 psi/pir/serializable.proto create mode 100644 psi/proto/BUILD.bazel create mode 100644 psi/proto/kuscia.proto create mode 100644 psi/proto/psi.proto create mode 100644 psi/psi/BUILD.bazel create mode 100644 psi/psi/benchmark/BUILD.bazel create mode 100644 psi/psi/benchmark/mparty_bench.cc create mode 100644 psi/psi/benchmark/mparty_bench.h create mode 100644 psi/psi/benchmark/standalone_bench.cc create mode 100644 psi/psi/benchmark/standalone_bench.h create mode 100644 psi/psi/bucket.cc create mode 100644 psi/psi/bucket.h create mode 100644 psi/psi/bucket_psi.cc create mode 100644 psi/psi/bucket_psi.h create mode 100644 psi/psi/bucket_psi_test.cc create mode 100644 psi/psi/bucket_ub_psi.cc create mode 100644 psi/psi/bucket_ub_psi.h create mode 100644 psi/psi/bucket_ub_psi_test.cc create mode 100644 psi/psi/core/BUILD.bazel create mode 100644 psi/psi/core/bc22_psi/BUILD.bazel create mode 100644 psi/psi/core/bc22_psi/bc22_psi.cc create mode 100644 psi/psi/core/bc22_psi/bc22_psi.h create mode 100644 psi/psi/core/bc22_psi/bc22_psi_bench.cc create mode 100644 psi/psi/core/bc22_psi/bc22_psi_test.cc create mode 100644 psi/psi/core/bc22_psi/emp_vole.cc create mode 100644 psi/psi/core/bc22_psi/emp_vole.h create mode 100644 psi/psi/core/bc22_psi/emp_vole_test.cc create mode 100644 psi/psi/core/bc22_psi/generalized_cuckoo_hash.cc create mode 100644 psi/psi/core/bc22_psi/generalized_cuckoo_hash.h create mode 100644 psi/psi/core/bc22_psi/generalized_cuckoo_hash_test.cc create mode 100644 psi/psi/core/communication.cc create mode 100644 psi/psi/core/communication.h create mode 100644 psi/psi/core/cuckoo_index.cc create mode 100644 psi/psi/core/cuckoo_index.h create mode 100644 psi/psi/core/cuckoo_index_test.cc create mode 100644 psi/psi/core/dp_psi/BUILD.bazel create mode 100644 psi/psi/core/dp_psi/dp_psi.cc create mode 100644 psi/psi/core/dp_psi/dp_psi.h create mode 100644 psi/psi/core/dp_psi/dp_psi_bench.cc create mode 100644 psi/psi/core/dp_psi/dp_psi_payload_bench.cc create mode 100644 psi/psi/core/dp_psi/dp_psi_test.cc create mode 100644 psi/psi/core/dp_psi/dp_psi_utils.cc create mode 100644 psi/psi/core/dp_psi/dp_psi_utils.h create mode 100644 psi/psi/core/ecdh_3pc_psi.cc create mode 100644 psi/psi/core/ecdh_3pc_psi.h create mode 100644 psi/psi/core/ecdh_3pc_psi_bench.cc create mode 100644 psi/psi/core/ecdh_3pc_psi_test.cc create mode 100644 psi/psi/core/ecdh_oprf/BUILD.bazel create mode 100644 psi/psi/core/ecdh_oprf/basic_ecdh_oprf.cc create mode 100644 psi/psi/core/ecdh_oprf/basic_ecdh_oprf.h create mode 100644 psi/psi/core/ecdh_oprf/basic_ecdh_oprf_test.cc create mode 100644 psi/psi/core/ecdh_oprf/ecdh_oprf.cc create mode 100644 psi/psi/core/ecdh_oprf/ecdh_oprf.h create mode 100644 psi/psi/core/ecdh_oprf/ecdh_oprf_selector.cc create mode 100644 psi/psi/core/ecdh_oprf/ecdh_oprf_selector.h create mode 100644 psi/psi/core/ecdh_oprf_psi.cc create mode 100644 psi/psi/core/ecdh_oprf_psi.h create mode 100644 psi/psi/core/ecdh_oprf_psi_test.cc create mode 100644 psi/psi/core/ecdh_psi.cc create mode 100644 psi/psi/core/ecdh_psi.h create mode 100644 psi/psi/core/ecdh_psi_bench.cc create mode 100644 psi/psi/core/ecdh_psi_test.cc create mode 100644 psi/psi/core/generate_psi.py create mode 100644 psi/psi/core/kkrt_psi.cc create mode 100644 psi/psi/core/kkrt_psi.h create mode 100644 psi/psi/core/kkrt_psi_bench.cc create mode 100644 psi/psi/core/kkrt_psi_test.cc create mode 100644 psi/psi/core/labeled_psi/BUILD.bazel create mode 100644 psi/psi/core/labeled_psi/README.md create mode 100644 psi/psi/core/labeled_psi/apsi_bench.cc create mode 100644 psi/psi/core/labeled_psi/apsi_label_test.cc create mode 100644 psi/psi/core/labeled_psi/apsi_test.cc create mode 100644 psi/psi/core/labeled_psi/kv_test.cc create mode 100644 psi/psi/core/labeled_psi/package.cc create mode 100644 psi/psi/core/labeled_psi/package.h create mode 100644 psi/psi/core/labeled_psi/psi_params.cc create mode 100644 psi/psi/core/labeled_psi/psi_params.h create mode 100644 psi/psi/core/labeled_psi/receiver.cc create mode 100644 psi/psi/core/labeled_psi/receiver.h create mode 100644 psi/psi/core/labeled_psi/sender.cc create mode 100644 psi/psi/core/labeled_psi/sender.h create mode 100644 psi/psi/core/labeled_psi/sender_db.cc create mode 100644 psi/psi/core/labeled_psi/sender_db.h create mode 100644 psi/psi/core/labeled_psi/sender_kvdb.cc create mode 100644 psi/psi/core/labeled_psi/sender_kvdb.h create mode 100644 psi/psi/core/labeled_psi/sender_memdb.cc create mode 100644 psi/psi/core/labeled_psi/sender_memdb.h create mode 100644 psi/psi/core/labeled_psi/serializable.proto create mode 100644 psi/psi/core/labeled_psi/serialize.h create mode 100644 psi/psi/core/mini_psi.cc create mode 100644 psi/psi/core/mini_psi.h create mode 100644 psi/psi/core/mini_psi_demo.cc create mode 100644 psi/psi/core/mini_psi_test.cc create mode 100644 psi/psi/core/polynomial/BUILD.bazel create mode 100644 psi/psi/core/polynomial/polynomial.cc create mode 100644 psi/psi/core/polynomial/polynomial.h create mode 100644 psi/psi/core/polynomial/polynomial_test.cc create mode 100644 psi/psi/core/vole_psi/BUILD.bazel create mode 100644 psi/psi/core/vole_psi/okvs/BUILD.bazel create mode 100644 psi/psi/core/vole_psi/okvs/aes_crhash.cc create mode 100644 psi/psi/core/vole_psi/okvs/aes_crhash.h create mode 100644 psi/psi/core/vole_psi/okvs/aes_crhash_test.cc create mode 100644 psi/psi/core/vole_psi/okvs/baxos.cc create mode 100644 psi/psi/core/vole_psi/okvs/baxos.h create mode 100644 psi/psi/core/vole_psi/okvs/baxos_test.cc create mode 100644 psi/psi/core/vole_psi/okvs/dense_mtx.cc create mode 100644 psi/psi/core/vole_psi/okvs/dense_mtx.h create mode 100644 psi/psi/core/vole_psi/okvs/galois128.cc create mode 100644 psi/psi/core/vole_psi/okvs/galois128.h create mode 100644 psi/psi/core/vole_psi/okvs/galois128_test.cc create mode 100644 psi/psi/core/vole_psi/okvs/paxos.cc create mode 100644 psi/psi/core/vole_psi/okvs/paxos.h create mode 100644 psi/psi/core/vole_psi/okvs/paxos_hash.cc create mode 100644 psi/psi/core/vole_psi/okvs/paxos_hash.h create mode 100644 psi/psi/core/vole_psi/okvs/paxos_hash_test.cc create mode 100644 psi/psi/core/vole_psi/okvs/paxos_test.cc create mode 100644 psi/psi/core/vole_psi/okvs/paxos_utils.cc create mode 100644 psi/psi/core/vole_psi/okvs/paxos_utils.h create mode 100644 psi/psi/core/vole_psi/okvs/simple_index.cc create mode 100644 psi/psi/core/vole_psi/okvs/simple_index.h create mode 100644 psi/psi/core/vole_psi/rr22_oprf.cc create mode 100644 psi/psi/core/vole_psi/rr22_oprf.h create mode 100644 psi/psi/core/vole_psi/rr22_oprf_test.cc create mode 100644 psi/psi/core/vole_psi/rr22_psi.cc create mode 100644 psi/psi/core/vole_psi/rr22_psi.h create mode 100644 psi/psi/core/vole_psi/rr22_psi_bench.cc create mode 100644 psi/psi/core/vole_psi/rr22_psi_test.cc create mode 100644 psi/psi/core/vole_psi/rr22_utils.cc create mode 100644 psi/psi/core/vole_psi/rr22_utils.h create mode 100644 psi/psi/core/vole_psi/sparseconfig.h create mode 100644 psi/psi/cryptor/BUILD.bazel create mode 100644 psi/psi/cryptor/cryptor_selector.cc create mode 100644 psi/psi/cryptor/cryptor_selector.h create mode 100644 psi/psi/cryptor/ecc_cryptor.cc create mode 100644 psi/psi/cryptor/ecc_cryptor.h create mode 100644 psi/psi/cryptor/ecc_utils.h create mode 100644 psi/psi/cryptor/ecc_utils_test.cc create mode 100644 psi/psi/cryptor/fourq_cryptor.cc create mode 100644 psi/psi/cryptor/fourq_cryptor.h create mode 100644 psi/psi/cryptor/fpga_ecc_cryptor.h create mode 100644 psi/psi/cryptor/ipp_ecc_cryptor.cc create mode 100644 psi/psi/cryptor/ipp_ecc_cryptor.h create mode 100644 psi/psi/cryptor/sm2_cryptor.cc create mode 100644 psi/psi/cryptor/sm2_cryptor.h create mode 100644 psi/psi/cryptor/sm2_cryptor_test.cc create mode 100644 psi/psi/cryptor/sodium_curve25519_cryptor.cc create mode 100644 psi/psi/cryptor/sodium_curve25519_cryptor.h create mode 100644 psi/psi/demo/BUILD.bazel create mode 100644 psi/psi/demo/README.md create mode 100644 psi/psi/demo/config/ecdh_receiver_inner_join.json create mode 100644 psi/psi/demo/config/ecdh_receiver_recovery.json create mode 100644 psi/psi/demo/config/ecdh_sender_inner_join.json create mode 100644 psi/psi/demo/config/ecdh_sender_recovery.json create mode 100644 psi/psi/demo/config/kkrt_receiver_recovery.json create mode 100644 psi/psi/demo/config/kkrt_sender_recovery.json create mode 100644 psi/psi/demo/config/rr22_receiver_recovery.json create mode 100644 psi/psi/demo/config/rr22_sender_recovery.json create mode 100644 psi/psi/demo/data/BUILD.bazel create mode 100644 psi/psi/demo/data/alice.csv create mode 100644 psi/psi/demo/data/bob.csv create mode 100644 psi/psi/demo/data/carol.csv create mode 100644 psi/psi/demo/data/test_data_generator.py create mode 100644 psi/psi/demo/psi_demo.cc create mode 100644 psi/psi/ecdh/BUILD.bazel create mode 100644 psi/psi/ecdh/common.h create mode 100644 psi/psi/ecdh/receiver.cc create mode 100644 psi/psi/ecdh/receiver.h create mode 100644 psi/psi/ecdh/sender.cc create mode 100644 psi/psi/ecdh/sender.h create mode 100644 psi/psi/factory.cc create mode 100644 psi/psi/factory.h create mode 100644 psi/psi/interface.cc create mode 100644 psi/psi/interface.h create mode 100644 psi/psi/io/BUILD.bazel create mode 100644 psi/psi/io/io.cc create mode 100644 psi/psi/io/io.h create mode 100644 psi/psi/kkrt/BUILD.bazel create mode 100644 psi/psi/kkrt/common.cc create mode 100644 psi/psi/kkrt/common.h create mode 100644 psi/psi/kkrt/receiver.cc create mode 100644 psi/psi/kkrt/receiver.h create mode 100644 psi/psi/kkrt/sender.cc create mode 100644 psi/psi/kkrt/sender.h create mode 100644 psi/psi/kuscia_adapter.cc create mode 100644 psi/psi/kuscia_adapter.h create mode 100644 psi/psi/kuscia_adapter_test.cc create mode 100644 psi/psi/main.cc create mode 100644 psi/psi/memory_psi.cc create mode 100644 psi/psi/memory_psi.h create mode 100644 psi/psi/memory_psi_test.cc create mode 100644 psi/psi/operator/BUILD.bazel create mode 100644 psi/psi/operator/base_operator.cc create mode 100644 psi/psi/operator/base_operator.h create mode 100644 psi/psi/operator/bc22_2party_psi.cc create mode 100644 psi/psi/operator/bc22_2party_psi.h create mode 100644 psi/psi/operator/dp_2party_psi.cc create mode 100644 psi/psi/operator/dp_2party_psi.h create mode 100644 psi/psi/operator/ecdh_3party_psi.cc create mode 100644 psi/psi/operator/ecdh_3party_psi.h create mode 100644 psi/psi/operator/factory.h create mode 100644 psi/psi/operator/kkrt_2party_psi.cc create mode 100644 psi/psi/operator/kkrt_2party_psi.h create mode 100644 psi/psi/operator/nparty_psi.cc create mode 100644 psi/psi/operator/nparty_psi.h create mode 100644 psi/psi/operator/nparty_psi_test.cc create mode 100644 psi/psi/operator/rr22_2party_psi.cc create mode 100644 psi/psi/operator/rr22_2party_psi.h create mode 100644 psi/psi/prelude.h create mode 100644 psi/psi/psi.proto create mode 100644 psi/psi/psi_test.cc create mode 100644 psi/psi/recovery.cc create mode 100644 psi/psi/recovery.h create mode 100644 psi/psi/recovery_test.cc create mode 100644 psi/psi/rr22/BUILD.bazel create mode 100644 psi/psi/rr22/common.cc create mode 100644 psi/psi/rr22/common.h create mode 100644 psi/psi/rr22/receiver.cc create mode 100644 psi/psi/rr22/receiver.h create mode 100644 psi/psi/rr22/sender.cc create mode 100644 psi/psi/rr22/sender.h create mode 100644 psi/psi/trace_categories.cc create mode 100644 psi/psi/trace_categories.h create mode 100644 psi/psi/utils/BUILD.bazel create mode 100644 psi/psi/utils/arrow_csv_batch_provider.cc create mode 100644 psi/psi/utils/arrow_csv_batch_provider.h create mode 100644 psi/psi/utils/arrow_csv_batch_provider_test.cc create mode 100644 psi/psi/utils/batch_provider.cc create mode 100644 psi/psi/utils/batch_provider.h create mode 100644 psi/psi/utils/csv_checker.cc create mode 100644 psi/psi/utils/csv_checker.h create mode 100644 psi/psi/utils/csv_checker_test.cc create mode 100644 psi/psi/utils/csv_header_analyzer.h create mode 100644 psi/psi/utils/csv_header_parser.cc create mode 100644 psi/psi/utils/csv_header_parser.h create mode 100644 psi/psi/utils/csv_header_parser_test.cc create mode 100644 psi/psi/utils/ec_point_store.cc create mode 100644 psi/psi/utils/ec_point_store.h create mode 100644 psi/psi/utils/emp_io_adapter.cc create mode 100644 psi/psi/utils/emp_io_adapter.h create mode 100644 psi/psi/utils/emp_io_adapter_test.cc create mode 100644 psi/psi/utils/file.cc create mode 100644 psi/psi/utils/file.h create mode 100644 psi/psi/utils/hash_bucket_cache.cc create mode 100644 psi/psi/utils/hash_bucket_cache.h create mode 100644 psi/psi/utils/index_store.cc create mode 100644 psi/psi/utils/index_store.h create mode 100644 psi/psi/utils/index_store_test.cc create mode 100644 psi/psi/utils/inner_join.cc create mode 100644 psi/psi/utils/inner_join.h create mode 100644 psi/psi/utils/inner_join_test.cc create mode 100644 psi/psi/utils/multiplex_disk_cache.cc create mode 100644 psi/psi/utils/multiplex_disk_cache.h create mode 100644 psi/psi/utils/multiplex_disk_cache_test.cc create mode 100644 psi/psi/utils/progress.cc create mode 100644 psi/psi/utils/progress.h create mode 100644 psi/psi/utils/progress_test.cc create mode 100644 psi/psi/utils/resource.cc create mode 100644 psi/psi/utils/resource.h create mode 100644 psi/psi/utils/serializable.proto create mode 100644 psi/psi/utils/serialize.h create mode 100644 psi/psi/utils/test_utils.h create mode 100644 psi/psi/utils/ub_psi_cache.cc create mode 100644 psi/psi/utils/ub_psi_cache.h create mode 100644 psi/psi/utils/ub_psi_cache_test.cc create mode 100644 psi/psi/utils/utils.cc create mode 100644 psi/psi/utils/utils.h create mode 100644 psi/version.h create mode 100644 requirements.txt diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 00000000..ecbc6b38 --- /dev/null +++ b/.bazelrc @@ -0,0 +1,76 @@ +# Copyright 2023 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +common --experimental_repo_remote_exec + +# Required by OpenXLA +build --nocheck_visibility + +build --incompatible_new_actions_api=false +build --copt=-fdiagnostics-color=always +build --enable_platform_specific_config + +build --cxxopt=-std=c++17 +build --host_cxxopt=-std=c++17 + +# Binary safety flags +build --copt=-fPIC +build --copt=-fstack-protector-strong +build:linux --copt=-Wl,-z,noexecstack +build:macos --copt=-Wa,--noexecstack + + +test --keep_going +test --test_output=errors + +build:benchmark --copt -O3 +build:benchmark --copt -march=native + +# static link runtime libraries on Linux +build:linux-release --action_env=BAZEL_LINKOPTS=-static-libstdc++:-static-libgcc +build:linux-release --action_env=BAZEL_LINKLIBS=-l%:libstdc++.a:-l%:libgcc.a + +# platform specific config +# Bazel will automatic pick platform config since we have enable_platform_specific_config set +build:macos --copt="-Xpreprocessor -fopenmp" +build:macos --copt=-Wno-unused-command-line-argument +build:macos --features=-supports_dynamic_linker +build:macos --cxxopt -Wno-deprecated-enum-enum-conversion +build:macos --cxxopt -Wno-deprecated-anon-enum-enum-conversion +build:macos --macos_minimum_os=11.0 +build:macos --host_macos_minimum_os=11.0 + +build:linux --copt=-fopenmp +build:linux --linkopt=-fopenmp + +build:asan --strip=never +build:asan --copt -fno-sanitize-recover=all +build:asan --copt -fsanitize=address +build:asan --copt -Og +build:asan --copt -g +build:asan --copt -fno-omit-frame-pointer +build:asan --linkopt -fsanitize=address +build:asan --linkopt -static-libasan + +build:ubsan --strip=never +build:ubsan --copt -fno-sanitize-recover=all +build:ubsan --copt -fsanitize=undefined +build:ubsan --copt -Og +build:ubsan --copt -g +build:ubsan --copt -fno-omit-frame-pointer +build:ubsan --linkopt -fsanitize=undefined +build:ubsan --linkopt -static-libubsan + +build:macos-asan --features=asan +build:macos-ubsan --features=ubsan diff --git a/.bazelversion b/.bazelversion new file mode 100644 index 00000000..024b066c --- /dev/null +++ b/.bazelversion @@ -0,0 +1 @@ +6.2.1 diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..65f558eb --- /dev/null +++ b/.clang-format @@ -0,0 +1,15 @@ +# Use the Google style in this project. +BasedOnStyle: Google + +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: "^<.*" + Priority: 2 + - Regex: '.*\.pb\.h"$' + Priority: 5 + - Regex: '^"psi.*' + Priority: 4 + - Regex: '^".*' + Priority: 3 diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 00000000..a82670a2 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,78 @@ +Checks: "abseil-cleanup-ctad, + abseil-faster-strsplit-delimiter, + abseil-duration-*, + abseil-no-namespace, + abseil-redundant-strcat-calls, + abseil-str-cat-append, + abseil-string-find-startswith, + abseil-upgrade-duration-conversions + bugprone-*, + -bugprone-easily-swappable-parameters, + -bugprone-implicit-widening-of-multiplication-result, + -bugprone-narrowing-conversions, # too many false positives around `std::size_t` vs. `*::difference_type`. + google-build-using-namespace, + google-explicit-constructor, + google-global-names-in-headers, + google-readability-casting, + google-runtime-int, + google-runtime-operator, + misc-unused-using-decls, + modernize-*, + -modernize-use-trailing-return-type, + -modernize-avoid-c-arrays, + -modernize-return-braced-init-list, # can hurt readability + -modernize-use-nodiscard, + performance-*, + readability-*, + -readability-else-after-return, + -readability-identifier-length, + -readability-function-cognitive-complexity, + -readability-magic-numbers, + -readability-named-parameter" + +CheckOptions: + - key: bugprone-argument-comment.StrictMode + value: 1 + + - key: bugprone-dangling-handle.HandleClasses + value: "std::basic_string_view;std::experimental::basic_string_view;absl::string_view" + + - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic + value: 1 + + # Ignore GoogleTest function macros. + - key: readability-identifier-naming.FunctionIgnoredRegexp + value: "(TEST|TEST_F|TEST_P|INSTANTIATE_TEST_SUITE_P|MOCK_METHOD|TYPED_TEST)" + + - key: readability-identifier-naming.ClassCase + value: "CamelCase" + + - key: readability-identifier-naming.EnumCase + value: "CamelCase" + + - key: readability-identifier-naming.EnumConstantCase + value: "CamelCase" + + - key: readability-identifier-naming.ParameterCase + value: "lower_case" + + - key: readability-identifier-naming.PrivateMemberCase + value: "lower_case" + + - key: readability-identifier-naming.PrivateMemberSuffix + value: "_" + + - key: readability-identifier-naming.StructCase + value: "CamelCase" + + - key: readability-identifier-naming.TypeAliasCase + value: "CamelCase" + + - key: readability-identifier-naming.UnionCase + value: "CamelCase" + + - key: readability-identifier-naming.FunctionCase + value: "CamelBack" + + - key: performance-unnecessary-value-param.AllowedTypes + value: PtBufferView diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..0f9ce30c --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +# devtools +.idea/ +*.plist +clion/ +*.swp +tags +sftp-config.json +build_cmakelist.py +.akconfig +.cloudide/ +*.code-workspace +vipclient-* +*.pb.h +*.pb.cc +compile_commands.json +.clangd +Pipfile + +# bazel +bazel-* + +# cmake related +abseil-cpp +bld +bld.install +CMakeCache.txt +cmake_install.cmake +CTestTestfile.cmake + +# mixed +.DS_Store +.ipynb_checkpoints +trace.*log + +dump/ + +# clangd cache +.cache +external + +#brpc +rpc_data + +coverity*/ +idir/ + +ossutil_output/ \ No newline at end of file diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 00000000..ab9f1349 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,59 @@ +# Copyright 2023 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Enable all markdownlint rules by default +default: true + +# List formatting: +# +# 1. Lists must have a blank line before AND after the list. +# 2. Lists start aligned to the left (do not indent the top level list items). +# NOTE: markdownlint currently checks indentation for unordered lists only. +# Please manually verify that your ordered lists are not indented. +# See https://github.com/DavidAnson/markdownlint/issues/138. +# 3. You may use one or zero blank lines between list items. +# 4. Nested list items should be indented to align with the first character of +# the first line. For bullet lists, that means 2 spaces. For numbered +# lists, that's 3 spaces (but 4 spaces is okay if that's easier). +# 5. In multiline list items, subsequent lines are indented by 2 spaces. +# This is not checked automatically, so we're documenting this convention +# to make sure the codebase stays consistent. +# +# Examples: +# +# * This is a list item that has multiple +# lines and each line aligns with the text from the first line. +# * This is a nested list, also aligned with the first line. +# +# For ordered lists, that means three spaces for wrapped lines: +# +# 1. This is an ordered list item. +# 1. The nested list aligns with the first line. +ul-indent: + indent: 2 + +line-length: + line_length: 150 + tables: false + code_blocks: false + +# Allow inline HTML +no-inline-html: false + +# Allow dupe heading names only if they're not siblings +no-duplicate-heading: + siblings_only: true + +# Allow images w/o alt-text +no-alt-text: false diff --git a/LEGAL.md b/LEGAL.md new file mode 100644 index 00000000..f9689208 --- /dev/null +++ b/LEGAL.md @@ -0,0 +1,7 @@ +Legal Disclaimer + +Within this source code, the comments in Chinese shall be the original, governing version. Any comment in other languages are for reference only. In the event of any conflict between the Chinese language version comments and other language version comments, the Chinese language version shall prevail. + +法律免责声明 + +关于代码注释部分,中文注释为官方版本,其它语言注释仅做参考。中文注释可能与其它语言注释存在不一致,当中文注释与其它语言注释存在不一致时,请以中文注释为准。 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..7287a4a8 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# SecretFlow PSI Library + +The repo of Private Set Intersection(PSI) and Private Information Retrieval(PIR) from SecretFlow. + +This repo is formerly psi/pir part from secretflow/spu repo. + +## Quick Start + + + +## Building SecretFlow PSI Library + +### System Setup + + +#### Docker + +We use the same dev docker from secretflow/spu. + +```sh +## start container +docker run -d -it --name spu-dev-$(whoami) \ + --mount type=bind,source="$(pwd)",target=/home/admin/dev/ \ + -w /home/admin/dev \ + --cap-add=SYS_PTRACE --security-opt seccomp=unconfined \ + --cap-add=NET_ADMIN \ + --privileged=true \ + secretflow/spu-ci:latest + +# attach to build container +docker exec -it spu-dev-$(whoami) bash +``` + +#### Linux + +```sh +Install gcc>=11.2, cmake>=3.26, ninja, nasm>=2.15, python>=3.8, bazel==6.2.1, golang, xxd, lld +``` + +### Build & UnitTest + + + + +``` sh +# build as debug +bazel build //... -c dbg + +# build as release +bazel build //... -c opt + +# test +bazel test //... +``` + + +### Trace + +We use [Perfetto](https://perfetto.dev/) from Google for tracing. + +Please use debug_options.trace_path field in PsiConfig to modify trace file path. The default path is /tmp/psi.trace. + +After running psi binaries, please check trace by using [Trace Viewer](https://ui.perfetto.dev/). If this is not applicable, please check [this link](https://github.com/google/perfetto/issues/170) to deploy your own website. + +The alternate way to visualize trace is to use **chrome://tracing**: +1. Download perfetto assets from https://github.com/google/perfetto/releases/tag/v37.0 +2. You should find traceconv binary in assets folder. +3. Transfer trace file to JSON format: + +```bash +chmod +x traceconv + +./traceconv json [trace file path] [json file path] +``` +4. Open chrome://tracing in your chrome and load JSON file. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..8b25c541 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,31 @@ +# Security + +If you believe you have found a security vulnerability in any SecretFlow repository that meets +[SecretFlow's definition of a security vulnerability](https://security.alipay.com/announcement.htm?id=1), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the ANT GROUP SECURITY Response Center at [https://security.alipay.com/](https://security.alipay.com/). + +If you prefer to submit without logging in, send email to [antsrc@alipay.com](mailto:antsrc@alipay.com). + +You should receive a response within 48 hours. If for some reason you do not, please follow up via email to ensure we received your original message. +Additional information can be found at [https://security.alipay.com/](https://security.alipay.com/). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + +* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Preferred Languages + +We prefer all communications to be in Chinese or English. diff --git a/VERSIONING.md b/VERSIONING.md new file mode 100644 index 00000000..f184f49d --- /dev/null +++ b/VERSIONING.md @@ -0,0 +1,89 @@ +# Versioning + +SecretFlow artifacts belongs to one of the following categories: + +- "Releases", i.e. artifact versions follow the **x.y.z** format, where **x** is called "major version", **y** is called "minor version", **z** is called "patch version". We follow [Semantic Versioning 2.0.0](https://semver.org/) to update versions. e.g. +``` +1.2.3 +``` + +- "Pre-releases", i.e. artifact versions follow the **x.y.zb0** format. **Pre-release segment** is fixed to **b0**. e.g. +``` +0.8.7b0 # Pre-release of 0.8.7 +``` + +- "Developmental releases", i.e. artifact versions follow the **x.y.z.devYYMMDD** or **x.y.z.devYYMMDD** format, where **YY** is the last two digits of year(00-99), **MM** is the zero-padded digits of month(01-12) and **DD** is zero-padded digits of day of the month(01-31). **YY**, **MM** and **DD** indicates the date when a Developmental release is published. e.g. +``` +1.1.0.dev230820 # Developmental release of 1.1.0 published at 23/08/20 +1.3.0.dev231115 # Developmental release of 1.3.0 published at 23/11/15 +``` + +- "Post-releases", i.e. artifact versions follow the **x.y.zb0.postN** or **x.y.z.postN** format. **x.y.zb0.postN** is a Developmental release of a Pre-release while **x.y.z.postN** is a Developmental release of a Release. e.g. +``` +1.1.0.post0 # The 1st Post-release of 1.1.0 +1.3.0b0.post9 # The 10th Post-release of 1.3.0b0 +``` + +## Releases + +Release **x.y.z** meets the following conditions: +- Pre-release **x.y.zb0** has been published before, and +- Pre-release **x.y.zb0** has passed through evaluation. + +## Pre-releases + +Pre-release **x.y.zb0** is to support testing by external users prior to release **x.y.z**. + +## Developmental releases + +Developmental releases are to provide the preview of new features. They are considered unstable. + +## Post-releases + +Post-releases are to address minor errors of Releases and Pre-releases. They are considered unstable. + +# 版本控制 + +SecretFlow 的 artifact 属于以下几种类别之一: + +- "Releases",即 artifact 版本遵循 **x.y.z** 格式,其中 **x** 称为 "主版本", **y** 称为 "次版本", **z** 称为 "修复版本"。我们遵循 [Semantic Versioning 2.0.0](https://semver.org/) 更新版本。例如: +``` +1.2.3 +``` + +- "Pre-releases",即 artifact 版本遵循 **x.y.zb0** 格式。预发布字段固定为 **b0**。例如: +``` +0.8.7b0 # 0.8.7的Pre-release +``` + +- "Developmental releases",即 artifact 版本遵循 **x.y.z.devYYMMDD** 或 **x.y.z.devYYMMDD** 格式,其中 **YY** 是年份的最后两位(00-99), **MM** 是月份的零填充数字(01-12), **DD** 是当月日期的零填充数字(01-31)。**YY**, **MM** 和 **DD** 用以表示发布开发版的日期。例如: +``` +1.1.0.dev230820 # 于23/08/20发布的1.1.0开发版 +1.3.0.dev231115 # 于23/11/15发布的1.3.0开发版 +``` + + +- "Post-releases",即 artifact 版本遵循 **x.y.zb0.postN** 或 **x.y.z.postN** 格式。 **x.y.zb0.postN** 是 Pre-releases 的 Post-releases,而 x.y.z.postN 是 Releases 的Post-releases 。例如: +``` +1.1.0.post0 # 1.1.0的第1个Post-release +1.3.0b0.post9 # 1.3.0b0的第10个Post-release +``` + +## Releases + +Release **x.y.z** 满足以下条件: + +- Pre-release **x.y.zb0** 已经发布,并且 +- Pre-release **x.y.zb0** 已经经过测试评估。 + +## Pre-releases + +Pre-release **x.y.zb0** 是对外提供的 Release **x.y.z** 的测试版本。 + +## Developmental releases + +Developmental releases 旨在提供新功能的预览。它们被认为是不稳定的。 + +## Post-releases + +Post-releases 用于解决 Releases 和 Pre-releases 的轻微错误。它们被认为是不稳定的。 diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 00000000..9fa078a7 --- /dev/null +++ b/WORKSPACE @@ -0,0 +1,55 @@ +# Copyright 2021 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +workspace(name = "psi") + +load("//bazel:repositories.bzl", "psi_deps") + +psi_deps() + +# +# yacl +# Warning: psi relies on yacl to bring in common 3p libraries. +# Please make sure yacl_deps are called right after psi_deps. +# +load("@yacl//bazel:repositories.bzl", "yacl_deps") + +yacl_deps() + +load( + "@rules_foreign_cc//foreign_cc:repositories.bzl", + "rules_foreign_cc_dependencies", +) + +rules_foreign_cc_dependencies( + register_built_tools = False, + register_default_tools = False, + register_preinstalled_tools = True, +) + +load("@com_github_grpc_grpc//bazel:grpc_deps.bzl", "grpc_deps") + +grpc_deps() + +# Not mentioned in official docs... mentioned here https://github.com/grpc/grpc/issues/20511 +load("@com_github_grpc_grpc//bazel:grpc_extra_deps.bzl", "grpc_extra_deps") + +grpc_extra_deps() + +# +# boost +# +load("@com_github_nelhage_rules_boost//:boost/boost.bzl", "boost_deps") + +boost_deps() diff --git a/bazel/BUILD.bazel b/bazel/BUILD.bazel new file mode 100644 index 00000000..9fba954b --- /dev/null +++ b/bazel/BUILD.bazel @@ -0,0 +1,30 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility = ["//visibility:public"]) + +config_setting( + name = "psi_build_as_release", + values = {"compilation_mode": "opt"}, +) + +config_setting( + name = "psi_build_as_debug", + values = {"compilation_mode": "dbg"}, +) + +config_setting( + name = "psi_build_as_fast", + values = {"compilation_mode": "fastbuild"}, +) diff --git a/bazel/arrow.BUILD b/bazel/arrow.BUILD new file mode 100644 index 00000000..a45bf6e8 --- /dev/null +++ b/bazel/arrow.BUILD @@ -0,0 +1,168 @@ +# Copyright 2023 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# copied from https://github.com/tensorflow/io/blob/master/third_party/arrow.BUILD and made some changes +# Description: +# Apache Arrow library + +load("@com_github_grpc_grpc//bazel:cc_grpc_library.bzl", "cc_grpc_library") + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # Apache 2.0 + +exports_files(["LICENSE.txt"]) + +genrule( + name = "arrow_util_config", + srcs = ["cpp/src/arrow/util/config.h.cmake"], + outs = ["cpp/src/arrow/util/config.h"], + cmd = ("sed " + + "-e 's/@ARROW_VERSION_MAJOR@/9/g' " + + "-e 's/@ARROW_VERSION_MINOR@/0/g' " + + "-e 's/@ARROW_VERSION_PATCH@/0/g' " + + "-e 's/cmakedefine ARROW_USE_NATIVE_INT128/undef ARROW_USE_NATIVE_INT128/g' " + + "-e 's/cmakedefine ARROW_WITH_OPENTELEMETRY/undef ARROW_WITH_OPENTELEMETRY/g' " + + "-e 's/cmakedefine ARROW_GCS/undef ARROW_GCS/g' " + + "-e 's/cmakedefine ARROW_S3/undef ARROW_S3/g' " + + "-e 's/cmakedefine ARROW_JEMALLOC/undef ARROW_JEMALLOC/g' " + + "-e 's/cmakedefine ARROW_JEMALLOC_VENDORED/undef ARROW_JEMALLOC_VENDORED/g' " + + "-e 's/cmakedefine/define/g' " + + "$< >$@"), +) + +genrule( + name = "parquet_version_h", + srcs = ["cpp/src/parquet/parquet_version.h.in"], + outs = ["cpp/src/parquet/parquet_version.h"], + cmd = ("sed " + + "-e 's/@PARQUET_VERSION_MAJOR@/1/g' " + + "-e 's/@PARQUET_VERSION_MINOR@/5/g' " + + "-e 's/@PARQUET_VERSION_PATCH@/1/g' " + + "$< >$@"), +) + +cc_library( + name = "arrow_vendored", + srcs = glob([ + "cpp/src/arrow/vendored/datetime/*.h", + "cpp/src/arrow/vendored/datetime/*.cpp", + "cpp/src/arrow/vendored/pcg/pcg_uint128.hpp", + "cpp/src/arrow/vendored/pcg/pcg_random.hpp", + "cpp/src/arrow/vendored/pcg/pcg_extras.hpp", + "cpp/src/arrow/vendored/uriparser/*.h", + "cpp/src/arrow/vendored/uriparser/*.c", + ]), + includes = [ + "cpp/src", + ], + visibility = ["//visibility:private"], +) + +cc_library( + name = "arrow", + srcs = glob( + [ + "cpp/src/arrow/*.cc", + "cpp/src/arrow/c/*.cc", + "cpp/src/arrow/array/*.cc", + "cpp/src/arrow/csv/*.cc", + "cpp/src/arrow/extension/**/*.cc", + "cpp/src/arrow/extension/**/*.h", + "cpp/src/arrow/io/*.cc", + "cpp/src/arrow/ipc/*.cc", + "cpp/src/arrow/json/*.cc", + "cpp/src/arrow/tensor/*.cc", + "cpp/src/arrow/compute/**/*.cc", + "cpp/src/arrow/util/*.cc", + "cpp/src/arrow/vendored/optional.hpp", + "cpp/src/arrow/vendored/string_view.hpp", + "cpp/src/arrow/vendored/variant.hpp", + "cpp/src/arrow/vendored/base64.cpp", + "cpp/src/arrow/**/*.h", + "cpp/src/parquet/**/*.h", + "cpp/src/parquet/**/*.cc", + "cpp/src/generated/*.h", + "cpp/src/generated/*.cpp", + "cpp/thirdparty/flatbuffers/include/flatbuffers/*.h", + ], + exclude = [ + "cpp/src/**/*_benchmark.cc", + "cpp/src/**/*_main.cc", + "cpp/src/**/*_nossl.cc", + "cpp/src/**/*_test.cc", + "cpp/src/**/test_*.h", + "cpp/src/**/test_*.cc", + "cpp/src/**/benchmark_util.h", + "cpp/src/**/benchmark_util.cc", + "cpp/src/**/*hdfs*.cc", + "cpp/src/**/*fuzz*.cc", + "cpp/src/arrow/memory_pool_jemalloc.cc", + "cpp/src/**/file_to_stream.cc", + "cpp/src/**/stream_to_file.cc", + "cpp/src/arrow/dataset/file_orc*", + "cpp/src/arrow/filesystem/gcsfs*.cc", + "cpp/src/arrow/filesystem/s3*.cc", + "cpp/src/arrow/filesystem/*_test_util.cc", + "cpp/src/arrow/util/bpacking_avx2.cc", + "cpp/src/arrow/util/bpacking_avx512.cc", + "cpp/src/arrow/util/bpacking_neon.cc", + "cpp/src/arrow/util/tracing_internal.cc", + "cpp/src/arrow/compute/**/*_avx2.cc", + ], + ), + hdrs = [ + # declare header from above genrule + "cpp/src/arrow/util/config.h", + "cpp/src/parquet/parquet_version.h", + ], + copts = [], + defines = [ + "ARROW_WITH_BROTLI", + "ARROW_WITH_SNAPPY", + "ARROW_WITH_LZ4", + "ARROW_WITH_ZLIB", + "ARROW_WITH_ZSTD", + "ARROW_WITH_BZ2", + "ARROW_STATIC", + "ARROW_EXPORT=", + "PARQUET_STATIC", + "PARQUET_EXPORT=", + ], + includes = [ + "cpp/src", + "cpp/src/arrow/vendored/xxhash", + "cpp/thirdparty/flatbuffers/include", + ], + textual_hdrs = [ + "cpp/src/arrow/vendored/xxhash/xxhash.c", + ], + deps = [ + ":arrow_vendored", + "@boost//:multiprecision", + "@brotli", + "@bzip2", + "@com_github_facebook_zstd//:zstd", + "@com_github_gflags_gflags//:gflags", + "@com_github_google_snappy//:snappy", + "@com_github_grpc_grpc//:grpc++", + "@com_github_grpc_grpc//:grpc++_reflection", + "@com_github_lz4_lz4//:lz4", + "@com_github_tencent_rapidjson//:rapidjson", + "@com_github_xtensor_xsimd//:xsimd", + "@com_google_double_conversion//:double-conversion", + "@org_apache_thrift//:thrift", + "@zlib", + ], +) diff --git a/bazel/brotli.BUILD b/bazel/brotli.BUILD new file mode 100644 index 00000000..c586412b --- /dev/null +++ b/bazel/brotli.BUILD @@ -0,0 +1,42 @@ +# Copyright 2023 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# copied from https://github.com/tensorflow/io/blob/v0.25.0/third_party/brotli.BUILD +# Description: +# Brotli library + +licenses(["notice"]) # MIT license + +exports_files(["LICENSE"]) + +cc_library( + name = "brotli", + srcs = glob([ + "c/common/*.c", + "c/common/*.h", + "c/dec/*.c", + "c/dec/*.h", + "c/enc/*.c", + "c/enc/*.h", + "c/include/brotli/*.h", + ]), + hdrs = [], + defines = [], + includes = [ + "c/dec", + "c/include", + ], + linkopts = [], + visibility = ["//visibility:public"], +) diff --git a/bazel/bzip2.BUILD b/bazel/bzip2.BUILD new file mode 100644 index 00000000..fc618d32 --- /dev/null +++ b/bazel/bzip2.BUILD @@ -0,0 +1,39 @@ +# Copyright 2023 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# copied from https://github.com/tensorflow/io/blob/v0.25.0/third_party/bzip2.BUILD + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # BSD-like license + +cc_library( + name = "bzip2", + srcs = [ + "blocksort.c", + "bzlib.c", + "bzlib_private.h", + "compress.c", + "crctable.c", + "decompress.c", + "huffman.c", + "randtable.c", + ], + hdrs = [ + "bzlib.h", + ], + copts = [ + ], + includes = ["."], +) diff --git a/bazel/curve25519-donna.BUILD b/bazel/curve25519-donna.BUILD new file mode 100644 index 00000000..9dd666d4 --- /dev/null +++ b/bazel/curve25519-donna.BUILD @@ -0,0 +1,21 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "curve25519_donna", + srcs = ["curve25519.c"], + hdrs = glob(["*.h"]), + visibility = ["//visibility:public"], +) diff --git a/bazel/double-conversion.BUILD b/bazel/double-conversion.BUILD new file mode 100644 index 00000000..1067d025 --- /dev/null +++ b/bazel/double-conversion.BUILD @@ -0,0 +1,31 @@ +# Copyright 2023 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_foreign_cc//foreign_cc:defs.bzl", "cmake") + +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "all_srcs", + srcs = glob(["**"]), +) + +cmake( + name = "double-conversion", + cache_entries = { + "CMAKE_INSTALL_LIBDIR": "lib", + }, + lib_source = ":all_srcs", + out_static_libs = ["libdouble-conversion.a"], +) diff --git a/bazel/eigen.BUILD b/bazel/eigen.BUILD new file mode 100644 index 00000000..73df40c8 --- /dev/null +++ b/bazel/eigen.BUILD @@ -0,0 +1,73 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# copy from tf: +# https://raw.githubusercontent.com/tensorflow/tensorflow/master/third_party/eigen.BUILD +# +# Description: +# Eigen is a C++ template library for linear algebra: vectors, +# matrices, and related algorithms. + +load("@rules_cc//cc:defs.bzl", "cc_library") + +licenses([ + # Note: Eigen is an MPL2 library that includes GPL v3 and LGPL v2.1+ code. + # We've taken special care to not reference any restricted code. + "reciprocal", # MPL2 + "notice", # Portions BSD +]) + +exports_files(["COPYING.MPL2"]) + +EIGEN_FILES = [ + "Eigen/**", + "unsupported/Eigen/CXX11/**", + "unsupported/Eigen/FFT", + "unsupported/Eigen/KroneckerProduct", + "unsupported/Eigen/src/FFT/**", + "unsupported/Eigen/src/KroneckerProduct/**", + "unsupported/Eigen/MatrixFunctions", + "unsupported/Eigen/SpecialFunctions", + "unsupported/Eigen/src/MatrixFunctions/**", + "unsupported/Eigen/src/SpecialFunctions/**", +] + +# Files known to be under MPL2 license. +EIGEN_MPL2_HEADER_FILES = glob( + EIGEN_FILES, + exclude = [ + # Guarantees that any non-MPL2 file added to the list above will fail to + # compile. + "Eigen/src/Core/util/NonMPL2.h", + "Eigen/**/CMakeLists.txt", + ], +) + +cc_library( + name = "eigen3", + hdrs = EIGEN_MPL2_HEADER_FILES, + defines = [ + # This define (mostly) guarantees we don't link any problematic + # code. We use it, but we do not rely on it, as evidenced above. + "EIGEN_MPL2_ONLY", + ], + includes = ["."], + visibility = ["//visibility:public"], +) + +filegroup( + name = "eigen_header_files", + srcs = EIGEN_MPL2_HEADER_FILES, + visibility = ["//visibility:public"], +) diff --git a/bazel/emp-ot.BUILD b/bazel/emp-ot.BUILD new file mode 100644 index 00000000..541b765b --- /dev/null +++ b/bazel/emp-ot.BUILD @@ -0,0 +1,39 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@psi//bazel:psi.bzl", "psi_cmake_external") + +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "all_srcs", + srcs = glob(["**"]), +) + +psi_cmake_external( + name = "emp-ot", + cache_entries = { + "CMAKE_FOLDER": "$EXT_BUILD_DEPS/emp-tool", + "EMP-TOOL_INCLUDE_DIR": "$EXT_BUILD_DEPS/emp-tool/include", + "EMP-TOOL_LIBRARY": "$EXT_BUILD_DEPS/emp-tool/lib", + "OPENSSL_ROOT_DIR": "$EXT_BUILD_DEPS/openssl", + "BUILD_TESTING": "OFF", + }, + lib_source = ":all_srcs", + out_headers_only = True, + deps = [ + "@com_github_emptoolkit_emp_tool//:emp-tool", + "@com_github_openssl_openssl//:openssl", + ], +) diff --git a/bazel/emp-tool.BUILD b/bazel/emp-tool.BUILD new file mode 100644 index 00000000..57a2c776 --- /dev/null +++ b/bazel/emp-tool.BUILD @@ -0,0 +1,38 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@yacl//bazel:yacl.bzl", "yacl_cmake_external") + +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "all_srcs", + srcs = glob(["**"]), +) + +yacl_cmake_external( + name = "emp-tool", + cache_entries = { + "OPENSSL_ROOT_DIR": "$EXT_BUILD_DEPS/openssl", + "BUILD_TESTING": "OFF", + }, + lib_source = ":all_srcs", + out_data_dirs = ["cmake"], + out_static_libs = [ + "libemp-tool.a", + ], + deps = [ + "@com_github_openssl_openssl//:openssl", + ], +) diff --git a/bazel/emp-zk.BUILD b/bazel/emp-zk.BUILD new file mode 100644 index 00000000..94ec7c89 --- /dev/null +++ b/bazel/emp-zk.BUILD @@ -0,0 +1,42 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@psi//bazel:psi.bzl", "psi_cmake_external") + +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "all_srcs", + srcs = glob(["**"]), +) + +psi_cmake_external( + name = "emp-zk", + cache_entries = { + "CMAKE_FOLDER": "$EXT_BUILD_DEPS/emp-tool", + "EMP-TOOL_INCLUDE_DIR": "$EXT_BUILD_DEPS/emp-tool/include", + "EMP-TOOL_LIBRARY": "$EXT_BUILD_DEPS/emp-tool/lib", + "EMP-OT_INCLUDE_DIR": "$EXT_BUILD_DEPS/emp-ot/include", + "EMP-OT_LIBRARY": "$EXT_BUILD_DEPS/emp-ot/lib", + "OPENSSL_ROOT_DIR": "$EXT_BUILD_DEPS/openssl", + "BUILD_TESTING": "OFF", + }, + lib_source = ":all_srcs", + out_headers_only = True, + deps = [ + "@com_github_emptoolkit_emp_ot//:emp-ot", + "@com_github_emptoolkit_emp_tool//:emp-tool", + "@com_github_openssl_openssl//:openssl", + ], +) diff --git a/bazel/hexl.BUILD b/bazel/hexl.BUILD new file mode 100644 index 00000000..91cb5b52 --- /dev/null +++ b/bazel/hexl.BUILD @@ -0,0 +1,39 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@psi//bazel:psi.bzl", "psi_cmake_external") + +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "all_srcs", + srcs = glob(["**"]), +) + +psi_cmake_external( + name = "hexl", + cache_entries = { + "CMAKE_BUILD_TYPE": "Release", + "CpuFeatures_DIR": "$EXT_BUILD_DEPS/cpu_features/lib/cmake/CpuFeatures/", + "HEXL_BENCHMARK": "OFF", + "HEXL_TESTING": "OFF", + "CMAKE_INSTALL_LIBDIR": "lib", + }, + lib_source = ":all_srcs", + out_data_dirs = ["lib/cmake"], + out_static_libs = ["libhexl.a"], + deps = [ + "@com_github_google_cpu_features//:cpu_features", + ], +) diff --git a/bazel/libdivide.BUILD b/bazel/libdivide.BUILD new file mode 100644 index 00000000..a95fca18 --- /dev/null +++ b/bazel/libdivide.BUILD @@ -0,0 +1,32 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@psi//bazel:psi.bzl", "psi_cmake_external") + +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "all_srcs", + srcs = glob(["**"]), +) + +psi_cmake_external( + name = "libdivide", + cache_entries = { + "BUILD_TESTS": "OFF", + }, + lib_source = ":all_srcs", + out_headers_only = True, + out_include_dir = "include", +) diff --git a/bazel/local_openmp_macos.BUILD b/bazel/local_openmp_macos.BUILD new file mode 100644 index 00000000..82d976b8 --- /dev/null +++ b/bazel/local_openmp_macos.BUILD @@ -0,0 +1,27 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "openmp", + srcs = [ + "lib/libomp.a", + ], + hdrs = ["include/omp.h"], + includes = [ + "include/", + ], + visibility = ["//visibility:public"], +) diff --git a/bazel/lz4.BUILD b/bazel/lz4.BUILD new file mode 100644 index 00000000..9c30f4ef --- /dev/null +++ b/bazel/lz4.BUILD @@ -0,0 +1,37 @@ +# Copyright 2023 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@psi//bazel:psi.bzl", "psi_cmake_external") + +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "all_srcs", + srcs = glob(["**"]), +) + +psi_cmake_external( + name = "lz4", + cache_entries = { + "LZ4_BUILD_CLI": "OFF", + "BUILD_SHARED_LIBS": "OFF", + "BUILD_STATIC_LIBS": "ON", + "CMAKE_INSTALL_LIBDIR": "lib", + }, + lib_source = ":all_srcs", + out_static_libs = [ + "liblz4.a", + ], + working_directory = "build/cmake", +) diff --git a/bazel/microsoft_apsi.BUILD b/bazel/microsoft_apsi.BUILD new file mode 100644 index 00000000..eac12ad5 --- /dev/null +++ b/bazel/microsoft_apsi.BUILD @@ -0,0 +1,49 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@psi//bazel:psi.bzl", "psi_cmake_external") + +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "all", + srcs = glob(["**"]), +) + +psi_cmake_external( + name = "apsi", + cache_entries = { + "APSI_USE_LOG4CPLUS": "OFF", + "APSI_USE_ZMQ": "OFF", + "CMAKE_INSTALL_LIBDIR": "lib", + "CpuFeatures_DIR": "$EXT_BUILD_DEPS/cpu_features/lib/cmake/CpuFeatures/", + "EXT_BUILD_DEPS": "$EXT_BUILD_DEPS", + }, + copts = [ + "-DAPSI_DISABLE_JSON", + "-I$EXT_BUILD_DEPS/gsl/include", + "-I$EXT_BUILD_ROOT/external/com_google_flatbuffers/include", + ], + lib_source = "@com_github_microsoft_apsi//:all", + out_include_dir = "include/APSI-0.11", + out_static_libs = ["libapsi-0.11.a"], + deps = [ + "@com_github_facebook_zstd//:zstd", + "@com_github_microsoft_gsl//:gsl", + "@com_github_microsoft_kuku//:kuku", + "@com_github_microsoft_seal//:seal", + "@com_google_flatbuffers//:flatbuffers", + "@zlib", + ], +) diff --git a/bazel/microsoft_gsl.BUILD b/bazel/microsoft_gsl.BUILD new file mode 100644 index 00000000..0340039c --- /dev/null +++ b/bazel/microsoft_gsl.BUILD @@ -0,0 +1,34 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@psi//bazel:psi.bzl", "psi_cmake_external") + +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "all_srcs", + srcs = glob(["**"]), +) + +psi_cmake_external( + name = "gsl", + cache_entries = { + "GSL_INSTALL": "ON", + "GSL_STANDALONE_PROJECT": "OFF", + "GSL_TEST": "OFF", + }, + lib_source = ":all_srcs", + out_headers_only = True, + out_include_dir = "include", +) diff --git a/bazel/microsoft_kuku.BUILD b/bazel/microsoft_kuku.BUILD new file mode 100644 index 00000000..f597796d --- /dev/null +++ b/bazel/microsoft_kuku.BUILD @@ -0,0 +1,36 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@psi//bazel:psi.bzl", "psi_cmake_external") + +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "all", + srcs = glob(["**"]), +) + +psi_cmake_external( + name = "kuku", + cache_entries = { + "KUKU_BUILD_TESTS": "OFF", + "KUKU_BUILD_EXAMPLES": "OFF", + "BUILD_SHARED_LIBS": "OFF", + "CMAKE_INSTALL_LIBDIR": "lib", + }, + lib_source = "@com_github_microsoft_kuku//:all", + out_include_dir = "include/Kuku-2.1", + out_static_libs = ["libkuku-2.1.a"], + deps = ["@com_github_microsoft_gsl//:gsl"], +) diff --git a/bazel/patches/apsi-gen.patch b/bazel/patches/apsi-gen.patch new file mode 100644 index 00000000..f23b9610 --- /dev/null +++ b/bazel/patches/apsi-gen.patch @@ -0,0 +1,1081 @@ +diff --git b/common/apsi/psi_params_generated.h b/common/apsi/psi_params_generated.h +new file mode 100644 +index 0000000..ed2837b +--- /dev/null ++++ b/common/apsi/psi_params_generated.h +@@ -0,0 +1,310 @@ ++// automatically generated by the FlatBuffers compiler, do not modify ++ ++ ++#ifndef FLATBUFFERS_GENERATED_PSIPARAMS_APSI_FBS_H_ ++#define FLATBUFFERS_GENERATED_PSIPARAMS_APSI_FBS_H_ ++ ++#include "flatbuffers/flatbuffers.h" ++ ++// Ensure the included flatbuffers.h is the same version as when this file was ++// generated, otherwise it may not be compatible. ++static_assert(FLATBUFFERS_VERSION_MAJOR == 23 && ++ FLATBUFFERS_VERSION_MINOR == 3 && ++ FLATBUFFERS_VERSION_REVISION == 3, ++ "Non-compatible flatbuffers version included"); ++ ++namespace apsi { ++namespace fbs { ++ ++struct ItemParams; ++ ++struct TableParams; ++ ++struct QueryParams; ++struct QueryParamsBuilder; ++ ++struct SEALParams; ++struct SEALParamsBuilder; ++ ++struct PSIParams; ++struct PSIParamsBuilder; ++ ++FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) ItemParams FLATBUFFERS_FINAL_CLASS { ++ private: ++ uint32_t felts_per_item_; ++ ++ public: ++ ItemParams() ++ : felts_per_item_(0) { ++ } ++ ItemParams(uint32_t _felts_per_item) ++ : felts_per_item_(::flatbuffers::EndianScalar(_felts_per_item)) { ++ } ++ uint32_t felts_per_item() const { ++ return ::flatbuffers::EndianScalar(felts_per_item_); ++ } ++}; ++FLATBUFFERS_STRUCT_END(ItemParams, 4); ++ ++FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(4) TableParams FLATBUFFERS_FINAL_CLASS { ++ private: ++ uint32_t table_size_; ++ uint32_t max_items_per_bin_; ++ uint32_t hash_func_count_; ++ ++ public: ++ TableParams() ++ : table_size_(0), ++ max_items_per_bin_(0), ++ hash_func_count_(0) { ++ } ++ TableParams(uint32_t _table_size, uint32_t _max_items_per_bin, uint32_t _hash_func_count) ++ : table_size_(::flatbuffers::EndianScalar(_table_size)), ++ max_items_per_bin_(::flatbuffers::EndianScalar(_max_items_per_bin)), ++ hash_func_count_(::flatbuffers::EndianScalar(_hash_func_count)) { ++ } ++ uint32_t table_size() const { ++ return ::flatbuffers::EndianScalar(table_size_); ++ } ++ uint32_t max_items_per_bin() const { ++ return ::flatbuffers::EndianScalar(max_items_per_bin_); ++ } ++ uint32_t hash_func_count() const { ++ return ::flatbuffers::EndianScalar(hash_func_count_); ++ } ++}; ++FLATBUFFERS_STRUCT_END(TableParams, 12); ++ ++struct QueryParams FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { ++ typedef QueryParamsBuilder Builder; ++ enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { ++ VT_PS_LOW_DEGREE = 4, ++ VT_QUERY_POWERS = 6 ++ }; ++ uint32_t ps_low_degree() const { ++ return GetField(VT_PS_LOW_DEGREE, 0); ++ } ++ const ::flatbuffers::Vector *query_powers() const { ++ return GetPointer *>(VT_QUERY_POWERS); ++ } ++ bool Verify(::flatbuffers::Verifier &verifier) const { ++ return VerifyTableStart(verifier) && ++ VerifyField(verifier, VT_PS_LOW_DEGREE, 4) && ++ VerifyOffset(verifier, VT_QUERY_POWERS) && ++ verifier.VerifyVector(query_powers()) && ++ verifier.EndTable(); ++ } ++}; ++ ++struct QueryParamsBuilder { ++ typedef QueryParams Table; ++ ::flatbuffers::FlatBufferBuilder &fbb_; ++ ::flatbuffers::uoffset_t start_; ++ void add_ps_low_degree(uint32_t ps_low_degree) { ++ fbb_.AddElement(QueryParams::VT_PS_LOW_DEGREE, ps_low_degree, 0); ++ } ++ void add_query_powers(::flatbuffers::Offset<::flatbuffers::Vector> query_powers) { ++ fbb_.AddOffset(QueryParams::VT_QUERY_POWERS, query_powers); ++ } ++ explicit QueryParamsBuilder(::flatbuffers::FlatBufferBuilder &_fbb) ++ : fbb_(_fbb) { ++ start_ = fbb_.StartTable(); ++ } ++ ::flatbuffers::Offset Finish() { ++ const auto end = fbb_.EndTable(start_); ++ auto o = ::flatbuffers::Offset(end); ++ return o; ++ } ++}; ++ ++inline ::flatbuffers::Offset CreateQueryParams( ++ ::flatbuffers::FlatBufferBuilder &_fbb, ++ uint32_t ps_low_degree = 0, ++ ::flatbuffers::Offset<::flatbuffers::Vector> query_powers = 0) { ++ QueryParamsBuilder builder_(_fbb); ++ builder_.add_query_powers(query_powers); ++ builder_.add_ps_low_degree(ps_low_degree); ++ return builder_.Finish(); ++} ++ ++inline ::flatbuffers::Offset CreateQueryParamsDirect( ++ ::flatbuffers::FlatBufferBuilder &_fbb, ++ uint32_t ps_low_degree = 0, ++ const std::vector *query_powers = nullptr) { ++ auto query_powers__ = query_powers ? _fbb.CreateVector(*query_powers) : 0; ++ return apsi::fbs::CreateQueryParams( ++ _fbb, ++ ps_low_degree, ++ query_powers__); ++} ++ ++struct SEALParams FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { ++ typedef SEALParamsBuilder Builder; ++ enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { ++ VT_DATA = 4 ++ }; ++ const ::flatbuffers::Vector *data() const { ++ return GetPointer *>(VT_DATA); ++ } ++ bool Verify(::flatbuffers::Verifier &verifier) const { ++ return VerifyTableStart(verifier) && ++ VerifyOffsetRequired(verifier, VT_DATA) && ++ verifier.VerifyVector(data()) && ++ verifier.EndTable(); ++ } ++}; ++ ++struct SEALParamsBuilder { ++ typedef SEALParams Table; ++ ::flatbuffers::FlatBufferBuilder &fbb_; ++ ::flatbuffers::uoffset_t start_; ++ void add_data(::flatbuffers::Offset<::flatbuffers::Vector> data) { ++ fbb_.AddOffset(SEALParams::VT_DATA, data); ++ } ++ explicit SEALParamsBuilder(::flatbuffers::FlatBufferBuilder &_fbb) ++ : fbb_(_fbb) { ++ start_ = fbb_.StartTable(); ++ } ++ ::flatbuffers::Offset Finish() { ++ const auto end = fbb_.EndTable(start_); ++ auto o = ::flatbuffers::Offset(end); ++ fbb_.Required(o, SEALParams::VT_DATA); ++ return o; ++ } ++}; ++ ++inline ::flatbuffers::Offset CreateSEALParams( ++ ::flatbuffers::FlatBufferBuilder &_fbb, ++ ::flatbuffers::Offset<::flatbuffers::Vector> data = 0) { ++ SEALParamsBuilder builder_(_fbb); ++ builder_.add_data(data); ++ return builder_.Finish(); ++} ++ ++inline ::flatbuffers::Offset CreateSEALParamsDirect( ++ ::flatbuffers::FlatBufferBuilder &_fbb, ++ const std::vector *data = nullptr) { ++ auto data__ = data ? _fbb.CreateVector(*data) : 0; ++ return apsi::fbs::CreateSEALParams( ++ _fbb, ++ data__); ++} ++ ++struct PSIParams FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { ++ typedef PSIParamsBuilder Builder; ++ enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { ++ VT_VERSION = 4, ++ VT_ITEM_PARAMS = 6, ++ VT_TABLE_PARAMS = 8, ++ VT_QUERY_PARAMS = 10, ++ VT_SEAL_PARAMS = 12 ++ }; ++ uint32_t version() const { ++ return GetField(VT_VERSION, 0); ++ } ++ const apsi::fbs::ItemParams *item_params() const { ++ return GetStruct(VT_ITEM_PARAMS); ++ } ++ const apsi::fbs::TableParams *table_params() const { ++ return GetStruct(VT_TABLE_PARAMS); ++ } ++ const apsi::fbs::QueryParams *query_params() const { ++ return GetPointer(VT_QUERY_PARAMS); ++ } ++ const apsi::fbs::SEALParams *seal_params() const { ++ return GetPointer(VT_SEAL_PARAMS); ++ } ++ bool Verify(::flatbuffers::Verifier &verifier) const { ++ return VerifyTableStart(verifier) && ++ VerifyField(verifier, VT_VERSION, 4) && ++ VerifyField(verifier, VT_ITEM_PARAMS, 4) && ++ VerifyField(verifier, VT_TABLE_PARAMS, 4) && ++ VerifyOffset(verifier, VT_QUERY_PARAMS) && ++ verifier.VerifyTable(query_params()) && ++ VerifyOffsetRequired(verifier, VT_SEAL_PARAMS) && ++ verifier.VerifyTable(seal_params()) && ++ verifier.EndTable(); ++ } ++}; ++ ++struct PSIParamsBuilder { ++ typedef PSIParams Table; ++ ::flatbuffers::FlatBufferBuilder &fbb_; ++ ::flatbuffers::uoffset_t start_; ++ void add_version(uint32_t version) { ++ fbb_.AddElement(PSIParams::VT_VERSION, version, 0); ++ } ++ void add_item_params(const apsi::fbs::ItemParams *item_params) { ++ fbb_.AddStruct(PSIParams::VT_ITEM_PARAMS, item_params); ++ } ++ void add_table_params(const apsi::fbs::TableParams *table_params) { ++ fbb_.AddStruct(PSIParams::VT_TABLE_PARAMS, table_params); ++ } ++ void add_query_params(::flatbuffers::Offset query_params) { ++ fbb_.AddOffset(PSIParams::VT_QUERY_PARAMS, query_params); ++ } ++ void add_seal_params(::flatbuffers::Offset seal_params) { ++ fbb_.AddOffset(PSIParams::VT_SEAL_PARAMS, seal_params); ++ } ++ explicit PSIParamsBuilder(::flatbuffers::FlatBufferBuilder &_fbb) ++ : fbb_(_fbb) { ++ start_ = fbb_.StartTable(); ++ } ++ ::flatbuffers::Offset Finish() { ++ const auto end = fbb_.EndTable(start_); ++ auto o = ::flatbuffers::Offset(end); ++ fbb_.Required(o, PSIParams::VT_SEAL_PARAMS); ++ return o; ++ } ++}; ++ ++inline ::flatbuffers::Offset CreatePSIParams( ++ ::flatbuffers::FlatBufferBuilder &_fbb, ++ uint32_t version = 0, ++ const apsi::fbs::ItemParams *item_params = nullptr, ++ const apsi::fbs::TableParams *table_params = nullptr, ++ ::flatbuffers::Offset query_params = 0, ++ ::flatbuffers::Offset seal_params = 0) { ++ PSIParamsBuilder builder_(_fbb); ++ builder_.add_seal_params(seal_params); ++ builder_.add_query_params(query_params); ++ builder_.add_table_params(table_params); ++ builder_.add_item_params(item_params); ++ builder_.add_version(version); ++ return builder_.Finish(); ++} ++ ++inline const apsi::fbs::PSIParams *GetPSIParams(const void *buf) { ++ return ::flatbuffers::GetRoot(buf); ++} ++ ++inline const apsi::fbs::PSIParams *GetSizePrefixedPSIParams(const void *buf) { ++ return ::flatbuffers::GetSizePrefixedRoot(buf); ++} ++ ++inline bool VerifyPSIParamsBuffer( ++ ::flatbuffers::Verifier &verifier) { ++ return verifier.VerifyBuffer(nullptr); ++} ++ ++inline bool VerifySizePrefixedPSIParamsBuffer( ++ ::flatbuffers::Verifier &verifier) { ++ return verifier.VerifySizePrefixedBuffer(nullptr); ++} ++ ++inline void FinishPSIParamsBuffer( ++ ::flatbuffers::FlatBufferBuilder &fbb, ++ ::flatbuffers::Offset root) { ++ fbb.Finish(root); ++} ++ ++inline void FinishSizePrefixedPSIParamsBuffer( ++ ::flatbuffers::FlatBufferBuilder &fbb, ++ ::flatbuffers::Offset root) { ++ fbb.FinishSizePrefixed(root); ++} ++ ++} // namespace fbs ++} // namespace apsi ++ ++#endif // FLATBUFFERS_GENERATED_PSIPARAMS_APSI_FBS_H_ +diff --git b/sender/apsi/bin_bundle_generated.h b/sender/apsi/bin_bundle_generated.h +new file mode 100644 +index 0000000..e821e33 +--- /dev/null ++++ b/sender/apsi/bin_bundle_generated.h +@@ -0,0 +1,490 @@ ++// automatically generated by the FlatBuffers compiler, do not modify ++ ++ ++#ifndef FLATBUFFERS_GENERATED_BINBUNDLE_APSI_FBS_H_ ++#define FLATBUFFERS_GENERATED_BINBUNDLE_APSI_FBS_H_ ++ ++#include "flatbuffers/flatbuffers.h" ++ ++// Ensure the included flatbuffers.h is the same version as when this file was ++// generated, otherwise it may not be compatible. ++static_assert(FLATBUFFERS_VERSION_MAJOR == 23 && ++ FLATBUFFERS_VERSION_MINOR == 3 && ++ FLATBUFFERS_VERSION_REVISION == 3, ++ "Non-compatible flatbuffers version included"); ++ ++namespace apsi { ++namespace fbs { ++ ++struct FEltArray; ++struct FEltArrayBuilder; ++ ++struct FEltMatrix; ++struct FEltMatrixBuilder; ++ ++struct Plaintext; ++struct PlaintextBuilder; ++ ++struct BatchedPlaintextPolyn; ++struct BatchedPlaintextPolynBuilder; ++ ++struct BinBundleCache; ++struct BinBundleCacheBuilder; ++ ++struct BinBundle; ++struct BinBundleBuilder; ++ ++struct FEltArray FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { ++ typedef FEltArrayBuilder Builder; ++ enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { ++ VT_FELTS = 4 ++ }; ++ const ::flatbuffers::Vector *felts() const { ++ return GetPointer *>(VT_FELTS); ++ } ++ bool Verify(::flatbuffers::Verifier &verifier) const { ++ return VerifyTableStart(verifier) && ++ VerifyOffsetRequired(verifier, VT_FELTS) && ++ verifier.VerifyVector(felts()) && ++ verifier.EndTable(); ++ } ++}; ++ ++struct FEltArrayBuilder { ++ typedef FEltArray Table; ++ ::flatbuffers::FlatBufferBuilder &fbb_; ++ ::flatbuffers::uoffset_t start_; ++ void add_felts(::flatbuffers::Offset<::flatbuffers::Vector> felts) { ++ fbb_.AddOffset(FEltArray::VT_FELTS, felts); ++ } ++ explicit FEltArrayBuilder(::flatbuffers::FlatBufferBuilder &_fbb) ++ : fbb_(_fbb) { ++ start_ = fbb_.StartTable(); ++ } ++ ::flatbuffers::Offset Finish() { ++ const auto end = fbb_.EndTable(start_); ++ auto o = ::flatbuffers::Offset(end); ++ fbb_.Required(o, FEltArray::VT_FELTS); ++ return o; ++ } ++}; ++ ++inline ::flatbuffers::Offset CreateFEltArray( ++ ::flatbuffers::FlatBufferBuilder &_fbb, ++ ::flatbuffers::Offset<::flatbuffers::Vector> felts = 0) { ++ FEltArrayBuilder builder_(_fbb); ++ builder_.add_felts(felts); ++ return builder_.Finish(); ++} ++ ++inline ::flatbuffers::Offset CreateFEltArrayDirect( ++ ::flatbuffers::FlatBufferBuilder &_fbb, ++ const std::vector *felts = nullptr) { ++ auto felts__ = felts ? _fbb.CreateVector(*felts) : 0; ++ return apsi::fbs::CreateFEltArray( ++ _fbb, ++ felts__); ++} ++ ++struct FEltMatrix FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { ++ typedef FEltMatrixBuilder Builder; ++ enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { ++ VT_ROWS = 4 ++ }; ++ const ::flatbuffers::Vector<::flatbuffers::Offset> *rows() const { ++ return GetPointer> *>(VT_ROWS); ++ } ++ bool Verify(::flatbuffers::Verifier &verifier) const { ++ return VerifyTableStart(verifier) && ++ VerifyOffsetRequired(verifier, VT_ROWS) && ++ verifier.VerifyVector(rows()) && ++ verifier.VerifyVectorOfTables(rows()) && ++ verifier.EndTable(); ++ } ++}; ++ ++struct FEltMatrixBuilder { ++ typedef FEltMatrix Table; ++ ::flatbuffers::FlatBufferBuilder &fbb_; ++ ::flatbuffers::uoffset_t start_; ++ void add_rows(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> rows) { ++ fbb_.AddOffset(FEltMatrix::VT_ROWS, rows); ++ } ++ explicit FEltMatrixBuilder(::flatbuffers::FlatBufferBuilder &_fbb) ++ : fbb_(_fbb) { ++ start_ = fbb_.StartTable(); ++ } ++ ::flatbuffers::Offset Finish() { ++ const auto end = fbb_.EndTable(start_); ++ auto o = ::flatbuffers::Offset(end); ++ fbb_.Required(o, FEltMatrix::VT_ROWS); ++ return o; ++ } ++}; ++ ++inline ::flatbuffers::Offset CreateFEltMatrix( ++ ::flatbuffers::FlatBufferBuilder &_fbb, ++ ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> rows = 0) { ++ FEltMatrixBuilder builder_(_fbb); ++ builder_.add_rows(rows); ++ return builder_.Finish(); ++} ++ ++inline ::flatbuffers::Offset CreateFEltMatrixDirect( ++ ::flatbuffers::FlatBufferBuilder &_fbb, ++ const std::vector<::flatbuffers::Offset> *rows = nullptr) { ++ auto rows__ = rows ? _fbb.CreateVector<::flatbuffers::Offset>(*rows) : 0; ++ return apsi::fbs::CreateFEltMatrix( ++ _fbb, ++ rows__); ++} ++ ++struct Plaintext FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { ++ typedef PlaintextBuilder Builder; ++ enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { ++ VT_DATA = 4 ++ }; ++ const ::flatbuffers::Vector *data() const { ++ return GetPointer *>(VT_DATA); ++ } ++ bool Verify(::flatbuffers::Verifier &verifier) const { ++ return VerifyTableStart(verifier) && ++ VerifyOffsetRequired(verifier, VT_DATA) && ++ verifier.VerifyVector(data()) && ++ verifier.EndTable(); ++ } ++}; ++ ++struct PlaintextBuilder { ++ typedef Plaintext Table; ++ ::flatbuffers::FlatBufferBuilder &fbb_; ++ ::flatbuffers::uoffset_t start_; ++ void add_data(::flatbuffers::Offset<::flatbuffers::Vector> data) { ++ fbb_.AddOffset(Plaintext::VT_DATA, data); ++ } ++ explicit PlaintextBuilder(::flatbuffers::FlatBufferBuilder &_fbb) ++ : fbb_(_fbb) { ++ start_ = fbb_.StartTable(); ++ } ++ ::flatbuffers::Offset Finish() { ++ const auto end = fbb_.EndTable(start_); ++ auto o = ::flatbuffers::Offset<Plaintext>(end); ++ fbb_.Required(o, Plaintext::VT_DATA); ++ return o; ++ } ++}; ++ ++inline ::flatbuffers::Offset<Plaintext> CreatePlaintext( ++ ::flatbuffers::FlatBufferBuilder &_fbb, ++ ::flatbuffers::Offset<::flatbuffers::Vector<uint8_t>> data = 0) { ++ PlaintextBuilder builder_(_fbb); ++ builder_.add_data(data); ++ return builder_.Finish(); ++} ++ ++inline ::flatbuffers::Offset<Plaintext> CreatePlaintextDirect( ++ ::flatbuffers::FlatBufferBuilder &_fbb, ++ const std::vector<uint8_t> *data = nullptr) { ++ auto data__ = data ? _fbb.CreateVector<uint8_t>(*data) : 0; ++ return apsi::fbs::CreatePlaintext( ++ _fbb, ++ data__); ++} ++ ++struct BatchedPlaintextPolyn FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { ++ typedef BatchedPlaintextPolynBuilder Builder; ++ enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { ++ VT_COEFFS = 4 ++ }; ++ const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::Plaintext>> *coeffs() const { ++ return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::Plaintext>> *>(VT_COEFFS); ++ } ++ bool Verify(::flatbuffers::Verifier &verifier) const { ++ return VerifyTableStart(verifier) && ++ VerifyOffsetRequired(verifier, VT_COEFFS) && ++ verifier.VerifyVector(coeffs()) && ++ verifier.VerifyVectorOfTables(coeffs()) && ++ verifier.EndTable(); ++ } ++}; ++ ++struct BatchedPlaintextPolynBuilder { ++ typedef BatchedPlaintextPolyn Table; ++ ::flatbuffers::FlatBufferBuilder &fbb_; ++ ::flatbuffers::uoffset_t start_; ++ void add_coeffs(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::Plaintext>>> coeffs) { ++ fbb_.AddOffset(BatchedPlaintextPolyn::VT_COEFFS, coeffs); ++ } ++ explicit BatchedPlaintextPolynBuilder(::flatbuffers::FlatBufferBuilder &_fbb) ++ : fbb_(_fbb) { ++ start_ = fbb_.StartTable(); ++ } ++ ::flatbuffers::Offset<BatchedPlaintextPolyn> Finish() { ++ const auto end = fbb_.EndTable(start_); ++ auto o = ::flatbuffers::Offset<BatchedPlaintextPolyn>(end); ++ fbb_.Required(o, BatchedPlaintextPolyn::VT_COEFFS); ++ return o; ++ } ++}; ++ ++inline ::flatbuffers::Offset<BatchedPlaintextPolyn> CreateBatchedPlaintextPolyn( ++ ::flatbuffers::FlatBufferBuilder &_fbb, ++ ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::Plaintext>>> coeffs = 0) { ++ BatchedPlaintextPolynBuilder builder_(_fbb); ++ builder_.add_coeffs(coeffs); ++ return builder_.Finish(); ++} ++ ++inline ::flatbuffers::Offset<BatchedPlaintextPolyn> CreateBatchedPlaintextPolynDirect( ++ ::flatbuffers::FlatBufferBuilder &_fbb, ++ const std::vector<::flatbuffers::Offset<apsi::fbs::Plaintext>> *coeffs = nullptr) { ++ auto coeffs__ = coeffs ? _fbb.CreateVector<::flatbuffers::Offset<apsi::fbs::Plaintext>>(*coeffs) : 0; ++ return apsi::fbs::CreateBatchedPlaintextPolyn( ++ _fbb, ++ coeffs__); ++} ++ ++struct BinBundleCache FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { ++ typedef BinBundleCacheBuilder Builder; ++ enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { ++ VT_FELT_MATCHING_POLYNS = 4, ++ VT_BATCHED_MATCHING_POLYN = 6, ++ VT_FELT_INTERP_POLYNS = 8, ++ VT_BATCHED_INTERP_POLYNS = 10 ++ }; ++ const apsi::fbs::FEltMatrix *felt_matching_polyns() const { ++ return GetPointer<const apsi::fbs::FEltMatrix *>(VT_FELT_MATCHING_POLYNS); ++ } ++ const apsi::fbs::BatchedPlaintextPolyn *batched_matching_polyn() const { ++ return GetPointer<const apsi::fbs::BatchedPlaintextPolyn *>(VT_BATCHED_MATCHING_POLYN); ++ } ++ const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>> *felt_interp_polyns() const { ++ return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>> *>(VT_FELT_INTERP_POLYNS); ++ } ++ const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn>> *batched_interp_polyns() const { ++ return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn>> *>(VT_BATCHED_INTERP_POLYNS); ++ } ++ bool Verify(::flatbuffers::Verifier &verifier) const { ++ return VerifyTableStart(verifier) && ++ VerifyOffsetRequired(verifier, VT_FELT_MATCHING_POLYNS) && ++ verifier.VerifyTable(felt_matching_polyns()) && ++ VerifyOffsetRequired(verifier, VT_BATCHED_MATCHING_POLYN) && ++ verifier.VerifyTable(batched_matching_polyn()) && ++ VerifyOffset(verifier, VT_FELT_INTERP_POLYNS) && ++ verifier.VerifyVector(felt_interp_polyns()) && ++ verifier.VerifyVectorOfTables(felt_interp_polyns()) && ++ VerifyOffset(verifier, VT_BATCHED_INTERP_POLYNS) && ++ verifier.VerifyVector(batched_interp_polyns()) && ++ verifier.VerifyVectorOfTables(batched_interp_polyns()) && ++ verifier.EndTable(); ++ } ++}; ++ ++struct BinBundleCacheBuilder { ++ typedef BinBundleCache Table; ++ ::flatbuffers::FlatBufferBuilder &fbb_; ++ ::flatbuffers::uoffset_t start_; ++ void add_felt_matching_polyns(::flatbuffers::Offset<apsi::fbs::FEltMatrix> felt_matching_polyns) { ++ fbb_.AddOffset(BinBundleCache::VT_FELT_MATCHING_POLYNS, felt_matching_polyns); ++ } ++ void add_batched_matching_polyn(::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn> batched_matching_polyn) { ++ fbb_.AddOffset(BinBundleCache::VT_BATCHED_MATCHING_POLYN, batched_matching_polyn); ++ } ++ void add_felt_interp_polyns(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>>> felt_interp_polyns) { ++ fbb_.AddOffset(BinBundleCache::VT_FELT_INTERP_POLYNS, felt_interp_polyns); ++ } ++ void add_batched_interp_polyns(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn>>> batched_interp_polyns) { ++ fbb_.AddOffset(BinBundleCache::VT_BATCHED_INTERP_POLYNS, batched_interp_polyns); ++ } ++ explicit BinBundleCacheBuilder(::flatbuffers::FlatBufferBuilder &_fbb) ++ : fbb_(_fbb) { ++ start_ = fbb_.StartTable(); ++ } ++ ::flatbuffers::Offset<BinBundleCache> Finish() { ++ const auto end = fbb_.EndTable(start_); ++ auto o = ::flatbuffers::Offset<BinBundleCache>(end); ++ fbb_.Required(o, BinBundleCache::VT_FELT_MATCHING_POLYNS); ++ fbb_.Required(o, BinBundleCache::VT_BATCHED_MATCHING_POLYN); ++ return o; ++ } ++}; ++ ++inline ::flatbuffers::Offset<BinBundleCache> CreateBinBundleCache( ++ ::flatbuffers::FlatBufferBuilder &_fbb, ++ ::flatbuffers::Offset<apsi::fbs::FEltMatrix> felt_matching_polyns = 0, ++ ::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn> batched_matching_polyn = 0, ++ ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>>> felt_interp_polyns = 0, ++ ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn>>> batched_interp_polyns = 0) { ++ BinBundleCacheBuilder builder_(_fbb); ++ builder_.add_batched_interp_polyns(batched_interp_polyns); ++ builder_.add_felt_interp_polyns(felt_interp_polyns); ++ builder_.add_batched_matching_polyn(batched_matching_polyn); ++ builder_.add_felt_matching_polyns(felt_matching_polyns); ++ return builder_.Finish(); ++} ++ ++inline ::flatbuffers::Offset<BinBundleCache> CreateBinBundleCacheDirect( ++ ::flatbuffers::FlatBufferBuilder &_fbb, ++ ::flatbuffers::Offset<apsi::fbs::FEltMatrix> felt_matching_polyns = 0, ++ ::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn> batched_matching_polyn = 0, ++ const std::vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>> *felt_interp_polyns = nullptr, ++ const std::vector<::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn>> *batched_interp_polyns = nullptr) { ++ auto felt_interp_polyns__ = felt_interp_polyns ? _fbb.CreateVector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>>(*felt_interp_polyns) : 0; ++ auto batched_interp_polyns__ = batched_interp_polyns ? _fbb.CreateVector<::flatbuffers::Offset<apsi::fbs::BatchedPlaintextPolyn>>(*batched_interp_polyns) : 0; ++ return apsi::fbs::CreateBinBundleCache( ++ _fbb, ++ felt_matching_polyns, ++ batched_matching_polyn, ++ felt_interp_polyns__, ++ batched_interp_polyns__); ++} ++ ++struct BinBundle FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { ++ typedef BinBundleBuilder Builder; ++ enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { ++ VT_BUNDLE_IDX = 4, ++ VT_MOD = 6, ++ VT_ITEM_BINS = 8, ++ VT_LABEL_BINS = 10, ++ VT_CACHE = 12, ++ VT_STRIPPED = 14 ++ }; ++ uint32_t bundle_idx() const { ++ return GetField<uint32_t>(VT_BUNDLE_IDX, 0); ++ } ++ uint64_t mod() const { ++ return GetField<uint64_t>(VT_MOD, 0); ++ } ++ const apsi::fbs::FEltMatrix *item_bins() const { ++ return GetPointer<const apsi::fbs::FEltMatrix *>(VT_ITEM_BINS); ++ } ++ const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>> *label_bins() const { ++ return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>> *>(VT_LABEL_BINS); ++ } ++ const apsi::fbs::BinBundleCache *cache() const { ++ return GetPointer<const apsi::fbs::BinBundleCache *>(VT_CACHE); ++ } ++ bool stripped() const { ++ return GetField<uint8_t>(VT_STRIPPED, 0) != 0; ++ } ++ bool Verify(::flatbuffers::Verifier &verifier) const { ++ return VerifyTableStart(verifier) && ++ VerifyField<uint32_t>(verifier, VT_BUNDLE_IDX, 4) && ++ VerifyField<uint64_t>(verifier, VT_MOD, 8) && ++ VerifyOffsetRequired(verifier, VT_ITEM_BINS) && ++ verifier.VerifyTable(item_bins()) && ++ VerifyOffset(verifier, VT_LABEL_BINS) && ++ verifier.VerifyVector(label_bins()) && ++ verifier.VerifyVectorOfTables(label_bins()) && ++ VerifyOffset(verifier, VT_CACHE) && ++ verifier.VerifyTable(cache()) && ++ VerifyField<uint8_t>(verifier, VT_STRIPPED, 1) && ++ verifier.EndTable(); ++ } ++}; ++ ++struct BinBundleBuilder { ++ typedef BinBundle Table; ++ ::flatbuffers::FlatBufferBuilder &fbb_; ++ ::flatbuffers::uoffset_t start_; ++ void add_bundle_idx(uint32_t bundle_idx) { ++ fbb_.AddElement<uint32_t>(BinBundle::VT_BUNDLE_IDX, bundle_idx, 0); ++ } ++ void add_mod(uint64_t mod) { ++ fbb_.AddElement<uint64_t>(BinBundle::VT_MOD, mod, 0); ++ } ++ void add_item_bins(::flatbuffers::Offset<apsi::fbs::FEltMatrix> item_bins) { ++ fbb_.AddOffset(BinBundle::VT_ITEM_BINS, item_bins); ++ } ++ void add_label_bins(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>>> label_bins) { ++ fbb_.AddOffset(BinBundle::VT_LABEL_BINS, label_bins); ++ } ++ void add_cache(::flatbuffers::Offset<apsi::fbs::BinBundleCache> cache) { ++ fbb_.AddOffset(BinBundle::VT_CACHE, cache); ++ } ++ void add_stripped(bool stripped) { ++ fbb_.AddElement<uint8_t>(BinBundle::VT_STRIPPED, static_cast<uint8_t>(stripped), 0); ++ } ++ explicit BinBundleBuilder(::flatbuffers::FlatBufferBuilder &_fbb) ++ : fbb_(_fbb) { ++ start_ = fbb_.StartTable(); ++ } ++ ::flatbuffers::Offset<BinBundle> Finish() { ++ const auto end = fbb_.EndTable(start_); ++ auto o = ::flatbuffers::Offset<BinBundle>(end); ++ fbb_.Required(o, BinBundle::VT_ITEM_BINS); ++ return o; ++ } ++}; ++ ++inline ::flatbuffers::Offset<BinBundle> CreateBinBundle( ++ ::flatbuffers::FlatBufferBuilder &_fbb, ++ uint32_t bundle_idx = 0, ++ uint64_t mod = 0, ++ ::flatbuffers::Offset<apsi::fbs::FEltMatrix> item_bins = 0, ++ ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>>> label_bins = 0, ++ ::flatbuffers::Offset<apsi::fbs::BinBundleCache> cache = 0, ++ bool stripped = false) { ++ BinBundleBuilder builder_(_fbb); ++ builder_.add_mod(mod); ++ builder_.add_cache(cache); ++ builder_.add_label_bins(label_bins); ++ builder_.add_item_bins(item_bins); ++ builder_.add_bundle_idx(bundle_idx); ++ builder_.add_stripped(stripped); ++ return builder_.Finish(); ++} ++ ++inline ::flatbuffers::Offset<BinBundle> CreateBinBundleDirect( ++ ::flatbuffers::FlatBufferBuilder &_fbb, ++ uint32_t bundle_idx = 0, ++ uint64_t mod = 0, ++ ::flatbuffers::Offset<apsi::fbs::FEltMatrix> item_bins = 0, ++ const std::vector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>> *label_bins = nullptr, ++ ::flatbuffers::Offset<apsi::fbs::BinBundleCache> cache = 0, ++ bool stripped = false) { ++ auto label_bins__ = label_bins ? _fbb.CreateVector<::flatbuffers::Offset<apsi::fbs::FEltMatrix>>(*label_bins) : 0; ++ return apsi::fbs::CreateBinBundle( ++ _fbb, ++ bundle_idx, ++ mod, ++ item_bins, ++ label_bins__, ++ cache, ++ stripped); ++} ++ ++inline const apsi::fbs::BinBundle *GetBinBundle(const void *buf) { ++ return ::flatbuffers::GetRoot<apsi::fbs::BinBundle>(buf); ++} ++ ++inline const apsi::fbs::BinBundle *GetSizePrefixedBinBundle(const void *buf) { ++ return ::flatbuffers::GetSizePrefixedRoot<apsi::fbs::BinBundle>(buf); ++} ++ ++inline bool VerifyBinBundleBuffer( ++ ::flatbuffers::Verifier &verifier) { ++ return verifier.VerifyBuffer<apsi::fbs::BinBundle>(nullptr); ++} ++ ++inline bool VerifySizePrefixedBinBundleBuffer( ++ ::flatbuffers::Verifier &verifier) { ++ return verifier.VerifySizePrefixedBuffer<apsi::fbs::BinBundle>(nullptr); ++} ++ ++inline void FinishBinBundleBuffer( ++ ::flatbuffers::FlatBufferBuilder &fbb, ++ ::flatbuffers::Offset<apsi::fbs::BinBundle> root) { ++ fbb.Finish(root); ++} ++ ++inline void FinishSizePrefixedBinBundleBuffer( ++ ::flatbuffers::FlatBufferBuilder &fbb, ++ ::flatbuffers::Offset<apsi::fbs::BinBundle> root) { ++ fbb.FinishSizePrefixed(root); ++} ++ ++} // namespace fbs ++} // namespace apsi ++ ++#endif // FLATBUFFERS_GENERATED_BINBUNDLE_APSI_FBS_H_ +diff --git b/sender/apsi/util/cuckoo_filter_generated.h b/sender/apsi/util/cuckoo_filter_generated.h +new file mode 100644 +index 0000000..2d2b2b7 +--- /dev/null ++++ b/sender/apsi/util/cuckoo_filter_generated.h +@@ -0,0 +1,263 @@ ++// automatically generated by the FlatBuffers compiler, do not modify ++ ++ ++#ifndef FLATBUFFERS_GENERATED_CUCKOOFILTER_APSI_FBS_H_ ++#define FLATBUFFERS_GENERATED_CUCKOOFILTER_APSI_FBS_H_ ++ ++#include "flatbuffers/flatbuffers.h" ++ ++// Ensure the included flatbuffers.h is the same version as when this file was ++// generated, otherwise it may not be compatible. ++static_assert(FLATBUFFERS_VERSION_MAJOR == 23 && ++ FLATBUFFERS_VERSION_MINOR == 3 && ++ FLATBUFFERS_VERSION_REVISION == 3, ++ "Non-compatible flatbuffers version included"); ++ ++namespace apsi { ++namespace fbs { ++ ++struct CuckooFilterOverflowCache; ++struct CuckooFilterOverflowCacheBuilder; ++ ++struct CuckooFilterTable; ++struct CuckooFilterTableBuilder; ++ ++struct CuckooFilter; ++struct CuckooFilterBuilder; ++ ++struct CuckooFilterOverflowCache FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { ++ typedef CuckooFilterOverflowCacheBuilder Builder; ++ enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { ++ VT_INDEX = 4, ++ VT_TAG = 6, ++ VT_USED = 8 ++ }; ++ uint64_t index() const { ++ return GetField<uint64_t>(VT_INDEX, 0); ++ } ++ uint64_t tag() const { ++ return GetField<uint64_t>(VT_TAG, 0); ++ } ++ bool used() const { ++ return GetField<uint8_t>(VT_USED, 0) != 0; ++ } ++ bool Verify(::flatbuffers::Verifier &verifier) const { ++ return VerifyTableStart(verifier) && ++ VerifyField<uint64_t>(verifier, VT_INDEX, 8) && ++ VerifyField<uint64_t>(verifier, VT_TAG, 8) && ++ VerifyField<uint8_t>(verifier, VT_USED, 1) && ++ verifier.EndTable(); ++ } ++}; ++ ++struct CuckooFilterOverflowCacheBuilder { ++ typedef CuckooFilterOverflowCache Table; ++ ::flatbuffers::FlatBufferBuilder &fbb_; ++ ::flatbuffers::uoffset_t start_; ++ void add_index(uint64_t index) { ++ fbb_.AddElement<uint64_t>(CuckooFilterOverflowCache::VT_INDEX, index, 0); ++ } ++ void add_tag(uint64_t tag) { ++ fbb_.AddElement<uint64_t>(CuckooFilterOverflowCache::VT_TAG, tag, 0); ++ } ++ void add_used(bool used) { ++ fbb_.AddElement<uint8_t>(CuckooFilterOverflowCache::VT_USED, static_cast<uint8_t>(used), 0); ++ } ++ explicit CuckooFilterOverflowCacheBuilder(::flatbuffers::FlatBufferBuilder &_fbb) ++ : fbb_(_fbb) { ++ start_ = fbb_.StartTable(); ++ } ++ ::flatbuffers::Offset<CuckooFilterOverflowCache> Finish() { ++ const auto end = fbb_.EndTable(start_); ++ auto o = ::flatbuffers::Offset<CuckooFilterOverflowCache>(end); ++ return o; ++ } ++}; ++ ++inline ::flatbuffers::Offset<CuckooFilterOverflowCache> CreateCuckooFilterOverflowCache( ++ ::flatbuffers::FlatBufferBuilder &_fbb, ++ uint64_t index = 0, ++ uint64_t tag = 0, ++ bool used = false) { ++ CuckooFilterOverflowCacheBuilder builder_(_fbb); ++ builder_.add_tag(tag); ++ builder_.add_index(index); ++ builder_.add_used(used); ++ return builder_.Finish(); ++} ++ ++struct CuckooFilterTable FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { ++ typedef CuckooFilterTableBuilder Builder; ++ enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { ++ VT_NUM_BUCKETS = 4, ++ VT_BITS_PER_TAG = 6, ++ VT_TABLE = 8 ++ }; ++ uint64_t num_buckets() const { ++ return GetField<uint64_t>(VT_NUM_BUCKETS, 0); ++ } ++ uint64_t bits_per_tag() const { ++ return GetField<uint64_t>(VT_BITS_PER_TAG, 0); ++ } ++ const ::flatbuffers::Vector<uint64_t> *table() const { ++ return GetPointer<const ::flatbuffers::Vector<uint64_t> *>(VT_TABLE); ++ } ++ bool Verify(::flatbuffers::Verifier &verifier) const { ++ return VerifyTableStart(verifier) && ++ VerifyField<uint64_t>(verifier, VT_NUM_BUCKETS, 8) && ++ VerifyField<uint64_t>(verifier, VT_BITS_PER_TAG, 8) && ++ VerifyOffsetRequired(verifier, VT_TABLE) && ++ verifier.VerifyVector(table()) && ++ verifier.EndTable(); ++ } ++}; ++ ++struct CuckooFilterTableBuilder { ++ typedef CuckooFilterTable Table; ++ ::flatbuffers::FlatBufferBuilder &fbb_; ++ ::flatbuffers::uoffset_t start_; ++ void add_num_buckets(uint64_t num_buckets) { ++ fbb_.AddElement<uint64_t>(CuckooFilterTable::VT_NUM_BUCKETS, num_buckets, 0); ++ } ++ void add_bits_per_tag(uint64_t bits_per_tag) { ++ fbb_.AddElement<uint64_t>(CuckooFilterTable::VT_BITS_PER_TAG, bits_per_tag, 0); ++ } ++ void add_table(::flatbuffers::Offset<::flatbuffers::Vector<uint64_t>> table) { ++ fbb_.AddOffset(CuckooFilterTable::VT_TABLE, table); ++ } ++ explicit CuckooFilterTableBuilder(::flatbuffers::FlatBufferBuilder &_fbb) ++ : fbb_(_fbb) { ++ start_ = fbb_.StartTable(); ++ } ++ ::flatbuffers::Offset<CuckooFilterTable> Finish() { ++ const auto end = fbb_.EndTable(start_); ++ auto o = ::flatbuffers::Offset<CuckooFilterTable>(end); ++ fbb_.Required(o, CuckooFilterTable::VT_TABLE); ++ return o; ++ } ++}; ++ ++inline ::flatbuffers::Offset<CuckooFilterTable> CreateCuckooFilterTable( ++ ::flatbuffers::FlatBufferBuilder &_fbb, ++ uint64_t num_buckets = 0, ++ uint64_t bits_per_tag = 0, ++ ::flatbuffers::Offset<::flatbuffers::Vector<uint64_t>> table = 0) { ++ CuckooFilterTableBuilder builder_(_fbb); ++ builder_.add_bits_per_tag(bits_per_tag); ++ builder_.add_num_buckets(num_buckets); ++ builder_.add_table(table); ++ return builder_.Finish(); ++} ++ ++inline ::flatbuffers::Offset<CuckooFilterTable> CreateCuckooFilterTableDirect( ++ ::flatbuffers::FlatBufferBuilder &_fbb, ++ uint64_t num_buckets = 0, ++ uint64_t bits_per_tag = 0, ++ const std::vector<uint64_t> *table = nullptr) { ++ auto table__ = table ? _fbb.CreateVector<uint64_t>(*table) : 0; ++ return apsi::fbs::CreateCuckooFilterTable( ++ _fbb, ++ num_buckets, ++ bits_per_tag, ++ table__); ++} ++ ++struct CuckooFilter FLATBUFFERS_FINAL_CLASS : private ::flatbuffers::Table { ++ typedef CuckooFilterBuilder Builder; ++ enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { ++ VT_TABLE = 4, ++ VT_NUM_ITEMS = 6, ++ VT_OVERFLOW = 8 ++ }; ++ const apsi::fbs::CuckooFilterTable *table() const { ++ return GetPointer<const apsi::fbs::CuckooFilterTable *>(VT_TABLE); ++ } ++ uint64_t num_items() const { ++ return GetField<uint64_t>(VT_NUM_ITEMS, 0); ++ } ++ const apsi::fbs::CuckooFilterOverflowCache *overflow() const { ++ return GetPointer<const apsi::fbs::CuckooFilterOverflowCache *>(VT_OVERFLOW); ++ } ++ bool Verify(::flatbuffers::Verifier &verifier) const { ++ return VerifyTableStart(verifier) && ++ VerifyOffsetRequired(verifier, VT_TABLE) && ++ verifier.VerifyTable(table()) && ++ VerifyField<uint64_t>(verifier, VT_NUM_ITEMS, 8) && ++ VerifyOffsetRequired(verifier, VT_OVERFLOW) && ++ verifier.VerifyTable(overflow()) && ++ verifier.EndTable(); ++ } ++}; ++ ++struct CuckooFilterBuilder { ++ typedef CuckooFilter Table; ++ ::flatbuffers::FlatBufferBuilder &fbb_; ++ ::flatbuffers::uoffset_t start_; ++ void add_table(::flatbuffers::Offset<apsi::fbs::CuckooFilterTable> table) { ++ fbb_.AddOffset(CuckooFilter::VT_TABLE, table); ++ } ++ void add_num_items(uint64_t num_items) { ++ fbb_.AddElement<uint64_t>(CuckooFilter::VT_NUM_ITEMS, num_items, 0); ++ } ++ void add_overflow(::flatbuffers::Offset<apsi::fbs::CuckooFilterOverflowCache> overflow) { ++ fbb_.AddOffset(CuckooFilter::VT_OVERFLOW, overflow); ++ } ++ explicit CuckooFilterBuilder(::flatbuffers::FlatBufferBuilder &_fbb) ++ : fbb_(_fbb) { ++ start_ = fbb_.StartTable(); ++ } ++ ::flatbuffers::Offset<CuckooFilter> Finish() { ++ const auto end = fbb_.EndTable(start_); ++ auto o = ::flatbuffers::Offset<CuckooFilter>(end); ++ fbb_.Required(o, CuckooFilter::VT_TABLE); ++ fbb_.Required(o, CuckooFilter::VT_OVERFLOW); ++ return o; ++ } ++}; ++ ++inline ::flatbuffers::Offset<CuckooFilter> CreateCuckooFilter( ++ ::flatbuffers::FlatBufferBuilder &_fbb, ++ ::flatbuffers::Offset<apsi::fbs::CuckooFilterTable> table = 0, ++ uint64_t num_items = 0, ++ ::flatbuffers::Offset<apsi::fbs::CuckooFilterOverflowCache> overflow = 0) { ++ CuckooFilterBuilder builder_(_fbb); ++ builder_.add_num_items(num_items); ++ builder_.add_overflow(overflow); ++ builder_.add_table(table); ++ return builder_.Finish(); ++} ++ ++inline const apsi::fbs::CuckooFilter *GetCuckooFilter(const void *buf) { ++ return ::flatbuffers::GetRoot<apsi::fbs::CuckooFilter>(buf); ++} ++ ++inline const apsi::fbs::CuckooFilter *GetSizePrefixedCuckooFilter(const void *buf) { ++ return ::flatbuffers::GetSizePrefixedRoot<apsi::fbs::CuckooFilter>(buf); ++} ++ ++inline bool VerifyCuckooFilterBuffer( ++ ::flatbuffers::Verifier &verifier) { ++ return verifier.VerifyBuffer<apsi::fbs::CuckooFilter>(nullptr); ++} ++ ++inline bool VerifySizePrefixedCuckooFilterBuffer( ++ ::flatbuffers::Verifier &verifier) { ++ return verifier.VerifySizePrefixedBuffer<apsi::fbs::CuckooFilter>(nullptr); ++} ++ ++inline void FinishCuckooFilterBuffer( ++ ::flatbuffers::FlatBufferBuilder &fbb, ++ ::flatbuffers::Offset<apsi::fbs::CuckooFilter> root) { ++ fbb.Finish(root); ++} ++ ++inline void FinishSizePrefixedCuckooFilterBuffer( ++ ::flatbuffers::FlatBufferBuilder &fbb, ++ ::flatbuffers::Offset<apsi::fbs::CuckooFilter> root) { ++ fbb.FinishSizePrefixed(root); ++} ++ ++} // namespace fbs ++} // namespace apsi ++ ++#endif // FLATBUFFERS_GENERATED_CUCKOOFILTER_APSI_FBS_H_ diff --git a/bazel/patches/apsi.patch b/bazel/patches/apsi.patch new file mode 100644 index 00000000..16a3f28f --- /dev/null +++ b/bazel/patches/apsi.patch @@ -0,0 +1,338 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 78d54a6..07df321 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -140,24 +140,6 @@ else() + message(STATUS "Microsoft Kuku: found") + endif() + +-# Flatbuffers +-find_package(Flatbuffers REQUIRED) +-if(NOT Flatbuffers_FOUND) +- message(FATAL_ERROR "Flatbuffers: not found") +-else() +- message(STATUS "Flatbuffers: found") +- get_target_property(FLATBUFFERS_FLATC_PATH flatbuffers::flatc IMPORTED_LOCATION_RELEASE) +- message(STATUS "flatc path: ${FLATBUFFERS_FLATC_PATH}") +- include(CompileSchemaCXX) +-endif() +- +-# jsoncpp: for parameter configuration +-find_package(jsoncpp REQUIRED) +-if (NOT jsoncpp_FOUND) +- message(FATAL_ERROR "jsoncpp: not found") +-else() +- message(STATUS "jsoncpp: found") +-endif() + + # [Option] APSI_USE_LOG4CPLUS (default: ON) + set(APSI_USE_LOG4CPLUS_OPTION_STR "Use Log4cplus for logging") +@@ -263,9 +245,7 @@ apsi_install_target(apsi APSITargets) + + target_link_libraries(apsi + PUBLIC SEAL::seal +- PUBLIC Kuku::kuku +- PUBLIC flatbuffers::flatbuffers +- PUBLIC jsoncpp_static) ++ PUBLIC Kuku::kuku) + if(APSI_USE_LOG4CPLUS) + target_link_libraries(apsi PUBLIC log4cplus::log4cplus) + endif() +diff --git a/common/apsi/CMakeLists.txt b/common/apsi/CMakeLists.txt +index a65bbfe..ee3b975 100644 +--- a/common/apsi/CMakeLists.txt ++++ b/common/apsi/CMakeLists.txt +@@ -29,7 +29,5 @@ install( + ) + + add_subdirectory(fourq) +-add_subdirectory(network) +-add_subdirectory(oprf) + add_subdirectory(util) + set(APSI_SOURCE_FILES ${APSI_SOURCE_FILES} PARENT_SCOPE) +diff --git a/common/apsi/fourq/CMakeLists.txt b/common/apsi/fourq/CMakeLists.txt +index 3b15780..5085038 100644 +--- a/common/apsi/fourq/CMakeLists.txt ++++ b/common/apsi/fourq/CMakeLists.txt +@@ -8,8 +8,21 @@ set(APSI_SOURCE_FILES ${APSI_SOURCE_FILES} + ${CMAKE_CURRENT_LIST_DIR}/eccp2_no_endo.c + ${CMAKE_CURRENT_LIST_DIR}/eccp2.c + ${CMAKE_CURRENT_LIST_DIR}/hash_to_curve.c ++ ${CMAKE_CURRENT_LIST_DIR}/kex.c + ${CMAKE_CURRENT_LIST_DIR}/random.c + ) ++# Add header files for installation ++install( ++ FILES ++ ${CMAKE_CURRENT_LIST_DIR}/FourQ_api.h ++ ${CMAKE_CURRENT_LIST_DIR}/FourQ_internal.h ++ ${CMAKE_CURRENT_LIST_DIR}/FourQ_params.h ++ ${CMAKE_CURRENT_LIST_DIR}/FourQ_tables.h ++ ${CMAKE_CURRENT_LIST_DIR}/FourQ.h ++ ${CMAKE_CURRENT_LIST_DIR}/table_lookup.h ++ DESTINATION ++ ${APSI_INCLUDES_INSTALL_DIR}/apsi/fourq ++) + + if(APSI_FOURQ_AMD64) + add_subdirectory(amd64) +diff --git a/common/apsi/fourq/kex.c b/common/apsi/fourq/kex.c +new file mode 100644 +index 0000000..5c37c60 +--- /dev/null ++++ b/common/apsi/fourq/kex.c +@@ -0,0 +1,181 @@ ++/******************************************************************************** ++* FourQlib: a high-performance crypto library based on the elliptic curve FourQ ++* ++* Copyright (c) Microsoft Corporation. All rights reserved. ++* ++* Abstract: Diffie-Hellman key exchange based on FourQ ++* option 1: co-factor ecdh using compressed 32-byte public keys, ++* (see https://datatracker.ietf.org/doc/draft-ladd-cfrg-4q/). ++* option 2: co-factor ecdh using uncompressed, 64-byte public keys. ++*********************************************************************************/ ++ ++#include "apsi/fourq/FourQ_internal.h" ++#include "apsi/fourq/FourQ_params.h" ++#include "apsi/fourq/random.h" ++#include <string.h> ++ ++ ++static __inline bool is_neutral_point(point_t P) ++{ // Is P the neutral point (0,1)? ++ // SECURITY NOTE: this function does not run in constant time (input point P is assumed to be public). ++ ++ if (is_zero_ct((digit_t*)P->x, 2*NWORDS_FIELD) && is_zero_ct(&((digit_t*)P->y)[1], 2*NWORDS_FIELD-1) && is_digit_zero_ct(P->y[0][0] - 1)) { ++ return true; ++ } ++ return false; ++} ++ ++ ++/*************** ECDH USING COMPRESSED, 32-BYTE PUBLIC KEYS ***************/ ++ ++ECCRYPTO_STATUS CompressedPublicKeyGeneration(const unsigned char* SecretKey, unsigned char* PublicKey) ++{ // Compressed public key generation for key exchange ++ // It produces a public key PublicKey, which is the encoding of P = SecretKey*G (G is the generator). ++ // Input: 32-byte SecretKey ++ // Output: 32-byte PublicKey ++ point_t P; ++ ++ ecc_mul_fixed((digit_t*)SecretKey, P); // Compute public key ++ encode(P, PublicKey); // Encode public key ++ ++ return ECCRYPTO_SUCCESS; ++} ++ ++ ++ECCRYPTO_STATUS CompressedKeyGeneration(unsigned char* SecretKey, unsigned char* PublicKey) ++{ // Keypair generation for key exchange. Public key is compressed to 32 bytes ++ // It produces a private key SecretKey and a public key PublicKey, which is the encoding of P = SecretKey*G (G is the generator). ++ // Outputs: 32-byte SecretKey and 32-byte PublicKey ++ ECCRYPTO_STATUS Status = ECCRYPTO_ERROR_UNKNOWN; ++ ++ Status = RandomBytesFunction(SecretKey, 32); ++ if (Status != ECCRYPTO_SUCCESS) { ++ goto cleanup; ++ } ++ ++ Status = CompressedPublicKeyGeneration(SecretKey, PublicKey); ++ if (Status != ECCRYPTO_SUCCESS) { ++ goto cleanup; ++ } ++ ++ return ECCRYPTO_SUCCESS; ++ ++cleanup: ++ clear_words((unsigned int*)SecretKey, 256/(sizeof(unsigned int)*8)); ++ clear_words((unsigned int*)PublicKey, 256/(sizeof(unsigned int)*8)); ++ ++ return Status; ++} ++ ++ ++ECCRYPTO_STATUS CompressedSecretAgreement(const unsigned char* SecretKey, const unsigned char* PublicKey, unsigned char* SharedSecret) ++{ // Secret agreement computation for key exchange using a compressed, 32-byte public key ++ // The output is the y-coordinate of SecretKey*A, where A is the decoding of the public key PublicKey. ++ // Inputs: 32-byte SecretKey and 32-byte PublicKey ++ // Output: 32-byte SharedSecret ++ point_t A; ++ ECCRYPTO_STATUS Status = ECCRYPTO_ERROR_UNKNOWN; ++ ++ if ((PublicKey[15] & 0x80) != 0) { // Is bit128(PublicKey) = 0? ++ Status = ECCRYPTO_ERROR_INVALID_PARAMETER; ++ goto cleanup; ++ } ++ ++ Status = decode(PublicKey, A); // Also verifies that A is on the curve. If it is not, it fails ++ if (Status != ECCRYPTO_SUCCESS) { ++ goto cleanup; ++ } ++ ++ Status = ecc_mul(A, (digit_t*)SecretKey, A, true); ++ if (Status != ECCRYPTO_SUCCESS) { ++ goto cleanup; ++ } ++ ++ if (is_neutral_point(A)) { // Is output = neutral point (0,1)? ++ Status = ECCRYPTO_ERROR_SHARED_KEY; ++ goto cleanup; ++ } ++ ++ memmove(SharedSecret, (unsigned char*)A->y, 32); ++ ++ return ECCRYPTO_SUCCESS; ++ ++cleanup: ++ clear_words((unsigned int*)SharedSecret, 256/(sizeof(unsigned int)*8)); ++ ++ return Status; ++} ++ ++ ++/*************** ECDH USING UNCOMPRESSED PUBLIC KEYS ***************/ ++ ++ECCRYPTO_STATUS PublicKeyGeneration(const unsigned char* SecretKey, unsigned char* PublicKey) ++{ // Public key generation for key exchange ++ // It produces the public key PublicKey = SecretKey*G, where G is the generator. ++ // Input: 32-byte SecretKey ++ // Output: 64-byte PublicKey ++ ++ ecc_mul_fixed((digit_t*)SecretKey, (point_affine*)PublicKey); // Compute public key ++ ++ return ECCRYPTO_SUCCESS; ++} ++ ++ ++ECCRYPTO_STATUS KeyGeneration(unsigned char* SecretKey, unsigned char* PublicKey) ++{ // Keypair generation for key exchange ++ // It produces a private key SecretKey and computes the public key PublicKey = SecretKey*G, where G is the generator. ++ // Outputs: 32-byte SecretKey and 64-byte PublicKey ++ ECCRYPTO_STATUS Status = ECCRYPTO_ERROR_UNKNOWN; ++ ++ Status = RandomBytesFunction(SecretKey, 32); ++ if (Status != ECCRYPTO_SUCCESS) { ++ goto cleanup; ++ } ++ ++ Status = PublicKeyGeneration(SecretKey, PublicKey); ++ if (Status != ECCRYPTO_SUCCESS) { ++ goto cleanup; ++ } ++ ++ return ECCRYPTO_SUCCESS; ++ ++cleanup: ++ clear_words((unsigned int*)SecretKey, 256/(sizeof(unsigned int)*8)); ++ clear_words((unsigned int*)PublicKey, 512/(sizeof(unsigned int)*8)); ++ ++ return Status; ++} ++ ++ ++ECCRYPTO_STATUS SecretAgreement(const unsigned char* SecretKey, const unsigned char* PublicKey, unsigned char* SharedSecret) ++{ // Secret agreement computation for key exchange ++ // The output is the y-coordinate of SecretKey*PublicKey. ++ // Inputs: 32-byte SecretKey and 64-byte PublicKey ++ // Output: 32-byte SharedSecret ++ point_t A; ++ ECCRYPTO_STATUS Status = ECCRYPTO_ERROR_UNKNOWN; ++ ++ if (((PublicKey[15] & 0x80) != 0) || ((PublicKey[31] & 0x80) != 0) || ((PublicKey[47] & 0x80) != 0) || ((PublicKey[63] & 0x80) != 0)) { // Are PublicKey_x[i] and PublicKey_y[i] < 2^127? ++ Status = ECCRYPTO_ERROR_INVALID_PARAMETER; ++ goto cleanup; ++ } ++ ++ Status = ecc_mul((point_affine*)PublicKey, (digit_t*)SecretKey, A, true); // Also verifies that PublicKey is a point on the curve. If it is not, it fails ++ if (Status != ECCRYPTO_SUCCESS) { ++ goto cleanup; ++ } ++ ++ if (is_neutral_point(A)) { // Is output = neutral point (0,1)? ++ Status = ECCRYPTO_ERROR_SHARED_KEY; ++ goto cleanup; ++ } ++ ++ memmove(SharedSecret, (unsigned char*)A->y, 32); ++ ++ return ECCRYPTO_SUCCESS; ++ ++cleanup: ++ clear_words((unsigned int*)SharedSecret, 256/(sizeof(unsigned int)*8)); ++ ++ return Status; ++} +diff --git a/receiver/apsi/CMakeLists.txt b/receiver/apsi/CMakeLists.txt +index afce298..7757b68 100644 +--- a/receiver/apsi/CMakeLists.txt ++++ b/receiver/apsi/CMakeLists.txt +@@ -4,7 +4,6 @@ + # Source files in this directory + set(APSI_SOURCE_FILES ${APSI_SOURCE_FILES} + ${CMAKE_CURRENT_LIST_DIR}/plaintext_powers.cpp +- ${CMAKE_CURRENT_LIST_DIR}/receiver.cpp + ) + + # Add header files for installation +@@ -12,6 +11,7 @@ install( + FILES + ${CMAKE_CURRENT_LIST_DIR}/itt.h + ${CMAKE_CURRENT_LIST_DIR}/match_record.h ++ ${CMAKE_CURRENT_LIST_DIR}/plaintext_powers.h + ${CMAKE_CURRENT_LIST_DIR}/receiver.h + DESTINATION + ${APSI_INCLUDES_INSTALL_DIR}/apsi +diff --git a/receiver/apsi/itt.h b/receiver/apsi/itt.h +index e683045..105e14c 100644 +--- a/receiver/apsi/itt.h ++++ b/receiver/apsi/itt.h +@@ -20,7 +20,15 @@ namespace apsi { + create this query. If the given table index was not populated, i.e., there is no + translation, then this function returns the number of items encoded by this query. + */ +- std::size_t find_item_idx(std::size_t table_idx) const noexcept; ++ std::size_t find_item_idx(std::size_t table_idx) const noexcept ++ { ++ auto item_idx = table_idx_to_item_idx_.find(table_idx); ++ if (item_idx == table_idx_to_item_idx_.cend()) { ++ return item_count(); ++ } ++ ++ return item_idx->second; ++ } + + /** + Returns the number of items encoded by this index translation table. +@@ -30,7 +38,7 @@ namespace apsi { + return item_count_; + } + +- private: ++ //private: + IndexTranslationTable() = default; + + std::unordered_map<std::size_t, std::size_t> table_idx_to_item_idx_; +diff --git a/sender/apsi/CMakeLists.txt b/sender/apsi/CMakeLists.txt +index fd245d7..99e4228 100644 +--- a/sender/apsi/CMakeLists.txt ++++ b/sender/apsi/CMakeLists.txt +@@ -4,18 +4,12 @@ + # Source files in this directory + set(APSI_SOURCE_FILES ${APSI_SOURCE_FILES} + ${CMAKE_CURRENT_LIST_DIR}/bin_bundle.cpp +- ${CMAKE_CURRENT_LIST_DIR}/query.cpp +- ${CMAKE_CURRENT_LIST_DIR}/sender.cpp +- ${CMAKE_CURRENT_LIST_DIR}/sender_db.cpp + ) + + # Add header files for installation + install( + FILES + ${CMAKE_CURRENT_LIST_DIR}/bin_bundle.h +- ${CMAKE_CURRENT_LIST_DIR}/query.h +- ${CMAKE_CURRENT_LIST_DIR}/sender.h +- ${CMAKE_CURRENT_LIST_DIR}/sender_db.h + DESTINATION + ${APSI_INCLUDES_INSTALL_DIR}/apsi + ) diff --git a/bazel/patches/apsi_bin_bundle.patch b/bazel/patches/apsi_bin_bundle.patch new file mode 100644 index 00000000..e9cccca2 --- /dev/null +++ b/bazel/patches/apsi_bin_bundle.patch @@ -0,0 +1,93 @@ +diff --git a/sender/apsi/bin_bundle.cpp b/sender/apsi/bin_bundle.cpp +index bb36ef2..51c32c0 100644 +--- a/sender/apsi/bin_bundle.cpp ++++ b/sender/apsi/bin_bundle.cpp +@@ -1192,24 +1192,25 @@ namespace apsi { + // The loaded label size must match the label size for this BinBundle + size_t label_size = get_label_size(); + +- for (size_t bin_idx = 0; !stripped_ && (bin_idx < num_bins); bin_idx++) { +- auto &item_bin = *item_bins[static_cast<flatbuffers::uoffset_t>(bin_idx)]->felts(); ++ std::vector<std::future<void>> futures; + +- // Check that the sizes of the bins are at most max_bin_size_ +- if (item_bin.size() > max_bin_size_) { +- APSI_LOG_ERROR( +- "The loaded BinBundle has an item bin of size " +- << item_bin.size() << " but this BinBundle has a maximum bin size " +- << max_bin_size_); +- throw runtime_error("failed to load BinBundle"); +- } +- +- // All is good; copy over the item data +- transform( +- item_bin.begin(), +- item_bin.end(), +- back_inserter(item_bins_[bin_idx]), +- [&](auto felt_item) { ++ auto item_bin_proc = [&](size_t begin, size_t end) -> void { ++ for (size_t bin_idx = begin; !stripped_ && (bin_idx < end); bin_idx++) { ++ auto &item_bin = *item_bins[static_cast<flatbuffers::uoffset_t>(bin_idx)]->felts(); ++ if (item_bin.size() > max_bin_size_) { ++ APSI_LOG_ERROR( ++ "The loaded BinBundle has an item bin of size " ++ << item_bin.size() << " but this BinBundle has a maximum bin size " ++ << max_bin_size_); ++ throw runtime_error("failed to load BinBundle"); ++ } ++ ++ // All is good; copy over the item data ++ std::transform( ++ item_bin.begin(), ++ item_bin.end(), ++ std::back_inserter(item_bins_[bin_idx]), ++ [&](auto felt_item) { + #ifdef APSI_DEBUG + if (label_size && + is_present(item_bins_[bin_idx], filters_[bin_idx], felt_item)) { +@@ -1225,6 +1226,16 @@ namespace apsi { + // Return to add the item to item_bins_[bin_idx] + return felt_item; + }); ++ } ++ }; ++ ++ size_t bin_step = std::max<size_t>(1, num_bins/5); ++ for(size_t begin=0; begin<num_bins; begin+=bin_step) { ++ futures.push_back(std::async(item_bin_proc, begin, std::min<size_t>(num_bins,begin+bin_step))); ++ } ++ ++ for (auto &f : futures) { ++ f.get(); + } + + // We are now done with the item data; next check that the label size is correct +@@ -1259,8 +1270,10 @@ namespace apsi { + throw runtime_error("failed to load BinBundle"); + } + +- // Check that each bin has the same size as the corresponding items bin +- for (size_t bin_idx = 0; bin_idx < num_bins; bin_idx++) { ++ futures.clear(); ++ ++ auto label_bin_proc = [&](size_t begin, size_t end) -> void { ++ for (size_t bin_idx = begin; bin_idx < end; bin_idx++) { + size_t item_bin_size = item_bins_[bin_idx].size(); + auto &label_bin = + *label_bins[static_cast<flatbuffers::uoffset_t>(bin_idx)]->felts(); +@@ -1277,6 +1290,16 @@ namespace apsi { + label_bin.begin(), + label_bin.end(), + back_inserter(label_bins_[label_idx][bin_idx])); ++ } ++ }; ++ ++ size_t step = std::max<size_t>(1, num_bins/5); ++ for(size_t begin=0; begin<num_bins; begin+=step) { ++ futures.push_back(std::async(label_bin_proc, begin, std::min<size_t>(num_bins,begin+step))); ++ } ++ ++ for (auto &f : futures) { ++ f.get(); + } + } diff --git a/bazel/patches/emp-ot.patch b/bazel/patches/emp-ot.patch new file mode 100644 index 00000000..779d7010 --- /dev/null +++ b/bazel/patches/emp-ot.patch @@ -0,0 +1,99 @@ +diff --git a/emp-ot/ferret/ferret_cot.hpp b/emp-ot/ferret/ferret_cot.hpp +index 9dc8222..fbd6170 100644 +--- a/emp-ot/ferret/ferret_cot.hpp ++++ b/emp-ot/ferret/ferret_cot.hpp +@@ -28,8 +28,8 @@ FerretCOT<T>::FerretCOT(int party, int threads, T **ios, + template<typename T> + FerretCOT<T>::~FerretCOT() { + if (ot_pre_data != nullptr) { +- if(party == ALICE) write_pre_data128_to_file((void*)ot_pre_data, (__uint128_t)Delta, pre_ot_filename); +- else write_pre_data128_to_file((void*)ot_pre_data, (__uint128_t)0, pre_ot_filename); ++ // if(party == ALICE) write_pre_data128_to_file((void*)ot_pre_data, (__uint128_t)Delta, pre_ot_filename); ++ // else write_pre_data128_to_file((void*)ot_pre_data, (__uint128_t)0, pre_ot_filename); + delete[] ot_pre_data; + } + if (ot_data != nullptr) delete[] ot_data; +@@ -100,7 +100,9 @@ void FerretCOT<T>::setup(std::string pre_file) { + }); + + ot_pre_data = new block[param.n_pre]; +- bool hasfile = file_exists(pre_ot_filename), hasfile2; ++ //bool hasfile = file_exists(pre_ot_filename), hasfile2; ++ bool hasfile = false; ++ bool hasfile2 = false; + if(party == ALICE) { + io->send_data(&hasfile, sizeof(bool)); + io->flush(); + +diff --git a/emp-ot/ferret/mpcot_reg.h b/emp-ot/ferret/mpcot_reg.h +index 6659aa7..6b01601 100644 +--- a/emp-ot/ferret/mpcot_reg.h ++++ b/emp-ot/ferret/mpcot_reg.h +@@ -123,6 +123,10 @@ public: + for(int i = start; i < end; ++i) + exec_f2k_sender(senders[i], ot, sparse_vector+i*leave_n, + ios[threads - 1], i); ++ ++ for (int i = 0; i < threads; i++) ++ ios[i]->flush(); ++ + for (auto & f : fut) f.get(); + } + +@@ -152,7 +156,7 @@ public: + block *ggm_tree_mem, IO *io, int i) { + sender->compute(ggm_tree_mem, Delta_f2k); + sender->template send_f2k<OTPre<IO>>(ot, io, i); +- io->flush(); ++ //io->flush(); + if(is_malicious) + sender->consistency_check_msg_gen(consist_check_VW+i); + } + +diff --git a/emp-ot/ferret/preot.h b/emp-ot/ferret/preot.h +index 0ac7641..a0ae2d3 100644 +--- a/emp-ot/ferret/preot.h ++++ b/emp-ot/ferret/preot.h +@@ -10,10 +10,6 @@ class OTPre { public: + block * pre_data = nullptr; + bool * bits = nullptr; + int n; +- vector<block*> pointers; +- vector<const bool*> choices; +- vector<const block*> pointers0; +- vector<const block*> pointers1; + + CCRH ccrh; + int length, count; + +diff --git a/emp-ot/ferret/twokeyprp.h b/emp-ot/ferret/twokeyprp.h +index fd6236d..c2361a3 100644 +--- a/emp-ot/ferret/twokeyprp.h ++++ b/emp-ot/ferret/twokeyprp.h +@@ -9,8 +9,8 @@ class TwoKeyPRP { public: + emp::AES_KEY aes_key[2]; + + TwoKeyPRP(block seed0, block seed1) { +- AES_set_encrypt_key((const block)seed0, aes_key); +- AES_set_encrypt_key((const block)seed1, &aes_key[1]); ++ AES_set_encrypt_key(seed0, aes_key); ++ AES_set_encrypt_key(seed1, &aes_key[1]); + } + + void node_expand_1to2(block *children, block parent) { + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index fa06fd7..faf9802 100755 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -12,5 +12,8 @@ include_directories(${EMP-TOOL_INCLUDE_DIRS}) + install(FILES cmake/emp-ot-config.cmake DESTINATION cmake/) + install(DIRECTORY emp-ot DESTINATION include/) + +-ENABLE_TESTING() +-ADD_SUBDIRECTORY(test) ++option(ENABLE_TESTS "Enable tests" OFF) ++if (${ENABLE_TESTS}) ++ ENABLE_TESTING() ++ ADD_SUBDIRECTORY(test) ++endif() diff --git a/bazel/patches/emp-tool-cmake.patch b/bazel/patches/emp-tool-cmake.patch new file mode 100644 index 00000000..5f36e80a --- /dev/null +++ b/bazel/patches/emp-tool-cmake.patch @@ -0,0 +1,22 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index d9abb31..4c2c171 100755 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -56,11 +56,14 @@ find_package(OpenSSL REQUIRED) + include_directories(${OPENSSL_INCLUDE_DIR}) + + +-add_library(${NAME} SHARED ${sources}) ++add_library(${NAME} STATIC ${sources}) + + install(DIRECTORY emp-tool DESTINATION include/) + install(DIRECTORY cmake/ DESTINATION cmake/) + install(TARGETS ${NAME} DESTINATION lib) + +-ENABLE_TESTING() +-ADD_SUBDIRECTORY(test) ++option(ENABLE_TESTS "Enable tests" OFF) ++if (${ENABLE_TESTS}) ++ ENABLE_TESTING() ++ ADD_SUBDIRECTORY(test) ++endif() diff --git a/bazel/patches/emp-tool-sse2neon.patch b/bazel/patches/emp-tool-sse2neon.patch new file mode 100644 index 00000000..26b2ed51 --- /dev/null +++ b/bazel/patches/emp-tool-sse2neon.patch @@ -0,0 +1,6507 @@ +diff --git a/emp-tool/utils/sse2neon.h b/emp-tool/utils/sse2neon.h +index d09b9c7..efa63a4 100644 +--- a/emp-tool/utils/sse2neon.h ++++ b/emp-tool/utils/sse2neon.h +@@ -113,7 +113,7 @@ + #ifdef _MSC_VER + #include <intrin.h> + #if (defined(_M_AMD64) || defined(__x86_64__)) || \ +- (defined(_M_ARM) || defined(__arm__)) ++ (defined(_M_ARM64) || defined(__arm64__)) + #define SSE2NEON_HAS_BITSCAN64 + #endif + #endif +@@ -441,7 +441,7 @@ typedef int64x2_t __m128i; /* 128-bit vector containing integers */ + // by applications which attempt to access the contents of an __m128 struct + // directly. It is important to note that accessing the __m128 struct directly + // is bad coding practice by Microsoft: @see: +-// https://docs.microsoft.com/en-us/cpp/cpp/m128 ++// https://learn.microsoft.com/en-us/cpp/cpp/m128 + // + // However, some legacy source code may try to access the contents of an __m128 + // struct directly so the developer can use the SIMDVec as an alias for it. Any +@@ -621,47 +621,6 @@ FORCE_INLINE uint16_t _sse2neon_vaddvq_u16(uint16x8_t a) + * 4, 5, 12, 13, 6, 7, 14, 15); + * // Shuffle packed 8-bit integers + * __m128i v_out = _mm_shuffle_epi8(v_in, v_perm); // pshufb +- * +- * Data (Number, Binary, Byte Index): +- +------+------+-------------+------+------+-------------+ +- | 1 | 2 | 3 | 4 | Number +- +------+------+------+------+------+------+------+------+ +- | 0000 | 0001 | 0000 | 0010 | 0000 | 0011 | 0000 | 0100 | Binary +- +------+------+------+------+------+------+------+------+ +- | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | Index +- +------+------+------+------+------+------+------+------+ +- +- +------+------+------+------+------+------+------+------+ +- | 5 | 6 | 7 | 8 | Number +- +------+------+------+------+------+------+------+------+ +- | 0000 | 0101 | 0000 | 0110 | 0000 | 0111 | 0000 | 1000 | Binary +- +------+------+------+------+------+------+------+------+ +- | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Index +- +------+------+------+------+------+------+------+------+ +- * Index (Byte Index): +- +------+------+------+------+------+------+------+------+ +- | 1 | 0 | 2 | 3 | 8 | 9 | 10 | 11 | +- +------+------+------+------+------+------+------+------+ +- +- +------+------+------+------+------+------+------+------+ +- | 4 | 5 | 12 | 13 | 6 | 7 | 14 | 15 | +- +------+------+------+------+------+------+------+------+ +- * Result: +- +------+------+------+------+------+------+------+------+ +- | 1 | 0 | 2 | 3 | 8 | 9 | 10 | 11 | Index +- +------+------+------+------+------+------+------+------+ +- | 0001 | 0000 | 0000 | 0010 | 0000 | 0101 | 0000 | 0110 | Binary +- +------+------+------+------+------+------+------+------+ +- | 256 | 2 | 5 | 6 | Number +- +------+------+------+------+------+------+------+------+ +- +- +------+------+------+------+------+------+------+------+ +- | 4 | 5 | 12 | 13 | 6 | 7 | 14 | 15 | Index +- +------+------+------+------+------+------+------+------+ +- | 0000 | 0011 | 0000 | 0111 | 0000 | 0100 | 0000 | 1000 | Binary +- +------+------+------+------+------+------+------+------+ +- | 3 | 7 | 4 | 8 | Number +- +------+------+------+------+------+------+-------------+ + */ + + /* Constants for use with _mm_prefetch. */ +@@ -1069,9 +1028,9 @@ FORCE_INLINE __m128i _mm_shuffle_epi_3332(__m128i a) + }) + #endif + +-// NEON does not support a general purpose permute intrinsic +-// Selects four specific single-precision, floating-point values from a and b, +-// based on the mask i. ++// NEON does not support a general purpose permute intrinsic. ++// Shuffle single-precision (32-bit) floating-point elements in a using the ++// control in imm8, and store the results in dst. + // + // C equivalent: + // __m128 _mm_shuffle_ps_default(__m128 a, __m128 b, +@@ -1082,7 +1041,7 @@ FORCE_INLINE __m128i _mm_shuffle_epi_3332(__m128i a) + // return ret; + // } + // +-// https://msdn.microsoft.com/en-us/library/vstudio/5f0858x0(v=vs.100).aspx ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_shuffle_ps + #define _mm_shuffle_ps_default(a, b, imm) \ + __extension__({ \ + float32x4_t ret; \ +@@ -1100,12 +1059,10 @@ FORCE_INLINE __m128i _mm_shuffle_epi_3332(__m128i a) + vreinterpretq_m128_f32(ret); \ + }) + +-// Shuffles the lower 4 signed or unsigned 16-bit integers in a as specified +-// by imm. +-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/y41dkk37(v=vs.100) +-// FORCE_INLINE __m128i _mm_shufflelo_epi16_function(__m128i a, +-// __constrange(0,255) int +-// imm) ++// Shuffle 16-bit integers in the low 64 bits of a using the control in imm8. ++// Store the results in the low 64 bits of dst, with the high 64 bits being ++// copied from from a to dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_shufflelo_epi16 + #define _mm_shufflelo_epi16_function(a, imm) \ + __extension__({ \ + int16x8_t ret = vreinterpretq_s16_m128i(a); \ +@@ -1120,12 +1077,10 @@ FORCE_INLINE __m128i _mm_shuffle_epi_3332(__m128i a) + vreinterpretq_m128i_s16(ret); \ + }) + +-// Shuffles the upper 4 signed or unsigned 16-bit integers in a as specified +-// by imm. +-// https://msdn.microsoft.com/en-us/library/13ywktbs(v=vs.100).aspx +-// FORCE_INLINE __m128i _mm_shufflehi_epi16_function(__m128i a, +-// __constrange(0,255) int +-// imm) ++// Shuffle 16-bit integers in the high 64 bits of a using the control in imm8. ++// Store the results in the high 64 bits of dst, with the low 64 bits being ++// copied from from a to dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_shufflehi_epi16 + #define _mm_shufflehi_epi16_function(a, imm) \ + __extension__({ \ + int16x8_t ret = vreinterpretq_s16_m128i(a); \ +@@ -1147,22 +1102,19 @@ FORCE_INLINE void _mm_empty(void) {} + + /* SSE */ + +-// Adds the four single-precision, floating-point values of a and b. +-// +-// r0 := a0 + b0 +-// r1 := a1 + b1 +-// r2 := a2 + b2 +-// r3 := a3 + b3 +-// +-// https://msdn.microsoft.com/en-us/library/vstudio/c9848chc(v=vs.100).aspx ++// Add packed single-precision (32-bit) floating-point elements in a and b, and ++// store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_add_ps + FORCE_INLINE __m128 _mm_add_ps(__m128 a, __m128 b) + { + return vreinterpretq_m128_f32( + vaddq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); + } + +-// adds the scalar single-precision floating point values of a and b. +-// https://msdn.microsoft.com/en-us/library/be94x2y6(v=vs.100).aspx ++// Add the lower single-precision (32-bit) floating-point element in a and b, ++// store the result in the lower element of dst, and copy the upper 3 packed ++// elements from a to the upper elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_add_ss + FORCE_INLINE __m128 _mm_add_ss(__m128 a, __m128 b) + { + float32_t b0 = vgetq_lane_f32(vreinterpretq_f32_m128(b), 0); +@@ -1171,30 +1123,18 @@ FORCE_INLINE __m128 _mm_add_ss(__m128 a, __m128 b) + return vreinterpretq_m128_f32(vaddq_f32(a, value)); + } + +-// Computes the bitwise AND of the four single-precision, floating-point values +-// of a and b. +-// +-// r0 := a0 & b0 +-// r1 := a1 & b1 +-// r2 := a2 & b2 +-// r3 := a3 & b3 +-// +-// https://msdn.microsoft.com/en-us/library/vstudio/73ck1xc5(v=vs.100).aspx ++// Compute the bitwise AND of packed single-precision (32-bit) floating-point ++// elements in a and b, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_and_ps + FORCE_INLINE __m128 _mm_and_ps(__m128 a, __m128 b) + { + return vreinterpretq_m128_s32( + vandq_s32(vreinterpretq_s32_m128(a), vreinterpretq_s32_m128(b))); + } + +-// Computes the bitwise AND-NOT of the four single-precision, floating-point +-// values of a and b. +-// +-// r0 := ~a0 & b0 +-// r1 := ~a1 & b1 +-// r2 := ~a2 & b2 +-// r3 := ~a3 & b3 +-// +-// https://msdn.microsoft.com/en-us/library/vstudio/68h7wd02(v=vs.100).aspx ++// Compute the bitwise NOT of packed single-precision (32-bit) floating-point ++// elements in a and then AND with b, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_andnot_ps + FORCE_INLINE __m128 _mm_andnot_ps(__m128 a, __m128 b) + { + return vreinterpretq_m128_s32( +@@ -1204,13 +1144,7 @@ FORCE_INLINE __m128 _mm_andnot_ps(__m128 a, __m128 b) + + // Average packed unsigned 16-bit integers in a and b, and store the results in + // dst. +-// +-// FOR j := 0 to 3 +-// i := j*16 +-// dst[i+15:i] := (a[i+15:i] + b[i+15:i] + 1) >> 1 +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_avg_pu16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_avg_pu16 + FORCE_INLINE __m64 _mm_avg_pu16(__m64 a, __m64 b) + { + return vreinterpret_m64_u16( +@@ -1219,186 +1153,199 @@ FORCE_INLINE __m64 _mm_avg_pu16(__m64 a, __m64 b) + + // Average packed unsigned 8-bit integers in a and b, and store the results in + // dst. +-// +-// FOR j := 0 to 7 +-// i := j*8 +-// dst[i+7:i] := (a[i+7:i] + b[i+7:i] + 1) >> 1 +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_avg_pu8 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_avg_pu8 + FORCE_INLINE __m64 _mm_avg_pu8(__m64 a, __m64 b) + { + return vreinterpret_m64_u8( + vrhadd_u8(vreinterpret_u8_m64(a), vreinterpret_u8_m64(b))); + } + +-// Compares for equality. +-// https://msdn.microsoft.com/en-us/library/vstudio/36aectz5(v=vs.100).aspx ++// Compare packed single-precision (32-bit) floating-point elements in a and b ++// for equality, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpeq_ps + FORCE_INLINE __m128 _mm_cmpeq_ps(__m128 a, __m128 b) + { + return vreinterpretq_m128_u32( + vceqq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); + } + +-// Compares for equality. +-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/k423z28e(v=vs.100) ++// Compare the lower single-precision (32-bit) floating-point elements in a and ++// b for equality, store the result in the lower element of dst, and copy the ++// upper 3 packed elements from a to the upper elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpeq_ss + FORCE_INLINE __m128 _mm_cmpeq_ss(__m128 a, __m128 b) + { + return _mm_move_ss(a, _mm_cmpeq_ps(a, b)); + } + +-// Compares for greater than or equal. +-// https://msdn.microsoft.com/en-us/library/vstudio/fs813y2t(v=vs.100).aspx ++// Compare packed single-precision (32-bit) floating-point elements in a and b ++// for greater-than-or-equal, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpge_ps + FORCE_INLINE __m128 _mm_cmpge_ps(__m128 a, __m128 b) + { + return vreinterpretq_m128_u32( + vcgeq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); + } + +-// Compares for greater than or equal. +-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/kesh3ddc(v=vs.100) ++// Compare the lower single-precision (32-bit) floating-point elements in a and ++// b for greater-than-or-equal, store the result in the lower element of dst, ++// and copy the upper 3 packed elements from a to the upper elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpge_ss + FORCE_INLINE __m128 _mm_cmpge_ss(__m128 a, __m128 b) + { + return _mm_move_ss(a, _mm_cmpge_ps(a, b)); + } + +-// Compares for greater than. +-// +-// r0 := (a0 > b0) ? 0xffffffff : 0x0 +-// r1 := (a1 > b1) ? 0xffffffff : 0x0 +-// r2 := (a2 > b2) ? 0xffffffff : 0x0 +-// r3 := (a3 > b3) ? 0xffffffff : 0x0 +-// +-// https://msdn.microsoft.com/en-us/library/vstudio/11dy102s(v=vs.100).aspx ++// Compare packed single-precision (32-bit) floating-point elements in a and b ++// for greater-than, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpgt_ps + FORCE_INLINE __m128 _mm_cmpgt_ps(__m128 a, __m128 b) + { + return vreinterpretq_m128_u32( + vcgtq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); + } + +-// Compares for greater than. +-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/1xyyyy9e(v=vs.100) ++// Compare the lower single-precision (32-bit) floating-point elements in a and ++// b for greater-than, store the result in the lower element of dst, and copy ++// the upper 3 packed elements from a to the upper elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpgt_ss + FORCE_INLINE __m128 _mm_cmpgt_ss(__m128 a, __m128 b) + { + return _mm_move_ss(a, _mm_cmpgt_ps(a, b)); + } + +-// Compares for less than or equal. +-// +-// r0 := (a0 <= b0) ? 0xffffffff : 0x0 +-// r1 := (a1 <= b1) ? 0xffffffff : 0x0 +-// r2 := (a2 <= b2) ? 0xffffffff : 0x0 +-// r3 := (a3 <= b3) ? 0xffffffff : 0x0 +-// +-// https://msdn.microsoft.com/en-us/library/vstudio/1s75w83z(v=vs.100).aspx ++// Compare packed single-precision (32-bit) floating-point elements in a and b ++// for less-than-or-equal, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmple_ps + FORCE_INLINE __m128 _mm_cmple_ps(__m128 a, __m128 b) + { + return vreinterpretq_m128_u32( + vcleq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); + } + +-// Compares for less than or equal. +-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/a7x0hbhw(v=vs.100) ++// Compare the lower single-precision (32-bit) floating-point elements in a and ++// b for less-than-or-equal, store the result in the lower element of dst, and ++// copy the upper 3 packed elements from a to the upper elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmple_ss + FORCE_INLINE __m128 _mm_cmple_ss(__m128 a, __m128 b) + { + return _mm_move_ss(a, _mm_cmple_ps(a, b)); + } + +-// Compares for less than +-// https://msdn.microsoft.com/en-us/library/vstudio/f330yhc8(v=vs.100).aspx ++// Compare packed single-precision (32-bit) floating-point elements in a and b ++// for less-than, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmplt_ps + FORCE_INLINE __m128 _mm_cmplt_ps(__m128 a, __m128 b) + { + return vreinterpretq_m128_u32( + vcltq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); + } + +-// Compares for less than +-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/fy94wye7(v=vs.100) ++// Compare the lower single-precision (32-bit) floating-point elements in a and ++// b for less-than, store the result in the lower element of dst, and copy the ++// upper 3 packed elements from a to the upper elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmplt_ss + FORCE_INLINE __m128 _mm_cmplt_ss(__m128 a, __m128 b) + { + return _mm_move_ss(a, _mm_cmplt_ps(a, b)); + } + +-// Compares for inequality. +-// https://msdn.microsoft.com/en-us/library/sf44thbx(v=vs.100).aspx ++// Compare packed single-precision (32-bit) floating-point elements in a and b ++// for not-equal, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpneq_ps + FORCE_INLINE __m128 _mm_cmpneq_ps(__m128 a, __m128 b) + { + return vreinterpretq_m128_u32(vmvnq_u32( + vceqq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)))); + } + +-// Compares for inequality. +-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/ekya8fh4(v=vs.100) ++// Compare the lower single-precision (32-bit) floating-point elements in a and ++// b for not-equal, store the result in the lower element of dst, and copy the ++// upper 3 packed elements from a to the upper elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpneq_ss + FORCE_INLINE __m128 _mm_cmpneq_ss(__m128 a, __m128 b) + { + return _mm_move_ss(a, _mm_cmpneq_ps(a, b)); + } + +-// Compares for not greater than or equal. +-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/wsexys62(v=vs.100) ++// Compare packed single-precision (32-bit) floating-point elements in a and b ++// for not-greater-than-or-equal, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnge_ps + FORCE_INLINE __m128 _mm_cmpnge_ps(__m128 a, __m128 b) + { + return vreinterpretq_m128_u32(vmvnq_u32( + vcgeq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)))); + } + +-// Compares for not greater than or equal. +-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/fk2y80s8(v=vs.100) ++// Compare the lower single-precision (32-bit) floating-point elements in a and ++// b for not-greater-than-or-equal, store the result in the lower element of ++// dst, and copy the upper 3 packed elements from a to the upper elements of ++// dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnge_ss + FORCE_INLINE __m128 _mm_cmpnge_ss(__m128 a, __m128 b) + { + return _mm_move_ss(a, _mm_cmpnge_ps(a, b)); + } + +-// Compares for not greater than. +-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/d0xh7w0s(v=vs.100) ++// Compare packed single-precision (32-bit) floating-point elements in a and b ++// for not-greater-than, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpngt_ps + FORCE_INLINE __m128 _mm_cmpngt_ps(__m128 a, __m128 b) + { + return vreinterpretq_m128_u32(vmvnq_u32( + vcgtq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)))); + } + +-// Compares for not greater than. +-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/z7x9ydwh(v=vs.100) ++// Compare the lower single-precision (32-bit) floating-point elements in a and ++// b for not-greater-than, store the result in the lower element of dst, and ++// copy the upper 3 packed elements from a to the upper elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpngt_ss + FORCE_INLINE __m128 _mm_cmpngt_ss(__m128 a, __m128 b) + { + return _mm_move_ss(a, _mm_cmpngt_ps(a, b)); + } + +-// Compares for not less than or equal. +-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/6a330kxw(v=vs.100) ++// Compare packed single-precision (32-bit) floating-point elements in a and b ++// for not-less-than-or-equal, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnle_ps + FORCE_INLINE __m128 _mm_cmpnle_ps(__m128 a, __m128 b) + { + return vreinterpretq_m128_u32(vmvnq_u32( + vcleq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)))); + } + +-// Compares for not less than or equal. +-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/z7x9ydwh(v=vs.100) ++// Compare the lower single-precision (32-bit) floating-point elements in a and ++// b for not-less-than-or-equal, store the result in the lower element of dst, ++// and copy the upper 3 packed elements from a to the upper elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnle_ss + FORCE_INLINE __m128 _mm_cmpnle_ss(__m128 a, __m128 b) + { + return _mm_move_ss(a, _mm_cmpnle_ps(a, b)); + } + +-// Compares for not less than. +-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/4686bbdw(v=vs.100) ++// Compare packed single-precision (32-bit) floating-point elements in a and b ++// for not-less-than, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnlt_ps + FORCE_INLINE __m128 _mm_cmpnlt_ps(__m128 a, __m128 b) + { + return vreinterpretq_m128_u32(vmvnq_u32( + vcltq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b)))); + } + +-// Compares for not less than. +-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/56b9z2wf(v=vs.100) ++// Compare the lower single-precision (32-bit) floating-point elements in a and ++// b for not-less-than, store the result in the lower element of dst, and copy ++// the upper 3 packed elements from a to the upper elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnlt_ss + FORCE_INLINE __m128 _mm_cmpnlt_ss(__m128 a, __m128 b) + { + return _mm_move_ss(a, _mm_cmpnlt_ps(a, b)); + } + +-// Compares the four 32-bit floats in a and b to check if any values are NaN. +-// Ordered compare between each value returns true for "orderable" and false for +-// "not orderable" (NaN). +-// https://msdn.microsoft.com/en-us/library/vstudio/0h9w00fx(v=vs.100).aspx see +-// also: ++// Compare packed single-precision (32-bit) floating-point elements in a and b ++// to see if neither is NaN, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpord_ps ++// ++// See also: + // http://stackoverflow.com/questions/8627331/what-does-ordered-unordered-comparison-mean + // http://stackoverflow.com/questions/29349621/neon-isnanval-intrinsics + FORCE_INLINE __m128 _mm_cmpord_ps(__m128 a, __m128 b) +@@ -1413,15 +1360,18 @@ FORCE_INLINE __m128 _mm_cmpord_ps(__m128 a, __m128 b) + return vreinterpretq_m128_u32(vandq_u32(ceqaa, ceqbb)); + } + +-// Compares for ordered. +-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/343t62da(v=vs.100) ++// Compare the lower single-precision (32-bit) floating-point elements in a and ++// b to see if neither is NaN, store the result in the lower element of dst, and ++// copy the upper 3 packed elements from a to the upper elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpord_ss + FORCE_INLINE __m128 _mm_cmpord_ss(__m128 a, __m128 b) + { + return _mm_move_ss(a, _mm_cmpord_ps(a, b)); + } + +-// Compares for unordered. +-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/khy6fk1t(v=vs.100) ++// Compare packed single-precision (32-bit) floating-point elements in a and b ++// to see if either is NaN, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpunord_ps + FORCE_INLINE __m128 _mm_cmpunord_ps(__m128 a, __m128 b) + { + uint32x4_t f32a = +@@ -1431,16 +1381,18 @@ FORCE_INLINE __m128 _mm_cmpunord_ps(__m128 a, __m128 b) + return vreinterpretq_m128_u32(vmvnq_u32(vandq_u32(f32a, f32b))); + } + +-// Compares for unordered. +-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/2as2387b(v=vs.100) ++// Compare the lower single-precision (32-bit) floating-point elements in a and ++// b to see if either is NaN, store the result in the lower element of dst, and ++// copy the upper 3 packed elements from a to the upper elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpunord_ss + FORCE_INLINE __m128 _mm_cmpunord_ss(__m128 a, __m128 b) + { + return _mm_move_ss(a, _mm_cmpunord_ps(a, b)); + } + +-// Compares the lower single-precision floating point scalar values of a and b +-// using an equality operation. : +-// https://msdn.microsoft.com/en-us/library/93yx2h2b(v=vs.100).aspx ++// Compare the lower single-precision (32-bit) floating-point element in a and b ++// for equality, and return the boolean result (0 or 1). ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comieq_ss + FORCE_INLINE int _mm_comieq_ss(__m128 a, __m128 b) + { + uint32x4_t a_eq_b = +@@ -1448,9 +1400,9 @@ FORCE_INLINE int _mm_comieq_ss(__m128 a, __m128 b) + return vgetq_lane_u32(a_eq_b, 0) & 0x1; + } + +-// Compares the lower single-precision floating point scalar values of a and b +-// using a greater than or equal operation. : +-// https://msdn.microsoft.com/en-us/library/8t80des6(v=vs.100).aspx ++// Compare the lower single-precision (32-bit) floating-point element in a and b ++// for greater-than-or-equal, and return the boolean result (0 or 1). ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comige_ss + FORCE_INLINE int _mm_comige_ss(__m128 a, __m128 b) + { + uint32x4_t a_ge_b = +@@ -1458,9 +1410,9 @@ FORCE_INLINE int _mm_comige_ss(__m128 a, __m128 b) + return vgetq_lane_u32(a_ge_b, 0) & 0x1; + } + +-// Compares the lower single-precision floating point scalar values of a and b +-// using a greater than operation. : +-// https://msdn.microsoft.com/en-us/library/b0738e0t(v=vs.100).aspx ++// Compare the lower single-precision (32-bit) floating-point element in a and b ++// for greater-than, and return the boolean result (0 or 1). ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comigt_ss + FORCE_INLINE int _mm_comigt_ss(__m128 a, __m128 b) + { + uint32x4_t a_gt_b = +@@ -1468,9 +1420,9 @@ FORCE_INLINE int _mm_comigt_ss(__m128 a, __m128 b) + return vgetq_lane_u32(a_gt_b, 0) & 0x1; + } + +-// Compares the lower single-precision floating point scalar values of a and b +-// using a less than or equal operation. : +-// https://msdn.microsoft.com/en-us/library/1w4t7c57(v=vs.90).aspx ++// Compare the lower single-precision (32-bit) floating-point element in a and b ++// for less-than-or-equal, and return the boolean result (0 or 1). ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comile_ss + FORCE_INLINE int _mm_comile_ss(__m128 a, __m128 b) + { + uint32x4_t a_le_b = +@@ -1478,11 +1430,9 @@ FORCE_INLINE int _mm_comile_ss(__m128 a, __m128 b) + return vgetq_lane_u32(a_le_b, 0) & 0x1; + } + +-// Compares the lower single-precision floating point scalar values of a and b +-// using a less than operation. : +-// https://msdn.microsoft.com/en-us/library/2kwe606b(v=vs.90).aspx Important +-// note!! The documentation on MSDN is incorrect! If either of the values is a +-// NAN the docs say you will get a one, but in fact, it will return a zero!! ++// Compare the lower single-precision (32-bit) floating-point element in a and b ++// for less-than, and return the boolean result (0 or 1). ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comilt_ss + FORCE_INLINE int _mm_comilt_ss(__m128 a, __m128 b) + { + uint32x4_t a_lt_b = +@@ -1490,9 +1440,9 @@ FORCE_INLINE int _mm_comilt_ss(__m128 a, __m128 b) + return vgetq_lane_u32(a_lt_b, 0) & 0x1; + } + +-// Compares the lower single-precision floating point scalar values of a and b +-// using an inequality operation. : +-// https://msdn.microsoft.com/en-us/library/bafh5e0a(v=vs.90).aspx ++// Compare the lower single-precision (32-bit) floating-point element in a and b ++// for not-equal, and return the boolean result (0 or 1). ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comineq_ss + FORCE_INLINE int _mm_comineq_ss(__m128 a, __m128 b) + { + return !_mm_comieq_ss(a, b); +@@ -1502,13 +1452,7 @@ FORCE_INLINE int _mm_comineq_ss(__m128 a, __m128 b) + // (32-bit) floating-point elements, store the results in the lower 2 elements + // of dst, and copy the upper 2 packed elements from a to the upper elements of + // dst. +-// +-// dst[31:0] := Convert_Int32_To_FP32(b[31:0]) +-// dst[63:32] := Convert_Int32_To_FP32(b[63:32]) +-// dst[95:64] := a[95:64] +-// dst[127:96] := a[127:96] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvt_pi2ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvt_pi2ps + FORCE_INLINE __m128 _mm_cvt_pi2ps(__m128 a, __m64 b) + { + return vreinterpretq_m128_f32( +@@ -1518,13 +1462,7 @@ FORCE_INLINE __m128 _mm_cvt_pi2ps(__m128 a, __m64 b) + + // Convert packed single-precision (32-bit) floating-point elements in a to + // packed 32-bit integers, and store the results in dst. +-// +-// FOR j := 0 to 1 +-// i := 32*j +-// dst[i+31:i] := Convert_FP32_To_Int32(a[i+31:i]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvt_ps2pi ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvt_ps2pi + FORCE_INLINE __m64 _mm_cvt_ps2pi(__m128 a) + { + #if defined(__aarch64__) || defined(__ARM_FEATURE_DIRECTED_ROUNDING) +@@ -1539,11 +1477,7 @@ FORCE_INLINE __m64 _mm_cvt_ps2pi(__m128 a) + // Convert the signed 32-bit integer b to a single-precision (32-bit) + // floating-point element, store the result in the lower element of dst, and + // copy the upper 3 packed elements from a to the upper elements of dst. +-// +-// dst[31:0] := Convert_Int32_To_FP32(b[31:0]) +-// dst[127:32] := a[127:32] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvt_si2ss ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvt_si2ss + FORCE_INLINE __m128 _mm_cvt_si2ss(__m128 a, int b) + { + return vreinterpretq_m128_f32( +@@ -1552,7 +1486,7 @@ FORCE_INLINE __m128 _mm_cvt_si2ss(__m128 a, int b) + + // Convert the lower single-precision (32-bit) floating-point element in a to a + // 32-bit integer, and store the result in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvt_ss2si ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvt_ss2si + FORCE_INLINE int _mm_cvt_ss2si(__m128 a) + { + #if defined(__aarch64__) || defined(__ARM_FEATURE_DIRECTED_ROUNDING) +@@ -1567,14 +1501,7 @@ FORCE_INLINE int _mm_cvt_ss2si(__m128 a) + + // Convert packed 16-bit integers in a to packed single-precision (32-bit) + // floating-point elements, and store the results in dst. +-// +-// FOR j := 0 to 3 +-// i := j*16 +-// m := j*32 +-// dst[m+31:m] := Convert_Int16_To_FP32(a[i+15:i]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpi16_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtpi16_ps + FORCE_INLINE __m128 _mm_cvtpi16_ps(__m64 a) + { + return vreinterpretq_m128_f32( +@@ -1584,13 +1511,7 @@ FORCE_INLINE __m128 _mm_cvtpi16_ps(__m64 a) + // Convert packed 32-bit integers in b to packed single-precision (32-bit) + // floating-point elements, store the results in the lower 2 elements of dst, + // and copy the upper 2 packed elements from a to the upper elements of dst. +-// +-// dst[31:0] := Convert_Int32_To_FP32(b[31:0]) +-// dst[63:32] := Convert_Int32_To_FP32(b[63:32]) +-// dst[95:64] := a[95:64] +-// dst[127:96] := a[127:96] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpi32_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtpi32_ps + FORCE_INLINE __m128 _mm_cvtpi32_ps(__m128 a, __m64 b) + { + return vreinterpretq_m128_f32( +@@ -1603,13 +1524,7 @@ FORCE_INLINE __m128 _mm_cvtpi32_ps(__m128 a, __m64 b) + // of dst, then convert the packed signed 32-bit integers in b to + // single-precision (32-bit) floating-point element, and store the results in + // the upper 2 elements of dst. +-// +-// dst[31:0] := Convert_Int32_To_FP32(a[31:0]) +-// dst[63:32] := Convert_Int32_To_FP32(a[63:32]) +-// dst[95:64] := Convert_Int32_To_FP32(b[31:0]) +-// dst[127:96] := Convert_Int32_To_FP32(b[63:32]) +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpi32x2_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtpi32x2_ps + FORCE_INLINE __m128 _mm_cvtpi32x2_ps(__m64 a, __m64 b) + { + return vreinterpretq_m128_f32(vcvtq_f32_s32( +@@ -1618,14 +1533,7 @@ FORCE_INLINE __m128 _mm_cvtpi32x2_ps(__m64 a, __m64 b) + + // Convert the lower packed 8-bit integers in a to packed single-precision + // (32-bit) floating-point elements, and store the results in dst. +-// +-// FOR j := 0 to 3 +-// i := j*8 +-// m := j*32 +-// dst[m+31:m] := Convert_Int8_To_FP32(a[i+7:i]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpi8_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtpi8_ps + FORCE_INLINE __m128 _mm_cvtpi8_ps(__m64 a) + { + return vreinterpretq_m128_f32(vcvtq_f32_s32( +@@ -1636,18 +1544,7 @@ FORCE_INLINE __m128 _mm_cvtpi8_ps(__m64 a) + // packed 16-bit integers, and store the results in dst. Note: this intrinsic + // will generate 0x7FFF, rather than 0x8000, for input values between 0x7FFF and + // 0x7FFFFFFF. +-// +-// FOR j := 0 to 3 +-// i := 16*j +-// k := 32*j +-// IF a[k+31:k] >= FP32(0x7FFF) && a[k+31:k] <= FP32(0x7FFFFFFF) +-// dst[i+15:i] := 0x7FFF +-// ELSE +-// dst[i+15:i] := Convert_FP32_To_Int16(a[k+31:k]) +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtps_pi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtps_pi16 + FORCE_INLINE __m64 _mm_cvtps_pi16(__m128 a) + { + return vreinterpret_m64_s16( +@@ -1656,31 +1553,14 @@ FORCE_INLINE __m64 _mm_cvtps_pi16(__m128 a) + + // Convert packed single-precision (32-bit) floating-point elements in a to + // packed 32-bit integers, and store the results in dst. +-// +-// FOR j := 0 to 1 +-// i := 32*j +-// dst[i+31:i] := Convert_FP32_To_Int32(a[i+31:i]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtps_pi32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtps_pi32 + #define _mm_cvtps_pi32(a) _mm_cvt_ps2pi(a) + + // Convert packed single-precision (32-bit) floating-point elements in a to + // packed 8-bit integers, and store the results in lower 4 elements of dst. + // Note: this intrinsic will generate 0x7F, rather than 0x80, for input values + // between 0x7F and 0x7FFFFFFF. +-// +-// FOR j := 0 to 3 +-// i := 8*j +-// k := 32*j +-// IF a[k+31:k] >= FP32(0x7F) && a[k+31:k] <= FP32(0x7FFFFFFF) +-// dst[i+7:i] := 0x7F +-// ELSE +-// dst[i+7:i] := Convert_FP32_To_Int8(a[k+31:k]) +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtps_pi8 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtps_pi8 + FORCE_INLINE __m64 _mm_cvtps_pi8(__m128 a) + { + return vreinterpret_m64_s8(vqmovn_s16( +@@ -1689,14 +1569,7 @@ FORCE_INLINE __m64 _mm_cvtps_pi8(__m128 a) + + // Convert packed unsigned 16-bit integers in a to packed single-precision + // (32-bit) floating-point elements, and store the results in dst. +-// +-// FOR j := 0 to 3 +-// i := j*16 +-// m := j*32 +-// dst[m+31:m] := Convert_UInt16_To_FP32(a[i+15:i]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpu16_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtpu16_ps + FORCE_INLINE __m128 _mm_cvtpu16_ps(__m64 a) + { + return vreinterpretq_m128_f32( +@@ -1706,14 +1579,7 @@ FORCE_INLINE __m128 _mm_cvtpu16_ps(__m64 a) + // Convert the lower packed unsigned 8-bit integers in a to packed + // single-precision (32-bit) floating-point elements, and store the results in + // dst. +-// +-// FOR j := 0 to 3 +-// i := j*8 +-// m := j*32 +-// dst[m+31:m] := Convert_UInt8_To_FP32(a[i+7:i]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpu8_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtpu8_ps + FORCE_INLINE __m128 _mm_cvtpu8_ps(__m64 a) + { + return vreinterpretq_m128_f32(vcvtq_f32_u32( +@@ -1723,21 +1589,13 @@ FORCE_INLINE __m128 _mm_cvtpu8_ps(__m64 a) + // Convert the signed 32-bit integer b to a single-precision (32-bit) + // floating-point element, store the result in the lower element of dst, and + // copy the upper 3 packed elements from a to the upper elements of dst. +-// +-// dst[31:0] := Convert_Int32_To_FP32(b[31:0]) +-// dst[127:32] := a[127:32] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi32_ss ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi32_ss + #define _mm_cvtsi32_ss(a, b) _mm_cvt_si2ss(a, b) + + // Convert the signed 64-bit integer b to a single-precision (32-bit) + // floating-point element, store the result in the lower element of dst, and + // copy the upper 3 packed elements from a to the upper elements of dst. +-// +-// dst[31:0] := Convert_Int64_To_FP32(b[63:0]) +-// dst[127:32] := a[127:32] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi64_ss ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi64_ss + FORCE_INLINE __m128 _mm_cvtsi64_ss(__m128 a, int64_t b) + { + return vreinterpretq_m128_f32( +@@ -1745,10 +1603,7 @@ FORCE_INLINE __m128 _mm_cvtsi64_ss(__m128 a, int64_t b) + } + + // Copy the lower single-precision (32-bit) floating-point element of a to dst. +-// +-// dst[31:0] := a[31:0] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtss_f32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtss_f32 + FORCE_INLINE float _mm_cvtss_f32(__m128 a) + { + return vgetq_lane_f32(vreinterpretq_f32_m128(a), 0); +@@ -1756,18 +1611,12 @@ FORCE_INLINE float _mm_cvtss_f32(__m128 a) + + // Convert the lower single-precision (32-bit) floating-point element in a to a + // 32-bit integer, and store the result in dst. +-// +-// dst[31:0] := Convert_FP32_To_Int32(a[31:0]) +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtss_si32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtss_si32 + #define _mm_cvtss_si32(a) _mm_cvt_ss2si(a) + + // Convert the lower single-precision (32-bit) floating-point element in a to a + // 64-bit integer, and store the result in dst. +-// +-// dst[63:0] := Convert_FP32_To_Int64(a[31:0]) +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtss_si64 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtss_si64 + FORCE_INLINE int64_t _mm_cvtss_si64(__m128 a) + { + #if defined(__aarch64__) || defined(__ARM_FEATURE_DIRECTED_ROUNDING) +@@ -1781,13 +1630,7 @@ FORCE_INLINE int64_t _mm_cvtss_si64(__m128 a) + + // Convert packed single-precision (32-bit) floating-point elements in a to + // packed 32-bit integers with truncation, and store the results in dst. +-// +-// FOR j := 0 to 1 +-// i := 32*j +-// dst[i+31:i] := Convert_FP32_To_Int32_Truncate(a[i+31:i]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtt_ps2pi ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtt_ps2pi + FORCE_INLINE __m64 _mm_cvtt_ps2pi(__m128 a) + { + return vreinterpret_m64_s32( +@@ -1796,10 +1639,7 @@ FORCE_INLINE __m64 _mm_cvtt_ps2pi(__m128 a) + + // Convert the lower single-precision (32-bit) floating-point element in a to a + // 32-bit integer with truncation, and store the result in dst. +-// +-// dst[31:0] := Convert_FP32_To_Int32_Truncate(a[31:0]) +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtt_ss2si ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtt_ss2si + FORCE_INLINE int _mm_cvtt_ss2si(__m128 a) + { + return vgetq_lane_s32(vcvtq_s32_f32(vreinterpretq_f32_m128(a)), 0); +@@ -1807,60 +1647,49 @@ FORCE_INLINE int _mm_cvtt_ss2si(__m128 a) + + // Convert packed single-precision (32-bit) floating-point elements in a to + // packed 32-bit integers with truncation, and store the results in dst. +-// +-// FOR j := 0 to 1 +-// i := 32*j +-// dst[i+31:i] := Convert_FP32_To_Int32_Truncate(a[i+31:i]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttps_pi32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvttps_pi32 + #define _mm_cvttps_pi32(a) _mm_cvtt_ps2pi(a) + + // Convert the lower single-precision (32-bit) floating-point element in a to a + // 32-bit integer with truncation, and store the result in dst. +-// +-// dst[31:0] := Convert_FP32_To_Int32_Truncate(a[31:0]) +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttss_si32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvttss_si32 + #define _mm_cvttss_si32(a) _mm_cvtt_ss2si(a) + + // Convert the lower single-precision (32-bit) floating-point element in a to a + // 64-bit integer with truncation, and store the result in dst. +-// +-// dst[63:0] := Convert_FP32_To_Int64_Truncate(a[31:0]) +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttss_si64 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvttss_si64 + FORCE_INLINE int64_t _mm_cvttss_si64(__m128 a) + { + return (int64_t) vgetq_lane_f32(vreinterpretq_f32_m128(a), 0); + } + +-// Divides the four single-precision, floating-point values of a and b. +-// +-// r0 := a0 / b0 +-// r1 := a1 / b1 +-// r2 := a2 / b2 +-// r3 := a3 / b3 +-// +-// https://msdn.microsoft.com/en-us/library/edaw8147(v=vs.100).aspx ++// Divide packed single-precision (32-bit) floating-point elements in a by ++// packed elements in b, and store the results in dst. ++// Due to ARMv7-A NEON's lack of a precise division intrinsic, we implement ++// division by multiplying a by b's reciprocal before using the Newton-Raphson ++// method to approximate the results. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_div_ps + FORCE_INLINE __m128 _mm_div_ps(__m128 a, __m128 b) + { +-#if defined(__aarch64__) && !SSE2NEON_PRECISE_DIV ++#if defined(__aarch64__) + return vreinterpretq_m128_f32( + vdivq_f32(vreinterpretq_f32_m128(a), vreinterpretq_f32_m128(b))); + #else + float32x4_t recip = vrecpeq_f32(vreinterpretq_f32_m128(b)); + recip = vmulq_f32(recip, vrecpsq_f32(recip, vreinterpretq_f32_m128(b))); +-#if SSE2NEON_PRECISE_DIV + // Additional Netwon-Raphson iteration for accuracy + recip = vmulq_f32(recip, vrecpsq_f32(recip, vreinterpretq_f32_m128(b))); +-#endif + return vreinterpretq_m128_f32(vmulq_f32(vreinterpretq_f32_m128(a), recip)); + #endif + } + +-// Divides the scalar single-precision floating point value of a by b. +-// https://msdn.microsoft.com/en-us/library/4y73xa49(v=vs.100).aspx ++// Divide the lower single-precision (32-bit) floating-point element in a by the ++// lower single-precision (32-bit) floating-point element in b, store the result ++// in the lower element of dst, and copy the upper 3 packed elements from a to ++// the upper elements of dst. ++// Warning: ARMv7-A does not produce the same result compared to Intel and not ++// IEEE-compliant. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_div_ss + FORCE_INLINE __m128 _mm_div_ss(__m128 a, __m128 b) + { + float32_t value = +@@ -1871,12 +1700,12 @@ FORCE_INLINE __m128 _mm_div_ss(__m128 a, __m128 b) + + // Extract a 16-bit integer from a, selected with imm8, and store the result in + // the lower element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_extract_pi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_extract_pi16 + #define _mm_extract_pi16(a, imm) \ + (int32_t) vget_lane_u16(vreinterpret_u16_m64(a), (imm)) + + // Free aligned memory that was allocated with _mm_malloc. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_free ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_free + #if !defined(SSE2NEON_ALLOC_DEFINED) + FORCE_INLINE void _mm_free(void *addr) + { +@@ -1887,7 +1716,7 @@ FORCE_INLINE void _mm_free(void *addr) + // Macro: Get the flush zero bits from the MXCSR control and status register. + // The flush zero may contain any of the following flags: _MM_FLUSH_ZERO_ON or + // _MM_FLUSH_ZERO_OFF +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_MM_GET_FLUSH_ZERO_MODE ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_MM_GET_FLUSH_ZERO_MODE + FORCE_INLINE unsigned int _sse2neon_mm_get_flush_zero_mode() + { + union { +@@ -1911,7 +1740,7 @@ FORCE_INLINE unsigned int _sse2neon_mm_get_flush_zero_mode() + // Macro: Get the rounding mode bits from the MXCSR control and status register. + // The rounding mode may contain any of the following flags: _MM_ROUND_NEAREST, + // _MM_ROUND_DOWN, _MM_ROUND_UP, _MM_ROUND_TOWARD_ZERO +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_MM_GET_ROUNDING_MODE ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_MM_GET_ROUNDING_MODE + FORCE_INLINE unsigned int _MM_GET_ROUNDING_MODE() + { + union { +@@ -1938,15 +1767,17 @@ FORCE_INLINE unsigned int _MM_GET_ROUNDING_MODE() + + // Copy a to dst, and insert the 16-bit integer i into dst at the location + // specified by imm8. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_insert_pi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_insert_pi16 + #define _mm_insert_pi16(a, b, imm) \ + __extension__({ \ + vreinterpret_m64_s16( \ + vset_lane_s16((b), vreinterpret_s16_m64(a), (imm))); \ + }) + +-// Loads four single-precision, floating-point values. +-// https://msdn.microsoft.com/en-us/library/vstudio/zzd50xxt(v=vs.100).aspx ++// Load 128-bits (composed of 4 packed single-precision (32-bit) floating-point ++// elements) from memory into dst. mem_addr must be aligned on a 16-byte ++// boundary or a general-protection exception may be generated. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_load_ps + FORCE_INLINE __m128 _mm_load_ps(const float *p) + { + return vreinterpretq_m128_f32(vld1q_f32(p)); +@@ -1960,52 +1791,40 @@ FORCE_INLINE __m128 _mm_load_ps(const float *p) + // dst[95:64] := MEM[mem_addr+31:mem_addr] + // dst[127:96] := MEM[mem_addr+31:mem_addr] + // +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_load_ps1 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_load_ps1 + #define _mm_load_ps1 _mm_load1_ps + +-// Loads an single - precision, floating - point value into the low word and +-// clears the upper three words. +-// https://msdn.microsoft.com/en-us/library/548bb9h4%28v=vs.90%29.aspx ++// Load a single-precision (32-bit) floating-point element from memory into the ++// lower of dst, and zero the upper 3 elements. mem_addr does not need to be ++// aligned on any particular boundary. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_load_ss + FORCE_INLINE __m128 _mm_load_ss(const float *p) + { + return vreinterpretq_m128_f32(vsetq_lane_f32(*p, vdupq_n_f32(0), 0)); + } + +-// Loads a single single-precision, floating-point value, copying it into all +-// four words +-// https://msdn.microsoft.com/en-us/library/vstudio/5cdkf716(v=vs.100).aspx ++// Load a single-precision (32-bit) floating-point element from memory into all ++// elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_load1_ps + FORCE_INLINE __m128 _mm_load1_ps(const float *p) + { + return vreinterpretq_m128_f32(vld1q_dup_f32(p)); + } + +-// Sets the upper two single-precision, floating-point values with 64 +-// bits of data loaded from the address p; the lower two values are passed +-// through from a. +-// +-// r0 := a0 +-// r1 := a1 +-// r2 := *p0 +-// r3 := *p1 +-// +-// https://msdn.microsoft.com/en-us/library/w92wta0x(v%3dvs.100).aspx ++// Load 2 single-precision (32-bit) floating-point elements from memory into the ++// upper 2 elements of dst, and copy the lower 2 elements from a to dst. ++// mem_addr does not need to be aligned on any particular boundary. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadh_pi + FORCE_INLINE __m128 _mm_loadh_pi(__m128 a, __m64 const *p) + { + return vreinterpretq_m128_f32( + vcombine_f32(vget_low_f32(a), vld1_f32((const float32_t *) p))); + } + +-// Sets the lower two single-precision, floating-point values with 64 +-// bits of data loaded from the address p; the upper two values are passed +-// through from a. +-// +-// Return Value +-// r0 := *p0 +-// r1 := *p1 +-// r2 := a2 +-// r3 := a3 +-// +-// https://msdn.microsoft.com/en-us/library/s57cyak2(v=vs.100).aspx ++// Load 2 single-precision (32-bit) floating-point elements from memory into the ++// lower 2 elements of dst, and copy the upper 2 elements from a to dst. ++// mem_addr does not need to be aligned on any particular boundary. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadl_pi + FORCE_INLINE __m128 _mm_loadl_pi(__m128 a, __m64 const *p) + { + return vreinterpretq_m128_f32( +@@ -2015,21 +1834,17 @@ FORCE_INLINE __m128 _mm_loadl_pi(__m128 a, __m64 const *p) + // Load 4 single-precision (32-bit) floating-point elements from memory into dst + // in reverse order. mem_addr must be aligned on a 16-byte boundary or a + // general-protection exception may be generated. +-// +-// dst[31:0] := MEM[mem_addr+127:mem_addr+96] +-// dst[63:32] := MEM[mem_addr+95:mem_addr+64] +-// dst[95:64] := MEM[mem_addr+63:mem_addr+32] +-// dst[127:96] := MEM[mem_addr+31:mem_addr] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadr_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadr_ps + FORCE_INLINE __m128 _mm_loadr_ps(const float *p) + { + float32x4_t v = vrev64q_f32(vld1q_f32(p)); + return vreinterpretq_m128_f32(vextq_f32(v, v, 2)); + } + +-// Loads four single-precision, floating-point values. +-// https://msdn.microsoft.com/en-us/library/x1b16s7z%28v=vs.90%29.aspx ++// Load 128-bits (composed of 4 packed single-precision (32-bit) floating-point ++// elements) from memory into dst. mem_addr does not need to be aligned on any ++// particular boundary. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadu_ps + FORCE_INLINE __m128 _mm_loadu_ps(const float *p) + { + // for neon, alignment doesn't matter, so _mm_load_ps and _mm_loadu_ps are +@@ -2038,11 +1853,7 @@ FORCE_INLINE __m128 _mm_loadu_ps(const float *p) + } + + // Load unaligned 16-bit integer from memory into the first element of dst. +-// +-// dst[15:0] := MEM[mem_addr+15:mem_addr] +-// dst[MAX:16] := 0 +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadu_si16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadu_si16 + FORCE_INLINE __m128i _mm_loadu_si16(const void *p) + { + return vreinterpretq_m128i_s16( +@@ -2050,20 +1861,17 @@ FORCE_INLINE __m128i _mm_loadu_si16(const void *p) + } + + // Load unaligned 64-bit integer from memory into the first element of dst. +-// +-// dst[63:0] := MEM[mem_addr+63:mem_addr] +-// dst[MAX:64] := 0 +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadu_si64 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadu_si64 + FORCE_INLINE __m128i _mm_loadu_si64(const void *p) + { + return vreinterpretq_m128i_s64( + vcombine_s64(vld1_s64((const int64_t *) p), vdup_n_s64(0))); + } + +-// Allocate aligned blocks of memory. +-// https://software.intel.com/en-us/ +-// cpp-compiler-developer-guide-and-reference-allocating-and-freeing-aligned-memory-blocks ++// Allocate size bytes of memory, aligned to the alignment specified in align, ++// and return a pointer to the allocated memory. _mm_free should be used to free ++// memory that is allocated with _mm_malloc. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_malloc + #if !defined(SSE2NEON_ALLOC_DEFINED) + FORCE_INLINE void *_mm_malloc(size_t size, size_t align) + { +@@ -2081,7 +1889,7 @@ FORCE_INLINE void *_mm_malloc(size_t size, size_t align) + // Conditionally store 8-bit integer elements from a into memory using mask + // (elements are not stored when the highest bit is not set in the corresponding + // element) and a non-temporal memory hint. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_maskmove_si64 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_maskmove_si64 + FORCE_INLINE void _mm_maskmove_si64(__m64 a, __m64 mask, char *mem_addr) + { + int8x8_t shr_mask = vshr_n_s8(vreinterpret_s8_m64(mask), 7); +@@ -2095,27 +1903,23 @@ FORCE_INLINE void _mm_maskmove_si64(__m64 a, __m64 mask, char *mem_addr) + // Conditionally store 8-bit integer elements from a into memory using mask + // (elements are not stored when the highest bit is not set in the corresponding + // element) and a non-temporal memory hint. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_maskmovq ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_m_maskmovq + #define _m_maskmovq(a, mask, mem_addr) _mm_maskmove_si64(a, mask, mem_addr) + + // Compare packed signed 16-bit integers in a and b, and store packed maximum + // values in dst. +-// +-// FOR j := 0 to 3 +-// i := j*16 +-// dst[i+15:i] := MAX(a[i+15:i], b[i+15:i]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_pi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_pi16 + FORCE_INLINE __m64 _mm_max_pi16(__m64 a, __m64 b) + { + return vreinterpret_m64_s16( + vmax_s16(vreinterpret_s16_m64(a), vreinterpret_s16_m64(b))); + } + +-// Computes the maximums of the four single-precision, floating-point values of +-// a and b. +-// https://msdn.microsoft.com/en-us/library/vstudio/ff5d607a(v=vs.100).aspx ++// Compare packed single-precision (32-bit) floating-point elements in a and b, ++// and store packed maximum values in dst. dst does not follow the IEEE Standard ++// for Floating-Point Arithmetic (IEEE 754) maximum value when inputs are NaN or ++// signed-zero values. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_ps + FORCE_INLINE __m128 _mm_max_ps(__m128 a, __m128 b) + { + #if SSE2NEON_PRECISE_MINMAX +@@ -2130,22 +1934,19 @@ FORCE_INLINE __m128 _mm_max_ps(__m128 a, __m128 b) + + // Compare packed unsigned 8-bit integers in a and b, and store packed maximum + // values in dst. +-// +-// FOR j := 0 to 7 +-// i := j*8 +-// dst[i+7:i] := MAX(a[i+7:i], b[i+7:i]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_pu8 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_pu8 + FORCE_INLINE __m64 _mm_max_pu8(__m64 a, __m64 b) + { + return vreinterpret_m64_u8( + vmax_u8(vreinterpret_u8_m64(a), vreinterpret_u8_m64(b))); + } + +-// Computes the maximum of the two lower scalar single-precision floating point +-// values of a and b. +-// https://msdn.microsoft.com/en-us/library/s6db5esz(v=vs.100).aspx ++// Compare the lower single-precision (32-bit) floating-point elements in a and ++// b, store the maximum value in the lower element of dst, and copy the upper 3 ++// packed elements from a to the upper element of dst. dst does not follow the ++// IEEE Standard for Floating-Point Arithmetic (IEEE 754) maximum value when ++// inputs are NaN or signed-zero values. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_ss + FORCE_INLINE __m128 _mm_max_ss(__m128 a, __m128 b) + { + float32_t value = vgetq_lane_f32(_mm_max_ps(a, b), 0); +@@ -2155,22 +1956,18 @@ FORCE_INLINE __m128 _mm_max_ss(__m128 a, __m128 b) + + // Compare packed signed 16-bit integers in a and b, and store packed minimum + // values in dst. +-// +-// FOR j := 0 to 3 +-// i := j*16 +-// dst[i+15:i] := MIN(a[i+15:i], b[i+15:i]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_pi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_min_pi16 + FORCE_INLINE __m64 _mm_min_pi16(__m64 a, __m64 b) + { + return vreinterpret_m64_s16( + vmin_s16(vreinterpret_s16_m64(a), vreinterpret_s16_m64(b))); + } + +-// Computes the minima of the four single-precision, floating-point values of a +-// and b. +-// https://msdn.microsoft.com/en-us/library/vstudio/wh13kadz(v=vs.100).aspx ++// Compare packed single-precision (32-bit) floating-point elements in a and b, ++// and store packed minimum values in dst. dst does not follow the IEEE Standard ++// for Floating-Point Arithmetic (IEEE 754) minimum value when inputs are NaN or ++// signed-zero values. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_min_ps + FORCE_INLINE __m128 _mm_min_ps(__m128 a, __m128 b) + { + #if SSE2NEON_PRECISE_MINMAX +@@ -2185,22 +1982,19 @@ FORCE_INLINE __m128 _mm_min_ps(__m128 a, __m128 b) + + // Compare packed unsigned 8-bit integers in a and b, and store packed minimum + // values in dst. +-// +-// FOR j := 0 to 7 +-// i := j*8 +-// dst[i+7:i] := MIN(a[i+7:i], b[i+7:i]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_pu8 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_min_pu8 + FORCE_INLINE __m64 _mm_min_pu8(__m64 a, __m64 b) + { + return vreinterpret_m64_u8( + vmin_u8(vreinterpret_u8_m64(a), vreinterpret_u8_m64(b))); + } + +-// Computes the minimum of the two lower scalar single-precision floating point +-// values of a and b. +-// https://msdn.microsoft.com/en-us/library/0a9y7xaa(v=vs.100).aspx ++// Compare the lower single-precision (32-bit) floating-point elements in a and ++// b, store the minimum value in the lower element of dst, and copy the upper 3 ++// packed elements from a to the upper element of dst. dst does not follow the ++// IEEE Standard for Floating-Point Arithmetic (IEEE 754) minimum value when ++// inputs are NaN or signed-zero values. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_min_ss + FORCE_INLINE __m128 _mm_min_ss(__m128 a, __m128 b) + { + float32_t value = vgetq_lane_f32(_mm_min_ps(a, b), 0); +@@ -2208,8 +2002,10 @@ FORCE_INLINE __m128 _mm_min_ss(__m128 a, __m128 b) + vsetq_lane_f32(value, vreinterpretq_f32_m128(a), 0)); + } + +-// Sets the low word to the single-precision, floating-point value of b +-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/35hdzazd(v=vs.100) ++// Move the lower single-precision (32-bit) floating-point element from b to the ++// lower element of dst, and copy the upper 3 packed elements from a to the ++// upper elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_move_ss + FORCE_INLINE __m128 _mm_move_ss(__m128 a, __m128 b) + { + return vreinterpretq_m128_f32( +@@ -2217,25 +2013,26 @@ FORCE_INLINE __m128 _mm_move_ss(__m128 a, __m128 b) + vreinterpretq_f32_m128(a), 0)); + } + +-// Moves the upper two values of B into the lower two values of A. +-// +-// r3 := a3 +-// r2 := a2 +-// r1 := b3 +-// r0 := b2 +-FORCE_INLINE __m128 _mm_movehl_ps(__m128 __A, __m128 __B) +-{ +- float32x2_t a32 = vget_high_f32(vreinterpretq_f32_m128(__A)); +- float32x2_t b32 = vget_high_f32(vreinterpretq_f32_m128(__B)); ++// Move the upper 2 single-precision (32-bit) floating-point elements from b to ++// the lower 2 elements of dst, and copy the upper 2 elements from a to the ++// upper 2 elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_movehl_ps ++FORCE_INLINE __m128 _mm_movehl_ps(__m128 a, __m128 b) ++{ ++#if defined(aarch64__) ++ return vreinterpretq_m128_u64( ++ vzip2q_u64(vreinterpretq_u64_m128(b), vreinterpretq_u64_m128(a))); ++#else ++ float32x2_t a32 = vget_high_f32(vreinterpretq_f32_m128(a)); ++ float32x2_t b32 = vget_high_f32(vreinterpretq_f32_m128(b)); + return vreinterpretq_m128_f32(vcombine_f32(b32, a32)); ++#endif + } + +-// Moves the lower two values of B into the upper two values of A. +-// +-// r3 := b1 +-// r2 := b0 +-// r1 := a1 +-// r0 := a0 ++// Move the lower 2 single-precision (32-bit) floating-point elements from b to ++// the upper 2 elements of dst, and copy the lower 2 elements from a to the ++// lower 2 elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_movelh_ps + FORCE_INLINE __m128 _mm_movelh_ps(__m128 __A, __m128 __B) + { + float32x2_t a10 = vget_low_f32(vreinterpretq_f32_m128(__A)); +@@ -2245,7 +2042,7 @@ FORCE_INLINE __m128 _mm_movelh_ps(__m128 __A, __m128 __B) + + // Create mask from the most significant bit of each 8-bit element in a, and + // store the result in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_movemask_pi8 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_movemask_pi8 + FORCE_INLINE int _mm_movemask_pi8(__m64 a) + { + uint8x8_t input = vreinterpret_u8_m64(a); +@@ -2264,10 +2061,9 @@ FORCE_INLINE int _mm_movemask_pi8(__m64 a) + #endif + } + +-// NEON does not provide this method +-// Creates a 4-bit mask from the most significant bits of the four +-// single-precision, floating-point values. +-// https://msdn.microsoft.com/en-us/library/vstudio/4490ys29(v=vs.100).aspx ++// Set each bit of mask dst based on the most significant bit of the ++// corresponding packed single-precision (32-bit) floating-point element in a. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_movemask_ps + FORCE_INLINE int _mm_movemask_ps(__m128 a) + { + uint32x4_t input = vreinterpretq_u32_m128(a); +@@ -2288,14 +2084,9 @@ FORCE_INLINE int _mm_movemask_ps(__m128 a) + #endif + } + +-// Multiplies the four single-precision, floating-point values of a and b. +-// +-// r0 := a0 * b0 +-// r1 := a1 * b1 +-// r2 := a2 * b2 +-// r3 := a3 * b3 +-// +-// https://msdn.microsoft.com/en-us/library/vstudio/22kbk6t9(v=vs.100).aspx ++// Multiply packed single-precision (32-bit) floating-point elements in a and b, ++// and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mul_ps + FORCE_INLINE __m128 _mm_mul_ps(__m128 a, __m128 b) + { + return vreinterpretq_m128_f32( +@@ -2305,11 +2096,7 @@ FORCE_INLINE __m128 _mm_mul_ps(__m128 a, __m128 b) + // Multiply the lower single-precision (32-bit) floating-point element in a and + // b, store the result in the lower element of dst, and copy the upper 3 packed + // elements from a to the upper elements of dst. +-// +-// dst[31:0] := a[31:0] * b[31:0] +-// dst[127:32] := a[127:32] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mul_ss ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mul_ss + FORCE_INLINE __m128 _mm_mul_ss(__m128 a, __m128 b) + { + return _mm_move_ss(a, _mm_mul_ps(a, b)); +@@ -2318,16 +2105,16 @@ FORCE_INLINE __m128 _mm_mul_ss(__m128 a, __m128 b) + // Multiply the packed unsigned 16-bit integers in a and b, producing + // intermediate 32-bit integers, and store the high 16 bits of the intermediate + // integers in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mulhi_pu16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mulhi_pu16 + FORCE_INLINE __m64 _mm_mulhi_pu16(__m64 a, __m64 b) + { + return vreinterpret_m64_u16(vshrn_n_u32( + vmull_u16(vreinterpret_u16_m64(a), vreinterpret_u16_m64(b)), 16)); + } + +-// Computes the bitwise OR of the four single-precision, floating-point values +-// of a and b. +-// https://msdn.microsoft.com/en-us/library/vstudio/7ctdsyy0(v=vs.100).aspx ++// Compute the bitwise OR of packed single-precision (32-bit) floating-point ++// elements in a and b, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_or_ps + FORCE_INLINE __m128 _mm_or_ps(__m128 a, __m128 b) + { + return vreinterpretq_m128_s32( +@@ -2336,65 +2123,53 @@ FORCE_INLINE __m128 _mm_or_ps(__m128 a, __m128 b) + + // Average packed unsigned 8-bit integers in a and b, and store the results in + // dst. +-// +-// FOR j := 0 to 7 +-// i := j*8 +-// dst[i+7:i] := (a[i+7:i] + b[i+7:i] + 1) >> 1 +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pavgb ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_m_pavgb + #define _m_pavgb(a, b) _mm_avg_pu8(a, b) + + // Average packed unsigned 16-bit integers in a and b, and store the results in + // dst. +-// +-// FOR j := 0 to 3 +-// i := j*16 +-// dst[i+15:i] := (a[i+15:i] + b[i+15:i] + 1) >> 1 +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pavgw ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_m_pavgw + #define _m_pavgw(a, b) _mm_avg_pu16(a, b) + + // Extract a 16-bit integer from a, selected with imm8, and store the result in + // the lower element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pextrw ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_m_pextrw + #define _m_pextrw(a, imm) _mm_extract_pi16(a, imm) + + // Copy a to dst, and insert the 16-bit integer i into dst at the location + // specified by imm8. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=m_pinsrw ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=m_pinsrw + #define _m_pinsrw(a, i, imm) _mm_insert_pi16(a, i, imm) + + // Compare packed signed 16-bit integers in a and b, and store packed maximum + // values in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pmaxsw ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_m_pmaxsw + #define _m_pmaxsw(a, b) _mm_max_pi16(a, b) + + // Compare packed unsigned 8-bit integers in a and b, and store packed maximum + // values in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pmaxub ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_m_pmaxub + #define _m_pmaxub(a, b) _mm_max_pu8(a, b) + + // Compare packed signed 16-bit integers in a and b, and store packed minimum + // values in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pminsw ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_m_pminsw + #define _m_pminsw(a, b) _mm_min_pi16(a, b) + + // Compare packed unsigned 8-bit integers in a and b, and store packed minimum + // values in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pminub ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_m_pminub + #define _m_pminub(a, b) _mm_min_pu8(a, b) + + // Create mask from the most significant bit of each 8-bit element in a, and + // store the result in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pmovmskb ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_m_pmovmskb + #define _m_pmovmskb(a) _mm_movemask_pi8(a) + + // Multiply the packed unsigned 16-bit integers in a and b, producing + // intermediate 32-bit integers, and store the high 16 bits of the intermediate + // integers in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pmulhuw ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_m_pmulhuw + #define _m_pmulhuw(a, b) _mm_mulhi_pu16(a, b) + + // Fetch the line of data from memory that contains address p to a location in +@@ -2422,26 +2197,22 @@ FORCE_INLINE void _mm_prefetch(char const *p, int i) + // b, then horizontally sum each consecutive 8 differences to produce four + // unsigned 16-bit integers, and pack these unsigned 16-bit integers in the low + // 16 bits of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=m_psadbw ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=m_psadbw + #define _m_psadbw(a, b) _mm_sad_pu8(a, b) + + // Shuffle 16-bit integers in a using the control in imm8, and store the results + // in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pshufw ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_m_pshufw + #define _m_pshufw(a, imm) _mm_shuffle_pi16(a, imm) + + // Compute the approximate reciprocal of packed single-precision (32-bit) + // floating-point elements in a, and store the results in dst. The maximum + // relative error for this approximation is less than 1.5*2^-12. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_rcp_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_rcp_ps + FORCE_INLINE __m128 _mm_rcp_ps(__m128 in) + { + float32x4_t recip = vrecpeq_f32(vreinterpretq_f32_m128(in)); + recip = vmulq_f32(recip, vrecpsq_f32(recip, vreinterpretq_f32_m128(in))); +-#if SSE2NEON_PRECISE_DIV +- // Additional Netwon-Raphson iteration for accuracy +- recip = vmulq_f32(recip, vrecpsq_f32(recip, vreinterpretq_f32_m128(in))); +-#endif + return vreinterpretq_m128_f32(recip); + } + +@@ -2449,30 +2220,21 @@ FORCE_INLINE __m128 _mm_rcp_ps(__m128 in) + // floating-point element in a, store the result in the lower element of dst, + // and copy the upper 3 packed elements from a to the upper elements of dst. The + // maximum relative error for this approximation is less than 1.5*2^-12. +-// +-// dst[31:0] := (1.0 / a[31:0]) +-// dst[127:32] := a[127:32] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_rcp_ss ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_rcp_ss + FORCE_INLINE __m128 _mm_rcp_ss(__m128 a) + { + return _mm_move_ss(a, _mm_rcp_ps(a)); + } + +-// Computes the approximations of the reciprocal square roots of the four +-// single-precision floating point values of in. +-// The current precision is 1% error. +-// https://msdn.microsoft.com/en-us/library/22hfsh53(v=vs.100).aspx ++// Compute the approximate reciprocal square root of packed single-precision ++// (32-bit) floating-point elements in a, and store the results in dst. The ++// maximum relative error for this approximation is less than 1.5*2^-12. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_rsqrt_ps + FORCE_INLINE __m128 _mm_rsqrt_ps(__m128 in) + { + float32x4_t out = vrsqrteq_f32(vreinterpretq_f32_m128(in)); +-#if SSE2NEON_PRECISE_SQRT +- // Additional Netwon-Raphson iteration for accuracy + out = vmulq_f32( + out, vrsqrtsq_f32(vmulq_f32(vreinterpretq_f32_m128(in), out), out)); +- out = vmulq_f32( +- out, vrsqrtsq_f32(vmulq_f32(vreinterpretq_f32_m128(in), out), out)); +-#endif + return vreinterpretq_m128_f32(out); + } + +@@ -2480,7 +2242,7 @@ FORCE_INLINE __m128 _mm_rsqrt_ps(__m128 in) + // (32-bit) floating-point element in a, store the result in the lower element + // of dst, and copy the upper 3 packed elements from a to the upper elements of + // dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_rsqrt_ss ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_rsqrt_ss + FORCE_INLINE __m128 _mm_rsqrt_ss(__m128 in) + { + return vsetq_lane_f32(vgetq_lane_f32(_mm_rsqrt_ps(in), 0), in, 0); +@@ -2490,7 +2252,7 @@ FORCE_INLINE __m128 _mm_rsqrt_ss(__m128 in) + // b, then horizontally sum each consecutive 8 differences to produce four + // unsigned 16-bit integers, and pack these unsigned 16-bit integers in the low + // 16 bits of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sad_pu8 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sad_pu8 + FORCE_INLINE __m64 _mm_sad_pu8(__m64 a, __m64 b) + { + uint64x1_t t = vpaddl_u32(vpaddl_u16( +@@ -2502,7 +2264,7 @@ FORCE_INLINE __m64 _mm_sad_pu8(__m64 a, __m64 b) + // Macro: Set the flush zero bits of the MXCSR control and status register to + // the value in unsigned 32-bit integer a. The flush zero may contain any of the + // following flags: _MM_FLUSH_ZERO_ON or _MM_FLUSH_ZERO_OFF +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_MM_SET_FLUSH_ZERO_MODE ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_MM_SET_FLUSH_ZERO_MODE + FORCE_INLINE void _sse2neon_mm_set_flush_zero_mode(unsigned int flag) + { + // AArch32 Advanced SIMD arithmetic always uses the Flush-to-zero setting, +@@ -2531,16 +2293,18 @@ FORCE_INLINE void _sse2neon_mm_set_flush_zero_mode(unsigned int flag) + #endif + } + +-// Sets the four single-precision, floating-point values to the four inputs. +-// https://msdn.microsoft.com/en-us/library/vstudio/afh0zf75(v=vs.100).aspx ++// Set packed single-precision (32-bit) floating-point elements in dst with the ++// supplied values. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set_ps + FORCE_INLINE __m128 _mm_set_ps(float w, float z, float y, float x) + { + float ALIGN_STRUCT(16) data[4] = {x, y, z, w}; + return vreinterpretq_m128_f32(vld1q_f32(data)); + } + +-// Sets the four single-precision, floating-point values to w. +-// https://msdn.microsoft.com/en-us/library/vstudio/2x1se8ha(v=vs.100).aspx ++// Broadcast single-precision (32-bit) floating-point value a to all elements of ++// dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set_ps1 + FORCE_INLINE __m128 _mm_set_ps1(float _w) + { + return vreinterpretq_m128_f32(vdupq_n_f32(_w)); +@@ -2550,7 +2314,7 @@ FORCE_INLINE __m128 _mm_set_ps1(float _w) + // the value in unsigned 32-bit integer a. The rounding mode may contain any of + // the following flags: _MM_ROUND_NEAREST, _MM_ROUND_DOWN, _MM_ROUND_UP, + // _MM_ROUND_TOWARD_ZERO +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_MM_SET_ROUNDING_MODE ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_MM_SET_ROUNDING_MODE + FORCE_INLINE void _MM_SET_ROUNDING_MODE(int rounding) + { + union { +@@ -2595,45 +2359,48 @@ FORCE_INLINE void _MM_SET_ROUNDING_MODE(int rounding) + + // Copy single-precision (32-bit) floating-point element a to the lower element + // of dst, and zero the upper 3 elements. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_set_ss ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set_ss + FORCE_INLINE __m128 _mm_set_ss(float a) + { + return vreinterpretq_m128_f32(vsetq_lane_f32(a, vdupq_n_f32(0), 0)); + } + +-// Sets the four single-precision, floating-point values to w. +-// +-// r0 := r1 := r2 := r3 := w +-// +-// https://msdn.microsoft.com/en-us/library/vstudio/2x1se8ha(v=vs.100).aspx ++// Broadcast single-precision (32-bit) floating-point value a to all elements of ++// dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set1_ps + FORCE_INLINE __m128 _mm_set1_ps(float _w) + { + return vreinterpretq_m128_f32(vdupq_n_f32(_w)); + } + ++// Set the MXCSR control and status register with the value in unsigned 32-bit ++// integer a. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_setcsr + // FIXME: _mm_setcsr() implementation supports changing the rounding mode only. + FORCE_INLINE void _mm_setcsr(unsigned int a) + { + _MM_SET_ROUNDING_MODE(a); + } + ++// Get the unsigned 32-bit value of the MXCSR control and status register. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_getcsr + // FIXME: _mm_getcsr() implementation supports reading the rounding mode only. + FORCE_INLINE unsigned int _mm_getcsr() + { + return _MM_GET_ROUNDING_MODE(); + } + +-// Sets the four single-precision, floating-point values to the four inputs in +-// reverse order. +-// https://msdn.microsoft.com/en-us/library/vstudio/d2172ct3(v=vs.100).aspx ++// Set packed single-precision (32-bit) floating-point elements in dst with the ++// supplied values in reverse order. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_setr_ps + FORCE_INLINE __m128 _mm_setr_ps(float w, float z, float y, float x) + { + float ALIGN_STRUCT(16) data[4] = {w, z, y, x}; + return vreinterpretq_m128_f32(vld1q_f32(data)); + } + +-// Clears the four single-precision, floating-point values. +-// https://msdn.microsoft.com/en-us/library/vstudio/tk1t2tbz(v=vs.100).aspx ++// Return vector of type __m128 with all elements set to zero. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_setzero_ps + FORCE_INLINE __m128 _mm_setzero_ps(void) + { + return vreinterpretq_m128_f32(vdupq_n_f32(0)); +@@ -2641,7 +2408,7 @@ FORCE_INLINE __m128 _mm_setzero_ps(void) + + // Shuffle 16-bit integers in a using the control in imm8, and store the results + // in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_shuffle_pi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_shuffle_pi16 + #ifdef _sse2neon_shuffle + #define _mm_shuffle_pi16(a, imm) \ + __extension__({ \ +@@ -2775,19 +2542,17 @@ FORCE_INLINE void _mm_lfence(void) + }) + #endif + +-// Computes the approximations of square roots of the four single-precision, +-// floating-point values of a. First computes reciprocal square roots and then +-// reciprocals of the four values. +-// +-// r0 := sqrt(a0) +-// r1 := sqrt(a1) +-// r2 := sqrt(a2) +-// r3 := sqrt(a3) +-// +-// https://msdn.microsoft.com/en-us/library/vstudio/8z67bwwk(v=vs.100).aspx ++// Compute the square root of packed single-precision (32-bit) floating-point ++// elements in a, and store the results in dst. ++// Due to ARMv7-A NEON's lack of a precise square root intrinsic, we implement ++// square root by multiplying input in with its reciprocal square root before ++// using the Newton-Raphson method to approximate the results. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sqrt_ps + FORCE_INLINE __m128 _mm_sqrt_ps(__m128 in) + { +-#if SSE2NEON_PRECISE_SQRT ++#if defined(__aarch64__) ++ return vreinterpretq_m128_f32(vsqrtq_f32(vreinterpretq_f32_m128(in))); ++#else + float32x4_t recip = vrsqrteq_f32(vreinterpretq_f32_m128(in)); + + // Test for vrsqrteq_f32(0) -> positive infinity case. +@@ -2798,28 +2563,23 @@ FORCE_INLINE __m128 _mm_sqrt_ps(__m128 in) + recip = vreinterpretq_f32_u32( + vandq_u32(vmvnq_u32(div_by_zero), vreinterpretq_u32_f32(recip))); + +- // Additional Netwon-Raphson iteration for accuracy + recip = vmulq_f32( + vrsqrtsq_f32(vmulq_f32(recip, recip), vreinterpretq_f32_m128(in)), + recip); ++ // Additional Netwon-Raphson iteration for accuracy + recip = vmulq_f32( + vrsqrtsq_f32(vmulq_f32(recip, recip), vreinterpretq_f32_m128(in)), + recip); + + // sqrt(s) = s * 1/sqrt(s) + return vreinterpretq_m128_f32(vmulq_f32(vreinterpretq_f32_m128(in), recip)); +-#elif defined(__aarch64__) +- return vreinterpretq_m128_f32(vsqrtq_f32(vreinterpretq_f32_m128(in))); +-#else +- float32x4_t recipsq = vrsqrteq_f32(vreinterpretq_f32_m128(in)); +- float32x4_t sq = vrecpeq_f32(recipsq); +- return vreinterpretq_m128_f32(sq); + #endif + } + +-// Computes the approximation of the square root of the scalar single-precision +-// floating point value of in. +-// https://msdn.microsoft.com/en-us/library/ahfsc22d(v=vs.100).aspx ++// Compute the square root of the lower single-precision (32-bit) floating-point ++// element in a, store the result in the lower element of dst, and copy the ++// upper 3 packed elements from a to the upper elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sqrt_ss + FORCE_INLINE __m128 _mm_sqrt_ss(__m128 in) + { + float32_t value = +@@ -2828,8 +2588,10 @@ FORCE_INLINE __m128 _mm_sqrt_ss(__m128 in) + vsetq_lane_f32(value, vreinterpretq_f32_m128(in), 0)); + } + +-// Stores four single-precision, floating-point values. +-// https://msdn.microsoft.com/en-us/library/vstudio/s3h4ay6y(v=vs.100).aspx ++// Store 128-bits (composed of 4 packed single-precision (32-bit) floating-point ++// elements) from a into memory. mem_addr must be aligned on a 16-byte boundary ++// or a general-protection exception may be generated. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_store_ps + FORCE_INLINE void _mm_store_ps(float *p, __m128 a) + { + vst1q_f32(p, vreinterpretq_f32_m128(a)); +@@ -2838,21 +2600,16 @@ FORCE_INLINE void _mm_store_ps(float *p, __m128 a) + // Store the lower single-precision (32-bit) floating-point element from a into + // 4 contiguous elements in memory. mem_addr must be aligned on a 16-byte + // boundary or a general-protection exception may be generated. +-// +-// MEM[mem_addr+31:mem_addr] := a[31:0] +-// MEM[mem_addr+63:mem_addr+32] := a[31:0] +-// MEM[mem_addr+95:mem_addr+64] := a[31:0] +-// MEM[mem_addr+127:mem_addr+96] := a[31:0] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_store_ps1 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_store_ps1 + FORCE_INLINE void _mm_store_ps1(float *p, __m128 a) + { + float32_t a0 = vgetq_lane_f32(vreinterpretq_f32_m128(a), 0); + vst1q_f32(p, vdupq_n_f32(a0)); + } + +-// Stores the lower single - precision, floating - point value. +-// https://msdn.microsoft.com/en-us/library/tzz10fbx(v=vs.100).aspx ++// Store the lower single-precision (32-bit) floating-point element from a into ++// memory. mem_addr does not need to be aligned on any particular boundary. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_store_ss + FORCE_INLINE void _mm_store_ss(float *p, __m128 a) + { + vst1q_lane_f32(p, vreinterpretq_f32_m128(a), 0); +@@ -2861,34 +2618,20 @@ FORCE_INLINE void _mm_store_ss(float *p, __m128 a) + // Store the lower single-precision (32-bit) floating-point element from a into + // 4 contiguous elements in memory. mem_addr must be aligned on a 16-byte + // boundary or a general-protection exception may be generated. +-// +-// MEM[mem_addr+31:mem_addr] := a[31:0] +-// MEM[mem_addr+63:mem_addr+32] := a[31:0] +-// MEM[mem_addr+95:mem_addr+64] := a[31:0] +-// MEM[mem_addr+127:mem_addr+96] := a[31:0] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_store1_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_store1_ps + #define _mm_store1_ps _mm_store_ps1 + +-// Stores the upper two single-precision, floating-point values of a to the +-// address p. +-// +-// *p0 := a2 +-// *p1 := a3 +-// +-// https://msdn.microsoft.com/en-us/library/a7525fs8(v%3dvs.90).aspx ++// Store the upper 2 single-precision (32-bit) floating-point elements from a ++// into memory. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storeh_pi + FORCE_INLINE void _mm_storeh_pi(__m64 *p, __m128 a) + { + *p = vreinterpret_m64_f32(vget_high_f32(a)); + } + +-// Stores the lower two single-precision floating point values of a to the +-// address p. +-// +-// *p0 := a0 +-// *p1 := a1 +-// +-// https://msdn.microsoft.com/en-us/library/h54t98ks(v=vs.90).aspx ++// Store the lower 2 single-precision (32-bit) floating-point elements from a ++// into memory. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storel_pi + FORCE_INLINE void _mm_storel_pi(__m64 *p, __m128 a) + { + *p = vreinterpret_m64_f32(vget_low_f32(a)); +@@ -2897,13 +2640,7 @@ FORCE_INLINE void _mm_storel_pi(__m64 *p, __m128 a) + // Store 4 single-precision (32-bit) floating-point elements from a into memory + // in reverse order. mem_addr must be aligned on a 16-byte boundary or a + // general-protection exception may be generated. +-// +-// MEM[mem_addr+31:mem_addr] := a[127:96] +-// MEM[mem_addr+63:mem_addr+32] := a[95:64] +-// MEM[mem_addr+95:mem_addr+64] := a[63:32] +-// MEM[mem_addr+127:mem_addr+96] := a[31:0] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storer_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storer_ps + FORCE_INLINE void _mm_storer_ps(float *p, __m128 a) + { + float32x4_t tmp = vrev64q_f32(vreinterpretq_f32_m128(a)); +@@ -2911,22 +2648,24 @@ FORCE_INLINE void _mm_storer_ps(float *p, __m128 a) + vst1q_f32(p, rev); + } + +-// Stores four single-precision, floating-point values. +-// https://msdn.microsoft.com/en-us/library/44e30x22(v=vs.100).aspx ++// Store 128-bits (composed of 4 packed single-precision (32-bit) floating-point ++// elements) from a into memory. mem_addr does not need to be aligned on any ++// particular boundary. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storeu_ps + FORCE_INLINE void _mm_storeu_ps(float *p, __m128 a) + { + vst1q_f32(p, vreinterpretq_f32_m128(a)); + } + + // Stores 16-bits of integer data a at the address p. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storeu_si16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storeu_si16 + FORCE_INLINE void _mm_storeu_si16(void *p, __m128i a) + { + vst1q_lane_s16((int16_t *) p, vreinterpretq_s16_m128i(a), 0); + } + + // Stores 64-bits of integer data a at the address p. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storeu_si64 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storeu_si64 + FORCE_INLINE void _mm_storeu_si64(void *p, __m128i a) + { + vst1q_lane_s64((int64_t *) p, vreinterpretq_s64_m128i(a), 0); +@@ -2934,7 +2673,7 @@ FORCE_INLINE void _mm_storeu_si64(void *p, __m128i a) + + // Store 64-bits of integer data from a into memory using a non-temporal memory + // hint. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_stream_pi ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_stream_pi + FORCE_INLINE void _mm_stream_pi(__m64 *p, __m64 a) + { + vst1_s64((int64_t *) p, vreinterpret_s64_m64(a)); +@@ -2942,7 +2681,7 @@ FORCE_INLINE void _mm_stream_pi(__m64 *p, __m64 a) + + // Store 128-bits (composed of 4 packed single-precision (32-bit) floating- + // point elements) from a into memory using a non-temporal memory hint. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_stream_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_stream_ps + FORCE_INLINE void _mm_stream_ps(float *p, __m128 a) + { + #if __has_builtin(__builtin_nontemporal_store) +@@ -2952,14 +2691,10 @@ FORCE_INLINE void _mm_stream_ps(float *p, __m128 a) + #endif + } + +-// Subtracts the four single-precision, floating-point values of a and b. +-// +-// r0 := a0 - b0 +-// r1 := a1 - b1 +-// r2 := a2 - b2 +-// r3 := a3 - b3 +-// +-// https://msdn.microsoft.com/en-us/library/vstudio/1zad2k61(v=vs.100).aspx ++// Subtract packed single-precision (32-bit) floating-point elements in b from ++// packed single-precision (32-bit) floating-point elements in a, and store the ++// results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sub_ps + FORCE_INLINE __m128 _mm_sub_ps(__m128 a, __m128 b) + { + return vreinterpretq_m128_f32( +@@ -2970,11 +2705,7 @@ FORCE_INLINE __m128 _mm_sub_ps(__m128 a, __m128 b) + // the lower single-precision (32-bit) floating-point element in a, store the + // result in the lower element of dst, and copy the upper 3 packed elements from + // a to the upper elements of dst. +-// +-// dst[31:0] := a[31:0] - b[31:0] +-// dst[127:32] := a[127:32] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sub_ss ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sub_ss + FORCE_INLINE __m128 _mm_sub_ss(__m128 a, __m128 b) + { + return _mm_move_ss(a, _mm_sub_ps(a, b)); +@@ -2983,7 +2714,7 @@ FORCE_INLINE __m128 _mm_sub_ss(__m128 a, __m128 b) + // Macro: Transpose the 4x4 matrix formed by the 4 rows of single-precision + // (32-bit) floating-point elements in row0, row1, row2, and row3, and store the + // transposed matrix in these vectors (row0 now contains column 0, etc.). +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=MM_TRANSPOSE4_PS ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=MM_TRANSPOSE4_PS + #define _MM_TRANSPOSE4_PS(row0, row1, row2, row3) \ + do { \ + float32x4x2_t ROW01 = vtrnq_f32(row0, row1); \ +@@ -3008,7 +2739,7 @@ FORCE_INLINE __m128 _mm_sub_ss(__m128 a, __m128 b) + #define _mm_ucomineq_ss _mm_comineq_ss + + // Return vector of type __m128i with undefined elements. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=mm_undefined_si128 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=mm_undefined_si128 + FORCE_INLINE __m128i _mm_undefined_si128(void) + { + #if defined(__GNUC__) || defined(__clang__) +@@ -3023,7 +2754,7 @@ FORCE_INLINE __m128i _mm_undefined_si128(void) + } + + // Return vector of type __m128 with undefined elements. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_undefined_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_undefined_ps + FORCE_INLINE __m128 _mm_undefined_ps(void) + { + #if defined(__GNUC__) || defined(__clang__) +@@ -3037,15 +2768,9 @@ FORCE_INLINE __m128 _mm_undefined_ps(void) + #endif + } + +-// Selects and interleaves the upper two single-precision, floating-point values +-// from a and b. +-// +-// r0 := a2 +-// r1 := b2 +-// r2 := a3 +-// r3 := b3 +-// +-// https://msdn.microsoft.com/en-us/library/skccxx7d%28v=vs.90%29.aspx ++// Unpack and interleave single-precision (32-bit) floating-point elements from ++// the high half a and b, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpackhi_ps + FORCE_INLINE __m128 _mm_unpackhi_ps(__m128 a, __m128 b) + { + #if defined(__aarch64__) +@@ -3059,15 +2784,9 @@ FORCE_INLINE __m128 _mm_unpackhi_ps(__m128 a, __m128 b) + #endif + } + +-// Selects and interleaves the lower two single-precision, floating-point values +-// from a and b. +-// +-// r0 := a0 +-// r1 := b0 +-// r2 := a1 +-// r3 := b1 +-// +-// https://msdn.microsoft.com/en-us/library/25st103b%28v=vs.90%29.aspx ++// Unpack and interleave single-precision (32-bit) floating-point elements from ++// the low half of a and b, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpacklo_ps + FORCE_INLINE __m128 _mm_unpacklo_ps(__m128 a, __m128 b) + { + #if defined(__aarch64__) +@@ -3081,9 +2800,9 @@ FORCE_INLINE __m128 _mm_unpacklo_ps(__m128 a, __m128 b) + #endif + } + +-// Computes bitwise EXOR (exclusive-or) of the four single-precision, +-// floating-point values of a and b. +-// https://msdn.microsoft.com/en-us/library/ss6k3wk8(v=vs.100).aspx ++// Compute the bitwise XOR of packed single-precision (32-bit) floating-point ++// elements in a and b, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_xor_ps + FORCE_INLINE __m128 _mm_xor_ps(__m128 a, __m128 b) + { + return vreinterpretq_m128_s32( +@@ -3092,42 +2811,32 @@ FORCE_INLINE __m128 _mm_xor_ps(__m128 a, __m128 b) + + /* SSE2 */ + +-// Adds the 8 signed or unsigned 16-bit integers in a to the 8 signed or +-// unsigned 16-bit integers in b. +-// https://msdn.microsoft.com/en-us/library/fceha5k4(v=vs.100).aspx ++// Add packed 16-bit integers in a and b, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_add_epi16 + FORCE_INLINE __m128i _mm_add_epi16(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s16( + vaddq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); + } + +-// Adds the 4 signed or unsigned 32-bit integers in a to the 4 signed or +-// unsigned 32-bit integers in b. +-// +-// r0 := a0 + b0 +-// r1 := a1 + b1 +-// r2 := a2 + b2 +-// r3 := a3 + b3 +-// +-// https://msdn.microsoft.com/en-us/library/vstudio/09xs4fkk(v=vs.100).aspx ++// Add packed 32-bit integers in a and b, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_add_epi32 + FORCE_INLINE __m128i _mm_add_epi32(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s32( + vaddq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); + } + +-// Adds the 4 signed or unsigned 64-bit integers in a to the 4 signed or +-// unsigned 32-bit integers in b. +-// https://msdn.microsoft.com/en-us/library/vstudio/09xs4fkk(v=vs.100).aspx ++// Add packed 64-bit integers in a and b, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_add_epi64 + FORCE_INLINE __m128i _mm_add_epi64(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s64( + vaddq_s64(vreinterpretq_s64_m128i(a), vreinterpretq_s64_m128i(b))); + } + +-// Adds the 16 signed or unsigned 8-bit integers in a to the 16 signed or +-// unsigned 8-bit integers in b. +-// https://technet.microsoft.com/en-us/subscriptions/yc7tcyzs(v=vs.90) ++// Add packed 8-bit integers in a and b, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_add_epi8 + FORCE_INLINE __m128i _mm_add_epi8(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s8( +@@ -3136,7 +2845,7 @@ FORCE_INLINE __m128i _mm_add_epi8(__m128i a, __m128i b) + + // Add packed double-precision (64-bit) floating-point elements in a and b, and + // store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_add_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_add_pd + FORCE_INLINE __m128d _mm_add_pd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -3155,11 +2864,7 @@ FORCE_INLINE __m128d _mm_add_pd(__m128d a, __m128d b) + // Add the lower double-precision (64-bit) floating-point element in a and b, + // store the result in the lower element of dst, and copy the upper element from + // a to the upper element of dst. +-// +-// dst[63:0] := a[63:0] + b[63:0] +-// dst[127:64] := a[127:64] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_add_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_add_sd + FORCE_INLINE __m128d _mm_add_sd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -3175,25 +2880,16 @@ FORCE_INLINE __m128d _mm_add_sd(__m128d a, __m128d b) + } + + // Add 64-bit integers a and b, and store the result in dst. +-// +-// dst[63:0] := a[63:0] + b[63:0] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_add_si64 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_add_si64 + FORCE_INLINE __m64 _mm_add_si64(__m64 a, __m64 b) + { + return vreinterpret_m64_s64( + vadd_s64(vreinterpret_s64_m64(a), vreinterpret_s64_m64(b))); + } + +-// Adds the 8 signed 16-bit integers in a to the 8 signed 16-bit integers in b +-// and saturates. +-// +-// r0 := SignedSaturate(a0 + b0) +-// r1 := SignedSaturate(a1 + b1) +-// ... +-// r7 := SignedSaturate(a7 + b7) +-// +-// https://msdn.microsoft.com/en-us/library/1a306ef8(v=vs.100).aspx ++// Add packed signed 16-bit integers in a and b using saturation, and store the ++// results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_adds_epi16 + FORCE_INLINE __m128i _mm_adds_epi16(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s16( +@@ -3202,13 +2898,7 @@ FORCE_INLINE __m128i _mm_adds_epi16(__m128i a, __m128i b) + + // Add packed signed 8-bit integers in a and b using saturation, and store the + // results in dst. +-// +-// FOR j := 0 to 15 +-// i := j*8 +-// dst[i+7:i] := Saturate8( a[i+7:i] + b[i+7:i] ) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_adds_epi8 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_adds_epi8 + FORCE_INLINE __m128i _mm_adds_epi8(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s8( +@@ -3217,16 +2907,16 @@ FORCE_INLINE __m128i _mm_adds_epi8(__m128i a, __m128i b) + + // Add packed unsigned 16-bit integers in a and b using saturation, and store + // the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_adds_epu16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_adds_epu16 + FORCE_INLINE __m128i _mm_adds_epu16(__m128i a, __m128i b) + { + return vreinterpretq_m128i_u16( + vqaddq_u16(vreinterpretq_u16_m128i(a), vreinterpretq_u16_m128i(b))); + } + +-// Adds the 16 unsigned 8-bit integers in a to the 16 unsigned 8-bit integers in +-// b and saturates.. +-// https://msdn.microsoft.com/en-us/library/9hahyddy(v=vs.100).aspx ++// Add packed unsigned 8-bit integers in a and b using saturation, and store the ++// results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_adds_epu8 + FORCE_INLINE __m128i _mm_adds_epu8(__m128i a, __m128i b) + { + return vreinterpretq_m128i_u8( +@@ -3235,25 +2925,16 @@ FORCE_INLINE __m128i _mm_adds_epu8(__m128i a, __m128i b) + + // Compute the bitwise AND of packed double-precision (64-bit) floating-point + // elements in a and b, and store the results in dst. +-// +-// FOR j := 0 to 1 +-// i := j*64 +-// dst[i+63:i] := a[i+63:i] AND b[i+63:i] +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_and_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_and_pd + FORCE_INLINE __m128d _mm_and_pd(__m128d a, __m128d b) + { + return vreinterpretq_m128d_s64( + vandq_s64(vreinterpretq_s64_m128d(a), vreinterpretq_s64_m128d(b))); + } + +-// Computes the bitwise AND of the 128-bit value in a and the 128-bit value in +-// b. +-// +-// r := a & b +-// +-// https://msdn.microsoft.com/en-us/library/vstudio/6d1txsa8(v=vs.100).aspx ++// Compute the bitwise AND of 128 bits (representing integer data) in a and b, ++// and store the result in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_and_si128 + FORCE_INLINE __m128i _mm_and_si128(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s32( +@@ -3262,13 +2943,7 @@ FORCE_INLINE __m128i _mm_and_si128(__m128i a, __m128i b) + + // Compute the bitwise NOT of packed double-precision (64-bit) floating-point + // elements in a and then AND with b, and store the results in dst. +-// +-// FOR j := 0 to 1 +-// i := j*64 +-// dst[i+63:i] := ((NOT a[i+63:i]) AND b[i+63:i]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_andnot_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_andnot_pd + FORCE_INLINE __m128d _mm_andnot_pd(__m128d a, __m128d b) + { + // *NOTE* argument swap +@@ -3276,12 +2951,9 @@ FORCE_INLINE __m128d _mm_andnot_pd(__m128d a, __m128d b) + vbicq_s64(vreinterpretq_s64_m128d(b), vreinterpretq_s64_m128d(a))); + } + +-// Computes the bitwise AND of the 128-bit value in b and the bitwise NOT of the +-// 128-bit value in a. +-// +-// r := (~a) & b +-// +-// https://msdn.microsoft.com/en-us/library/vstudio/1beaceh8(v=vs.100).aspx ++// Compute the bitwise NOT of 128 bits (representing integer data) in a and then ++// AND with b, and store the result in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_andnot_si128 + FORCE_INLINE __m128i _mm_andnot_si128(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s32( +@@ -3289,30 +2961,18 @@ FORCE_INLINE __m128i _mm_andnot_si128(__m128i a, __m128i b) + vreinterpretq_s32_m128i(a))); // *NOTE* argument swap + } + +-// Computes the average of the 8 unsigned 16-bit integers in a and the 8 +-// unsigned 16-bit integers in b and rounds. +-// +-// r0 := (a0 + b0) / 2 +-// r1 := (a1 + b1) / 2 +-// ... +-// r7 := (a7 + b7) / 2 +-// +-// https://msdn.microsoft.com/en-us/library/vstudio/y13ca3c8(v=vs.90).aspx ++// Average packed unsigned 16-bit integers in a and b, and store the results in ++// dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_avg_epu16 + FORCE_INLINE __m128i _mm_avg_epu16(__m128i a, __m128i b) + { + return (__m128i) vrhaddq_u16(vreinterpretq_u16_m128i(a), + vreinterpretq_u16_m128i(b)); + } + +-// Computes the average of the 16 unsigned 8-bit integers in a and the 16 +-// unsigned 8-bit integers in b and rounds. +-// +-// r0 := (a0 + b0) / 2 +-// r1 := (a1 + b1) / 2 +-// ... +-// r15 := (a15 + b15) / 2 +-// +-// https://msdn.microsoft.com/en-us/library/vstudio/8zwh554a(v%3dvs.90).aspx ++// Average packed unsigned 8-bit integers in a and b, and store the results in ++// dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_avg_epu8 + FORCE_INLINE __m128i _mm_avg_epu8(__m128i a, __m128i b) + { + return vreinterpretq_m128i_u8( +@@ -3321,17 +2981,17 @@ FORCE_INLINE __m128i _mm_avg_epu8(__m128i a, __m128i b) + + // Shift a left by imm8 bytes while shifting in zeros, and store the results in + // dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_bslli_si128 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_bslli_si128 + #define _mm_bslli_si128(a, imm) _mm_slli_si128(a, imm) + + // Shift a right by imm8 bytes while shifting in zeros, and store the results in + // dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_bsrli_si128 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_bsrli_si128 + #define _mm_bsrli_si128(a, imm) _mm_srli_si128(a, imm) + + // Cast vector of type __m128d to type __m128. This intrinsic is only used for + // compilation and does not generate any instructions, thus it has zero latency. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_castpd_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_castpd_ps + FORCE_INLINE __m128 _mm_castpd_ps(__m128d a) + { + return vreinterpretq_m128_s64(vreinterpretq_s64_m128d(a)); +@@ -3339,7 +2999,7 @@ FORCE_INLINE __m128 _mm_castpd_ps(__m128d a) + + // Cast vector of type __m128d to type __m128i. This intrinsic is only used for + // compilation and does not generate any instructions, thus it has zero latency. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_castpd_si128 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_castpd_si128 + FORCE_INLINE __m128i _mm_castpd_si128(__m128d a) + { + return vreinterpretq_m128i_s64(vreinterpretq_s64_m128d(a)); +@@ -3347,15 +3007,15 @@ FORCE_INLINE __m128i _mm_castpd_si128(__m128d a) + + // Cast vector of type __m128 to type __m128d. This intrinsic is only used for + // compilation and does not generate any instructions, thus it has zero latency. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_castps_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_castps_pd + FORCE_INLINE __m128d _mm_castps_pd(__m128 a) + { + return vreinterpretq_m128d_s32(vreinterpretq_s32_m128(a)); + } + +-// Applies a type cast to reinterpret four 32-bit floating point values passed +-// in as a 128-bit parameter as packed 32-bit integers. +-// https://msdn.microsoft.com/en-us/library/bb514099.aspx ++// Cast vector of type __m128 to type __m128i. This intrinsic is only used for ++// compilation and does not generate any instructions, thus it has zero latency. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_castps_si128 + FORCE_INLINE __m128i _mm_castps_si128(__m128 a) + { + return vreinterpretq_m128i_s32(vreinterpretq_s32_m128(a)); +@@ -3363,7 +3023,7 @@ FORCE_INLINE __m128i _mm_castps_si128(__m128 a) + + // Cast vector of type __m128i to type __m128d. This intrinsic is only used for + // compilation and does not generate any instructions, thus it has zero latency. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_castsi128_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_castsi128_pd + FORCE_INLINE __m128d _mm_castsi128_pd(__m128i a) + { + #if defined(__aarch64__) +@@ -3373,9 +3033,9 @@ FORCE_INLINE __m128d _mm_castsi128_pd(__m128i a) + #endif + } + +-// Applies a type cast to reinterpret four 32-bit integers passed in as a +-// 128-bit parameter as packed 32-bit floating point values. +-// https://msdn.microsoft.com/en-us/library/bb514029.aspx ++// Cast vector of type __m128i to type __m128. This intrinsic is only used for ++// compilation and does not generate any instructions, thus it has zero latency. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_castsi128_ps + FORCE_INLINE __m128 _mm_castsi128_ps(__m128i a) + { + return vreinterpretq_m128_s32(vreinterpretq_s32_m128i(a)); +@@ -3406,9 +3066,9 @@ FORCE_INLINE void _mm_clflush(void const *p) + #endif + } + +-// Compares the 8 signed or unsigned 16-bit integers in a and the 8 signed or +-// unsigned 16-bit integers in b for equality. +-// https://msdn.microsoft.com/en-us/library/2ay060te(v=vs.100).aspx ++// Compare packed 16-bit integers in a and b for equality, and store the results ++// in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpeq_epi16 + FORCE_INLINE __m128i _mm_cmpeq_epi16(__m128i a, __m128i b) + { + return vreinterpretq_m128i_u16( +@@ -3416,16 +3076,17 @@ FORCE_INLINE __m128i _mm_cmpeq_epi16(__m128i a, __m128i b) + } + + // Compare packed 32-bit integers in a and b for equality, and store the results +-// in dst ++// in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpeq_epi32 + FORCE_INLINE __m128i _mm_cmpeq_epi32(__m128i a, __m128i b) + { + return vreinterpretq_m128i_u32( + vceqq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); + } + +-// Compares the 16 signed or unsigned 8-bit integers in a and the 16 signed or +-// unsigned 8-bit integers in b for equality. +-// https://msdn.microsoft.com/en-us/library/windows/desktop/bz5xk21a(v=vs.90).aspx ++// Compare packed 8-bit integers in a and b for equality, and store the results ++// in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpeq_epi8 + FORCE_INLINE __m128i _mm_cmpeq_epi8(__m128i a, __m128i b) + { + return vreinterpretq_m128i_u8( +@@ -3434,7 +3095,7 @@ FORCE_INLINE __m128i _mm_cmpeq_epi8(__m128i a, __m128i b) + + // Compare packed double-precision (64-bit) floating-point elements in a and b + // for equality, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpeq_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpeq_pd + FORCE_INLINE __m128d _mm_cmpeq_pd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -3452,7 +3113,7 @@ FORCE_INLINE __m128d _mm_cmpeq_pd(__m128d a, __m128d b) + // Compare the lower double-precision (64-bit) floating-point elements in a and + // b for equality, store the result in the lower element of dst, and copy the + // upper element from a to the upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpeq_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpeq_sd + FORCE_INLINE __m128d _mm_cmpeq_sd(__m128d a, __m128d b) + { + return _mm_move_sd(a, _mm_cmpeq_pd(a, b)); +@@ -3460,7 +3121,7 @@ FORCE_INLINE __m128d _mm_cmpeq_sd(__m128d a, __m128d b) + + // Compare packed double-precision (64-bit) floating-point elements in a and b + // for greater-than-or-equal, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpge_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpge_pd + FORCE_INLINE __m128d _mm_cmpge_pd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -3482,7 +3143,7 @@ FORCE_INLINE __m128d _mm_cmpge_pd(__m128d a, __m128d b) + // Compare the lower double-precision (64-bit) floating-point elements in a and + // b for greater-than-or-equal, store the result in the lower element of dst, + // and copy the upper element from a to the upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpge_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpge_sd + FORCE_INLINE __m128d _mm_cmpge_sd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -3500,39 +3161,27 @@ FORCE_INLINE __m128d _mm_cmpge_sd(__m128d a, __m128d b) + #endif + } + +-// Compares the 8 signed 16-bit integers in a and the 8 signed 16-bit integers +-// in b for greater than. +-// +-// r0 := (a0 > b0) ? 0xffff : 0x0 +-// r1 := (a1 > b1) ? 0xffff : 0x0 +-// ... +-// r7 := (a7 > b7) ? 0xffff : 0x0 +-// +-// https://technet.microsoft.com/en-us/library/xd43yfsa(v=vs.100).aspx ++// Compare packed signed 16-bit integers in a and b for greater-than, and store ++// the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpgt_epi16 + FORCE_INLINE __m128i _mm_cmpgt_epi16(__m128i a, __m128i b) + { + return vreinterpretq_m128i_u16( + vcgtq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); + } + +-// Compares the 4 signed 32-bit integers in a and the 4 signed 32-bit integers +-// in b for greater than. +-// https://msdn.microsoft.com/en-us/library/vstudio/1s9f2z0y(v=vs.100).aspx ++// Compare packed signed 32-bit integers in a and b for greater-than, and store ++// the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpgt_epi32 + FORCE_INLINE __m128i _mm_cmpgt_epi32(__m128i a, __m128i b) + { + return vreinterpretq_m128i_u32( + vcgtq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); + } + +-// Compares the 16 signed 8-bit integers in a and the 16 signed 8-bit integers +-// in b for greater than. +-// +-// r0 := (a0 > b0) ? 0xff : 0x0 +-// r1 := (a1 > b1) ? 0xff : 0x0 +-// ... +-// r15 := (a15 > b15) ? 0xff : 0x0 +-// +-// https://msdn.microsoft.com/zh-tw/library/wf45zt2b(v=vs.100).aspx ++// Compare packed signed 8-bit integers in a and b for greater-than, and store ++// the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpgt_epi8 + FORCE_INLINE __m128i _mm_cmpgt_epi8(__m128i a, __m128i b) + { + return vreinterpretq_m128i_u8( +@@ -3541,7 +3190,7 @@ FORCE_INLINE __m128i _mm_cmpgt_epi8(__m128i a, __m128i b) + + // Compare packed double-precision (64-bit) floating-point elements in a and b + // for greater-than, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpgt_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpgt_pd + FORCE_INLINE __m128d _mm_cmpgt_pd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -3563,7 +3212,7 @@ FORCE_INLINE __m128d _mm_cmpgt_pd(__m128d a, __m128d b) + // Compare the lower double-precision (64-bit) floating-point elements in a and + // b for greater-than, store the result in the lower element of dst, and copy + // the upper element from a to the upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpgt_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpgt_sd + FORCE_INLINE __m128d _mm_cmpgt_sd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -3583,7 +3232,7 @@ FORCE_INLINE __m128d _mm_cmpgt_sd(__m128d a, __m128d b) + + // Compare packed double-precision (64-bit) floating-point elements in a and b + // for less-than-or-equal, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmple_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmple_pd + FORCE_INLINE __m128d _mm_cmple_pd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -3605,7 +3254,7 @@ FORCE_INLINE __m128d _mm_cmple_pd(__m128d a, __m128d b) + // Compare the lower double-precision (64-bit) floating-point elements in a and + // b for less-than-or-equal, store the result in the lower element of dst, and + // copy the upper element from a to the upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmple_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmple_sd + FORCE_INLINE __m128d _mm_cmple_sd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -3623,34 +3272,30 @@ FORCE_INLINE __m128d _mm_cmple_sd(__m128d a, __m128d b) + #endif + } + +-// Compares the 8 signed 16-bit integers in a and the 8 signed 16-bit integers +-// in b for less than. +-// +-// r0 := (a0 < b0) ? 0xffff : 0x0 +-// r1 := (a1 < b1) ? 0xffff : 0x0 +-// ... +-// r7 := (a7 < b7) ? 0xffff : 0x0 +-// +-// https://technet.microsoft.com/en-us/library/t863edb2(v=vs.100).aspx ++// Compare packed signed 16-bit integers in a and b for less-than, and store the ++// results in dst. Note: This intrinsic emits the pcmpgtw instruction with the ++// order of the operands switched. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmplt_epi16 + FORCE_INLINE __m128i _mm_cmplt_epi16(__m128i a, __m128i b) + { + return vreinterpretq_m128i_u16( + vcltq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); + } + +- +-// Compares the 4 signed 32-bit integers in a and the 4 signed 32-bit integers +-// in b for less than. +-// https://msdn.microsoft.com/en-us/library/vstudio/4ak0bf5d(v=vs.100).aspx ++// Compare packed signed 32-bit integers in a and b for less-than, and store the ++// results in dst. Note: This intrinsic emits the pcmpgtd instruction with the ++// order of the operands switched. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmplt_epi32 + FORCE_INLINE __m128i _mm_cmplt_epi32(__m128i a, __m128i b) + { + return vreinterpretq_m128i_u32( + vcltq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); + } + +-// Compares the 16 signed 8-bit integers in a and the 16 signed 8-bit integers +-// in b for lesser than. +-// https://msdn.microsoft.com/en-us/library/windows/desktop/9s46csht(v=vs.90).aspx ++// Compare packed signed 8-bit integers in a and b for less-than, and store the ++// results in dst. Note: This intrinsic emits the pcmpgtb instruction with the ++// order of the operands switched. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmplt_epi8 + FORCE_INLINE __m128i _mm_cmplt_epi8(__m128i a, __m128i b) + { + return vreinterpretq_m128i_u8( +@@ -3659,7 +3304,7 @@ FORCE_INLINE __m128i _mm_cmplt_epi8(__m128i a, __m128i b) + + // Compare packed double-precision (64-bit) floating-point elements in a and b + // for less-than, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmplt_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmplt_pd + FORCE_INLINE __m128d _mm_cmplt_pd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -3681,7 +3326,7 @@ FORCE_INLINE __m128d _mm_cmplt_pd(__m128d a, __m128d b) + // Compare the lower double-precision (64-bit) floating-point elements in a and + // b for less-than, store the result in the lower element of dst, and copy the + // upper element from a to the upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmplt_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmplt_sd + FORCE_INLINE __m128d _mm_cmplt_sd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -3700,7 +3345,7 @@ FORCE_INLINE __m128d _mm_cmplt_sd(__m128d a, __m128d b) + + // Compare packed double-precision (64-bit) floating-point elements in a and b + // for not-equal, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpneq_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpneq_pd + FORCE_INLINE __m128d _mm_cmpneq_pd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -3718,7 +3363,7 @@ FORCE_INLINE __m128d _mm_cmpneq_pd(__m128d a, __m128d b) + // Compare the lower double-precision (64-bit) floating-point elements in a and + // b for not-equal, store the result in the lower element of dst, and copy the + // upper element from a to the upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpneq_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpneq_sd + FORCE_INLINE __m128d _mm_cmpneq_sd(__m128d a, __m128d b) + { + return _mm_move_sd(a, _mm_cmpneq_pd(a, b)); +@@ -3726,7 +3371,7 @@ FORCE_INLINE __m128d _mm_cmpneq_sd(__m128d a, __m128d b) + + // Compare packed double-precision (64-bit) floating-point elements in a and b + // for not-greater-than-or-equal, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpnge_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnge_pd + FORCE_INLINE __m128d _mm_cmpnge_pd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -3751,7 +3396,7 @@ FORCE_INLINE __m128d _mm_cmpnge_pd(__m128d a, __m128d b) + // Compare the lower double-precision (64-bit) floating-point elements in a and + // b for not-greater-than-or-equal, store the result in the lower element of + // dst, and copy the upper element from a to the upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpnge_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnge_sd + FORCE_INLINE __m128d _mm_cmpnge_sd(__m128d a, __m128d b) + { + return _mm_move_sd(a, _mm_cmpnge_pd(a, b)); +@@ -3759,7 +3404,7 @@ FORCE_INLINE __m128d _mm_cmpnge_sd(__m128d a, __m128d b) + + // Compare packed double-precision (64-bit) floating-point elements in a and b + // for not-greater-than, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_cmpngt_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_cmpngt_pd + FORCE_INLINE __m128d _mm_cmpngt_pd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -3784,7 +3429,7 @@ FORCE_INLINE __m128d _mm_cmpngt_pd(__m128d a, __m128d b) + // Compare the lower double-precision (64-bit) floating-point elements in a and + // b for not-greater-than, store the result in the lower element of dst, and + // copy the upper element from a to the upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpngt_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpngt_sd + FORCE_INLINE __m128d _mm_cmpngt_sd(__m128d a, __m128d b) + { + return _mm_move_sd(a, _mm_cmpngt_pd(a, b)); +@@ -3792,7 +3437,7 @@ FORCE_INLINE __m128d _mm_cmpngt_sd(__m128d a, __m128d b) + + // Compare packed double-precision (64-bit) floating-point elements in a and b + // for not-less-than-or-equal, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpnle_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnle_pd + FORCE_INLINE __m128d _mm_cmpnle_pd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -3817,7 +3462,7 @@ FORCE_INLINE __m128d _mm_cmpnle_pd(__m128d a, __m128d b) + // Compare the lower double-precision (64-bit) floating-point elements in a and + // b for not-less-than-or-equal, store the result in the lower element of dst, + // and copy the upper element from a to the upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpnle_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnle_sd + FORCE_INLINE __m128d _mm_cmpnle_sd(__m128d a, __m128d b) + { + return _mm_move_sd(a, _mm_cmpnle_pd(a, b)); +@@ -3825,7 +3470,7 @@ FORCE_INLINE __m128d _mm_cmpnle_sd(__m128d a, __m128d b) + + // Compare packed double-precision (64-bit) floating-point elements in a and b + // for not-less-than, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpnlt_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnlt_pd + FORCE_INLINE __m128d _mm_cmpnlt_pd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -3850,7 +3495,7 @@ FORCE_INLINE __m128d _mm_cmpnlt_pd(__m128d a, __m128d b) + // Compare the lower double-precision (64-bit) floating-point elements in a and + // b for not-less-than, store the result in the lower element of dst, and copy + // the upper element from a to the upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpnlt_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpnlt_sd + FORCE_INLINE __m128d _mm_cmpnlt_sd(__m128d a, __m128d b) + { + return _mm_move_sd(a, _mm_cmpnlt_pd(a, b)); +@@ -3858,7 +3503,7 @@ FORCE_INLINE __m128d _mm_cmpnlt_sd(__m128d a, __m128d b) + + // Compare packed double-precision (64-bit) floating-point elements in a and b + // to see if neither is NaN, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpord_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpord_pd + FORCE_INLINE __m128d _mm_cmpord_pd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -3890,7 +3535,7 @@ FORCE_INLINE __m128d _mm_cmpord_pd(__m128d a, __m128d b) + // Compare the lower double-precision (64-bit) floating-point elements in a and + // b to see if neither is NaN, store the result in the lower element of dst, and + // copy the upper element from a to the upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpord_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpord_sd + FORCE_INLINE __m128d _mm_cmpord_sd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -3912,7 +3557,7 @@ FORCE_INLINE __m128d _mm_cmpord_sd(__m128d a, __m128d b) + + // Compare packed double-precision (64-bit) floating-point elements in a and b + // to see if either is NaN, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpunord_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpunord_pd + FORCE_INLINE __m128d _mm_cmpunord_pd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -3945,7 +3590,7 @@ FORCE_INLINE __m128d _mm_cmpunord_pd(__m128d a, __m128d b) + // Compare the lower double-precision (64-bit) floating-point elements in a and + // b to see if either is NaN, store the result in the lower element of dst, and + // copy the upper element from a to the upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpunord_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpunord_sd + FORCE_INLINE __m128d _mm_cmpunord_sd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -3967,7 +3612,7 @@ FORCE_INLINE __m128d _mm_cmpunord_sd(__m128d a, __m128d b) + + // Compare the lower double-precision (64-bit) floating-point element in a and b + // for greater-than-or-equal, and return the boolean result (0 or 1). +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_comige_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comige_sd + FORCE_INLINE int _mm_comige_sd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -3982,7 +3627,7 @@ FORCE_INLINE int _mm_comige_sd(__m128d a, __m128d b) + + // Compare the lower double-precision (64-bit) floating-point element in a and b + // for greater-than, and return the boolean result (0 or 1). +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_comigt_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comigt_sd + FORCE_INLINE int _mm_comigt_sd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -3997,7 +3642,7 @@ FORCE_INLINE int _mm_comigt_sd(__m128d a, __m128d b) + + // Compare the lower double-precision (64-bit) floating-point element in a and b + // for less-than-or-equal, and return the boolean result (0 or 1). +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_comile_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comile_sd + FORCE_INLINE int _mm_comile_sd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -4012,7 +3657,7 @@ FORCE_INLINE int _mm_comile_sd(__m128d a, __m128d b) + + // Compare the lower double-precision (64-bit) floating-point element in a and b + // for less-than, and return the boolean result (0 or 1). +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_comilt_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comilt_sd + FORCE_INLINE int _mm_comilt_sd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -4027,7 +3672,7 @@ FORCE_INLINE int _mm_comilt_sd(__m128d a, __m128d b) + + // Compare the lower double-precision (64-bit) floating-point element in a and b + // for equality, and return the boolean result (0 or 1). +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_comieq_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comieq_sd + FORCE_INLINE int _mm_comieq_sd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -4048,7 +3693,7 @@ FORCE_INLINE int _mm_comieq_sd(__m128d a, __m128d b) + + // Compare the lower double-precision (64-bit) floating-point element in a and b + // for not-equal, and return the boolean result (0 or 1). +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_comineq_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_comineq_sd + FORCE_INLINE int _mm_comineq_sd(__m128d a, __m128d b) + { + return !_mm_comieq_sd(a, b); +@@ -4056,14 +3701,7 @@ FORCE_INLINE int _mm_comineq_sd(__m128d a, __m128d b) + + // Convert packed signed 32-bit integers in a to packed double-precision + // (64-bit) floating-point elements, and store the results in dst. +-// +-// FOR j := 0 to 1 +-// i := j*32 +-// m := j*64 +-// dst[m+63:m] := Convert_Int32_To_FP64(a[i+31:i]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtepi32_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepi32_pd + FORCE_INLINE __m128d _mm_cvtepi32_pd(__m128i a) + { + #if defined(__aarch64__) +@@ -4076,9 +3714,9 @@ FORCE_INLINE __m128d _mm_cvtepi32_pd(__m128i a) + #endif + } + +-// Converts the four signed 32-bit integer values of a to single-precision, +-// floating-point values +-// https://msdn.microsoft.com/en-us/library/vstudio/36bwxcx5(v=vs.100).aspx ++// Convert packed signed 32-bit integers in a to packed single-precision ++// (32-bit) floating-point elements, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepi32_ps + FORCE_INLINE __m128 _mm_cvtepi32_ps(__m128i a) + { + return vreinterpretq_m128_f32(vcvtq_f32_s32(vreinterpretq_s32_m128i(a))); +@@ -4086,14 +3724,7 @@ FORCE_INLINE __m128 _mm_cvtepi32_ps(__m128i a) + + // Convert packed double-precision (64-bit) floating-point elements in a to + // packed 32-bit integers, and store the results in dst. +-// +-// FOR j := 0 to 1 +-// i := 32*j +-// k := 64*j +-// dst[i+31:i] := Convert_FP64_To_Int32(a[k+63:k]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpd_epi32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtpd_epi32 + FORCE_INLINE __m128i _mm_cvtpd_epi32(__m128d a) + { + // vrnd32xq_f64 not supported on clang +@@ -4112,14 +3743,7 @@ FORCE_INLINE __m128i _mm_cvtpd_epi32(__m128d a) + + // Convert packed double-precision (64-bit) floating-point elements in a to + // packed 32-bit integers, and store the results in dst. +-// +-// FOR j := 0 to 1 +-// i := 32*j +-// k := 64*j +-// dst[i+31:i] := Convert_FP64_To_Int32(a[k+63:k]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpd_pi32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtpd_pi32 + FORCE_INLINE __m64 _mm_cvtpd_pi32(__m128d a) + { + __m128d rnd = _mm_round_pd(a, _MM_FROUND_CUR_DIRECTION); +@@ -4132,15 +3756,7 @@ FORCE_INLINE __m64 _mm_cvtpd_pi32(__m128d a) + // Convert packed double-precision (64-bit) floating-point elements in a to + // packed single-precision (32-bit) floating-point elements, and store the + // results in dst. +-// +-// FOR j := 0 to 1 +-// i := 32*j +-// k := 64*j +-// dst[i+31:i] := Convert_FP64_To_FP32(a[k+64:k]) +-// ENDFOR +-// dst[127:64] := 0 +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpd_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtpd_ps + FORCE_INLINE __m128 _mm_cvtpd_ps(__m128d a) + { + #if defined(__aarch64__) +@@ -4155,14 +3771,7 @@ FORCE_INLINE __m128 _mm_cvtpd_ps(__m128d a) + + // Convert packed signed 32-bit integers in a to packed double-precision + // (64-bit) floating-point elements, and store the results in dst. +-// +-// FOR j := 0 to 1 +-// i := j*32 +-// m := j*64 +-// dst[m+63:m] := Convert_Int32_To_FP64(a[i+31:i]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtpi32_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtpi32_pd + FORCE_INLINE __m128d _mm_cvtpi32_pd(__m64 a) + { + #if defined(__aarch64__) +@@ -4175,15 +3784,9 @@ FORCE_INLINE __m128d _mm_cvtpi32_pd(__m64 a) + #endif + } + +-// Converts the four single-precision, floating-point values of a to signed +-// 32-bit integer values. +-// +-// r0 := (int) a0 +-// r1 := (int) a1 +-// r2 := (int) a2 +-// r3 := (int) a3 +-// +-// https://msdn.microsoft.com/en-us/library/vstudio/xdc42k5e(v=vs.100).aspx ++// Convert packed single-precision (32-bit) floating-point elements in a to ++// packed 32-bit integers, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtps_epi32 + // *NOTE*. The default rounding mode on SSE is 'round to even', which ARMv7-A + // does not support! It is supported on ARMv8-A however. + FORCE_INLINE __m128i _mm_cvtps_epi32(__m128 a) +@@ -4240,14 +3843,7 @@ FORCE_INLINE __m128i _mm_cvtps_epi32(__m128 a) + // Convert packed single-precision (32-bit) floating-point elements in a to + // packed double-precision (64-bit) floating-point elements, and store the + // results in dst. +-// +-// FOR j := 0 to 1 +-// i := 64*j +-// k := 32*j +-// dst[i+63:i] := Convert_FP32_To_FP64(a[k+31:k]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtps_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtps_pd + FORCE_INLINE __m128d _mm_cvtps_pd(__m128 a) + { + #if defined(__aarch64__) +@@ -4261,10 +3857,7 @@ FORCE_INLINE __m128d _mm_cvtps_pd(__m128 a) + } + + // Copy the lower double-precision (64-bit) floating-point element of a to dst. +-// +-// dst[63:0] := a[63:0] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsd_f64 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsd_f64 + FORCE_INLINE double _mm_cvtsd_f64(__m128d a) + { + #if defined(__aarch64__) +@@ -4276,10 +3869,7 @@ FORCE_INLINE double _mm_cvtsd_f64(__m128d a) + + // Convert the lower double-precision (64-bit) floating-point element in a to a + // 32-bit integer, and store the result in dst. +-// +-// dst[31:0] := Convert_FP64_To_Int32(a[63:0]) +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsd_si32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsd_si32 + FORCE_INLINE int32_t _mm_cvtsd_si32(__m128d a) + { + #if defined(__aarch64__) +@@ -4293,10 +3883,7 @@ FORCE_INLINE int32_t _mm_cvtsd_si32(__m128d a) + + // Convert the lower double-precision (64-bit) floating-point element in a to a + // 64-bit integer, and store the result in dst. +-// +-// dst[63:0] := Convert_FP64_To_Int64(a[63:0]) +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsd_si64 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsd_si64 + FORCE_INLINE int64_t _mm_cvtsd_si64(__m128d a) + { + #if defined(__aarch64__) +@@ -4310,17 +3897,14 @@ FORCE_INLINE int64_t _mm_cvtsd_si64(__m128d a) + + // Convert the lower double-precision (64-bit) floating-point element in a to a + // 64-bit integer, and store the result in dst. +-// +-// dst[63:0] := Convert_FP64_To_Int64(a[63:0]) +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsd_si64x ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsd_si64x + #define _mm_cvtsd_si64x _mm_cvtsd_si64 + + // Convert the lower double-precision (64-bit) floating-point element in b to a + // single-precision (32-bit) floating-point element, store the result in the + // lower element of dst, and copy the upper 3 packed elements from a to the + // upper elements of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsd_ss ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsd_ss + FORCE_INLINE __m128 _mm_cvtsd_ss(__m128 a, __m128d b) + { + #if defined(__aarch64__) +@@ -4334,33 +3918,27 @@ FORCE_INLINE __m128 _mm_cvtsd_ss(__m128 a, __m128d b) + } + + // Copy the lower 32-bit integer in a to dst. +-// +-// dst[31:0] := a[31:0] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi128_si32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi128_si32 + FORCE_INLINE int _mm_cvtsi128_si32(__m128i a) + { + return vgetq_lane_s32(vreinterpretq_s32_m128i(a), 0); + } + + // Copy the lower 64-bit integer in a to dst. +-// +-// dst[63:0] := a[63:0] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi128_si64 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi128_si64 + FORCE_INLINE int64_t _mm_cvtsi128_si64(__m128i a) + { + return vgetq_lane_s64(vreinterpretq_s64_m128i(a), 0); + } + + // Copy the lower 64-bit integer in a to dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi128_si64x ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi128_si64x + #define _mm_cvtsi128_si64x(a) _mm_cvtsi128_si64(a) + + // Convert the signed 32-bit integer b to a double-precision (64-bit) + // floating-point element, store the result in the lower element of dst, and + // copy the upper element from a to the upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi32_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi32_sd + FORCE_INLINE __m128d _mm_cvtsi32_sd(__m128d a, int32_t b) + { + #if defined(__aarch64__) +@@ -4374,21 +3952,12 @@ FORCE_INLINE __m128d _mm_cvtsi32_sd(__m128d a, int32_t b) + } + + // Copy the lower 64-bit integer in a to dst. +-// +-// dst[63:0] := a[63:0] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi128_si64x ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi128_si64x + #define _mm_cvtsi128_si64x(a) _mm_cvtsi128_si64(a) + +-// Moves 32-bit integer a to the least significant 32 bits of an __m128 object, +-// zero extending the upper bits. +-// +-// r0 := a +-// r1 := 0x0 +-// r2 := 0x0 +-// r3 := 0x0 +-// +-// https://msdn.microsoft.com/en-us/library/ct3539ha%28v=vs.90%29.aspx ++// Copy 32-bit integer a to the lower elements of dst, and zero the upper ++// elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi32_si128 + FORCE_INLINE __m128i _mm_cvtsi32_si128(int a) + { + return vreinterpretq_m128i_s32(vsetq_lane_s32(a, vdupq_n_s32(0), 0)); +@@ -4397,7 +3966,7 @@ FORCE_INLINE __m128i _mm_cvtsi32_si128(int a) + // Convert the signed 64-bit integer b to a double-precision (64-bit) + // floating-point element, store the result in the lower element of dst, and + // copy the upper element from a to the upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi64_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi64_sd + FORCE_INLINE __m128d _mm_cvtsi64_sd(__m128d a, int64_t b) + { + #if defined(__aarch64__) +@@ -4410,11 +3979,9 @@ FORCE_INLINE __m128d _mm_cvtsi64_sd(__m128d a, int64_t b) + #endif + } + +-// Moves 64-bit integer a to the least significant 64 bits of an __m128 object, +-// zero extending the upper bits. +-// +-// r0 := a +-// r1 := 0x0 ++// Copy 64-bit integer a to the lower element of dst, and zero the upper ++// element. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi64_si128 + FORCE_INLINE __m128i _mm_cvtsi64_si128(int64_t a) + { + return vreinterpretq_m128i_s64(vsetq_lane_s64(a, vdupq_n_s64(0), 0)); +@@ -4422,24 +3989,20 @@ FORCE_INLINE __m128i _mm_cvtsi64_si128(int64_t a) + + // Copy 64-bit integer a to the lower element of dst, and zero the upper + // element. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi64x_si128 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi64x_si128 + #define _mm_cvtsi64x_si128(a) _mm_cvtsi64_si128(a) + + // Convert the signed 64-bit integer b to a double-precision (64-bit) + // floating-point element, store the result in the lower element of dst, and + // copy the upper element from a to the upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtsi64x_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtsi64x_sd + #define _mm_cvtsi64x_sd(a, b) _mm_cvtsi64_sd(a, b) + + // Convert the lower single-precision (32-bit) floating-point element in b to a + // double-precision (64-bit) floating-point element, store the result in the + // lower element of dst, and copy the upper element from a to the upper element + // of dst. +-// +-// dst[63:0] := Convert_FP32_To_FP64(b[31:0]) +-// dst[127:64] := a[127:64] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtss_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtss_sd + FORCE_INLINE __m128d _mm_cvtss_sd(__m128d a, __m128 b) + { + double d = (double) vgetq_lane_f32(vreinterpretq_f32_m128(b), 0); +@@ -4454,7 +4017,7 @@ FORCE_INLINE __m128d _mm_cvtss_sd(__m128d a, __m128 b) + + // Convert packed double-precision (64-bit) floating-point elements in a to + // packed 32-bit integers with truncation, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttpd_epi32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvttpd_epi32 + FORCE_INLINE __m128i _mm_cvttpd_epi32(__m128d a) + { + double a0 = ((double *) &a)[0]; +@@ -4464,7 +4027,7 @@ FORCE_INLINE __m128i _mm_cvttpd_epi32(__m128d a) + + // Convert packed double-precision (64-bit) floating-point elements in a to + // packed 32-bit integers with truncation, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttpd_pi32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvttpd_pi32 + FORCE_INLINE __m64 _mm_cvttpd_pi32(__m128d a) + { + double a0 = ((double *) &a)[0]; +@@ -4473,9 +4036,9 @@ FORCE_INLINE __m64 _mm_cvttpd_pi32(__m128d a) + return vreinterpret_m64_s32(vld1_s32(data)); + } + +-// Converts the four single-precision, floating-point values of a to signed +-// 32-bit integer values using truncate. +-// https://msdn.microsoft.com/en-us/library/vstudio/1h005y6x(v=vs.100).aspx ++// Convert packed single-precision (32-bit) floating-point elements in a to ++// packed 32-bit integers with truncation, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvttps_epi32 + FORCE_INLINE __m128i _mm_cvttps_epi32(__m128 a) + { + return vreinterpretq_m128i_s32(vcvtq_s32_f32(vreinterpretq_f32_m128(a))); +@@ -4483,10 +4046,7 @@ FORCE_INLINE __m128i _mm_cvttps_epi32(__m128 a) + + // Convert the lower double-precision (64-bit) floating-point element in a to a + // 32-bit integer with truncation, and store the result in dst. +-// +-// dst[63:0] := Convert_FP64_To_Int32_Truncate(a[63:0]) +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttsd_si32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvttsd_si32 + FORCE_INLINE int32_t _mm_cvttsd_si32(__m128d a) + { + double ret = *((double *) &a); +@@ -4495,10 +4055,7 @@ FORCE_INLINE int32_t _mm_cvttsd_si32(__m128d a) + + // Convert the lower double-precision (64-bit) floating-point element in a to a + // 64-bit integer with truncation, and store the result in dst. +-// +-// dst[63:0] := Convert_FP64_To_Int64_Truncate(a[63:0]) +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttsd_si64 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvttsd_si64 + FORCE_INLINE int64_t _mm_cvttsd_si64(__m128d a) + { + #if defined(__aarch64__) +@@ -4511,21 +4068,12 @@ FORCE_INLINE int64_t _mm_cvttsd_si64(__m128d a) + + // Convert the lower double-precision (64-bit) floating-point element in a to a + // 64-bit integer with truncation, and store the result in dst. +-// +-// dst[63:0] := Convert_FP64_To_Int64_Truncate(a[63:0]) +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvttsd_si64x ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvttsd_si64x + #define _mm_cvttsd_si64x(a) _mm_cvttsd_si64(a) + + // Divide packed double-precision (64-bit) floating-point elements in a by + // packed elements in b, and store the results in dst. +-// +-// FOR j := 0 to 1 +-// i := 64*j +-// dst[i+63:i] := a[i+63:i] / b[i+63:i] +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_div_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_div_pd + FORCE_INLINE __m128d _mm_div_pd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -4545,7 +4093,7 @@ FORCE_INLINE __m128d _mm_div_pd(__m128d a, __m128d b) + // lower double-precision (64-bit) floating-point element in b, store the result + // in the lower element of dst, and copy the upper element from a to the upper + // element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_div_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_div_sd + FORCE_INLINE __m128d _mm_div_sd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -4558,16 +4106,16 @@ FORCE_INLINE __m128d _mm_div_sd(__m128d a, __m128d b) + #endif + } + +-// Extracts the selected signed or unsigned 16-bit integer from a and zero +-// extends. +-// https://msdn.microsoft.com/en-us/library/6dceta0c(v=vs.100).aspx ++// Extract a 16-bit integer from a, selected with imm8, and store the result in ++// the lower element of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_extract_epi16 + // FORCE_INLINE int _mm_extract_epi16(__m128i a, __constrange(0,8) int imm) + #define _mm_extract_epi16(a, imm) \ + vgetq_lane_u16(vreinterpretq_u16_m128i(a), (imm)) + +-// Inserts the least significant 16 bits of b into the selected 16-bit integer +-// of a. +-// https://msdn.microsoft.com/en-us/library/kaze8hz1%28v=vs.100%29.aspx ++// Copy a to dst, and insert the 16-bit integer i into dst at the location ++// specified by imm8. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_insert_epi16 + // FORCE_INLINE __m128i _mm_insert_epi16(__m128i a, int b, + // __constrange(0,8) int imm) + #define _mm_insert_epi16(a, b, imm) \ +@@ -4576,12 +4124,10 @@ FORCE_INLINE __m128d _mm_div_sd(__m128d a, __m128d b) + vsetq_lane_s16((b), vreinterpretq_s16_m128i(a), (imm))); \ + }) + +-// Loads two double-precision from 16-byte aligned memory, floating-point +-// values. +-// +-// dst[127:0] := MEM[mem_addr+127:mem_addr] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_load_pd ++// Load 128-bits (composed of 2 packed double-precision (64-bit) floating-point ++// elements) from memory into dst. mem_addr must be aligned on a 16-byte ++// boundary or a general-protection exception may be generated. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_load_pd + FORCE_INLINE __m128d _mm_load_pd(const double *p) + { + #if defined(__aarch64__) +@@ -4595,21 +4141,13 @@ FORCE_INLINE __m128d _mm_load_pd(const double *p) + + // Load a double-precision (64-bit) floating-point element from memory into both + // elements of dst. +-// +-// dst[63:0] := MEM[mem_addr+63:mem_addr] +-// dst[127:64] := MEM[mem_addr+63:mem_addr] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_load_pd1 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_load_pd1 + #define _mm_load_pd1 _mm_load1_pd + + // Load a double-precision (64-bit) floating-point element from memory into the + // lower of dst, and zero the upper element. mem_addr does not need to be + // aligned on any particular boundary. +-// +-// dst[63:0] := MEM[mem_addr+63:mem_addr] +-// dst[127:64] := 0 +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_load_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_load_sd + FORCE_INLINE __m128d _mm_load_sd(const double *p) + { + #if defined(__aarch64__) +@@ -4621,8 +4159,9 @@ FORCE_INLINE __m128d _mm_load_sd(const double *p) + #endif + } + +-// Loads 128-bit value. : +-// https://msdn.microsoft.com/en-us/library/atzzad1h(v=vs.80).aspx ++// Load 128-bits of integer data from memory into dst. mem_addr must be aligned ++// on a 16-byte boundary or a general-protection exception may be generated. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_load_si128 + FORCE_INLINE __m128i _mm_load_si128(const __m128i *p) + { + return vreinterpretq_m128i_s32(vld1q_s32((const int32_t *) p)); +@@ -4630,11 +4169,7 @@ FORCE_INLINE __m128i _mm_load_si128(const __m128i *p) + + // Load a double-precision (64-bit) floating-point element from memory into both + // elements of dst. +-// +-// dst[63:0] := MEM[mem_addr+63:mem_addr] +-// dst[127:64] := MEM[mem_addr+63:mem_addr] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_load1_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_load1_pd + FORCE_INLINE __m128d _mm_load1_pd(const double *p) + { + #if defined(__aarch64__) +@@ -4647,11 +4182,7 @@ FORCE_INLINE __m128d _mm_load1_pd(const double *p) + // Load a double-precision (64-bit) floating-point element from memory into the + // upper element of dst, and copy the lower element from a to dst. mem_addr does + // not need to be aligned on any particular boundary. +-// +-// dst[63:0] := a[63:0] +-// dst[127:64] := MEM[mem_addr+63:mem_addr] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadh_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadh_pd + FORCE_INLINE __m128d _mm_loadh_pd(__m128d a, const double *p) + { + #if defined(__aarch64__) +@@ -4664,7 +4195,7 @@ FORCE_INLINE __m128d _mm_loadh_pd(__m128d a, const double *p) + } + + // Load 64-bit integer from memory into the first element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadl_epi64 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadl_epi64 + FORCE_INLINE __m128i _mm_loadl_epi64(__m128i const *p) + { + /* Load the lower 64 bits of the value pointed to by p into the +@@ -4677,11 +4208,7 @@ FORCE_INLINE __m128i _mm_loadl_epi64(__m128i const *p) + // Load a double-precision (64-bit) floating-point element from memory into the + // lower element of dst, and copy the upper element from a to dst. mem_addr does + // not need to be aligned on any particular boundary. +-// +-// dst[63:0] := MEM[mem_addr+63:mem_addr] +-// dst[127:64] := a[127:64] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadl_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadl_pd + FORCE_INLINE __m128d _mm_loadl_pd(__m128d a, const double *p) + { + #if defined(__aarch64__) +@@ -4697,11 +4224,7 @@ FORCE_INLINE __m128d _mm_loadl_pd(__m128d a, const double *p) + // Load 2 double-precision (64-bit) floating-point elements from memory into dst + // in reverse order. mem_addr must be aligned on a 16-byte boundary or a + // general-protection exception may be generated. +-// +-// dst[63:0] := MEM[mem_addr+127:mem_addr+64] +-// dst[127:64] := MEM[mem_addr+63:mem_addr] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadr_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadr_pd + FORCE_INLINE __m128d _mm_loadr_pd(const double *p) + { + #if defined(__aarch64__) +@@ -4714,39 +4237,32 @@ FORCE_INLINE __m128d _mm_loadr_pd(const double *p) + } + + // Loads two double-precision from unaligned memory, floating-point values. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadu_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadu_pd + FORCE_INLINE __m128d _mm_loadu_pd(const double *p) + { + return _mm_load_pd(p); + } + +-// Loads 128-bit value. : +-// https://msdn.microsoft.com/zh-cn/library/f4k12ae8(v=vs.90).aspx ++// Load 128-bits of integer data from memory into dst. mem_addr does not need to ++// be aligned on any particular boundary. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadu_si128 + FORCE_INLINE __m128i _mm_loadu_si128(const __m128i *p) + { + return vreinterpretq_m128i_s32(vld1q_s32((const int32_t *) p)); + } + + // Load unaligned 32-bit integer from memory into the first element of dst. +-// +-// dst[31:0] := MEM[mem_addr+31:mem_addr] +-// dst[MAX:32] := 0 +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loadu_si32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadu_si32 + FORCE_INLINE __m128i _mm_loadu_si32(const void *p) + { + return vreinterpretq_m128i_s32( + vsetq_lane_s32(*(const int32_t *) p, vdupq_n_s32(0), 0)); + } + +-// Multiplies the 8 signed 16-bit integers from a by the 8 signed 16-bit +-// integers from b. +-// +-// r0 := (a0 * b0) + (a1 * b1) +-// r1 := (a2 * b2) + (a3 * b3) +-// r2 := (a4 * b4) + (a5 * b5) +-// r3 := (a6 * b6) + (a7 * b7) +-// https://msdn.microsoft.com/en-us/library/yht36sa6(v=vs.90).aspx ++// Multiply packed signed 16-bit integers in a and b, producing intermediate ++// signed 32-bit integers. Horizontally add adjacent pairs of intermediate ++// 32-bit integers, and pack the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_madd_epi16 + FORCE_INLINE __m128i _mm_madd_epi16(__m128i a, __m128i b) + { + int32x4_t low = vmull_s16(vget_low_s16(vreinterpretq_s16_m128i(a)), +@@ -4771,7 +4287,7 @@ FORCE_INLINE __m128i _mm_madd_epi16(__m128i a, __m128i b) + // (elements are not stored when the highest bit is not set in the corresponding + // element) and a non-temporal memory hint. mem_addr does not need to be aligned + // on any particular boundary. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_maskmoveu_si128 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_maskmoveu_si128 + FORCE_INLINE void _mm_maskmoveu_si128(__m128i a, __m128i mask, char *mem_addr) + { + int8x16_t shr_mask = vshrq_n_s8(vreinterpretq_s8_m128i(mask), 7); +@@ -4782,18 +4298,18 @@ FORCE_INLINE void _mm_maskmoveu_si128(__m128i a, __m128i mask, char *mem_addr) + vst1q_s8((int8_t *) mem_addr, masked); + } + +-// Computes the pairwise maxima of the 8 signed 16-bit integers from a and the 8 +-// signed 16-bit integers from b. +-// https://msdn.microsoft.com/en-us/LIBRary/3x060h7c(v=vs.100).aspx ++// Compare packed signed 16-bit integers in a and b, and store packed maximum ++// values in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_epi16 + FORCE_INLINE __m128i _mm_max_epi16(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s16( + vmaxq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); + } + +-// Computes the pairwise maxima of the 16 unsigned 8-bit integers from a and the +-// 16 unsigned 8-bit integers from b. +-// https://msdn.microsoft.com/en-us/library/st6634za(v=vs.100).aspx ++// Compare packed unsigned 8-bit integers in a and b, and store packed maximum ++// values in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_epu8 + FORCE_INLINE __m128i _mm_max_epu8(__m128i a, __m128i b) + { + return vreinterpretq_m128i_u8( +@@ -4802,7 +4318,7 @@ FORCE_INLINE __m128i _mm_max_epu8(__m128i a, __m128i b) + + // Compare packed double-precision (64-bit) floating-point elements in a and b, + // and store packed maximum values in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_pd + FORCE_INLINE __m128d _mm_max_pd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -4830,7 +4346,7 @@ FORCE_INLINE __m128d _mm_max_pd(__m128d a, __m128d b) + // Compare the lower double-precision (64-bit) floating-point elements in a and + // b, store the maximum value in the lower element of dst, and copy the upper + // element from a to the upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_sd + FORCE_INLINE __m128d _mm_max_sd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -4843,18 +4359,18 @@ FORCE_INLINE __m128d _mm_max_sd(__m128d a, __m128d b) + #endif + } + +-// Computes the pairwise minima of the 8 signed 16-bit integers from a and the 8 +-// signed 16-bit integers from b. +-// https://msdn.microsoft.com/en-us/library/vstudio/6te997ew(v=vs.100).aspx ++// Compare packed signed 16-bit integers in a and b, and store packed minimum ++// values in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_min_epi16 + FORCE_INLINE __m128i _mm_min_epi16(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s16( + vminq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); + } + +-// Computes the pairwise minima of the 16 unsigned 8-bit integers from a and the +-// 16 unsigned 8-bit integers from b. +-// https://msdn.microsoft.com/ko-kr/library/17k8cf58(v=vs.100).aspxx ++// Compare packed unsigned 8-bit integers in a and b, and store packed minimum ++// values in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_min_epu8 + FORCE_INLINE __m128i _mm_min_epu8(__m128i a, __m128i b) + { + return vreinterpretq_m128i_u8( +@@ -4863,7 +4379,7 @@ FORCE_INLINE __m128i _mm_min_epu8(__m128i a, __m128i b) + + // Compare packed double-precision (64-bit) floating-point elements in a and b, + // and store packed minimum values in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_min_pd + FORCE_INLINE __m128d _mm_min_pd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -4890,7 +4406,7 @@ FORCE_INLINE __m128d _mm_min_pd(__m128d a, __m128d b) + // Compare the lower double-precision (64-bit) floating-point elements in a and + // b, store the minimum value in the lower element of dst, and copy the upper + // element from a to the upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_min_sd + FORCE_INLINE __m128d _mm_min_sd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -4905,11 +4421,7 @@ FORCE_INLINE __m128d _mm_min_sd(__m128d a, __m128d b) + + // Copy the lower 64-bit integer in a to the lower element of dst, and zero the + // upper element. +-// +-// dst[63:0] := a[63:0] +-// dst[127:64] := 0 +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_move_epi64 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_move_epi64 + FORCE_INLINE __m128i _mm_move_epi64(__m128i a) + { + return vreinterpretq_m128i_s64( +@@ -4919,11 +4431,7 @@ FORCE_INLINE __m128i _mm_move_epi64(__m128i a) + // Move the lower double-precision (64-bit) floating-point element from b to the + // lower element of dst, and copy the upper element from a to the upper element + // of dst. +-// +-// dst[63:0] := b[63:0] +-// dst[127:64] := a[127:64] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_move_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_move_sd + FORCE_INLINE __m128d _mm_move_sd(__m128d a, __m128d b) + { + return vreinterpretq_m128d_f32( +@@ -4931,10 +4439,9 @@ FORCE_INLINE __m128d _mm_move_sd(__m128d a, __m128d b) + vget_high_f32(vreinterpretq_f32_m128d(a)))); + } + +-// NEON does not provide a version of this function. +-// Creates a 16-bit mask from the most significant bits of the 16 signed or +-// unsigned 8-bit integers in a and zero extends the upper bits. +-// https://msdn.microsoft.com/en-us/library/vstudio/s090c8fk(v=vs.100).aspx ++// Create mask from the most significant bit of each 8-bit element in a, and ++// store the result in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_movemask_epi8 + FORCE_INLINE int _mm_movemask_epi8(__m128i a) + { + // Use increasingly wide shifts+adds to collect the sign bits +@@ -5017,7 +4524,7 @@ FORCE_INLINE int _mm_movemask_epi8(__m128i a) + + // Set each bit of mask dst based on the most significant bit of the + // corresponding packed double-precision (64-bit) floating-point element in a. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_movemask_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_movemask_pd + FORCE_INLINE int _mm_movemask_pd(__m128d a) + { + uint64x2_t input = vreinterpretq_u64_m128d(a); +@@ -5026,10 +4533,7 @@ FORCE_INLINE int _mm_movemask_pd(__m128d a) + } + + // Copy the lower 64-bit integer in a to dst. +-// +-// dst[63:0] := a[63:0] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_movepi64_pi64 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_movepi64_pi64 + FORCE_INLINE __m64 _mm_movepi64_pi64(__m128i a) + { + return vreinterpret_m64_s64(vget_low_s64(vreinterpretq_s64_m128i(a))); +@@ -5037,11 +4541,7 @@ FORCE_INLINE __m64 _mm_movepi64_pi64(__m128i a) + + // Copy the 64-bit integer a to the lower element of dst, and zero the upper + // element. +-// +-// dst[63:0] := a[63:0] +-// dst[127:64] := 0 +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_movpi64_epi64 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_movpi64_epi64 + FORCE_INLINE __m128i _mm_movpi64_epi64(__m64 a) + { + return vreinterpretq_m128i_s64( +@@ -5050,9 +4550,7 @@ FORCE_INLINE __m128i _mm_movpi64_epi64(__m64 a) + + // Multiply the low unsigned 32-bit integers from each packed 64-bit element in + // a and b, and store the unsigned 64-bit results in dst. +-// +-// r0 := (a0 & 0xFFFFFFFF) * (b0 & 0xFFFFFFFF) +-// r1 := (a2 & 0xFFFFFFFF) * (b2 & 0xFFFFFFFF) ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mul_epu32 + FORCE_INLINE __m128i _mm_mul_epu32(__m128i a, __m128i b) + { + // vmull_u32 upcasts instead of masking, so we downcast. +@@ -5063,7 +4561,7 @@ FORCE_INLINE __m128i _mm_mul_epu32(__m128i a, __m128i b) + + // Multiply packed double-precision (64-bit) floating-point elements in a and b, + // and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mul_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mul_pd + FORCE_INLINE __m128d _mm_mul_pd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -5082,7 +4580,7 @@ FORCE_INLINE __m128d _mm_mul_pd(__m128d a, __m128d b) + // Multiply the lower double-precision (64-bit) floating-point element in a and + // b, store the result in the lower element of dst, and copy the upper element + // from a to the upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=mm_mul_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=mm_mul_sd + FORCE_INLINE __m128d _mm_mul_sd(__m128d a, __m128d b) + { + return _mm_move_sd(a, _mm_mul_pd(a, b)); +@@ -5090,25 +4588,17 @@ FORCE_INLINE __m128d _mm_mul_sd(__m128d a, __m128d b) + + // Multiply the low unsigned 32-bit integers from a and b, and store the + // unsigned 64-bit result in dst. +-// +-// dst[63:0] := a[31:0] * b[31:0] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mul_su32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mul_su32 + FORCE_INLINE __m64 _mm_mul_su32(__m64 a, __m64 b) + { + return vreinterpret_m64_u64(vget_low_u64( + vmull_u32(vreinterpret_u32_m64(a), vreinterpret_u32_m64(b)))); + } + +-// Multiplies the 8 signed 16-bit integers from a by the 8 signed 16-bit +-// integers from b. +-// +-// r0 := (a0 * b0)[31:16] +-// r1 := (a1 * b1)[31:16] +-// ... +-// r7 := (a7 * b7)[31:16] +-// +-// https://msdn.microsoft.com/en-us/library/vstudio/59hddw1d(v=vs.100).aspx ++// Multiply the packed signed 16-bit integers in a and b, producing intermediate ++// 32-bit integers, and store the high 16 bits of the intermediate integers in ++// dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mulhi_epi16 + FORCE_INLINE __m128i _mm_mulhi_epi16(__m128i a, __m128i b) + { + /* FIXME: issue with large values because of result saturation */ +@@ -5129,7 +4619,7 @@ FORCE_INLINE __m128i _mm_mulhi_epi16(__m128i a, __m128i b) + // Multiply the packed unsigned 16-bit integers in a and b, producing + // intermediate 32-bit integers, and store the high 16 bits of the intermediate + // integers in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mulhi_epu16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mulhi_epu16 + FORCE_INLINE __m128i _mm_mulhi_epu16(__m128i a, __m128i b) + { + uint16x4_t a3210 = vget_low_u16(vreinterpretq_u16_m128i(a)); +@@ -5151,15 +4641,9 @@ FORCE_INLINE __m128i _mm_mulhi_epu16(__m128i a, __m128i b) + #endif + } + +-// Multiplies the 8 signed or unsigned 16-bit integers from a by the 8 signed or +-// unsigned 16-bit integers from b. +-// +-// r0 := (a0 * b0)[15:0] +-// r1 := (a1 * b1)[15:0] +-// ... +-// r7 := (a7 * b7)[15:0] +-// +-// https://msdn.microsoft.com/en-us/library/vstudio/9ks1472s(v=vs.100).aspx ++// Multiply the packed 16-bit integers in a and b, producing intermediate 32-bit ++// integers, and store the low 16 bits of the intermediate integers in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mullo_epi16 + FORCE_INLINE __m128i _mm_mullo_epi16(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s16( +@@ -5168,27 +4652,25 @@ FORCE_INLINE __m128i _mm_mullo_epi16(__m128i a, __m128i b) + + // Compute the bitwise OR of packed double-precision (64-bit) floating-point + // elements in a and b, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=mm_or_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=mm_or_pd + FORCE_INLINE __m128d _mm_or_pd(__m128d a, __m128d b) + { + return vreinterpretq_m128d_s64( + vorrq_s64(vreinterpretq_s64_m128d(a), vreinterpretq_s64_m128d(b))); + } + +-// Computes the bitwise OR of the 128-bit value in a and the 128-bit value in b. +-// +-// r := a | b +-// +-// https://msdn.microsoft.com/en-us/library/vstudio/ew8ty0db(v=vs.100).aspx ++// Compute the bitwise OR of 128 bits (representing integer data) in a and b, ++// and store the result in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_or_si128 + FORCE_INLINE __m128i _mm_or_si128(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s32( + vorrq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); + } + +-// Packs the 16 signed 16-bit integers from a and b into 8-bit integers and +-// saturates. +-// https://msdn.microsoft.com/en-us/library/k4y4f7w5%28v=vs.90%29.aspx ++// Convert packed signed 16-bit integers from a and b to packed 8-bit integers ++// using signed saturation, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_packs_epi16 + FORCE_INLINE __m128i _mm_packs_epi16(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s8( +@@ -5196,19 +4678,9 @@ FORCE_INLINE __m128i _mm_packs_epi16(__m128i a, __m128i b) + vqmovn_s16(vreinterpretq_s16_m128i(b)))); + } + +-// Packs the 8 signed 32-bit integers from a and b into signed 16-bit integers +-// and saturates. +-// +-// r0 := SignedSaturate(a0) +-// r1 := SignedSaturate(a1) +-// r2 := SignedSaturate(a2) +-// r3 := SignedSaturate(a3) +-// r4 := SignedSaturate(b0) +-// r5 := SignedSaturate(b1) +-// r6 := SignedSaturate(b2) +-// r7 := SignedSaturate(b3) +-// +-// https://msdn.microsoft.com/en-us/library/393t56f9%28v=vs.90%29.aspx ++// Convert packed signed 32-bit integers from a and b to packed 16-bit integers ++// using signed saturation, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_packs_epi32 + FORCE_INLINE __m128i _mm_packs_epi32(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s16( +@@ -5216,19 +4688,9 @@ FORCE_INLINE __m128i _mm_packs_epi32(__m128i a, __m128i b) + vqmovn_s32(vreinterpretq_s32_m128i(b)))); + } + +-// Packs the 16 signed 16 - bit integers from a and b into 8 - bit unsigned +-// integers and saturates. +-// +-// r0 := UnsignedSaturate(a0) +-// r1 := UnsignedSaturate(a1) +-// ... +-// r7 := UnsignedSaturate(a7) +-// r8 := UnsignedSaturate(b0) +-// r9 := UnsignedSaturate(b1) +-// ... +-// r15 := UnsignedSaturate(b7) +-// +-// https://msdn.microsoft.com/en-us/library/07ad1wx4(v=vs.100).aspx ++// Convert packed signed 16-bit integers from a and b to packed 8-bit integers ++// using unsigned saturation, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_packus_epi16 + FORCE_INLINE __m128i _mm_packus_epi16(const __m128i a, const __m128i b) + { + return vreinterpretq_m128i_u8( +@@ -5241,6 +4703,7 @@ FORCE_INLINE __m128i _mm_packus_epi16(const __m128i a, const __m128i b) + // 'yield' instruction isn't a good fit because it's effectively a nop on most + // Arm cores. Experience with several databases has shown has shown an 'isb' is + // a reasonable approximation. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_pause + FORCE_INLINE void _mm_pause() + { + __asm__ __volatile__("isb\n"); +@@ -5250,15 +4713,15 @@ FORCE_INLINE void _mm_pause() + // b, then horizontally sum each consecutive 8 differences to produce two + // unsigned 16-bit integers, and pack these unsigned 16-bit integers in the low + // 16 bits of 64-bit elements in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sad_epu8 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sad_epu8 + FORCE_INLINE __m128i _mm_sad_epu8(__m128i a, __m128i b) + { + uint16x8_t t = vpaddlq_u8(vabdq_u8((uint8x16_t) a, (uint8x16_t) b)); + return vreinterpretq_m128i_u64(vpaddlq_u32(vpaddlq_u16(t))); + } + +-// Sets the 8 signed 16-bit integer values. +-// https://msdn.microsoft.com/en-au/library/3e0fek84(v=vs.90).aspx ++// Set packed 16-bit integers in dst with the supplied values. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set_epi16 + FORCE_INLINE __m128i _mm_set_epi16(short i7, + short i6, + short i5, +@@ -5272,33 +4735,31 @@ FORCE_INLINE __m128i _mm_set_epi16(short i7, + return vreinterpretq_m128i_s16(vld1q_s16(data)); + } + +-// Sets the 4 signed 32-bit integer values. +-// https://msdn.microsoft.com/en-us/library/vstudio/019beekt(v=vs.100).aspx ++// Set packed 32-bit integers in dst with the supplied values. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set_epi32 + FORCE_INLINE __m128i _mm_set_epi32(int i3, int i2, int i1, int i0) + { + int32_t ALIGN_STRUCT(16) data[4] = {i0, i1, i2, i3}; + return vreinterpretq_m128i_s32(vld1q_s32(data)); + } + +-// Returns the __m128i structure with its two 64-bit integer values +-// initialized to the values of the two 64-bit integers passed in. +-// https://msdn.microsoft.com/en-us/library/dk2sdw0h(v=vs.120).aspx ++// Set packed 64-bit integers in dst with the supplied values. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set_epi64 + FORCE_INLINE __m128i _mm_set_epi64(__m64 i1, __m64 i2) + { + return _mm_set_epi64x((int64_t) i1, (int64_t) i2); + } + +-// Returns the __m128i structure with its two 64-bit integer values +-// initialized to the values of the two 64-bit integers passed in. +-// https://msdn.microsoft.com/en-us/library/dk2sdw0h(v=vs.120).aspx ++// Set packed 64-bit integers in dst with the supplied values. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set_epi64x + FORCE_INLINE __m128i _mm_set_epi64x(int64_t i1, int64_t i2) + { + return vreinterpretq_m128i_s64( + vcombine_s64(vcreate_s64(i2), vcreate_s64(i1))); + } + +-// Sets the 16 signed 8-bit integer values. +-// https://msdn.microsoft.com/en-us/library/x0cx8zd3(v=vs.90).aspx ++// Set packed 8-bit integers in dst with the supplied values. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set_epi8 + FORCE_INLINE __m128i _mm_set_epi8(signed char b15, + signed char b14, + signed char b13, +@@ -5326,7 +4787,7 @@ FORCE_INLINE __m128i _mm_set_epi8(signed char b15, + + // Set packed double-precision (64-bit) floating-point elements in dst with the + // supplied values. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_set_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set_pd + FORCE_INLINE __m128d _mm_set_pd(double e1, double e0) + { + double ALIGN_STRUCT(16) data[2] = {e0, e1}; +@@ -5339,12 +4800,12 @@ FORCE_INLINE __m128d _mm_set_pd(double e1, double e0) + + // Broadcast double-precision (64-bit) floating-point value a to all elements of + // dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_set_pd1 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set_pd1 + #define _mm_set_pd1 _mm_set1_pd + + // Copy double-precision (64-bit) floating-point element a to the lower element + // of dst, and zero the upper element. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_set_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set_sd + FORCE_INLINE __m128d _mm_set_sd(double a) + { + #if defined(__aarch64__) +@@ -5354,54 +4815,36 @@ FORCE_INLINE __m128d _mm_set_sd(double a) + #endif + } + +-// Sets the 8 signed 16-bit integer values to w. +-// +-// r0 := w +-// r1 := w +-// ... +-// r7 := w +-// +-// https://msdn.microsoft.com/en-us/library/k0ya3x0e(v=vs.90).aspx ++// Broadcast 16-bit integer a to all all elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set1_epi16 + FORCE_INLINE __m128i _mm_set1_epi16(short w) + { + return vreinterpretq_m128i_s16(vdupq_n_s16(w)); + } + +-// Sets the 4 signed 32-bit integer values to i. +-// +-// r0 := i +-// r1 := i +-// r2 := i +-// r3 := I +-// +-// https://msdn.microsoft.com/en-us/library/vstudio/h4xscxat(v=vs.100).aspx ++// Broadcast 32-bit integer a to all elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set1_epi32 + FORCE_INLINE __m128i _mm_set1_epi32(int _i) + { + return vreinterpretq_m128i_s32(vdupq_n_s32(_i)); + } + +-// Sets the 2 signed 64-bit integer values to i. +-// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/whtfzhzk(v=vs.100) ++// Broadcast 64-bit integer a to all elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set1_epi64 + FORCE_INLINE __m128i _mm_set1_epi64(__m64 _i) + { + return vreinterpretq_m128i_s64(vdupq_n_s64((int64_t) _i)); + } + +-// Sets the 2 signed 64-bit integer values to i. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_set1_epi64x ++// Broadcast 64-bit integer a to all elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set1_epi64x + FORCE_INLINE __m128i _mm_set1_epi64x(int64_t _i) + { + return vreinterpretq_m128i_s64(vdupq_n_s64(_i)); + } + +-// Sets the 16 signed 8-bit integer values to b. +-// +-// r0 := b +-// r1 := b +-// ... +-// r15 := b +-// +-// https://msdn.microsoft.com/en-us/library/6e14xhyf(v=vs.100).aspx ++// Broadcast 8-bit integer a to all elements of dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set1_epi8 + FORCE_INLINE __m128i _mm_set1_epi8(signed char w) + { + return vreinterpretq_m128i_s8(vdupq_n_s8(w)); +@@ -5409,7 +4852,7 @@ FORCE_INLINE __m128i _mm_set1_epi8(signed char w) + + // Broadcast double-precision (64-bit) floating-point value a to all elements of + // dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_set1_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_set1_pd + FORCE_INLINE __m128d _mm_set1_pd(double d) + { + #if defined(__aarch64__) +@@ -5419,13 +4862,8 @@ FORCE_INLINE __m128d _mm_set1_pd(double d) + #endif + } + +-// Sets the 8 signed 16-bit integer values in reverse order. +-// +-// Return Value +-// r0 := w0 +-// r1 := w1 +-// ... +-// r7 := w7 ++// Set packed 16-bit integers in dst with the supplied values in reverse order. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_setr_epi16 + FORCE_INLINE __m128i _mm_setr_epi16(short w0, + short w1, + short w2, +@@ -5439,8 +4877,8 @@ FORCE_INLINE __m128i _mm_setr_epi16(short w0, + return vreinterpretq_m128i_s16(vld1q_s16((int16_t *) data)); + } + +-// Sets the 4 signed 32-bit integer values in reverse order +-// https://technet.microsoft.com/en-us/library/security/27yb3ee5(v=vs.90).aspx ++// Set packed 32-bit integers in dst with the supplied values in reverse order. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_setr_epi32 + FORCE_INLINE __m128i _mm_setr_epi32(int i3, int i2, int i1, int i0) + { + int32_t ALIGN_STRUCT(16) data[4] = {i3, i2, i1, i0}; +@@ -5448,14 +4886,14 @@ FORCE_INLINE __m128i _mm_setr_epi32(int i3, int i2, int i1, int i0) + } + + // Set packed 64-bit integers in dst with the supplied values in reverse order. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_setr_epi64 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_setr_epi64 + FORCE_INLINE __m128i _mm_setr_epi64(__m64 e1, __m64 e0) + { + return vreinterpretq_m128i_s64(vcombine_s64(e1, e0)); + } + +-// Sets the 16 signed 8-bit integer values in reverse order. +-// https://msdn.microsoft.com/en-us/library/2khb9c7k(v=vs.90).aspx ++// Set packed 8-bit integers in dst with the supplied values in reverse order. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_setr_epi8 + FORCE_INLINE __m128i _mm_setr_epi8(signed char b0, + signed char b1, + signed char b2, +@@ -5483,14 +4921,14 @@ FORCE_INLINE __m128i _mm_setr_epi8(signed char b0, + + // Set packed double-precision (64-bit) floating-point elements in dst with the + // supplied values in reverse order. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_setr_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_setr_pd + FORCE_INLINE __m128d _mm_setr_pd(double e1, double e0) + { + return _mm_set_pd(e0, e1); + } + + // Return vector of type __m128d with all elements set to zero. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_setzero_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_setzero_pd + FORCE_INLINE __m128d _mm_setzero_pd(void) + { + #if defined(__aarch64__) +@@ -5500,15 +4938,16 @@ FORCE_INLINE __m128d _mm_setzero_pd(void) + #endif + } + +-// Sets the 128-bit value to zero +-// https://msdn.microsoft.com/en-us/library/vstudio/ys7dw0kh(v=vs.100).aspx ++// Return vector of type __m128i with all elements set to zero. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_setzero_si128 + FORCE_INLINE __m128i _mm_setzero_si128(void) + { + return vreinterpretq_m128i_s32(vdupq_n_s32(0)); + } + +-// Shuffles the 4 signed or unsigned 32-bit integers in a as specified by imm. +-// https://msdn.microsoft.com/en-us/library/56f67xbk%28v=vs.90%29.aspx ++// Shuffle 32-bit integers in a using the control in imm8, and store the results ++// in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_shuffle_epi32 + // FORCE_INLINE __m128i _mm_shuffle_epi32(__m128i a, + // __constrange(0,255) int imm) + #ifdef _sse2neon_shuffle +@@ -5577,11 +5016,7 @@ FORCE_INLINE __m128i _mm_setzero_si128(void) + + // Shuffle double-precision (64-bit) floating-point elements using the control + // in imm8, and store the results in dst. +-// +-// dst[63:0] := (imm8[0] == 0) ? a[63:0] : a[127:64] +-// dst[127:64] := (imm8[1] == 0) ? b[63:0] : b[127:64] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_shuffle_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_shuffle_pd + #ifdef _sse2neon_shuffle + #define _mm_shuffle_pd(a, b, imm8) \ + vreinterpretq_m128d_s64( \ +@@ -5627,17 +5062,7 @@ FORCE_INLINE __m128i _mm_setzero_si128(void) + + // Shift packed 16-bit integers in a left by count while shifting in zeros, and + // store the results in dst. +-// +-// FOR j := 0 to 7 +-// i := j*16 +-// IF count[63:0] > 15 +-// dst[i+15:i] := 0 +-// ELSE +-// dst[i+15:i] := ZeroExtend16(a[i+15:i] << count[63:0]) +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sll_epi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sll_epi16 + FORCE_INLINE __m128i _mm_sll_epi16(__m128i a, __m128i count) + { + uint64_t c = vreinterpretq_nth_u64_m128i(count, 0); +@@ -5650,17 +5075,7 @@ FORCE_INLINE __m128i _mm_sll_epi16(__m128i a, __m128i count) + + // Shift packed 32-bit integers in a left by count while shifting in zeros, and + // store the results in dst. +-// +-// FOR j := 0 to 3 +-// i := j*32 +-// IF count[63:0] > 31 +-// dst[i+31:i] := 0 +-// ELSE +-// dst[i+31:i] := ZeroExtend32(a[i+31:i] << count[63:0]) +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sll_epi32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sll_epi32 + FORCE_INLINE __m128i _mm_sll_epi32(__m128i a, __m128i count) + { + uint64_t c = vreinterpretq_nth_u64_m128i(count, 0); +@@ -5673,17 +5088,7 @@ FORCE_INLINE __m128i _mm_sll_epi32(__m128i a, __m128i count) + + // Shift packed 64-bit integers in a left by count while shifting in zeros, and + // store the results in dst. +-// +-// FOR j := 0 to 1 +-// i := j*64 +-// IF count[63:0] > 63 +-// dst[i+63:i] := 0 +-// ELSE +-// dst[i+63:i] := ZeroExtend64(a[i+63:i] << count[63:0]) +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sll_epi64 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sll_epi64 + FORCE_INLINE __m128i _mm_sll_epi64(__m128i a, __m128i count) + { + uint64_t c = vreinterpretq_nth_u64_m128i(count, 0); +@@ -5696,17 +5101,7 @@ FORCE_INLINE __m128i _mm_sll_epi64(__m128i a, __m128i count) + + // Shift packed 16-bit integers in a left by imm8 while shifting in zeros, and + // store the results in dst. +-// +-// FOR j := 0 to 7 +-// i := j*16 +-// IF imm8[7:0] > 15 +-// dst[i+15:i] := 0 +-// ELSE +-// dst[i+15:i] := ZeroExtend16(a[i+15:i] << imm8[7:0]) +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_slli_epi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_slli_epi16 + FORCE_INLINE __m128i _mm_slli_epi16(__m128i a, int imm) + { + if (_sse2neon_unlikely(imm & ~15)) +@@ -5717,17 +5112,7 @@ FORCE_INLINE __m128i _mm_slli_epi16(__m128i a, int imm) + + // Shift packed 32-bit integers in a left by imm8 while shifting in zeros, and + // store the results in dst. +-// +-// FOR j := 0 to 3 +-// i := j*32 +-// IF imm8[7:0] > 31 +-// dst[i+31:i] := 0 +-// ELSE +-// dst[i+31:i] := ZeroExtend32(a[i+31:i] << imm8[7:0]) +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_slli_epi32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_slli_epi32 + FORCE_INLINE __m128i _mm_slli_epi32(__m128i a, int imm) + { + if (_sse2neon_unlikely(imm & ~31)) +@@ -5738,17 +5123,7 @@ FORCE_INLINE __m128i _mm_slli_epi32(__m128i a, int imm) + + // Shift packed 64-bit integers in a left by imm8 while shifting in zeros, and + // store the results in dst. +-// +-// FOR j := 0 to 1 +-// i := j*64 +-// IF imm8[7:0] > 63 +-// dst[i+63:i] := 0 +-// ELSE +-// dst[i+63:i] := ZeroExtend64(a[i+63:i] << imm8[7:0]) +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_slli_epi64 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_slli_epi64 + FORCE_INLINE __m128i _mm_slli_epi64(__m128i a, int imm) + { + if (_sse2neon_unlikely(imm & ~63)) +@@ -5759,14 +5134,7 @@ FORCE_INLINE __m128i _mm_slli_epi64(__m128i a, int imm) + + // Shift a left by imm8 bytes while shifting in zeros, and store the results in + // dst. +-// +-// tmp := imm8[7:0] +-// IF tmp > 15 +-// tmp := 16 +-// FI +-// dst[127:0] := a[127:0] << (tmp*8) +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_slli_si128 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_slli_si128 + #define _mm_slli_si128(a, imm) \ + __extension__({ \ + int8x16_t ret; \ +@@ -5782,7 +5150,7 @@ FORCE_INLINE __m128i _mm_slli_epi64(__m128i a, int imm) + + // Compute the square root of packed double-precision (64-bit) floating-point + // elements in a, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sqrt_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sqrt_pd + FORCE_INLINE __m128d _mm_sqrt_pd(__m128d a) + { + #if defined(__aarch64__) +@@ -5797,7 +5165,7 @@ FORCE_INLINE __m128d _mm_sqrt_pd(__m128d a) + // Compute the square root of the lower double-precision (64-bit) floating-point + // element in b, store the result in the lower element of dst, and copy the + // upper element from a to the upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sqrt_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sqrt_sd + FORCE_INLINE __m128d _mm_sqrt_sd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -5809,17 +5177,7 @@ FORCE_INLINE __m128d _mm_sqrt_sd(__m128d a, __m128d b) + + // Shift packed 16-bit integers in a right by count while shifting in sign bits, + // and store the results in dst. +-// +-// FOR j := 0 to 7 +-// i := j*16 +-// IF count[63:0] > 15 +-// dst[i+15:i] := (a[i+15] ? 0xFFFF : 0x0) +-// ELSE +-// dst[i+15:i] := SignExtend16(a[i+15:i] >> count[63:0]) +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sra_epi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sra_epi16 + FORCE_INLINE __m128i _mm_sra_epi16(__m128i a, __m128i count) + { + int64_t c = (int64_t) vget_low_s64((int64x2_t) count); +@@ -5830,17 +5188,7 @@ FORCE_INLINE __m128i _mm_sra_epi16(__m128i a, __m128i count) + + // Shift packed 32-bit integers in a right by count while shifting in sign bits, + // and store the results in dst. +-// +-// FOR j := 0 to 3 +-// i := j*32 +-// IF count[63:0] > 31 +-// dst[i+31:i] := (a[i+31] ? 0xFFFFFFFF : 0x0) +-// ELSE +-// dst[i+31:i] := SignExtend32(a[i+31:i] >> count[63:0]) +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sra_epi32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sra_epi32 + FORCE_INLINE __m128i _mm_sra_epi32(__m128i a, __m128i count) + { + int64_t c = (int64_t) vget_low_s64((int64x2_t) count); +@@ -5851,17 +5199,7 @@ FORCE_INLINE __m128i _mm_sra_epi32(__m128i a, __m128i count) + + // Shift packed 16-bit integers in a right by imm8 while shifting in sign + // bits, and store the results in dst. +-// +-// FOR j := 0 to 7 +-// i := j*16 +-// IF imm8[7:0] > 15 +-// dst[i+15:i] := (a[i+15] ? 0xFFFF : 0x0) +-// ELSE +-// dst[i+15:i] := SignExtend16(a[i+15:i] >> imm8[7:0]) +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srai_epi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_srai_epi16 + FORCE_INLINE __m128i _mm_srai_epi16(__m128i a, int imm) + { + const int count = (imm & ~15) ? 15 : imm; +@@ -5870,17 +5208,7 @@ FORCE_INLINE __m128i _mm_srai_epi16(__m128i a, int imm) + + // Shift packed 32-bit integers in a right by imm8 while shifting in sign bits, + // and store the results in dst. +-// +-// FOR j := 0 to 3 +-// i := j*32 +-// IF imm8[7:0] > 31 +-// dst[i+31:i] := (a[i+31] ? 0xFFFFFFFF : 0x0) +-// ELSE +-// dst[i+31:i] := SignExtend32(a[i+31:i] >> imm8[7:0]) +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srai_epi32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_srai_epi32 + // FORCE_INLINE __m128i _mm_srai_epi32(__m128i a, __constrange(0,255) int imm) + #define _mm_srai_epi32(a, imm) \ + __extension__({ \ +@@ -5899,17 +5227,7 @@ FORCE_INLINE __m128i _mm_srai_epi16(__m128i a, int imm) + + // Shift packed 16-bit integers in a right by count while shifting in zeros, and + // store the results in dst. +-// +-// FOR j := 0 to 7 +-// i := j*16 +-// IF count[63:0] > 15 +-// dst[i+15:i] := 0 +-// ELSE +-// dst[i+15:i] := ZeroExtend16(a[i+15:i] >> count[63:0]) +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srl_epi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_srl_epi16 + FORCE_INLINE __m128i _mm_srl_epi16(__m128i a, __m128i count) + { + uint64_t c = vreinterpretq_nth_u64_m128i(count, 0); +@@ -5922,17 +5240,7 @@ FORCE_INLINE __m128i _mm_srl_epi16(__m128i a, __m128i count) + + // Shift packed 32-bit integers in a right by count while shifting in zeros, and + // store the results in dst. +-// +-// FOR j := 0 to 3 +-// i := j*32 +-// IF count[63:0] > 31 +-// dst[i+31:i] := 0 +-// ELSE +-// dst[i+31:i] := ZeroExtend32(a[i+31:i] >> count[63:0]) +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srl_epi32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_srl_epi32 + FORCE_INLINE __m128i _mm_srl_epi32(__m128i a, __m128i count) + { + uint64_t c = vreinterpretq_nth_u64_m128i(count, 0); +@@ -5945,17 +5253,7 @@ FORCE_INLINE __m128i _mm_srl_epi32(__m128i a, __m128i count) + + // Shift packed 64-bit integers in a right by count while shifting in zeros, and + // store the results in dst. +-// +-// FOR j := 0 to 1 +-// i := j*64 +-// IF count[63:0] > 63 +-// dst[i+63:i] := 0 +-// ELSE +-// dst[i+63:i] := ZeroExtend64(a[i+63:i] >> count[63:0]) +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srl_epi64 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_srl_epi64 + FORCE_INLINE __m128i _mm_srl_epi64(__m128i a, __m128i count) + { + uint64_t c = vreinterpretq_nth_u64_m128i(count, 0); +@@ -5968,17 +5266,7 @@ FORCE_INLINE __m128i _mm_srl_epi64(__m128i a, __m128i count) + + // Shift packed 16-bit integers in a right by imm8 while shifting in zeros, and + // store the results in dst. +-// +-// FOR j := 0 to 7 +-// i := j*16 +-// IF imm8[7:0] > 15 +-// dst[i+15:i] := 0 +-// ELSE +-// dst[i+15:i] := ZeroExtend16(a[i+15:i] >> imm8[7:0]) +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srli_epi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_srli_epi16 + #define _mm_srli_epi16(a, imm) \ + __extension__({ \ + __m128i ret; \ +@@ -5993,17 +5281,7 @@ FORCE_INLINE __m128i _mm_srl_epi64(__m128i a, __m128i count) + + // Shift packed 32-bit integers in a right by imm8 while shifting in zeros, and + // store the results in dst. +-// +-// FOR j := 0 to 3 +-// i := j*32 +-// IF imm8[7:0] > 31 +-// dst[i+31:i] := 0 +-// ELSE +-// dst[i+31:i] := ZeroExtend32(a[i+31:i] >> imm8[7:0]) +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srli_epi32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_srli_epi32 + // FORCE_INLINE __m128i _mm_srli_epi32(__m128i a, __constrange(0,255) int imm) + #define _mm_srli_epi32(a, imm) \ + __extension__({ \ +@@ -6019,17 +5297,7 @@ FORCE_INLINE __m128i _mm_srl_epi64(__m128i a, __m128i count) + + // Shift packed 64-bit integers in a right by imm8 while shifting in zeros, and + // store the results in dst. +-// +-// FOR j := 0 to 1 +-// i := j*64 +-// IF imm8[7:0] > 63 +-// dst[i+63:i] := 0 +-// ELSE +-// dst[i+63:i] := ZeroExtend64(a[i+63:i] >> imm8[7:0]) +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srli_epi64 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_srli_epi64 + #define _mm_srli_epi64(a, imm) \ + __extension__({ \ + __m128i ret; \ +@@ -6044,14 +5312,7 @@ FORCE_INLINE __m128i _mm_srl_epi64(__m128i a, __m128i count) + + // Shift a right by imm8 bytes while shifting in zeros, and store the results in + // dst. +-// +-// tmp := imm8[7:0] +-// IF tmp > 15 +-// tmp := 16 +-// FI +-// dst[127:0] := a[127:0] >> (tmp*8) +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_srli_si128 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_srli_si128 + #define _mm_srli_si128(a, imm) \ + __extension__({ \ + int8x16_t ret; \ +@@ -6066,7 +5327,7 @@ FORCE_INLINE __m128i _mm_srl_epi64(__m128i a, __m128i count) + // Store 128-bits (composed of 2 packed double-precision (64-bit) floating-point + // elements) from a into memory. mem_addr must be aligned on a 16-byte boundary + // or a general-protection exception may be generated. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_store_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_store_pd + FORCE_INLINE void _mm_store_pd(double *mem_addr, __m128d a) + { + #if defined(__aarch64__) +@@ -6079,7 +5340,7 @@ FORCE_INLINE void _mm_store_pd(double *mem_addr, __m128d a) + // Store the lower double-precision (64-bit) floating-point element from a into + // 2 contiguous elements in memory. mem_addr must be aligned on a 16-byte + // boundary or a general-protection exception may be generated. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_store_pd1 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_store_pd1 + FORCE_INLINE void _mm_store_pd1(double *mem_addr, __m128d a) + { + #if defined(__aarch64__) +@@ -6095,7 +5356,7 @@ FORCE_INLINE void _mm_store_pd1(double *mem_addr, __m128d a) + + // Store the lower double-precision (64-bit) floating-point element from a into + // memory. mem_addr does not need to be aligned on any particular boundary. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=mm_store_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=mm_store_sd + FORCE_INLINE void _mm_store_sd(double *mem_addr, __m128d a) + { + #if defined(__aarch64__) +@@ -6105,8 +5366,9 @@ FORCE_INLINE void _mm_store_sd(double *mem_addr, __m128d a) + #endif + } + +-// Stores four 32-bit integer values as (as a __m128i value) at the address p. +-// https://msdn.microsoft.com/en-us/library/vstudio/edk11s13(v=vs.100).aspx ++// Store 128-bits of integer data from a into memory. mem_addr must be aligned ++// on a 16-byte boundary or a general-protection exception may be generated. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_store_si128 + FORCE_INLINE void _mm_store_si128(__m128i *p, __m128i a) + { + vst1q_s32((int32_t *) p, vreinterpretq_s32_m128i(a)); +@@ -6115,15 +5377,12 @@ FORCE_INLINE void _mm_store_si128(__m128i *p, __m128i a) + // Store the lower double-precision (64-bit) floating-point element from a into + // 2 contiguous elements in memory. mem_addr must be aligned on a 16-byte + // boundary or a general-protection exception may be generated. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#expand=9,526,5601&text=_mm_store1_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#expand=9,526,5601&text=_mm_store1_pd + #define _mm_store1_pd _mm_store_pd1 + + // Store the upper double-precision (64-bit) floating-point element from a into + // memory. +-// +-// MEM[mem_addr+63:mem_addr] := a[127:64] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storeh_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storeh_pd + FORCE_INLINE void _mm_storeh_pd(double *mem_addr, __m128d a) + { + #if defined(__aarch64__) +@@ -6133,8 +5392,8 @@ FORCE_INLINE void _mm_storeh_pd(double *mem_addr, __m128d a) + #endif + } + +-// Reads the lower 64 bits of b and stores them into the lower 64 bits of a. +-// https://msdn.microsoft.com/en-us/library/hhwf428f%28v=vs.90%29.aspx ++// Store 64-bit integer from the first element of a into memory. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storel_epi64 + FORCE_INLINE void _mm_storel_epi64(__m128i *a, __m128i b) + { + vst1_u64((uint64_t *) a, vget_low_u64(vreinterpretq_u64_m128i(b))); +@@ -6142,10 +5401,7 @@ FORCE_INLINE void _mm_storel_epi64(__m128i *a, __m128i b) + + // Store the lower double-precision (64-bit) floating-point element from a into + // memory. +-// +-// MEM[mem_addr+63:mem_addr] := a[63:0] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storel_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storel_pd + FORCE_INLINE void _mm_storel_pd(double *mem_addr, __m128d a) + { + #if defined(__aarch64__) +@@ -6158,11 +5414,7 @@ FORCE_INLINE void _mm_storel_pd(double *mem_addr, __m128d a) + // Store 2 double-precision (64-bit) floating-point elements from a into memory + // in reverse order. mem_addr must be aligned on a 16-byte boundary or a + // general-protection exception may be generated. +-// +-// MEM[mem_addr+63:mem_addr] := a[127:64] +-// MEM[mem_addr+127:mem_addr+64] := a[63:0] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storer_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storer_pd + FORCE_INLINE void _mm_storer_pd(double *mem_addr, __m128d a) + { + float32x4_t f = vreinterpretq_f32_m128d(a); +@@ -6172,21 +5424,23 @@ FORCE_INLINE void _mm_storer_pd(double *mem_addr, __m128d a) + // Store 128-bits (composed of 2 packed double-precision (64-bit) floating-point + // elements) from a into memory. mem_addr does not need to be aligned on any + // particular boundary. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storeu_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storeu_pd + FORCE_INLINE void _mm_storeu_pd(double *mem_addr, __m128d a) + { + _mm_store_pd(mem_addr, a); + } + +-// Stores 128-bits of integer data a at the address p. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storeu_si128 ++// Store 128-bits of integer data from a into memory. mem_addr does not need to ++// be aligned on any particular boundary. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storeu_si128 + FORCE_INLINE void _mm_storeu_si128(__m128i *p, __m128i a) + { + vst1q_s32((int32_t *) p, vreinterpretq_s32_m128i(a)); + } + +-// Stores 32-bits of integer data a at the address p. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_storeu_si32 ++// Store 32-bit integer from the first element of a into memory. mem_addr does ++// not need to be aligned on any particular boundary. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_storeu_si32 + FORCE_INLINE void _mm_storeu_si32(void *p, __m128i a) + { + vst1q_lane_s32((int32_t *) p, vreinterpretq_s32_m128i(a), 0); +@@ -6196,7 +5450,7 @@ FORCE_INLINE void _mm_storeu_si32(void *p, __m128i a) + // elements) from a into memory using a non-temporal memory hint. mem_addr must + // be aligned on a 16-byte boundary or a general-protection exception may be + // generated. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_stream_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_stream_pd + FORCE_INLINE void _mm_stream_pd(double *p, __m128d a) + { + #if __has_builtin(__builtin_nontemporal_store) +@@ -6208,10 +5462,10 @@ FORCE_INLINE void _mm_stream_pd(double *p, __m128d a) + #endif + } + +-// Stores the data in a to the address p without polluting the caches. If the +-// cache line containing address p is already in the cache, the cache will be +-// updated. +-// https://msdn.microsoft.com/en-us/library/ba08y07y%28v=vs.90%29.aspx ++// Store 128-bits of integer data from a into memory using a non-temporal memory ++// hint. mem_addr must be aligned on a 16-byte boundary or a general-protection ++// exception may be generated. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_stream_si128 + FORCE_INLINE void _mm_stream_si128(__m128i *p, __m128i a) + { + #if __has_builtin(__builtin_nontemporal_store) +@@ -6224,7 +5478,7 @@ FORCE_INLINE void _mm_stream_si128(__m128i *p, __m128i a) + // Store 32-bit integer a into memory using a non-temporal hint to minimize + // cache pollution. If the cache line containing address mem_addr is already in + // the cache, the cache will be updated. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_stream_si32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_stream_si32 + FORCE_INLINE void _mm_stream_si32(int *p, int a) + { + vst1q_lane_s32((int32_t *) p, vdupq_n_s32(a), 0); +@@ -6233,7 +5487,7 @@ FORCE_INLINE void _mm_stream_si32(int *p, int a) + // Store 64-bit integer a into memory using a non-temporal hint to minimize + // cache pollution. If the cache line containing address mem_addr is already in + // the cache, the cache will be updated. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_stream_si64 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_stream_si64 + FORCE_INLINE void _mm_stream_si64(__int64 *p, __int64 a) + { + vst1_s64((int64_t *) p, vdup_n_s64((int64_t) a)); +@@ -6241,32 +5495,25 @@ FORCE_INLINE void _mm_stream_si64(__int64 *p, __int64 a) + + // Subtract packed 16-bit integers in b from packed 16-bit integers in a, and + // store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sub_epi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sub_epi16 + FORCE_INLINE __m128i _mm_sub_epi16(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s16( + vsubq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); + } + +-// Subtracts the 4 signed or unsigned 32-bit integers of b from the 4 signed or +-// unsigned 32-bit integers of a. +-// +-// r0 := a0 - b0 +-// r1 := a1 - b1 +-// r2 := a2 - b2 +-// r3 := a3 - b3 +-// +-// https://msdn.microsoft.com/en-us/library/vstudio/fhh866h0(v=vs.100).aspx ++// Subtract packed 32-bit integers in b from packed 32-bit integers in a, and ++// store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sub_epi32 + FORCE_INLINE __m128i _mm_sub_epi32(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s32( + vsubq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); + } + +-// Subtract 2 packed 64-bit integers in b from 2 packed 64-bit integers in a, +-// and store the results in dst. +-// r0 := a0 - b0 +-// r1 := a1 - b1 ++// Subtract packed 64-bit integers in b from packed 64-bit integers in a, and ++// store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sub_epi64 + FORCE_INLINE __m128i _mm_sub_epi64(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s64( +@@ -6275,7 +5522,7 @@ FORCE_INLINE __m128i _mm_sub_epi64(__m128i a, __m128i b) + + // Subtract packed 8-bit integers in b from packed 8-bit integers in a, and + // store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sub_epi8 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sub_epi8 + FORCE_INLINE __m128i _mm_sub_epi8(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s8( +@@ -6285,13 +5532,7 @@ FORCE_INLINE __m128i _mm_sub_epi8(__m128i a, __m128i b) + // Subtract packed double-precision (64-bit) floating-point elements in b from + // packed double-precision (64-bit) floating-point elements in a, and store the + // results in dst. +-// +-// FOR j := 0 to 1 +-// i := j*64 +-// dst[i+63:i] := a[i+63:i] - b[i+63:i] +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=mm_sub_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=mm_sub_pd + FORCE_INLINE __m128d _mm_sub_pd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -6311,71 +5552,50 @@ FORCE_INLINE __m128d _mm_sub_pd(__m128d a, __m128d b) + // the lower double-precision (64-bit) floating-point element in a, store the + // result in the lower element of dst, and copy the upper element from a to the + // upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sub_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sub_sd + FORCE_INLINE __m128d _mm_sub_sd(__m128d a, __m128d b) + { + return _mm_move_sd(a, _mm_sub_pd(a, b)); + } + + // Subtract 64-bit integer b from 64-bit integer a, and store the result in dst. +-// +-// dst[63:0] := a[63:0] - b[63:0] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sub_si64 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sub_si64 + FORCE_INLINE __m64 _mm_sub_si64(__m64 a, __m64 b) + { + return vreinterpret_m64_s64( + vsub_s64(vreinterpret_s64_m64(a), vreinterpret_s64_m64(b))); + } + +-// Subtracts the 8 signed 16-bit integers of b from the 8 signed 16-bit integers +-// of a and saturates. +-// +-// r0 := SignedSaturate(a0 - b0) +-// r1 := SignedSaturate(a1 - b1) +-// ... +-// r7 := SignedSaturate(a7 - b7) +-// +-// https://technet.microsoft.com/en-us/subscriptions/3247z5b8(v=vs.90) ++// Subtract packed signed 16-bit integers in b from packed 16-bit integers in a ++// using saturation, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_subs_epi16 + FORCE_INLINE __m128i _mm_subs_epi16(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s16( + vqsubq_s16(vreinterpretq_s16_m128i(a), vreinterpretq_s16_m128i(b))); + } + +-// Subtracts the 16 signed 8-bit integers of b from the 16 signed 8-bit integers +-// of a and saturates. +-// +-// r0 := SignedSaturate(a0 - b0) +-// r1 := SignedSaturate(a1 - b1) +-// ... +-// r15 := SignedSaturate(a15 - b15) +-// +-// https://technet.microsoft.com/en-us/subscriptions/by7kzks1(v=vs.90) ++// Subtract packed signed 8-bit integers in b from packed 8-bit integers in a ++// using saturation, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_subs_epi8 + FORCE_INLINE __m128i _mm_subs_epi8(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s8( + vqsubq_s8(vreinterpretq_s8_m128i(a), vreinterpretq_s8_m128i(b))); + } + +-// Subtracts the 8 unsigned 16-bit integers of bfrom the 8 unsigned 16-bit +-// integers of a and saturates.. +-// https://technet.microsoft.com/en-us/subscriptions/index/f44y0s19(v=vs.90).aspx ++// Subtract packed unsigned 16-bit integers in b from packed unsigned 16-bit ++// integers in a using saturation, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_subs_epu16 + FORCE_INLINE __m128i _mm_subs_epu16(__m128i a, __m128i b) + { + return vreinterpretq_m128i_u16( + vqsubq_u16(vreinterpretq_u16_m128i(a), vreinterpretq_u16_m128i(b))); + } + +-// Subtracts the 16 unsigned 8-bit integers of b from the 16 unsigned 8-bit +-// integers of a and saturates. +-// +-// r0 := UnsignedSaturate(a0 - b0) +-// r1 := UnsignedSaturate(a1 - b1) +-// ... +-// r15 := UnsignedSaturate(a15 - b15) +-// +-// https://technet.microsoft.com/en-us/subscriptions/yadkxc18(v=vs.90) ++// Subtract packed unsigned 8-bit integers in b from packed unsigned 8-bit ++// integers in a using saturation, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_subs_epu8 + FORCE_INLINE __m128i _mm_subs_epu8(__m128i a, __m128i b) + { + return vreinterpretq_m128i_u8( +@@ -6390,7 +5610,7 @@ FORCE_INLINE __m128i _mm_subs_epu8(__m128i a, __m128i b) + #define _mm_ucomineq_sd _mm_comineq_sd + + // Return vector of type __m128d with undefined elements. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_undefined_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_undefined_pd + FORCE_INLINE __m128d _mm_undefined_pd(void) + { + #if defined(__GNUC__) || defined(__clang__) +@@ -6404,19 +5624,9 @@ FORCE_INLINE __m128d _mm_undefined_pd(void) + #endif + } + +-// Interleaves the upper 4 signed or unsigned 16-bit integers in a with the +-// upper 4 signed or unsigned 16-bit integers in b. +-// +-// r0 := a4 +-// r1 := b4 +-// r2 := a5 +-// r3 := b5 +-// r4 := a6 +-// r5 := b6 +-// r6 := a7 +-// r7 := b7 +-// +-// https://msdn.microsoft.com/en-us/library/03196cz7(v=vs.100).aspx ++// Unpack and interleave 16-bit integers from the high half of a and b, and ++// store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpackhi_epi16 + FORCE_INLINE __m128i _mm_unpackhi_epi16(__m128i a, __m128i b) + { + #if defined(__aarch64__) +@@ -6430,9 +5640,9 @@ FORCE_INLINE __m128i _mm_unpackhi_epi16(__m128i a, __m128i b) + #endif + } + +-// Interleaves the upper 2 signed or unsigned 32-bit integers in a with the +-// upper 2 signed or unsigned 32-bit integers in b. +-// https://msdn.microsoft.com/en-us/library/65sa7cbs(v=vs.100).aspx ++// Unpack and interleave 32-bit integers from the high half of a and b, and ++// store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpackhi_epi32 + FORCE_INLINE __m128i _mm_unpackhi_epi32(__m128i a, __m128i b) + { + #if defined(__aarch64__) +@@ -6446,30 +5656,24 @@ FORCE_INLINE __m128i _mm_unpackhi_epi32(__m128i a, __m128i b) + #endif + } + +-// Interleaves the upper signed or unsigned 64-bit integer in a with the +-// upper signed or unsigned 64-bit integer in b. +-// +-// r0 := a1 +-// r1 := b1 ++// Unpack and interleave 64-bit integers from the high half of a and b, and ++// store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpackhi_epi64 + FORCE_INLINE __m128i _mm_unpackhi_epi64(__m128i a, __m128i b) + { ++#if defined(__aarch64__) ++ return vreinterpretq_m128i_s64( ++ vzip2q_s64(vreinterpretq_s64_m128i(a), vreinterpretq_s64_m128i(b))); ++#else + int64x1_t a_h = vget_high_s64(vreinterpretq_s64_m128i(a)); + int64x1_t b_h = vget_high_s64(vreinterpretq_s64_m128i(b)); + return vreinterpretq_m128i_s64(vcombine_s64(a_h, b_h)); ++#endif + } + +-// Interleaves the upper 8 signed or unsigned 8-bit integers in a with the upper +-// 8 signed or unsigned 8-bit integers in b. +-// +-// r0 := a8 +-// r1 := b8 +-// r2 := a9 +-// r3 := b9 +-// ... +-// r14 := a15 +-// r15 := b15 +-// +-// https://msdn.microsoft.com/en-us/library/t5h7783k(v=vs.100).aspx ++// Unpack and interleave 8-bit integers from the high half of a and b, and store ++// the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpackhi_epi8 + FORCE_INLINE __m128i _mm_unpackhi_epi8(__m128i a, __m128i b) + { + #if defined(__aarch64__) +@@ -6487,15 +5691,7 @@ FORCE_INLINE __m128i _mm_unpackhi_epi8(__m128i a, __m128i b) + + // Unpack and interleave double-precision (64-bit) floating-point elements from + // the high half of a and b, and store the results in dst. +-// +-// DEFINE INTERLEAVE_HIGH_QWORDS(src1[127:0], src2[127:0]) { +-// dst[63:0] := src1[127:64] +-// dst[127:64] := src2[127:64] +-// RETURN dst[127:0] +-// } +-// dst[127:0] := INTERLEAVE_HIGH_QWORDS(a[127:0], b[127:0]) +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_unpackhi_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpackhi_pd + FORCE_INLINE __m128d _mm_unpackhi_pd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -6508,19 +5704,9 @@ FORCE_INLINE __m128d _mm_unpackhi_pd(__m128d a, __m128d b) + #endif + } + +-// Interleaves the lower 4 signed or unsigned 16-bit integers in a with the +-// lower 4 signed or unsigned 16-bit integers in b. +-// +-// r0 := a0 +-// r1 := b0 +-// r2 := a1 +-// r3 := b1 +-// r4 := a2 +-// r5 := b2 +-// r6 := a3 +-// r7 := b3 +-// +-// https://msdn.microsoft.com/en-us/library/btxb17bw%28v=vs.90%29.aspx ++// Unpack and interleave 16-bit integers from the low half of a and b, and store ++// the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpacklo_epi16 + FORCE_INLINE __m128i _mm_unpacklo_epi16(__m128i a, __m128i b) + { + #if defined(__aarch64__) +@@ -6534,15 +5720,9 @@ FORCE_INLINE __m128i _mm_unpacklo_epi16(__m128i a, __m128i b) + #endif + } + +-// Interleaves the lower 2 signed or unsigned 32 - bit integers in a with the +-// lower 2 signed or unsigned 32 - bit integers in b. +-// +-// r0 := a0 +-// r1 := b0 +-// r2 := a1 +-// r3 := b1 +-// +-// https://msdn.microsoft.com/en-us/library/x8atst9d(v=vs.100).aspx ++// Unpack and interleave 32-bit integers from the low half of a and b, and store ++// the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpacklo_epi32 + FORCE_INLINE __m128i _mm_unpacklo_epi32(__m128i a, __m128i b) + { + #if defined(__aarch64__) +@@ -6556,25 +5736,24 @@ FORCE_INLINE __m128i _mm_unpacklo_epi32(__m128i a, __m128i b) + #endif + } + ++// Unpack and interleave 64-bit integers from the low half of a and b, and store ++// the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpacklo_epi64 + FORCE_INLINE __m128i _mm_unpacklo_epi64(__m128i a, __m128i b) + { ++#if defined(__aarch64__) ++ return vreinterpretq_m128i_s64( ++ vzip1q_s64(vreinterpretq_s64_m128i(a), vreinterpretq_s64_m128i(b))); ++#else + int64x1_t a_l = vget_low_s64(vreinterpretq_s64_m128i(a)); + int64x1_t b_l = vget_low_s64(vreinterpretq_s64_m128i(b)); + return vreinterpretq_m128i_s64(vcombine_s64(a_l, b_l)); ++#endif + } + +-// Interleaves the lower 8 signed or unsigned 8-bit integers in a with the lower +-// 8 signed or unsigned 8-bit integers in b. +-// +-// r0 := a0 +-// r1 := b0 +-// r2 := a1 +-// r3 := b1 +-// ... +-// r14 := a7 +-// r15 := b7 +-// +-// https://msdn.microsoft.com/en-us/library/xf7k860c%28v=vs.90%29.aspx ++// Unpack and interleave 8-bit integers from the low half of a and b, and store ++// the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpacklo_epi8 + FORCE_INLINE __m128i _mm_unpacklo_epi8(__m128i a, __m128i b) + { + #if defined(__aarch64__) +@@ -6590,15 +5769,7 @@ FORCE_INLINE __m128i _mm_unpacklo_epi8(__m128i a, __m128i b) + + // Unpack and interleave double-precision (64-bit) floating-point elements from + // the low half of a and b, and store the results in dst. +-// +-// DEFINE INTERLEAVE_QWORDS(src1[127:0], src2[127:0]) { +-// dst[63:0] := src1[63:0] +-// dst[127:64] := src2[63:0] +-// RETURN dst[127:0] +-// } +-// dst[127:0] := INTERLEAVE_QWORDS(a[127:0], b[127:0]) +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_unpacklo_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_unpacklo_pd + FORCE_INLINE __m128d _mm_unpacklo_pd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -6613,21 +5784,16 @@ FORCE_INLINE __m128d _mm_unpacklo_pd(__m128d a, __m128d b) + + // Compute the bitwise XOR of packed double-precision (64-bit) floating-point + // elements in a and b, and store the results in dst. +-// +-// FOR j := 0 to 1 +-// i := j*64 +-// dst[i+63:i] := a[i+63:i] XOR b[i+63:i] +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_xor_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_xor_pd + FORCE_INLINE __m128d _mm_xor_pd(__m128d a, __m128d b) + { + return vreinterpretq_m128d_s64( + veorq_s64(vreinterpretq_s64_m128d(a), vreinterpretq_s64_m128d(b))); + } + +-// Computes the bitwise XOR of the 128-bit value in a and the 128-bit value in +-// b. https://msdn.microsoft.com/en-us/library/fzt08www(v=vs.100).aspx ++// Compute the bitwise XOR of 128 bits (representing integer data) in a and b, ++// and store the result in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_xor_si128 + FORCE_INLINE __m128i _mm_xor_si128(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s32( +@@ -6639,17 +5805,7 @@ FORCE_INLINE __m128i _mm_xor_si128(__m128i a, __m128i b) + // Alternatively add and subtract packed double-precision (64-bit) + // floating-point elements in a to/from packed elements in b, and store the + // results in dst. +-// +-// FOR j := 0 to 1 +-// i := j*64 +-// IF ((j & 1) == 0) +-// dst[i+63:i] := a[i+63:i] - b[i+63:i] +-// ELSE +-// dst[i+63:i] := a[i+63:i] + b[i+63:i] +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_addsub_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_addsub_pd + FORCE_INLINE __m128d _mm_addsub_pd(__m128d a, __m128d b) + { + _sse2neon_const __m128d mask = _mm_set_pd(1.0f, -1.0f); +@@ -6665,7 +5821,7 @@ FORCE_INLINE __m128d _mm_addsub_pd(__m128d a, __m128d b) + // Alternatively add and subtract packed single-precision (32-bit) + // floating-point elements in a to/from packed elements in b, and store the + // results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=addsub_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=addsub_ps + FORCE_INLINE __m128 _mm_addsub_ps(__m128 a, __m128 b) + { + _sse2neon_const __m128 mask = _mm_setr_ps(-1.0f, 1.0f, -1.0f, 1.0f); +@@ -6680,7 +5836,7 @@ FORCE_INLINE __m128 _mm_addsub_ps(__m128 a, __m128 b) + + // Horizontally add adjacent pairs of double-precision (64-bit) floating-point + // elements in a and b, and pack the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hadd_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hadd_pd + FORCE_INLINE __m128d _mm_hadd_pd(__m128d a, __m128d b) + { + #if defined(__aarch64__) +@@ -6694,9 +5850,9 @@ FORCE_INLINE __m128d _mm_hadd_pd(__m128d a, __m128d b) + #endif + } + +-// Computes pairwise add of each argument as single-precision, floating-point +-// values a and b. +-// https://msdn.microsoft.com/en-us/library/yd9wecaa.aspx ++// Horizontally add adjacent pairs of single-precision (32-bit) floating-point ++// elements in a and b, and pack the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hadd_ps + FORCE_INLINE __m128 _mm_hadd_ps(__m128 a, __m128 b) + { + #if defined(__aarch64__) +@@ -6714,7 +5870,7 @@ FORCE_INLINE __m128 _mm_hadd_ps(__m128 a, __m128 b) + + // Horizontally subtract adjacent pairs of double-precision (64-bit) + // floating-point elements in a and b, and pack the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsub_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hsub_pd + FORCE_INLINE __m128d _mm_hsub_pd(__m128d _a, __m128d _b) + { + #if defined(__aarch64__) +@@ -6732,7 +5888,7 @@ FORCE_INLINE __m128d _mm_hsub_pd(__m128d _a, __m128d _b) + + // Horizontally subtract adjacent pairs of single-precision (32-bit) + // floating-point elements in a and b, and pack the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsub_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hsub_ps + FORCE_INLINE __m128 _mm_hsub_ps(__m128 _a, __m128 _b) + { + float32x4_t a = vreinterpretq_f32_m128(_a); +@@ -6749,24 +5905,17 @@ FORCE_INLINE __m128 _mm_hsub_ps(__m128 _a, __m128 _b) + // Load 128-bits of integer data from unaligned memory into dst. This intrinsic + // may perform better than _mm_loadu_si128 when the data crosses a cache line + // boundary. +-// +-// dst[127:0] := MEM[mem_addr+127:mem_addr] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_lddqu_si128 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_lddqu_si128 + #define _mm_lddqu_si128 _mm_loadu_si128 + + // Load a double-precision (64-bit) floating-point element from memory into both + // elements of dst. +-// +-// dst[63:0] := MEM[mem_addr+63:mem_addr] +-// dst[127:64] := MEM[mem_addr+63:mem_addr] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_loaddup_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loaddup_pd + #define _mm_loaddup_pd _mm_load1_pd + + // Duplicate the low double-precision (64-bit) floating-point element from a, + // and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_movedup_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_movedup_pd + FORCE_INLINE __m128d _mm_movedup_pd(__m128d a) + { + #if defined(__aarch64__) +@@ -6780,7 +5929,7 @@ FORCE_INLINE __m128d _mm_movedup_pd(__m128d a) + + // Duplicate odd-indexed single-precision (32-bit) floating-point elements + // from a, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_movehdup_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_movehdup_ps + FORCE_INLINE __m128 _mm_movehdup_ps(__m128 a) + { + #if defined(__aarch64__) +@@ -6799,7 +5948,7 @@ FORCE_INLINE __m128 _mm_movehdup_ps(__m128 a) + + // Duplicate even-indexed single-precision (32-bit) floating-point elements + // from a, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_moveldup_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_moveldup_ps + FORCE_INLINE __m128 _mm_moveldup_ps(__m128 a) + { + #if defined(__aarch64__) +@@ -6820,13 +5969,7 @@ FORCE_INLINE __m128 _mm_moveldup_ps(__m128 a) + + // Compute the absolute value of packed signed 16-bit integers in a, and store + // the unsigned results in dst. +-// +-// FOR j := 0 to 7 +-// i := j*16 +-// dst[i+15:i] := ABS(a[i+15:i]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_epi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_abs_epi16 + FORCE_INLINE __m128i _mm_abs_epi16(__m128i a) + { + return vreinterpretq_m128i_s16(vabsq_s16(vreinterpretq_s16_m128i(a))); +@@ -6834,13 +5977,7 @@ FORCE_INLINE __m128i _mm_abs_epi16(__m128i a) + + // Compute the absolute value of packed signed 32-bit integers in a, and store + // the unsigned results in dst. +-// +-// FOR j := 0 to 3 +-// i := j*32 +-// dst[i+31:i] := ABS(a[i+31:i]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_epi32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_abs_epi32 + FORCE_INLINE __m128i _mm_abs_epi32(__m128i a) + { + return vreinterpretq_m128i_s32(vabsq_s32(vreinterpretq_s32_m128i(a))); +@@ -6848,13 +5985,7 @@ FORCE_INLINE __m128i _mm_abs_epi32(__m128i a) + + // Compute the absolute value of packed signed 8-bit integers in a, and store + // the unsigned results in dst. +-// +-// FOR j := 0 to 15 +-// i := j*8 +-// dst[i+7:i] := ABS(a[i+7:i]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_epi8 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_abs_epi8 + FORCE_INLINE __m128i _mm_abs_epi8(__m128i a) + { + return vreinterpretq_m128i_s8(vabsq_s8(vreinterpretq_s8_m128i(a))); +@@ -6862,13 +5993,7 @@ FORCE_INLINE __m128i _mm_abs_epi8(__m128i a) + + // Compute the absolute value of packed signed 16-bit integers in a, and store + // the unsigned results in dst. +-// +-// FOR j := 0 to 3 +-// i := j*16 +-// dst[i+15:i] := ABS(a[i+15:i]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_pi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_abs_pi16 + FORCE_INLINE __m64 _mm_abs_pi16(__m64 a) + { + return vreinterpret_m64_s16(vabs_s16(vreinterpret_s16_m64(a))); +@@ -6876,13 +6001,7 @@ FORCE_INLINE __m64 _mm_abs_pi16(__m64 a) + + // Compute the absolute value of packed signed 32-bit integers in a, and store + // the unsigned results in dst. +-// +-// FOR j := 0 to 1 +-// i := j*32 +-// dst[i+31:i] := ABS(a[i+31:i]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_pi32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_abs_pi32 + FORCE_INLINE __m64 _mm_abs_pi32(__m64 a) + { + return vreinterpret_m64_s32(vabs_s32(vreinterpret_s32_m64(a))); +@@ -6890,13 +6009,7 @@ FORCE_INLINE __m64 _mm_abs_pi32(__m64 a) + + // Compute the absolute value of packed signed 8-bit integers in a, and store + // the unsigned results in dst. +-// +-// FOR j := 0 to 7 +-// i := j*8 +-// dst[i+7:i] := ABS(a[i+7:i]) +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_abs_pi8 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_abs_pi8 + FORCE_INLINE __m64 _mm_abs_pi8(__m64 a) + { + return vreinterpret_m64_s8(vabs_s8(vreinterpret_s8_m64(a))); +@@ -6904,11 +6017,7 @@ FORCE_INLINE __m64 _mm_abs_pi8(__m64 a) + + // Concatenate 16-byte blocks in a and b into a 32-byte temporary result, shift + // the result right by imm8 bytes, and store the low 16 bytes in dst. +-// +-// tmp[255:0] := ((a[127:0] << 128)[255:0] OR b[127:0]) >> (imm8*8) +-// dst[127:0] := tmp[127:0] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_alignr_epi8 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_alignr_epi8 + #define _mm_alignr_epi8(a, b, imm) \ + __extension__({ \ + uint8x16_t _a = vreinterpretq_u8_m128i(a); \ +@@ -6926,11 +6035,7 @@ FORCE_INLINE __m64 _mm_abs_pi8(__m64 a) + + // Concatenate 8-byte blocks in a and b into a 16-byte temporary result, shift + // the result right by imm8 bytes, and store the low 8 bytes in dst. +-// +-// tmp[127:0] := ((a[63:0] << 64)[127:0] OR b[63:0]) >> (imm8*8) +-// dst[63:0] := tmp[63:0] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_alignr_pi8 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_alignr_pi8 + #define _mm_alignr_pi8(a, b, imm) \ + __extension__({ \ + __m64 ret; \ +@@ -6953,8 +6058,9 @@ FORCE_INLINE __m64 _mm_abs_pi8(__m64 a) + ret; \ + }) + +-// Computes pairwise add of each argument as a 16-bit signed or unsigned integer +-// values a and b. ++// Horizontally add adjacent pairs of 16-bit integers in a and b, and pack the ++// signed 16-bit results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hadd_epi16 + FORCE_INLINE __m128i _mm_hadd_epi16(__m128i _a, __m128i _b) + { + int16x8_t a = vreinterpretq_s16_m128i(_a); +@@ -6968,8 +6074,9 @@ FORCE_INLINE __m128i _mm_hadd_epi16(__m128i _a, __m128i _b) + #endif + } + +-// Computes pairwise add of each argument as a 32-bit signed or unsigned integer +-// values a and b. ++// Horizontally add adjacent pairs of 32-bit integers in a and b, and pack the ++// signed 32-bit results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hadd_epi32 + FORCE_INLINE __m128i _mm_hadd_epi32(__m128i _a, __m128i _b) + { + int32x4_t a = vreinterpretq_s32_m128i(_a); +@@ -6985,7 +6092,7 @@ FORCE_INLINE __m128i _mm_hadd_epi32(__m128i _a, __m128i _b) + + // Horizontally add adjacent pairs of 16-bit integers in a and b, and pack the + // signed 16-bit results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hadd_pi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hadd_pi16 + FORCE_INLINE __m64 _mm_hadd_pi16(__m64 a, __m64 b) + { + return vreinterpret_m64_s16( +@@ -6994,15 +6101,16 @@ FORCE_INLINE __m64 _mm_hadd_pi16(__m64 a, __m64 b) + + // Horizontally add adjacent pairs of 32-bit integers in a and b, and pack the + // signed 32-bit results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hadd_pi32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hadd_pi32 + FORCE_INLINE __m64 _mm_hadd_pi32(__m64 a, __m64 b) + { + return vreinterpret_m64_s32( + vpadd_s32(vreinterpret_s32_m64(a), vreinterpret_s32_m64(b))); + } + +-// Computes saturated pairwise sub of each argument as a 16-bit signed +-// integer values a and b. ++// Horizontally add adjacent pairs of signed 16-bit integers in a and b using ++// saturation, and pack the signed 16-bit results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hadds_epi16 + FORCE_INLINE __m128i _mm_hadds_epi16(__m128i _a, __m128i _b) + { + #if defined(__aarch64__) +@@ -7025,7 +6133,7 @@ FORCE_INLINE __m128i _mm_hadds_epi16(__m128i _a, __m128i _b) + + // Horizontally add adjacent pairs of signed 16-bit integers in a and b using + // saturation, and pack the signed 16-bit results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hadds_pi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hadds_pi16 + FORCE_INLINE __m64 _mm_hadds_pi16(__m64 _a, __m64 _b) + { + int16x4_t a = vreinterpret_s16_m64(_a); +@@ -7040,7 +6148,7 @@ FORCE_INLINE __m64 _mm_hadds_pi16(__m64 _a, __m64 _b) + + // Horizontally subtract adjacent pairs of 16-bit integers in a and b, and pack + // the signed 16-bit results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsub_epi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hsub_epi16 + FORCE_INLINE __m128i _mm_hsub_epi16(__m128i _a, __m128i _b) + { + int16x8_t a = vreinterpretq_s16_m128i(_a); +@@ -7056,7 +6164,7 @@ FORCE_INLINE __m128i _mm_hsub_epi16(__m128i _a, __m128i _b) + + // Horizontally subtract adjacent pairs of 32-bit integers in a and b, and pack + // the signed 32-bit results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsub_epi32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hsub_epi32 + FORCE_INLINE __m128i _mm_hsub_epi32(__m128i _a, __m128i _b) + { + int32x4_t a = vreinterpretq_s32_m128i(_a); +@@ -7072,7 +6180,7 @@ FORCE_INLINE __m128i _mm_hsub_epi32(__m128i _a, __m128i _b) + + // Horizontally subtract adjacent pairs of 16-bit integers in a and b, and pack + // the signed 16-bit results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsub_pi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hsub_pi16 + FORCE_INLINE __m64 _mm_hsub_pi16(__m64 _a, __m64 _b) + { + int16x4_t a = vreinterpret_s16_m64(_a); +@@ -7087,7 +6195,7 @@ FORCE_INLINE __m64 _mm_hsub_pi16(__m64 _a, __m64 _b) + + // Horizontally subtract adjacent pairs of 32-bit integers in a and b, and pack + // the signed 32-bit results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=mm_hsub_pi32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=mm_hsub_pi32 + FORCE_INLINE __m64 _mm_hsub_pi32(__m64 _a, __m64 _b) + { + int32x2_t a = vreinterpret_s32_m64(_a); +@@ -7100,9 +6208,9 @@ FORCE_INLINE __m64 _mm_hsub_pi32(__m64 _a, __m64 _b) + #endif + } + +-// Computes saturated pairwise difference of each argument as a 16-bit signed +-// integer values a and b. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsubs_epi16 ++// Horizontally subtract adjacent pairs of signed 16-bit integers in a and b ++// using saturation, and pack the signed 16-bit results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hsubs_epi16 + FORCE_INLINE __m128i _mm_hsubs_epi16(__m128i _a, __m128i _b) + { + int16x8_t a = vreinterpretq_s16_m128i(_a); +@@ -7118,7 +6226,7 @@ FORCE_INLINE __m128i _mm_hsubs_epi16(__m128i _a, __m128i _b) + + // Horizontally subtract adjacent pairs of signed 16-bit integers in a and b + // using saturation, and pack the signed 16-bit results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_hsubs_pi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_hsubs_pi16 + FORCE_INLINE __m64 _mm_hsubs_pi16(__m64 _a, __m64 _b) + { + int16x4_t a = vreinterpret_s16_m64(_a); +@@ -7135,12 +6243,7 @@ FORCE_INLINE __m64 _mm_hsubs_pi16(__m64 _a, __m64 _b) + // signed 8-bit integer from b, producing intermediate signed 16-bit integers. + // Horizontally add adjacent pairs of intermediate signed 16-bit integers, + // and pack the saturated results in dst. +-// +-// FOR j := 0 to 7 +-// i := j*16 +-// dst[i+15:i] := Saturate_To_Int16( a[i+15:i+8]*b[i+15:i+8] + +-// a[i+7:i]*b[i+7:i] ) +-// ENDFOR ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_maddubs_epi16 + FORCE_INLINE __m128i _mm_maddubs_epi16(__m128i _a, __m128i _b) + { + #if defined(__aarch64__) +@@ -7179,7 +6282,7 @@ FORCE_INLINE __m128i _mm_maddubs_epi16(__m128i _a, __m128i _b) + // signed 8-bit integer from b, producing intermediate signed 16-bit integers. + // Horizontally add adjacent pairs of intermediate signed 16-bit integers, and + // pack the saturated results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_maddubs_pi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_maddubs_pi16 + FORCE_INLINE __m64 _mm_maddubs_pi16(__m64 _a, __m64 _b) + { + uint16x4_t a = vreinterpret_u16_m64(_a); +@@ -7204,12 +6307,7 @@ FORCE_INLINE __m64 _mm_maddubs_pi16(__m64 _a, __m64 _b) + // Multiply packed signed 16-bit integers in a and b, producing intermediate + // signed 32-bit integers. Shift right by 15 bits while rounding up, and store + // the packed 16-bit integers in dst. +-// +-// r0 := Round(((int32_t)a0 * (int32_t)b0) >> 15) +-// r1 := Round(((int32_t)a1 * (int32_t)b1) >> 15) +-// r2 := Round(((int32_t)a2 * (int32_t)b2) >> 15) +-// ... +-// r7 := Round(((int32_t)a7 * (int32_t)b7) >> 15) ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mulhrs_epi16 + FORCE_INLINE __m128i _mm_mulhrs_epi16(__m128i a, __m128i b) + { + // Has issues due to saturation +@@ -7233,7 +6331,7 @@ FORCE_INLINE __m128i _mm_mulhrs_epi16(__m128i a, __m128i b) + // Multiply packed signed 16-bit integers in a and b, producing intermediate + // signed 32-bit integers. Truncate each intermediate integer to the 18 most + // significant bits, round by adding 1, and store bits [16:1] to dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mulhrs_pi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mulhrs_pi16 + FORCE_INLINE __m64 _mm_mulhrs_pi16(__m64 a, __m64 b) + { + int32x4_t mul_extend = +@@ -7245,7 +6343,7 @@ FORCE_INLINE __m64 _mm_mulhrs_pi16(__m64 a, __m64 b) + + // Shuffle packed 8-bit integers in a according to shuffle control mask in the + // corresponding 8-bit element of b, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_shuffle_epi8 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_shuffle_epi8 + FORCE_INLINE __m128i _mm_shuffle_epi8(__m128i a, __m128i b) + { + int8x16_t tbl = vreinterpretq_s8_m128i(a); // input a +@@ -7275,18 +6373,7 @@ FORCE_INLINE __m128i _mm_shuffle_epi8(__m128i a, __m128i b) + + // Shuffle packed 8-bit integers in a according to shuffle control mask in the + // corresponding 8-bit element of b, and store the results in dst. +-// +-// FOR j := 0 to 7 +-// i := j*8 +-// IF b[i+7] == 1 +-// dst[i+7:i] := 0 +-// ELSE +-// index[2:0] := b[i+2:i] +-// dst[i+7:i] := a[index*8+7:index*8] +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_shuffle_pi8 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_shuffle_pi8 + FORCE_INLINE __m64 _mm_shuffle_pi8(__m64 a, __m64 b) + { + const int8x8_t controlMask = +@@ -7299,16 +6386,7 @@ FORCE_INLINE __m64 _mm_shuffle_pi8(__m64 a, __m64 b) + // 16-bit integer in b is negative, and store the results in dst. + // Element in dst are zeroed out when the corresponding element + // in b is zero. +-// +-// for i in 0..7 +-// if b[i] < 0 +-// r[i] := -a[i] +-// else if b[i] == 0 +-// r[i] := 0 +-// else +-// r[i] := a[i] +-// fi +-// done ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sign_epi16 + FORCE_INLINE __m128i _mm_sign_epi16(__m128i _a, __m128i _b) + { + int16x8_t a = vreinterpretq_s16_m128i(_a); +@@ -7336,16 +6414,7 @@ FORCE_INLINE __m128i _mm_sign_epi16(__m128i _a, __m128i _b) + // 32-bit integer in b is negative, and store the results in dst. + // Element in dst are zeroed out when the corresponding element + // in b is zero. +-// +-// for i in 0..3 +-// if b[i] < 0 +-// r[i] := -a[i] +-// else if b[i] == 0 +-// r[i] := 0 +-// else +-// r[i] := a[i] +-// fi +-// done ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sign_epi32 + FORCE_INLINE __m128i _mm_sign_epi32(__m128i _a, __m128i _b) + { + int32x4_t a = vreinterpretq_s32_m128i(_a); +@@ -7374,16 +6443,7 @@ FORCE_INLINE __m128i _mm_sign_epi32(__m128i _a, __m128i _b) + // 8-bit integer in b is negative, and store the results in dst. + // Element in dst are zeroed out when the corresponding element + // in b is zero. +-// +-// for i in 0..15 +-// if b[i] < 0 +-// r[i] := -a[i] +-// else if b[i] == 0 +-// r[i] := 0 +-// else +-// r[i] := a[i] +-// fi +-// done ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sign_epi8 + FORCE_INLINE __m128i _mm_sign_epi8(__m128i _a, __m128i _b) + { + int8x16_t a = vreinterpretq_s8_m128i(_a); +@@ -7412,19 +6472,7 @@ FORCE_INLINE __m128i _mm_sign_epi8(__m128i _a, __m128i _b) + // Negate packed 16-bit integers in a when the corresponding signed 16-bit + // integer in b is negative, and store the results in dst. Element in dst are + // zeroed out when the corresponding element in b is zero. +-// +-// FOR j := 0 to 3 +-// i := j*16 +-// IF b[i+15:i] < 0 +-// dst[i+15:i] := -(a[i+15:i]) +-// ELSE IF b[i+15:i] == 0 +-// dst[i+15:i] := 0 +-// ELSE +-// dst[i+15:i] := a[i+15:i] +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sign_pi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sign_pi16 + FORCE_INLINE __m64 _mm_sign_pi16(__m64 _a, __m64 _b) + { + int16x4_t a = vreinterpret_s16_m64(_a); +@@ -7453,19 +6501,7 @@ FORCE_INLINE __m64 _mm_sign_pi16(__m64 _a, __m64 _b) + // Negate packed 32-bit integers in a when the corresponding signed 32-bit + // integer in b is negative, and store the results in dst. Element in dst are + // zeroed out when the corresponding element in b is zero. +-// +-// FOR j := 0 to 1 +-// i := j*32 +-// IF b[i+31:i] < 0 +-// dst[i+31:i] := -(a[i+31:i]) +-// ELSE IF b[i+31:i] == 0 +-// dst[i+31:i] := 0 +-// ELSE +-// dst[i+31:i] := a[i+31:i] +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sign_pi32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sign_pi32 + FORCE_INLINE __m64 _mm_sign_pi32(__m64 _a, __m64 _b) + { + int32x2_t a = vreinterpret_s32_m64(_a); +@@ -7494,19 +6530,7 @@ FORCE_INLINE __m64 _mm_sign_pi32(__m64 _a, __m64 _b) + // Negate packed 8-bit integers in a when the corresponding signed 8-bit integer + // in b is negative, and store the results in dst. Element in dst are zeroed out + // when the corresponding element in b is zero. +-// +-// FOR j := 0 to 7 +-// i := j*8 +-// IF b[i+7:i] < 0 +-// dst[i+7:i] := -(a[i+7:i]) +-// ELSE IF b[i+7:i] == 0 +-// dst[i+7:i] := 0 +-// ELSE +-// dst[i+7:i] := a[i+7:i] +-// FI +-// ENDFOR +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_sign_pi8 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sign_pi8 + FORCE_INLINE __m64 _mm_sign_pi8(__m64 _a, __m64 _b) + { + int8x8_t a = vreinterpret_s8_m64(_a); +@@ -7536,15 +6560,7 @@ FORCE_INLINE __m64 _mm_sign_pi8(__m64 _a, __m64 _b) + + // Blend packed 16-bit integers from a and b using control mask imm8, and store + // the results in dst. +-// +-// FOR j := 0 to 7 +-// i := j*16 +-// IF imm8[j] +-// dst[i+15:i] := b[i+15:i] +-// ELSE +-// dst[i+15:i] := a[i+15:i] +-// FI +-// ENDFOR ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_blend_epi16 + // FORCE_INLINE __m128i _mm_blend_epi16(__m128i a, __m128i b, + // __constrange(0,255) int imm) + #define _mm_blend_epi16(a, b, imm) \ +@@ -7565,7 +6581,7 @@ FORCE_INLINE __m64 _mm_sign_pi8(__m64 _a, __m64 _b) + + // Blend packed double-precision (64-bit) floating-point elements from a and b + // using control mask imm8, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_blend_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_blend_pd + #define _mm_blend_pd(a, b, imm) \ + __extension__({ \ + const uint64_t _mask[2] = { \ +@@ -7579,7 +6595,7 @@ FORCE_INLINE __m64 _mm_sign_pi8(__m64 _a, __m64 _b) + + // Blend packed single-precision (32-bit) floating-point elements from a and b + // using mask, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_blend_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_blend_ps + FORCE_INLINE __m128 _mm_blend_ps(__m128 _a, __m128 _b, const char imm8) + { + const uint32_t ALIGN_STRUCT(16) +@@ -7595,15 +6611,7 @@ FORCE_INLINE __m128 _mm_blend_ps(__m128 _a, __m128 _b, const char imm8) + + // Blend packed 8-bit integers from a and b using mask, and store the results in + // dst. +-// +-// FOR j := 0 to 15 +-// i := j*8 +-// IF mask[i+7] +-// dst[i+7:i] := b[i+7:i] +-// ELSE +-// dst[i+7:i] := a[i+7:i] +-// FI +-// ENDFOR ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_blendv_epi8 + FORCE_INLINE __m128i _mm_blendv_epi8(__m128i _a, __m128i _b, __m128i _mask) + { + // Use a signed shift right to create a mask with the sign bit +@@ -7616,7 +6624,7 @@ FORCE_INLINE __m128i _mm_blendv_epi8(__m128i _a, __m128i _b, __m128i _mask) + + // Blend packed double-precision (64-bit) floating-point elements from a and b + // using mask, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_blendv_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_blendv_pd + FORCE_INLINE __m128d _mm_blendv_pd(__m128d _a, __m128d _b, __m128d _mask) + { + uint64x2_t mask = +@@ -7634,7 +6642,7 @@ FORCE_INLINE __m128d _mm_blendv_pd(__m128d _a, __m128d _b, __m128d _mask) + + // Blend packed single-precision (32-bit) floating-point elements from a and b + // using mask, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_blendv_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_blendv_ps + FORCE_INLINE __m128 _mm_blendv_ps(__m128 _a, __m128 _b, __m128 _mask) + { + // Use a signed shift right to create a mask with the sign bit +@@ -7648,7 +6656,7 @@ FORCE_INLINE __m128 _mm_blendv_ps(__m128 _a, __m128 _b, __m128 _mask) + // Round the packed double-precision (64-bit) floating-point elements in a up + // to an integer value, and store the results as packed double-precision + // floating-point elements in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_ceil_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_ceil_pd + FORCE_INLINE __m128d _mm_ceil_pd(__m128d a) + { + #if defined(__aarch64__) +@@ -7662,7 +6670,7 @@ FORCE_INLINE __m128d _mm_ceil_pd(__m128d a) + // Round the packed single-precision (32-bit) floating-point elements in a up to + // an integer value, and store the results as packed single-precision + // floating-point elements in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_ceil_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_ceil_ps + FORCE_INLINE __m128 _mm_ceil_ps(__m128 a) + { + #if defined(__aarch64__) || defined(__ARM_FEATURE_DIRECTED_ROUNDING) +@@ -7677,7 +6685,7 @@ FORCE_INLINE __m128 _mm_ceil_ps(__m128 a) + // an integer value, store the result as a double-precision floating-point + // element in the lower element of dst, and copy the upper element from a to the + // upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_ceil_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_ceil_sd + FORCE_INLINE __m128d _mm_ceil_sd(__m128d a, __m128d b) + { + return _mm_move_sd(a, _mm_ceil_pd(b)); +@@ -7687,11 +6695,7 @@ FORCE_INLINE __m128d _mm_ceil_sd(__m128d a, __m128d b) + // an integer value, store the result as a single-precision floating-point + // element in the lower element of dst, and copy the upper 3 packed elements + // from a to the upper elements of dst. +-// +-// dst[31:0] := CEIL(b[31:0]) +-// dst[127:32] := a[127:32] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_ceil_ss ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_ceil_ss + FORCE_INLINE __m128 _mm_ceil_ss(__m128 a, __m128 b) + { + return _mm_move_ss(a, _mm_ceil_ps(b)); +@@ -7714,16 +6718,18 @@ FORCE_INLINE __m128i _mm_cmpeq_epi64(__m128i a, __m128i b) + #endif + } + +-// Converts the four signed 16-bit integers in the lower 64 bits to four signed +-// 32-bit integers. ++// Sign extend packed 16-bit integers in a to packed 32-bit integers, and store ++// the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepi16_epi32 + FORCE_INLINE __m128i _mm_cvtepi16_epi32(__m128i a) + { + return vreinterpretq_m128i_s32( + vmovl_s16(vget_low_s16(vreinterpretq_s16_m128i(a)))); + } + +-// Converts the two signed 16-bit integers in the lower 32 bits two signed +-// 32-bit integers. ++// Sign extend packed 16-bit integers in a to packed 64-bit integers, and store ++// the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepi16_epi64 + FORCE_INLINE __m128i _mm_cvtepi16_epi64(__m128i a) + { + int16x8_t s16x8 = vreinterpretq_s16_m128i(a); /* xxxx xxxx xxxx 0B0A */ +@@ -7732,16 +6738,18 @@ FORCE_INLINE __m128i _mm_cvtepi16_epi64(__m128i a) + return vreinterpretq_m128i_s64(s64x2); + } + +-// Converts the two signed 32-bit integers in the lower 64 bits to two signed +-// 64-bit integers. ++// Sign extend packed 32-bit integers in a to packed 64-bit integers, and store ++// the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepi32_epi64 + FORCE_INLINE __m128i _mm_cvtepi32_epi64(__m128i a) + { + return vreinterpretq_m128i_s64( + vmovl_s32(vget_low_s32(vreinterpretq_s32_m128i(a)))); + } + +-// Converts the four unsigned 8-bit integers in the lower 16 bits to four +-// unsigned 32-bit integers. ++// Sign extend packed 8-bit integers in a to packed 16-bit integers, and store ++// the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepi8_epi16 + FORCE_INLINE __m128i _mm_cvtepi8_epi16(__m128i a) + { + int8x16_t s8x16 = vreinterpretq_s8_m128i(a); /* xxxx xxxx xxxx DCBA */ +@@ -7749,8 +6757,9 @@ FORCE_INLINE __m128i _mm_cvtepi8_epi16(__m128i a) + return vreinterpretq_m128i_s16(s16x8); + } + +-// Converts the four unsigned 8-bit integers in the lower 32 bits to four +-// unsigned 32-bit integers. ++// Sign extend packed 8-bit integers in a to packed 32-bit integers, and store ++// the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepi8_epi32 + FORCE_INLINE __m128i _mm_cvtepi8_epi32(__m128i a) + { + int8x16_t s8x16 = vreinterpretq_s8_m128i(a); /* xxxx xxxx xxxx DCBA */ +@@ -7759,8 +6768,9 @@ FORCE_INLINE __m128i _mm_cvtepi8_epi32(__m128i a) + return vreinterpretq_m128i_s32(s32x4); + } + +-// Converts the two signed 8-bit integers in the lower 32 bits to four +-// signed 64-bit integers. ++// Sign extend packed 8-bit integers in the low 8 bytes of a to packed 64-bit ++// integers, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepi8_epi64 + FORCE_INLINE __m128i _mm_cvtepi8_epi64(__m128i a) + { + int8x16_t s8x16 = vreinterpretq_s8_m128i(a); /* xxxx xxxx xxxx xxBA */ +@@ -7770,16 +6780,18 @@ FORCE_INLINE __m128i _mm_cvtepi8_epi64(__m128i a) + return vreinterpretq_m128i_s64(s64x2); + } + +-// Converts the four unsigned 16-bit integers in the lower 64 bits to four +-// unsigned 32-bit integers. ++// Zero extend packed unsigned 16-bit integers in a to packed 32-bit integers, ++// and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepu16_epi32 + FORCE_INLINE __m128i _mm_cvtepu16_epi32(__m128i a) + { + return vreinterpretq_m128i_u32( + vmovl_u16(vget_low_u16(vreinterpretq_u16_m128i(a)))); + } + +-// Converts the two unsigned 16-bit integers in the lower 32 bits to two +-// unsigned 64-bit integers. ++// Zero extend packed unsigned 16-bit integers in a to packed 64-bit integers, ++// and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepu16_epi64 + FORCE_INLINE __m128i _mm_cvtepu16_epi64(__m128i a) + { + uint16x8_t u16x8 = vreinterpretq_u16_m128i(a); /* xxxx xxxx xxxx 0B0A */ +@@ -7788,8 +6800,9 @@ FORCE_INLINE __m128i _mm_cvtepu16_epi64(__m128i a) + return vreinterpretq_m128i_u64(u64x2); + } + +-// Converts the two unsigned 32-bit integers in the lower 64 bits to two +-// unsigned 64-bit integers. ++// Zero extend packed unsigned 32-bit integers in a to packed 64-bit integers, ++// and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepu32_epi64 + FORCE_INLINE __m128i _mm_cvtepu32_epi64(__m128i a) + { + return vreinterpretq_m128i_u64( +@@ -7798,7 +6811,7 @@ FORCE_INLINE __m128i _mm_cvtepu32_epi64(__m128i a) + + // Zero extend packed unsigned 8-bit integers in a to packed 16-bit integers, + // and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cvtepu8_epi16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepu8_epi16 + FORCE_INLINE __m128i _mm_cvtepu8_epi16(__m128i a) + { + uint8x16_t u8x16 = vreinterpretq_u8_m128i(a); /* xxxx xxxx HGFE DCBA */ +@@ -7806,9 +6819,9 @@ FORCE_INLINE __m128i _mm_cvtepu8_epi16(__m128i a) + return vreinterpretq_m128i_u16(u16x8); + } + +-// Converts the four unsigned 8-bit integers in the lower 32 bits to four +-// unsigned 32-bit integers. +-// https://msdn.microsoft.com/en-us/library/bb531467%28v=vs.100%29.aspx ++// Zero extend packed unsigned 8-bit integers in a to packed 32-bit integers, ++// and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepu8_epi32 + FORCE_INLINE __m128i _mm_cvtepu8_epi32(__m128i a) + { + uint8x16_t u8x16 = vreinterpretq_u8_m128i(a); /* xxxx xxxx xxxx DCBA */ +@@ -7817,8 +6830,9 @@ FORCE_INLINE __m128i _mm_cvtepu8_epi32(__m128i a) + return vreinterpretq_m128i_u32(u32x4); + } + +-// Converts the two unsigned 8-bit integers in the lower 16 bits to two +-// unsigned 64-bit integers. ++// Zero extend packed unsigned 8-bit integers in the low 8 byte sof a to packed ++// 64-bit integers, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cvtepu8_epi64 + FORCE_INLINE __m128i _mm_cvtepu8_epi64(__m128i a) + { + uint8x16_t u8x16 = vreinterpretq_u8_m128i(a); /* xxxx xxxx xxxx xxBA */ +@@ -7831,7 +6845,7 @@ FORCE_INLINE __m128i _mm_cvtepu8_epi64(__m128i a) + // Conditionally multiply the packed double-precision (64-bit) floating-point + // elements in a and b using the high 4 bits in imm8, sum the four products, and + // conditionally store the sum in dst using the low 4 bits of imm8. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_dp_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_dp_pd + FORCE_INLINE __m128d _mm_dp_pd(__m128d a, __m128d b, const int imm) + { + // Generate mask value from constant immediate bit value +@@ -7877,7 +6891,7 @@ FORCE_INLINE __m128d _mm_dp_pd(__m128d a, __m128d b, const int imm) + // Conditionally multiply the packed single-precision (32-bit) floating-point + // elements in a and b using the high 4 bits in imm8, sum the four products, + // and conditionally store the sum in dst using the low 4 bits of imm. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_dp_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_dp_ps + FORCE_INLINE __m128 _mm_dp_ps(__m128 a, __m128 b, const int imm) + { + #if defined(__aarch64__) +@@ -7918,22 +6932,24 @@ FORCE_INLINE __m128 _mm_dp_ps(__m128 a, __m128 b, const int imm) + return vreinterpretq_m128_f32(res); + } + +-// Extracts the selected signed or unsigned 32-bit integer from a and zero +-// extends. ++// Extract a 32-bit integer from a, selected with imm8, and store the result in ++// dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_extract_epi32 + // FORCE_INLINE int _mm_extract_epi32(__m128i a, __constrange(0,4) int imm) + #define _mm_extract_epi32(a, imm) \ + vgetq_lane_s32(vreinterpretq_s32_m128i(a), (imm)) + +-// Extracts the selected signed or unsigned 64-bit integer from a and zero +-// extends. ++// Extract a 64-bit integer from a, selected with imm8, and store the result in ++// dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_extract_epi64 + // FORCE_INLINE __int64 _mm_extract_epi64(__m128i a, __constrange(0,2) int imm) + #define _mm_extract_epi64(a, imm) \ + vgetq_lane_s64(vreinterpretq_s64_m128i(a), (imm)) + +-// Extracts the selected signed or unsigned 8-bit integer from a and zero +-// extends. +-// FORCE_INLINE int _mm_extract_epi8(__m128i a, __constrange(0,16) int imm) +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_extract_epi8 ++// Extract an 8-bit integer from a, selected with imm8, and store the result in ++// the lower element of dst. FORCE_INLINE int _mm_extract_epi8(__m128i a, ++// __constrange(0,16) int imm) ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_extract_epi8 + #define _mm_extract_epi8(a, imm) vgetq_lane_u8(vreinterpretq_u8_m128i(a), (imm)) + + // Extracts the selected single-precision (32-bit) floating-point from a. +@@ -7943,7 +6959,7 @@ FORCE_INLINE __m128 _mm_dp_ps(__m128 a, __m128 b, const int imm) + // Round the packed double-precision (64-bit) floating-point elements in a down + // to an integer value, and store the results as packed double-precision + // floating-point elements in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_floor_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_floor_pd + FORCE_INLINE __m128d _mm_floor_pd(__m128d a) + { + #if defined(__aarch64__) +@@ -7957,7 +6973,7 @@ FORCE_INLINE __m128d _mm_floor_pd(__m128d a) + // Round the packed single-precision (32-bit) floating-point elements in a down + // to an integer value, and store the results as packed single-precision + // floating-point elements in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_floor_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_floor_ps + FORCE_INLINE __m128 _mm_floor_ps(__m128 a) + { + #if defined(__aarch64__) || defined(__ARM_FEATURE_DIRECTED_ROUNDING) +@@ -7972,7 +6988,7 @@ FORCE_INLINE __m128 _mm_floor_ps(__m128 a) + // an integer value, store the result as a double-precision floating-point + // element in the lower element of dst, and copy the upper element from a to the + // upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_floor_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_floor_sd + FORCE_INLINE __m128d _mm_floor_sd(__m128d a, __m128d b) + { + return _mm_move_sd(a, _mm_floor_pd(b)); +@@ -7982,18 +6998,15 @@ FORCE_INLINE __m128d _mm_floor_sd(__m128d a, __m128d b) + // an integer value, store the result as a single-precision floating-point + // element in the lower element of dst, and copy the upper 3 packed elements + // from a to the upper elements of dst. +-// +-// dst[31:0] := FLOOR(b[31:0]) +-// dst[127:32] := a[127:32] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_floor_ss ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_floor_ss + FORCE_INLINE __m128 _mm_floor_ss(__m128 a, __m128 b) + { + return _mm_move_ss(a, _mm_floor_ps(b)); + } + +-// Inserts the least significant 32 bits of b into the selected 32-bit integer +-// of a. ++// Copy a to dst, and insert the 32-bit integer i into dst at the location ++// specified by imm8. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_insert_epi32 + // FORCE_INLINE __m128i _mm_insert_epi32(__m128i a, int b, + // __constrange(0,4) int imm) + #define _mm_insert_epi32(a, b, imm) \ +@@ -8002,8 +7015,9 @@ FORCE_INLINE __m128 _mm_floor_ss(__m128 a, __m128 b) + vsetq_lane_s32((b), vreinterpretq_s32_m128i(a), (imm))); \ + }) + +-// Inserts the least significant 64 bits of b into the selected 64-bit integer +-// of a. ++// Copy a to dst, and insert the 64-bit integer i into dst at the location ++// specified by imm8. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_insert_epi64 + // FORCE_INLINE __m128i _mm_insert_epi64(__m128i a, __int64 b, + // __constrange(0,2) int imm) + #define _mm_insert_epi64(a, b, imm) \ +@@ -8012,8 +7026,9 @@ FORCE_INLINE __m128 _mm_floor_ss(__m128 a, __m128 b) + vsetq_lane_s64((b), vreinterpretq_s64_m128i(a), (imm))); \ + }) + +-// Inserts the least significant 8 bits of b into the selected 8-bit integer +-// of a. ++// Copy a to dst, and insert the lower 8-bit integer from i into dst at the ++// location specified by imm8. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_insert_epi8 + // FORCE_INLINE __m128i _mm_insert_epi8(__m128i a, int b, + // __constrange(0,16) int imm) + #define _mm_insert_epi8(a, b, imm) \ +@@ -8025,7 +7040,7 @@ FORCE_INLINE __m128 _mm_floor_ss(__m128 a, __m128 b) + // Copy a to tmp, then insert a single-precision (32-bit) floating-point + // element from b into tmp using the control in imm8. Store tmp to dst using + // the mask in imm8 (elements are zeroed out when the corresponding bit is set). +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=insert_ps ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=insert_ps + #define _mm_insert_ps(a, b, imm8) \ + __extension__({ \ + float32x4_t tmp1 = \ +@@ -8045,17 +7060,9 @@ FORCE_INLINE __m128 _mm_floor_ss(__m128 a, __m128 b) + vbslq_f32(mask, all_zeros, vreinterpretq_f32_m128(tmp2))); \ + }) + +-// epi versions of min/max +-// Computes the pariwise maximums of the four signed 32-bit integer values of a +-// and b. +-// +-// A 128-bit parameter that can be defined with the following equations: +-// r0 := (a0 > b0) ? a0 : b0 +-// r1 := (a1 > b1) ? a1 : b1 +-// r2 := (a2 > b2) ? a2 : b2 +-// r3 := (a3 > b3) ? a3 : b3 +-// +-// https://msdn.microsoft.com/en-us/library/vstudio/bb514055(v=vs.100).aspx ++// Compare packed signed 32-bit integers in a and b, and store packed maximum ++// values in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_epi32 + FORCE_INLINE __m128i _mm_max_epi32(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s32( +@@ -8064,7 +7071,7 @@ FORCE_INLINE __m128i _mm_max_epi32(__m128i a, __m128i b) + + // Compare packed signed 8-bit integers in a and b, and store packed maximum + // values in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_epi8 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_epi8 + FORCE_INLINE __m128i _mm_max_epi8(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s8( +@@ -8073,7 +7080,7 @@ FORCE_INLINE __m128i _mm_max_epi8(__m128i a, __m128i b) + + // Compare packed unsigned 16-bit integers in a and b, and store packed maximum + // values in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_epu16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_epu16 + FORCE_INLINE __m128i _mm_max_epu16(__m128i a, __m128i b) + { + return vreinterpretq_m128i_u16( +@@ -8082,23 +7089,16 @@ FORCE_INLINE __m128i _mm_max_epu16(__m128i a, __m128i b) + + // Compare packed unsigned 32-bit integers in a and b, and store packed maximum + // values in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_epu32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_epu32 + FORCE_INLINE __m128i _mm_max_epu32(__m128i a, __m128i b) + { + return vreinterpretq_m128i_u32( + vmaxq_u32(vreinterpretq_u32_m128i(a), vreinterpretq_u32_m128i(b))); + } + +-// Computes the pariwise minima of the four signed 32-bit integer values of a +-// and b. +-// +-// A 128-bit parameter that can be defined with the following equations: +-// r0 := (a0 < b0) ? a0 : b0 +-// r1 := (a1 < b1) ? a1 : b1 +-// r2 := (a2 < b2) ? a2 : b2 +-// r3 := (a3 < b3) ? a3 : b3 +-// +-// https://msdn.microsoft.com/en-us/library/vstudio/bb531476(v=vs.100).aspx ++// Compare packed signed 32-bit integers in a and b, and store packed minimum ++// values in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_min_epi32 + FORCE_INLINE __m128i _mm_min_epi32(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s32( +@@ -8107,7 +7107,7 @@ FORCE_INLINE __m128i _mm_min_epi32(__m128i a, __m128i b) + + // Compare packed signed 8-bit integers in a and b, and store packed minimum + // values in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_epi8 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_min_epi8 + FORCE_INLINE __m128i _mm_min_epi8(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s8( +@@ -8116,7 +7116,7 @@ FORCE_INLINE __m128i _mm_min_epi8(__m128i a, __m128i b) + + // Compare packed unsigned 16-bit integers in a and b, and store packed minimum + // values in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_min_epu16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_min_epu16 + FORCE_INLINE __m128i _mm_min_epu16(__m128i a, __m128i b) + { + return vreinterpretq_m128i_u16( +@@ -8125,7 +7125,7 @@ FORCE_INLINE __m128i _mm_min_epu16(__m128i a, __m128i b) + + // Compare packed unsigned 32-bit integers in a and b, and store packed minimum + // values in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_max_epu32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_max_epu32 + FORCE_INLINE __m128i _mm_min_epu32(__m128i a, __m128i b) + { + return vreinterpretq_m128i_u32( +@@ -8134,21 +7134,7 @@ FORCE_INLINE __m128i _mm_min_epu32(__m128i a, __m128i b) + + // Horizontally compute the minimum amongst the packed unsigned 16-bit integers + // in a, store the minimum and index in dst, and zero the remaining bits in dst. +-// +-// index[2:0] := 0 +-// min[15:0] := a[15:0] +-// FOR j := 0 to 7 +-// i := j*16 +-// IF a[i+15:i] < min[15:0] +-// index[2:0] := j +-// min[15:0] := a[i+15:i] +-// FI +-// ENDFOR +-// dst[15:0] := min[15:0] +-// dst[18:16] := index[2:0] +-// dst[127:19] := 0 +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_minpos_epu16 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_minpos_epu16 + FORCE_INLINE __m128i _mm_minpos_epu16(__m128i a) + { + __m128i dst; +@@ -8198,7 +7184,7 @@ FORCE_INLINE __m128i _mm_minpos_epu16(__m128i a) + // quadruplets from a. One quadruplet is selected from b starting at on the + // offset specified in imm8. Eight quadruplets are formed from sequential 8-bit + // integers selected from a starting at the offset specified in imm8. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_mpsadbw_epu8 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mpsadbw_epu8 + FORCE_INLINE __m128i _mm_mpsadbw_epu8(__m128i a, __m128i b, const int imm) + { + uint8x16_t _a, _b; +@@ -8278,9 +7264,7 @@ FORCE_INLINE __m128i _mm_mpsadbw_epu8(__m128i a, __m128i b, const int imm) + + // Multiply the low signed 32-bit integers from each packed 64-bit element in + // a and b, and store the signed 64-bit results in dst. +-// +-// r0 := (int64_t)(int32_t)a0 * (int64_t)(int32_t)b0 +-// r1 := (int64_t)(int32_t)a2 * (int64_t)(int32_t)b2 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mul_epi32 + FORCE_INLINE __m128i _mm_mul_epi32(__m128i a, __m128i b) + { + // vmull_s32 upcasts instead of masking, so we downcast. +@@ -8289,26 +7273,18 @@ FORCE_INLINE __m128i _mm_mul_epi32(__m128i a, __m128i b) + return vreinterpretq_m128i_s64(vmull_s32(a_lo, b_lo)); + } + +-// Multiplies the 4 signed or unsigned 32-bit integers from a by the 4 signed or +-// unsigned 32-bit integers from b. +-// https://msdn.microsoft.com/en-us/library/vstudio/bb531409(v=vs.100).aspx ++// Multiply the packed 32-bit integers in a and b, producing intermediate 64-bit ++// integers, and store the low 32 bits of the intermediate integers in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mullo_epi32 + FORCE_INLINE __m128i _mm_mullo_epi32(__m128i a, __m128i b) + { + return vreinterpretq_m128i_s32( + vmulq_s32(vreinterpretq_s32_m128i(a), vreinterpretq_s32_m128i(b))); + } + +-// Packs the 8 unsigned 32-bit integers from a and b into unsigned 16-bit +-// integers and saturates. +-// +-// r0 := UnsignedSaturate(a0) +-// r1 := UnsignedSaturate(a1) +-// r2 := UnsignedSaturate(a2) +-// r3 := UnsignedSaturate(a3) +-// r4 := UnsignedSaturate(b0) +-// r5 := UnsignedSaturate(b1) +-// r6 := UnsignedSaturate(b2) +-// r7 := UnsignedSaturate(b3) ++// Convert packed signed 32-bit integers from a and b to packed 16-bit integers ++// using unsigned saturation, and store the results in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_packus_epi32 + FORCE_INLINE __m128i _mm_packus_epi32(__m128i a, __m128i b) + { + return vreinterpretq_m128i_u16( +@@ -8319,7 +7295,7 @@ FORCE_INLINE __m128i _mm_packus_epi32(__m128i a, __m128i b) + // Round the packed double-precision (64-bit) floating-point elements in a using + // the rounding parameter, and store the results as packed double-precision + // floating-point elements in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_round_pd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_round_pd + FORCE_INLINE __m128d _mm_round_pd(__m128d a, int rounding) + { + #if defined(__aarch64__) +@@ -8448,7 +7424,7 @@ FORCE_INLINE __m128 _mm_round_ps(__m128 a, int rounding) + // the rounding parameter, store the result as a double-precision floating-point + // element in the lower element of dst, and copy the upper element from a to the + // upper element of dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_round_sd ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_round_sd + FORCE_INLINE __m128d _mm_round_sd(__m128d a, __m128d b, int rounding) + { + return _mm_move_sd(a, _mm_round_pd(b, rounding)); +@@ -8468,7 +7444,7 @@ FORCE_INLINE __m128d _mm_round_sd(__m128d a, __m128d b, int rounding) + // (_MM_FROUND_TO_ZERO |_MM_FROUND_NO_EXC) // truncate, and suppress + // exceptions _MM_FROUND_CUR_DIRECTION // use MXCSR.RC; see + // _MM_SET_ROUNDING_MODE +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_round_ss ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_round_ss + FORCE_INLINE __m128 _mm_round_ss(__m128 a, __m128 b, int rounding) + { + return _mm_move_ss(a, _mm_round_ps(b, rounding)); +@@ -8477,10 +7453,7 @@ FORCE_INLINE __m128 _mm_round_ss(__m128 a, __m128 b, int rounding) + // Load 128-bits of integer data from memory into dst using a non-temporal + // memory hint. mem_addr must be aligned on a 16-byte boundary or a + // general-protection exception may be generated. +-// +-// dst[127:0] := MEM[mem_addr+127:mem_addr] +-// +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_stream_load_si128 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_stream_load_si128 + FORCE_INLINE __m128i _mm_stream_load_si128(__m128i *p) + { + #if __has_builtin(__builtin_nontemporal_store) +@@ -8492,7 +7465,7 @@ FORCE_INLINE __m128i _mm_stream_load_si128(__m128i *p) + + // Compute the bitwise NOT of a and then AND with a 128-bit vector containing + // all 1's, and return 1 if the result is zero, otherwise return 0. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_test_all_ones ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_test_all_ones + FORCE_INLINE int _mm_test_all_ones(__m128i a) + { + return (uint64_t) (vgetq_lane_s64(a, 0) & vgetq_lane_s64(a, 1)) == +@@ -8501,7 +7474,7 @@ FORCE_INLINE int _mm_test_all_ones(__m128i a) + + // Compute the bitwise AND of 128 bits (representing integer data) in a and + // mask, and return 1 if the result is zero, otherwise return 0. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_test_all_zeros ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_test_all_zeros + FORCE_INLINE int _mm_test_all_zeros(__m128i a, __m128i mask) + { + int64x2_t a_and_mask = +@@ -8514,7 +7487,7 @@ FORCE_INLINE int _mm_test_all_zeros(__m128i a, __m128i mask) + // the bitwise NOT of a and then AND with mask, and set CF to 1 if the result is + // zero, otherwise set CF to 0. Return 1 if both the ZF and CF values are zero, + // otherwise return 0. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=mm_test_mix_ones_zero ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=mm_test_mix_ones_zero + FORCE_INLINE int _mm_test_mix_ones_zeros(__m128i a, __m128i mask) + { + uint64x2_t zf = +@@ -8529,7 +7502,7 @@ FORCE_INLINE int _mm_test_mix_ones_zeros(__m128i a, __m128i mask) + // and set ZF to 1 if the result is zero, otherwise set ZF to 0. Compute the + // bitwise NOT of a and then AND with b, and set CF to 1 if the result is zero, + // otherwise set CF to 0. Return the CF value. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_testc_si128 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_testc_si128 + FORCE_INLINE int _mm_testc_si128(__m128i a, __m128i b) + { + int64x2_t s64 = +@@ -8542,14 +7515,14 @@ FORCE_INLINE int _mm_testc_si128(__m128i a, __m128i b) + // bitwise NOT of a and then AND with b, and set CF to 1 if the result is zero, + // otherwise set CF to 0. Return 1 if both the ZF and CF values are zero, + // otherwise return 0. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_testnzc_si128 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_testnzc_si128 + #define _mm_testnzc_si128(a, b) _mm_test_mix_ones_zeros(a, b) + + // Compute the bitwise AND of 128 bits (representing integer data) in a and b, + // and set ZF to 1 if the result is zero, otherwise set ZF to 0. Compute the + // bitwise NOT of a and then AND with b, and set CF to 1 if the result is zero, + // otherwise set CF to 0. Return the ZF value. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_testz_si128 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_testz_si128 + FORCE_INLINE int _mm_testz_si128(__m128i a, __m128i b) + { + int64x2_t s64 = +@@ -9028,7 +8001,7 @@ FORCE_INLINE int _sse2neon_sido_negative(int res, int lb, int imm8, int bound) + FORCE_INLINE int _sse2neon_clz(unsigned int x) + { + #if _MSC_VER +- DWORD cnt = 0; ++ unsigned long cnt = 0; + if (_BitScanForward(&cnt, x)) + return cnt; + return 32; +@@ -9040,7 +8013,7 @@ FORCE_INLINE int _sse2neon_clz(unsigned int x) + FORCE_INLINE int _sse2neon_ctz(unsigned int x) + { + #if _MSC_VER +- DWORD cnt = 0; ++ unsigned long cnt = 0; + if (_BitScanReverse(&cnt, x)) + return 31 - cnt; + return 32; +@@ -9053,18 +8026,16 @@ FORCE_INLINE int _sse2neon_ctzll(unsigned long long x) + { + #if _MSC_VER + unsigned long cnt; +-#ifdef defined(SSE2NEON_HAS_BITSCAN64) +- (defined(_M_AMD64) || defined(__x86_64__)) +- if((_BitScanForward64(&cnt, x)) +- return (int)(cnt); ++#if defined(SSE2NEON_HAS_BITSCAN64) ++ if ((_BitScanForward64(&cnt, x)) ++ return (int)(cnt); + #else + if (_BitScanForward(&cnt, (unsigned long) (x))) + return (int) cnt; + if (_BitScanForward(&cnt, (unsigned long) (x >> 32))) + return (int) (cnt + 32); +-#endif +- return 64; +-#else ++#endif /* SSE2NEON_HAS_BITSCAN64 */ ++#else /* assume GNU compatible compilers */ + return x != 0 ? __builtin_ctzll(x) : 64; + #endif + } +@@ -9155,7 +8126,7 @@ FORCE_INLINE int _mm_cmpestrc(__m128i a, + + // Compare packed strings in a and b with lengths la and lb using the control + // in imm8, and store the generated index in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpestri ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpestri + FORCE_INLINE int _mm_cmpestri(__m128i a, + int la, + __m128i b, +@@ -9168,7 +8139,7 @@ FORCE_INLINE int _mm_cmpestri(__m128i a, + + // Compare packed strings in a and b with lengths la and lb using the control + // in imm8, and store the generated mask in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_cmpestrm ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_cmpestrm + FORCE_INLINE __m128i + _mm_cmpestrm(__m128i a, int la, __m128i b, int lb, const int imm8) + { +@@ -9324,8 +8295,8 @@ FORCE_INLINE __m128i _mm_cmpgt_epi64(__m128i a, __m128i b) + } + + // Starting with the initial value in crc, accumulates a CRC32 value for +-// unsigned 16-bit integer v. +-// https://msdn.microsoft.com/en-us/library/bb531411(v=vs.100) ++// unsigned 16-bit integer v, and stores the result in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_crc32_u16 + FORCE_INLINE uint32_t _mm_crc32_u16(uint32_t crc, uint16_t v) + { + #if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) +@@ -9342,8 +8313,8 @@ FORCE_INLINE uint32_t _mm_crc32_u16(uint32_t crc, uint16_t v) + } + + // Starting with the initial value in crc, accumulates a CRC32 value for +-// unsigned 32-bit integer v. +-// https://msdn.microsoft.com/en-us/library/bb531394(v=vs.100) ++// unsigned 32-bit integer v, and stores the result in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_crc32_u32 + FORCE_INLINE uint32_t _mm_crc32_u32(uint32_t crc, uint32_t v) + { + #if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) +@@ -9360,8 +8331,8 @@ FORCE_INLINE uint32_t _mm_crc32_u32(uint32_t crc, uint32_t v) + } + + // Starting with the initial value in crc, accumulates a CRC32 value for +-// unsigned 64-bit integer v. +-// https://msdn.microsoft.com/en-us/library/bb514033(v=vs.100) ++// unsigned 64-bit integer v, and stores the result in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_crc32_u64 + FORCE_INLINE uint64_t _mm_crc32_u64(uint64_t crc, uint64_t v) + { + #if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) +@@ -9376,8 +8347,8 @@ FORCE_INLINE uint64_t _mm_crc32_u64(uint64_t crc, uint64_t v) + } + + // Starting with the initial value in crc, accumulates a CRC32 value for +-// unsigned 8-bit integer v. +-// https://msdn.microsoft.com/en-us/library/bb514036(v=vs.100) ++// unsigned 8-bit integer v, and stores the result in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_crc32_u8 + FORCE_INLINE uint32_t _mm_crc32_u8(uint32_t crc, uint8_t v) + { + #if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) +@@ -9486,43 +8457,61 @@ FORCE_INLINE uint32_t _mm_crc32_u8(uint32_t crc, uint8_t v) + + /* X Macro trick. See https://en.wikipedia.org/wiki/X_Macro */ + #define SSE2NEON_AES_H0(x) (x) +-static const uint8_t SSE2NEON_sbox[256] = SSE2NEON_AES_SBOX(SSE2NEON_AES_H0); +-static const uint8_t SSE2NEON_rsbox[256] = SSE2NEON_AES_RSBOX(SSE2NEON_AES_H0); ++static const uint8_t _sse2neon_sbox[256] = SSE2NEON_AES_SBOX(SSE2NEON_AES_H0); ++static const uint8_t _sse2neon_rsbox[256] = SSE2NEON_AES_RSBOX(SSE2NEON_AES_H0); + #undef SSE2NEON_AES_H0 + +-// In the absence of crypto extensions, implement aesenc using regular neon ++/* x_time function and matrix multiply function */ ++#if !defined(__aarch64__) ++#define SSE2NEON_XT(x) (((x) << 1) ^ ((((x) >> 7) & 1) * 0x1b)) ++#define SSE2NEON_MULTIPLY(x, y) \ ++ (((y & 1) * x) ^ ((y >> 1 & 1) * SSE2NEON_XT(x)) ^ \ ++ ((y >> 2 & 1) * SSE2NEON_XT(SSE2NEON_XT(x))) ^ \ ++ ((y >> 3 & 1) * SSE2NEON_XT(SSE2NEON_XT(SSE2NEON_XT(x)))) ^ \ ++ ((y >> 4 & 1) * SSE2NEON_XT(SSE2NEON_XT(SSE2NEON_XT(SSE2NEON_XT(x)))))) ++#endif ++ ++// In the absence of crypto extensions, implement aesenc using regular NEON + // intrinsics instead. See: + // https://www.workofard.com/2017/01/accelerated-aes-for-the-arm64-linux-kernel/ + // https://www.workofard.com/2017/07/ghash-for-low-end-cores/ and +-// https://github.com/ColinIanKing/linux-next-mirror/blob/b5f466091e130caaf0735976648f72bd5e09aa84/crypto/aegis128-neon-inner.c#L52 +-// for more information Reproduced with permission of the author. ++// for more information. + FORCE_INLINE __m128i _mm_aesenc_si128(__m128i a, __m128i RoundKey) + { + #if defined(__aarch64__) +- static const uint8_t shift_rows[] = {0x0, 0x5, 0xa, 0xf, 0x4, 0x9, +- 0xe, 0x3, 0x8, 0xd, 0x2, 0x7, +- 0xc, 0x1, 0x6, 0xb}; +- static const uint8_t ror32by8[] = {0x1, 0x2, 0x3, 0x0, 0x5, 0x6, 0x7, 0x4, +- 0x9, 0xa, 0xb, 0x8, 0xd, 0xe, 0xf, 0xc}; ++ static const uint8_t shift_rows[] = { ++ 0x0, 0x5, 0xa, 0xf, 0x4, 0x9, 0xe, 0x3, ++ 0x8, 0xd, 0x2, 0x7, 0xc, 0x1, 0x6, 0xb, ++ }; ++ static const uint8_t ror32by8[] = { ++ 0x1, 0x2, 0x3, 0x0, 0x5, 0x6, 0x7, 0x4, ++ 0x9, 0xa, 0xb, 0x8, 0xd, 0xe, 0xf, 0xc, ++ }; + + uint8x16_t v; + uint8x16_t w = vreinterpretq_u8_m128i(a); + +- // shift rows ++ /* shift rows */ + w = vqtbl1q_u8(w, vld1q_u8(shift_rows)); + +- // sub bytes +- v = vqtbl4q_u8(_sse2neon_vld1q_u8_x4(SSE2NEON_sbox), w); +- v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(SSE2NEON_sbox + 0x40), w - 0x40); +- v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(SSE2NEON_sbox + 0x80), w - 0x80); +- v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(SSE2NEON_sbox + 0xc0), w - 0xc0); ++ /* sub bytes */ ++ // Here, we separate the whole 256-bytes table into 4 64-bytes tables, and ++ // look up each of the table. After each lookup, we load the next table ++ // which locates at the next 64-bytes. In the meantime, the index in the ++ // table would be smaller than it was, so the index parameters of ++ // `vqtbx4q_u8()` need to be added the same constant as the loaded tables. ++ v = vqtbl4q_u8(_sse2neon_vld1q_u8_x4(_sse2neon_sbox), w); ++ // 'w-0x40' equals to 'vsubq_u8(w, vdupq_n_u8(0x40))' ++ v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_sbox + 0x40), w - 0x40); ++ v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_sbox + 0x80), w - 0x80); ++ v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_sbox + 0xc0), w - 0xc0); + +- // mix columns ++ /* mix columns */ + w = (v << 1) ^ (uint8x16_t) (((int8x16_t) v >> 7) & 0x1b); + w ^= (uint8x16_t) vrev32q_u16((uint16x8_t) v); + w ^= vqtbl1q_u8(v ^ w, vld1q_u8(ror32by8)); + +- // add round key ++ /* add round key */ + return vreinterpretq_m128i_u8(w) ^ RoundKey; + + #else /* ARMv7-A implementation for a table-based AES */ +@@ -9587,31 +8576,34 @@ FORCE_INLINE __m128i _mm_aesenc_si128(__m128i a, __m128i RoundKey) + FORCE_INLINE __m128i _mm_aesdec_si128(__m128i a, __m128i RoundKey) + { + #if defined(__aarch64__) +- static const uint8_t inv_shift_rows[] = {0x0, 0xd, 0xa, 0x7, 0x4, 0x1, +- 0xe, 0xb, 0x8, 0x5, 0x2, 0xf, +- 0xc, 0x9, 0x6, 0x3}; +- static const uint8_t ror32by8[] = {0x1, 0x2, 0x3, 0x0, 0x5, 0x6, 0x7, 0x4, +- 0x9, 0xa, 0xb, 0x8, 0xd, 0xe, 0xf, 0xc}; ++ static const uint8_t inv_shift_rows[] = { ++ 0x0, 0xd, 0xa, 0x7, 0x4, 0x1, 0xe, 0xb, ++ 0x8, 0x5, 0x2, 0xf, 0xc, 0x9, 0x6, 0x3, ++ }; ++ static const uint8_t ror32by8[] = { ++ 0x1, 0x2, 0x3, 0x0, 0x5, 0x6, 0x7, 0x4, ++ 0x9, 0xa, 0xb, 0x8, 0xd, 0xe, 0xf, 0xc, ++ }; + + uint8x16_t v; + uint8x16_t w = vreinterpretq_u8_m128i(a); + +- // shift rows ++ // inverse shift rows + w = vqtbl1q_u8(w, vld1q_u8(inv_shift_rows)); + +- // sub bytes +- v = vqtbl4q_u8(_sse2neon_vld1q_u8_x4(SSE2NEON_rsbox), w); +- v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(SSE2NEON_rsbox + 0x40), w - 0x40); +- v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(SSE2NEON_rsbox + 0x80), w - 0x80); +- v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(SSE2NEON_rsbox + 0xc0), w - 0xc0); ++ // inverse sub bytes ++ v = vqtbl4q_u8(_sse2neon_vld1q_u8_x4(_sse2neon_rsbox), w); ++ v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_rsbox + 0x40), w - 0x40); ++ v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_rsbox + 0x80), w - 0x80); ++ v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_rsbox + 0xc0), w - 0xc0); + ++ // inverse mix columns + // muliplying 'v' by 4 in GF(2^8) + w = (v << 1) ^ (uint8x16_t) (((int8x16_t) v >> 7) & 0x1b); + w = (w << 1) ^ (uint8x16_t) (((int8x16_t) w >> 7) & 0x1b); + v ^= w; + v ^= (uint8x16_t) vrev32q_u16((uint16x8_t) w); + +- // mix columns + w = (v << 1) ^ (uint8x16_t) (((int8x16_t) v >> 7) & + 0x1b); // muliplying 'v' by 2 in GF(2^8) + w ^= (uint8x16_t) vrev32q_u16((uint16x8_t) v); +@@ -9621,35 +8613,29 @@ FORCE_INLINE __m128i _mm_aesdec_si128(__m128i a, __m128i RoundKey) + return vreinterpretq_m128i_u8(w) ^ RoundKey; + + #else /* ARMv7-A NEON implementation */ +-/* FIXME: optimized for NEON */ +-#define XT(x) (((x) << 1) ^ ((((x) >> 7) & 1) * 0x1b)) +-#define MULTIPLY(x, y) \ +- (((y & 1) * x) ^ ((y >> 1 & 1) * XT(x)) ^ ((y >> 2 & 1) * XT(XT(x))) ^ \ +- ((y >> 3 & 1) * XT(XT(XT(x)))) ^ ((y >> 4 & 1) * XT(XT(XT(XT(x)))))) +- ++ /* FIXME: optimized for NEON */ + uint8_t i, e, f, g, h, v[4][4]; + uint8_t *_a = (uint8_t *) &a; + for (i = 0; i < 16; ++i) { +- v[((i / 4) + (i % 4)) % 4][i % 4] = SSE2NEON_rsbox[_a[i]]; ++ v[((i / 4) + (i % 4)) % 4][i % 4] = _sse2neon_rsbox[_a[i]]; + } + ++ // inverse mix columns + for (i = 0; i < 4; ++i) { + e = v[i][0]; + f = v[i][1]; + g = v[i][2]; + h = v[i][3]; + +- v[i][0] = MULTIPLY(e, 0x0e) ^ MULTIPLY(f, 0x0b) ^ MULTIPLY(g, 0x0d) ^ +- MULTIPLY(h, 0x09); +- v[i][1] = MULTIPLY(e, 0x09) ^ MULTIPLY(f, 0x0e) ^ MULTIPLY(g, 0x0b) ^ +- MULTIPLY(h, 0x0d); +- v[i][2] = MULTIPLY(e, 0x0d) ^ MULTIPLY(f, 0x09) ^ MULTIPLY(g, 0x0e) ^ +- MULTIPLY(h, 0x0b); +- v[i][3] = MULTIPLY(e, 0x0b) ^ MULTIPLY(f, 0x0d) ^ MULTIPLY(g, 0x09) ^ +- MULTIPLY(h, 0x0e); ++ v[i][0] = SSE2NEON_MULTIPLY(e, 0x0e) ^ SSE2NEON_MULTIPLY(f, 0x0b) ^ ++ SSE2NEON_MULTIPLY(g, 0x0d) ^ SSE2NEON_MULTIPLY(h, 0x09); ++ v[i][1] = SSE2NEON_MULTIPLY(e, 0x09) ^ SSE2NEON_MULTIPLY(f, 0x0e) ^ ++ SSE2NEON_MULTIPLY(g, 0x0b) ^ SSE2NEON_MULTIPLY(h, 0x0d); ++ v[i][2] = SSE2NEON_MULTIPLY(e, 0x0d) ^ SSE2NEON_MULTIPLY(f, 0x09) ^ ++ SSE2NEON_MULTIPLY(g, 0x0e) ^ SSE2NEON_MULTIPLY(h, 0x0b); ++ v[i][3] = SSE2NEON_MULTIPLY(e, 0x0b) ^ SSE2NEON_MULTIPLY(f, 0x0d) ^ ++ SSE2NEON_MULTIPLY(g, 0x09) ^ SSE2NEON_MULTIPLY(h, 0x0e); + } +-#undef XT +-#undef MULTIPLY + + return vreinterpretq_m128i_u8(vld1q_u8((uint8_t *) v)) ^ RoundKey; + #endif +@@ -9657,7 +8643,7 @@ FORCE_INLINE __m128i _mm_aesdec_si128(__m128i a, __m128i RoundKey) + + // Perform the last round of an AES encryption flow on data (state) in a using + // the round key in RoundKey, and store the result in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_aesenclast_si128 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesenclast_si128 + FORCE_INLINE __m128i _mm_aesenclast_si128(__m128i a, __m128i RoundKey) + { + #if defined(__aarch64__) +@@ -9673,59 +8659,166 @@ FORCE_INLINE __m128i _mm_aesenclast_si128(__m128i a, __m128i RoundKey) + w = vqtbl1q_u8(w, vld1q_u8(shift_rows)); + + // sub bytes +- v = vqtbl4q_u8(_sse2neon_vld1q_u8_x4(SSE2NEON_sbox), w); +- // 'w-0x40' equals to 'vsubq_u8(w, vdupq_n_u8(0x40))' +- v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(SSE2NEON_sbox + 0x40), w - 0x40); +- v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(SSE2NEON_sbox + 0x80), w - 0x80); +- v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(SSE2NEON_sbox + 0xc0), w - 0xc0); ++ v = vqtbl4q_u8(_sse2neon_vld1q_u8_x4(_sse2neon_sbox), w); ++ v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_sbox + 0x40), w - 0x40); ++ v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_sbox + 0x80), w - 0x80); ++ v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_sbox + 0xc0), w - 0xc0); + +- // add round key ++ // add round key + return vreinterpretq_m128i_u8(v) ^ RoundKey; + + #else /* ARMv7-A implementation */ + uint8_t v[16] = { +- SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 0)], +- SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 5)], +- SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 10)], +- SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 15)], +- SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 4)], +- SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 9)], +- SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 14)], +- SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 3)], +- SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 8)], +- SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 13)], +- SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 2)], +- SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 7)], +- SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 12)], +- SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 1)], +- SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 6)], +- SSE2NEON_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 11)], ++ _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 0)], ++ _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 5)], ++ _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 10)], ++ _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 15)], ++ _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 4)], ++ _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 9)], ++ _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 14)], ++ _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 3)], ++ _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 8)], ++ _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 13)], ++ _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 2)], ++ _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 7)], ++ _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 12)], ++ _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 1)], ++ _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 6)], ++ _sse2neon_sbox[vgetq_lane_u8(vreinterpretq_u8_m128i(a), 11)], + }; + + return vreinterpretq_m128i_u8(vld1q_u8(v)) ^ RoundKey; + #endif + } + ++// Perform the last round of an AES decryption flow on data (state) in a using ++// the round key in RoundKey, and store the result in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesdeclast_si128 ++FORCE_INLINE __m128i _mm_aesdeclast_si128(__m128i a, __m128i RoundKey) ++{ ++#if defined(__aarch64__) ++ static const uint8_t inv_shift_rows[] = { ++ 0x0, 0xd, 0xa, 0x7, 0x4, 0x1, 0xe, 0xb, ++ 0x8, 0x5, 0x2, 0xf, 0xc, 0x9, 0x6, 0x3, ++ }; ++ ++ uint8x16_t v; ++ uint8x16_t w = vreinterpretq_u8_m128i(a); ++ ++ // inverse shift rows ++ w = vqtbl1q_u8(w, vld1q_u8(inv_shift_rows)); ++ ++ // inverse sub bytes ++ v = vqtbl4q_u8(_sse2neon_vld1q_u8_x4(_sse2neon_rsbox), w); ++ v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_rsbox + 0x40), w - 0x40); ++ v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_rsbox + 0x80), w - 0x80); ++ v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_rsbox + 0xc0), w - 0xc0); ++ ++ // add round key ++ return vreinterpretq_m128i_u8(v) ^ RoundKey; ++ ++#else /* ARMv7-A NEON implementation */ ++ /* FIXME: optimized for NEON */ ++ uint8_t v[4][4]; ++ uint8_t *_a = (uint8_t *) &a; ++ for (int i = 0; i < 16; ++i) { ++ v[((i / 4) + (i % 4)) % 4][i % 4] = _sse2neon_rsbox[_a[i]]; ++ } ++ ++ return vreinterpretq_m128i_u8(vld1q_u8((uint8_t *) v)) ^ RoundKey; ++#endif ++} ++ ++// Perform the InvMixColumns transformation on a and store the result in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesimc_si128 ++FORCE_INLINE __m128i _mm_aesimc_si128(__m128i a) ++{ ++#if defined(__aarch64__) ++ static const uint8_t ror32by8[] = { ++ 0x1, 0x2, 0x3, 0x0, 0x5, 0x6, 0x7, 0x4, ++ 0x9, 0xa, 0xb, 0x8, 0xd, 0xe, 0xf, 0xc, ++ }; ++ uint8x16_t v = vreinterpretq_u8_m128i(a); ++ uint8x16_t w; ++ ++ // multiplying 'v' by 4 in GF(2^8) ++ w = (v << 1) ^ (uint8x16_t) (((int8x16_t) v >> 7) & 0x1b); ++ w = (w << 1) ^ (uint8x16_t) (((int8x16_t) w >> 7) & 0x1b); ++ v ^= w; ++ v ^= (uint8x16_t) vrev32q_u16((uint16x8_t) w); ++ ++ // multiplying 'v' by 2 in GF(2^8) ++ w = (v << 1) ^ (uint8x16_t) (((int8x16_t) v >> 7) & 0x1b); ++ w ^= (uint8x16_t) vrev32q_u16((uint16x8_t) v); ++ w ^= vqtbl1q_u8(v ^ w, vld1q_u8(ror32by8)); ++ return vreinterpretq_m128i_u8(w); ++ ++#else /* ARMv7-A NEON implementation */ ++ uint8_t i, e, f, g, h, v[4][4]; ++ vst1q_u8((uint8_t *) v, vreinterpretq_u8_m128i(a)); ++ for (i = 0; i < 4; ++i) { ++ e = v[i][0]; ++ f = v[i][1]; ++ g = v[i][2]; ++ h = v[i][3]; ++ ++ v[i][0] = SSE2NEON_MULTIPLY(e, 0x0e) ^ SSE2NEON_MULTIPLY(f, 0x0b) ^ ++ SSE2NEON_MULTIPLY(g, 0x0d) ^ SSE2NEON_MULTIPLY(h, 0x09); ++ v[i][1] = SSE2NEON_MULTIPLY(e, 0x09) ^ SSE2NEON_MULTIPLY(f, 0x0e) ^ ++ SSE2NEON_MULTIPLY(g, 0x0b) ^ SSE2NEON_MULTIPLY(h, 0x0d); ++ v[i][2] = SSE2NEON_MULTIPLY(e, 0x0d) ^ SSE2NEON_MULTIPLY(f, 0x09) ^ ++ SSE2NEON_MULTIPLY(g, 0x0e) ^ SSE2NEON_MULTIPLY(h, 0x0b); ++ v[i][3] = SSE2NEON_MULTIPLY(e, 0x0b) ^ SSE2NEON_MULTIPLY(f, 0x0d) ^ ++ SSE2NEON_MULTIPLY(g, 0x09) ^ SSE2NEON_MULTIPLY(h, 0x0e); ++ } ++ ++ return vreinterpretq_m128i_u8(vld1q_u8((uint8_t *) v)); ++#endif ++} ++ ++// Assist in expanding the AES cipher key by computing steps towards generating ++// a round key for encryption cipher using data from a and an 8-bit round ++// constant specified in imm8, and store the result in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aeskeygenassist_si128 ++// + // Emits the Advanced Encryption Standard (AES) instruction aeskeygenassist. + // This instruction generates a round key for AES encryption. See + // https://kazakov.life/2017/11/01/cryptocurrency-mining-on-ios-devices/ + // for details. +-// +-// https://msdn.microsoft.com/en-us/library/cc714138(v=vs.120).aspx +-FORCE_INLINE __m128i _mm_aeskeygenassist_si128(__m128i key, const int rcon) ++FORCE_INLINE __m128i _mm_aeskeygenassist_si128(__m128i a, const int rcon) + { +- uint32_t X1 = _mm_cvtsi128_si32(_mm_shuffle_epi32(key, 0x55)); +- uint32_t X3 = _mm_cvtsi128_si32(_mm_shuffle_epi32(key, 0xFF)); ++#if defined(__aarch64__) ++ uint8x16_t _a = vreinterpretq_u8_m128i(a); ++ uint8x16_t v = vqtbl4q_u8(_sse2neon_vld1q_u8_x4(_sse2neon_sbox), _a); ++ v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_sbox + 0x40), _a - 0x40); ++ v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_sbox + 0x80), _a - 0x80); ++ v = vqtbx4q_u8(v, _sse2neon_vld1q_u8_x4(_sse2neon_sbox + 0xc0), _a - 0xc0); ++ ++ uint32x4_t v_u32 = vreinterpretq_u32_u8(v); ++ uint32x4_t ror_v = vorrq_u32(vshrq_n_u32(v_u32, 8), vshlq_n_u32(v_u32, 24)); ++ uint32x4_t ror_xor_v = veorq_u32(ror_v, vdupq_n_u32(rcon)); ++ ++ return vreinterpretq_m128i_u32(vtrn2q_u32(v_u32, ror_xor_v)); ++ ++#else /* ARMv7-A NEON implementation */ ++ uint32_t X1 = _mm_cvtsi128_si32(_mm_shuffle_epi32(a, 0x55)); ++ uint32_t X3 = _mm_cvtsi128_si32(_mm_shuffle_epi32(a, 0xFF)); + for (int i = 0; i < 4; ++i) { +- ((uint8_t *) &X1)[i] = SSE2NEON_sbox[((uint8_t *) &X1)[i]]; +- ((uint8_t *) &X3)[i] = SSE2NEON_sbox[((uint8_t *) &X3)[i]]; ++ ((uint8_t *) &X1)[i] = _sse2neon_sbox[((uint8_t *) &X1)[i]]; ++ ((uint8_t *) &X3)[i] = _sse2neon_sbox[((uint8_t *) &X3)[i]]; + } + return _mm_set_epi32(((X3 >> 8) | (X3 << 24)) ^ rcon, X3, + ((X1 >> 8) | (X1 << 24)) ^ rcon, X1); ++#endif + } + #undef SSE2NEON_AES_SBOX + #undef SSE2NEON_AES_RSBOX + ++#if defined(__aarch64__) ++#undef SSE2NEON_XT ++#undef SSE2NEON_MULTIPLY ++#endif ++ + #else /* __ARM_FEATURE_CRYPTO */ + // Implements equivalent of 'aesenc' by combining AESE (with an empty key) and + // AESMC and then manually applying the real key as an xor operation. This +@@ -9750,7 +8843,9 @@ FORCE_INLINE __m128i _mm_aesdec_si128(__m128i a, __m128i RoundKey) + vreinterpretq_u8_m128i(RoundKey))); + } + +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_aesenclast_si128 ++// Perform the last round of an AES encryption flow on data (state) in a using ++// the round key in RoundKey, and store the result in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesenclast_si128 + FORCE_INLINE __m128i _mm_aesenclast_si128(__m128i a, __m128i RoundKey) + { + return _mm_xor_si128(vreinterpretq_m128i_u8(vaeseq_u8( +@@ -9758,6 +8853,23 @@ FORCE_INLINE __m128i _mm_aesenclast_si128(__m128i a, __m128i RoundKey) + RoundKey); + } + ++// Perform the last round of an AES decryption flow on data (state) in a using ++// the round key in RoundKey, and store the result in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesdeclast_si128 ++FORCE_INLINE __m128i _mm_aesdeclast_si128(__m128i a, __m128i RoundKey) ++{ ++ return vreinterpretq_m128i_u8( ++ vaesdq_u8(vreinterpretq_u8_m128i(a), vdupq_n_u8(0)) ^ ++ vreinterpretq_u8_m128i(RoundKey)); ++} ++ ++// Perform the InvMixColumns transformation on a and store the result in dst. ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesimc_si128 ++FORCE_INLINE __m128i _mm_aesimc_si128(__m128i a) ++{ ++ return vreinterpretq_m128i_u8(vaesimcq_u8(vreinterpretq_u8_m128i(a))); ++} ++ + // Assist in expanding the AES cipher key by computing steps towards generating + // a round key for encryption cipher using data from a and an 8-bit round + // constant specified in imm8, and store the result in dst." +@@ -9783,7 +8895,7 @@ FORCE_INLINE __m128i _mm_aeskeygenassist_si128(__m128i a, const int rcon) + + // Perform a carry-less multiplication of two 64-bit integers, selected from a + // and b according to imm8, and store the results in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_clmulepi64_si128 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_clmulepi64_si128 + FORCE_INLINE __m128i _mm_clmulepi64_si128(__m128i _a, __m128i _b, const int imm) + { + uint64x2_t a = vreinterpretq_u64_m128i(_a); +@@ -9828,7 +8940,7 @@ FORCE_INLINE unsigned int _sse2neon_mm_get_denormals_zero_mode() + + // Count the number of bits set to 1 in unsigned 32-bit integer a, and + // return that count in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_popcnt_u32 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_popcnt_u32 + FORCE_INLINE int _mm_popcnt_u32(unsigned int a) + { + #if defined(__aarch64__) +@@ -9855,7 +8967,7 @@ FORCE_INLINE int _mm_popcnt_u32(unsigned int a) + + // Count the number of bits set to 1 in unsigned 64-bit integer a, and + // return that count in dst. +-// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_popcnt_u64 ++// https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_popcnt_u64 + FORCE_INLINE int64_t _mm_popcnt_u64(uint64_t a) + { + #if defined(__aarch64__) +@@ -9911,7 +9023,6 @@ FORCE_INLINE void _sse2neon_mm_set_denormals_zero_mode(unsigned int flag) + + // Return the current 64-bit value of the processor's time-stamp counter. + // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=rdtsc +- + FORCE_INLINE uint64_t _rdtsc(void) + { + #if defined(__aarch64__) diff --git a/bazel/patches/emp-tool.patch b/bazel/patches/emp-tool.patch new file mode 100644 index 00000000..122a9655 --- /dev/null +++ b/bazel/patches/emp-tool.patch @@ -0,0 +1,162 @@ +diff --git a/emp-tool/utils/aes.h b/emp-tool/utils/aes.h +index 0235544..75a8486 100644 +--- a/emp-tool/utils/aes.h ++++ b/emp-tool/utils/aes.h +@@ -54,6 +54,10 @@ + + #include "emp-tool/utils/block.h" + ++#ifdef __aarch64__ ++#include "emp-tool/utils/sse2neon.h" ++#endif ++ + namespace emp { + + typedef struct { block rd_key[11]; unsigned int rounds; } AES_KEY; +@@ -103,6 +107,7 @@ AES_set_encrypt_key(const block userkey, AES_KEY *key) { + + #ifdef __x86_64__ + __attribute__((target("aes,sse2"))) ++#endif + inline void AES_ecb_encrypt_blks(block *blks, unsigned int nblks, const AES_KEY *key) { + for (unsigned int i = 0; i < nblks; ++i) + blks[i] = _mm_xor_si128(blks[i], key->rd_key[0]); +@@ -112,22 +117,6 @@ inline void AES_ecb_encrypt_blks(block *blks, unsigned int nblks, const AES_KEY + for (unsigned int i = 0; i < nblks; ++i) + blks[i] = _mm_aesenclast_si128(blks[i], key->rd_key[key->rounds]); + } +-#elif __aarch64__ +-inline void AES_ecb_encrypt_blks(block *_blks, unsigned int nblks, const AES_KEY *key) { +- uint8x16_t * blks = (uint8x16_t*)(_blks); +- uint8x16_t * keys = (uint8x16_t*)(key->rd_key); +- auto * first = blks; +- for (unsigned int j = 0; j < key->rounds-1; ++j) { +- uint8x16_t key_j = (uint8x16_t)keys[j]; +- blks = first; +- for (unsigned int i = 0; i < nblks; ++i, ++blks) +- *blks = vaesmcq_u8(vaeseq_u8(*blks, key_j)); +- } +- uint8x16_t last_key = (uint8x16_t)keys[key->rounds-1]; +- for (unsigned int i = 0; i < nblks; ++i, ++first) +- *first = vaeseq_u8(*first, last_key) ^ (uint8x16_t)keys[key->rounds]; +-} +-#endif + + #ifdef __GNUC__ + #ifndef __clang__ +diff --git a/emp-tool/utils/aes_opt.h b/emp-tool/utils/aes_opt.h +index 2594e32..6a78b75 100644 +--- a/emp-tool/utils/aes_opt.h ++++ b/emp-tool/utils/aes_opt.h +@@ -58,7 +58,6 @@ static inline void AES_opt_key_schedule(block* user_key, AES_KEY *keys) { + /* + * With numKeys keys, use each key to encrypt numEncs blocks. + */ +-#ifdef __x86_64__ + template<int numKeys, int numEncs> + static inline void ParaEnc(block *blks, AES_KEY *keys) { + block * first = blks; +@@ -90,29 +89,6 @@ static inline void ParaEnc(block *blks, AES_KEY *keys) { + } + } + } +-#elif __aarch64__ +-template<int numKeys, int numEncs> +-static inline void ParaEnc(block *_blks, AES_KEY *keys) { +- uint8x16_t * first = (uint8x16_t*)(_blks); +- +- for (unsigned int r = 0; r < 9; ++r) { +- auto blks = first; +- for(size_t i = 0; i < numKeys; ++i) { +- uint8x16_t K = vreinterpretq_u8_m128i(keys[i].rd_key[r]); +- for(size_t j = 0; j < numEncs; ++j, ++blks) +- *blks = vaesmcq_u8(vaeseq_u8(*blks, K)); +- } +- } +- +- auto blks = first; +- for(size_t i = 0; i < numKeys; ++i) { +- uint8x16_t K = vreinterpretq_u8_m128i(keys[i].rd_key[9]); +- uint8x16_t K2 = vreinterpretq_u8_m128i(keys[i].rd_key[10]); +- for(size_t j = 0; j < numEncs; ++j, ++blks) +- *blks = vaeseq_u8(*blks, K) ^ K2; +- } +-} +-#endif + + } + #endif +diff --git a/emp-tool/utils/block.h b/emp-tool/utils/block.h +index f7d3d34..fcc21c1 100644 +--- a/emp-tool/utils/block.h ++++ b/emp-tool/utils/block.h +@@ -5,16 +5,7 @@ + #include <immintrin.h> + #elif __aarch64__ + #include "sse2neon.h" +-inline __m128i _mm_aesimc_si128(__m128i a) { +- return vreinterpretq_m128i_u8(vaesimcq_u8(vreinterpretq_u8_m128i(a))); +-} +- +-inline __m128i _mm_aesdeclast_si128 (__m128i a, __m128i RoundKey) +-{ +- return vreinterpretq_m128i_u8(vaesdq_u8(vreinterpretq_u8_m128i(a), vdupq_n_u8(0)) ^ vreinterpretq_u8_m128i(RoundKey)); +-} + #endif +- + #include <assert.h> + #include <cstring> + #include <iostream> +diff --git a/emp-tool/utils/f2k.h b/emp-tool/utils/f2k.h +index 7fe1b1b..f6186a1 100644 +--- a/emp-tool/utils/f2k.h ++++ b/emp-tool/utils/f2k.h +@@ -6,6 +6,7 @@ namespace emp { + /* multiplication in galois field without reduction */ + #ifdef __x86_64__ + __attribute__((target("sse2,pclmul"))) ++ #endif + inline void mul128(__m128i a, __m128i b, __m128i *res1, __m128i *res2) { + __m128i tmp3, tmp4, tmp5, tmp6; + tmp3 = _mm_clmulepi64_si128(a, b, 0x00); +@@ -22,28 +23,6 @@ namespace emp { + *res1 = tmp3; + *res2 = tmp6; + } +- #elif __aarch64__ +- inline void mul128(__m128i a, __m128i b, __m128i *res1, __m128i *res2) { +- __m128i tmp3, tmp4, tmp5, tmp6; +- poly64_t a_lo = (poly64_t)vget_low_u64(vreinterpretq_u64_m128i(a)); +- poly64_t a_hi = (poly64_t)vget_high_u64(vreinterpretq_u64_m128i(a)); +- poly64_t b_lo = (poly64_t)vget_low_u64(vreinterpretq_u64_m128i(b)); +- poly64_t b_hi = (poly64_t)vget_high_u64(vreinterpretq_u64_m128i(b)); +- tmp3 = (__m128i)vmull_p64(a_lo, b_lo); +- tmp4 = (__m128i)vmull_p64(a_hi, b_lo); +- tmp5 = (__m128i)vmull_p64(a_lo, b_hi); +- tmp6 = (__m128i)vmull_p64(a_hi, b_hi); +- +- tmp4 = _mm_xor_si128(tmp4, tmp5); +- tmp5 = _mm_slli_si128(tmp4, 8); +- tmp4 = _mm_srli_si128(tmp4, 8); +- tmp3 = _mm_xor_si128(tmp3, tmp5); +- tmp6 = _mm_xor_si128(tmp6, tmp4); +- // initial mul now in tmp3, tmp6 +- *res1 = tmp3; +- *res2 = tmp6; +- } +- #endif + + /* multiplication in galois field with reduction */ + #ifdef __x86_64__ +diff --git a/emp-tool/utils/prg.h b/emp-tool/utils/prg.h +index 23bbf42..5101d7e 100644 +--- a/emp-tool/utils/prg.h ++++ b/emp-tool/utils/prg.h +@@ -82,7 +82,7 @@ class PRG { public: + } else { + block tmp[2]; + random_block(tmp, 2); +- memcpy(data, tmp, nbytes); ++ memcpy(data, tmp, nbytes <= 32? nbytes : 32); + } + } diff --git a/bazel/patches/emp-zk.patch b/bazel/patches/emp-zk.patch new file mode 100644 index 00000000..3cf9aacf --- /dev/null +++ b/bazel/patches/emp-zk.patch @@ -0,0 +1,30 @@ +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 3a533d2..be0e490 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -17,5 +17,11 @@ install(DIRECTORY emp-zk DESTINATION include/) + install(FILES cmake/emp-zk-config.cmake DESTINATION cmake/) + install(TARGETS ${NAME} DESTINATION lib) + +-ENABLE_TESTING() +-ADD_SUBDIRECTORY(test) ++#ENABLE_TESTING() ++#ADD_SUBDIRECTORY(test) ++# add enable testing option, default OFF ++option(ENABLE_TESTS "Enable tests" OFF) ++if (${ENABLE_TESTS}) ++ ENABLE_TESTING() ++ ADD_SUBDIRECTORY(test) ++endif() +diff --git a/emp-zk/emp-vole/vole_triple.h b/emp-zk/emp-vole/vole_triple.h +index 3be3377..6759497 100644 +--- a/emp-zk/emp-vole/vole_triple.h ++++ b/emp-zk/emp-vole/vole_triple.h +@@ -80,6 +80,7 @@ public: + if(mpfss != nullptr) delete mpfss; + if(vole_triples != nullptr) delete[] vole_triples; + if(vole_x != nullptr) delete[] vole_x; ++ if(cot != nullptr) delete cot; + } + + void setup(__uint128_t delta) { diff --git a/bazel/patches/grpc.patch b/bazel/patches/grpc.patch new file mode 100644 index 00000000..ba2822c4 --- /dev/null +++ b/bazel/patches/grpc.patch @@ -0,0 +1,32 @@ +diff --git a/bazel/grpc_deps.bzl b/bazel/grpc_deps.bzl +index 5e65a65df4..03bbd2361e 100644 +--- a/bazel/grpc_deps.bzl ++++ b/bazel/grpc_deps.bzl +@@ -57,12 +57,12 @@ def grpc_deps(): + + native.bind( + name = "libssl", +- actual = "@boringssl//:ssl", ++ actual = "@com_github_openssl_openssl//:openssl", + ) + + native.bind( + name = "libcrypto", +- actual = "@boringssl//:crypto", ++ actual = "@com_github_openssl_openssl//:openssl", + ) + + native.bind( +diff --git a/bazel/grpc_extra_deps.bzl b/bazel/grpc_extra_deps.bzl +index 4d8afa3131..6aa977a08d 100644 +--- a/bazel/grpc_extra_deps.bzl ++++ b/bazel/grpc_extra_deps.bzl +@@ -53,7 +53,7 @@ def grpc_extra_deps(ignore_version_differences = False): + api_dependencies() + + go_rules_dependencies() +- go_register_toolchains(version = "1.18") ++ go_register_toolchains(version = "host") + gazelle_dependencies() + + # Pull-in the go 3rd party dependencies for protoc_gen_validate, which is diff --git a/bazel/patches/seal.patch b/bazel/patches/seal.patch new file mode 100644 index 00000000..502a9afb --- /dev/null +++ b/bazel/patches/seal.patch @@ -0,0 +1,120 @@ +diff --git a/native/src/seal/serializable.h b/native/src/seal/serializable.h +index a940190..e490b30 100644 +--- a/native/src/seal/serializable.h ++++ b/native/src/seal/serializable.h +@@ -135,6 +135,9 @@ namespace seal + return obj_.save(out, size, compr_mode); + } + ++ const T& obj() const { return obj_; } ++ ++ T& obj() { return obj_; } + private: + Serializable(T &&obj) : obj_(std::move(obj)) + {} + +diff --git a/native/src/seal/context.cpp b/native/src/seal/context.cpp +index 887a1312..932d9774 100644 +--- a/native/src/seal/context.cpp ++++ b/native/src/seal/context.cpp +@@ -477,7 +477,8 @@ namespace seal + // more than one modulus in coeff_modulus. This is equivalent to expanding + // the chain by one step. Otherwise, we set first_parms_id_ to equal + // key_parms_id_. +- if (!context_data_map_.at(key_parms_id_)->qualifiers_.parameters_set() || parms.coeff_modulus().size() == 1) ++ if (!context_data_map_.at(key_parms_id_)->qualifiers_.parameters_set() || parms.coeff_modulus().size() == 1 || ++ !parms.use_special_prime()) + { + first_parms_id_ = key_parms_id_; + } +diff --git a/native/src/seal/encryptionparams.cpp b/native/src/seal/encryptionparams.cpp +index 31e07441..c34d0a45 100644 +--- a/native/src/seal/encryptionparams.cpp ++++ b/native/src/seal/encryptionparams.cpp +@@ -23,8 +23,10 @@ namespace seal + uint64_t poly_modulus_degree64 = static_cast<uint64_t>(poly_modulus_degree_); + uint64_t coeff_modulus_size64 = static_cast<uint64_t>(coeff_modulus_.size()); + uint8_t scheme = static_cast<uint8_t>(scheme_); ++ uint8_t use_special_prime = static_cast<uint8_t>(use_special_prime); + + stream.write(reinterpret_cast<const char *>(&scheme), sizeof(uint8_t)); ++ stream.write(reinterpret_cast<const char *>(&use_special_prime), sizeof(uint8_t)); + stream.write(reinterpret_cast<const char *>(&poly_modulus_degree64), sizeof(uint64_t)); + stream.write(reinterpret_cast<const char *>(&coeff_modulus_size64), sizeof(uint64_t)); + for (const auto &mod : coeff_modulus_) +@@ -63,6 +65,10 @@ namespace seal + // This constructor will throw if scheme is invalid + EncryptionParameters parms(scheme); + ++ uint8_t use_special_prime; ++ stream.read(reinterpret_cast<char *>(&use_special_prime), sizeof(uint8_t)); ++ parms.set_use_special_prime(use_special_prime); ++ + // Read the poly_modulus_degree + uint64_t poly_modulus_degree64 = 0; + stream.read(reinterpret_cast<char *>(&poly_modulus_degree64), sizeof(uint64_t)); +@@ -128,7 +134,8 @@ namespace seal + size_t total_uint64_count = add_safe( + size_t(1), // scheme + size_t(1), // poly_modulus_degree +- coeff_modulus_size, plain_modulus_.uint64_count()); ++ size_t(1), // use_special_prime ++ coeff_modulus_size); + + auto param_data(allocate_uint(total_uint64_count, pool_)); + uint64_t *param_data_ptr = param_data.get(); +@@ -139,13 +146,15 @@ namespace seal + // Write the poly_modulus_degree. Note that it will always be positive. + *param_data_ptr++ = static_cast<uint64_t>(poly_modulus_degree_); + ++ *param_data_ptr++ = static_cast<uint64_t>(use_special_prime_); + for (const auto &mod : coeff_modulus_) + { + *param_data_ptr++ = mod.value(); + } + +- set_uint(plain_modulus_.data(), plain_modulus_.uint64_count(), param_data_ptr); +- param_data_ptr += plain_modulus_.uint64_count(); ++ // NOTE(juhou): we skip the plain modulus for parms_id ++ // set_uint(plain_modulus_.data(), plain_modulus_.uint64_count(), param_data_ptr); ++ // param_data_ptr += plain_modulus_.uint64_count(); + + HashFunction::hash(param_data.get(), total_uint64_count, parms_id_); + +diff --git a/native/src/seal/encryptionparams.h b/native/src/seal/encryptionparams.h +index 9e1fbe48..eb71c4ac 100644 +--- a/native/src/seal/encryptionparams.h ++++ b/native/src/seal/encryptionparams.h +@@ -266,6 +266,11 @@ namespace seal + random_generator_ = std::move(random_generator); + } + ++ inline void set_use_special_prime(bool flag) ++ { ++ use_special_prime_ = flag; ++ } ++ + /** + Returns the encryption scheme type. + */ +@@ -274,6 +279,11 @@ namespace seal + return scheme_; + } + ++ bool use_special_prime() const noexcept ++ { ++ return use_special_prime_; ++ } ++ + /** + Returns the degree of the polynomial modulus parameter. + */ +@@ -501,6 +511,8 @@ namespace seal + + Modulus plain_modulus_{}; + ++ bool use_special_prime_ = true; ++ + parms_id_type parms_id_ = parms_id_zero; + }; + } // namespace seal diff --git a/bazel/perfetto.BUILD b/bazel/perfetto.BUILD new file mode 100644 index 00000000..33e93055 --- /dev/null +++ b/bazel/perfetto.BUILD @@ -0,0 +1,26 @@ +# Copyright 2023 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cc_library( + name = "perfetto", + srcs = [ + "sdk/perfetto.cc", + "sdk/perfetto.h", + ], + hdrs = [ + "sdk/perfetto.h", + ], + includes = ["sdk"], + visibility = ["//visibility:public"], +) diff --git a/bazel/psi.bzl b/bazel/psi.bzl new file mode 100644 index 00000000..8ae11328 --- /dev/null +++ b/bazel/psi.bzl @@ -0,0 +1,110 @@ +# Copyright 2021 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +warpper bazel cc_xx to modify flags. +""" + +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test") +load("@rules_foreign_cc//foreign_cc:defs.bzl", "cmake", "configure_make") + +WARNING_FLAGS = [ + "-Wall", + "-Wextra", + "-Werror", +] +DEBUG_FLAGS = ["-O0", "-g"] +RELEASE_FLAGS = ["-O2"] +FAST_FLAGS = ["-O1"] + +def _psi_copts(): + return select({ + "@psi//bazel:psi_build_as_release": RELEASE_FLAGS, + "@psi//bazel:psi_build_as_debug": DEBUG_FLAGS, + "@psi//bazel:psi_build_as_fast": FAST_FLAGS, + "//conditions:default": FAST_FLAGS, + }) + WARNING_FLAGS + +def psi_cc_binary( + linkopts = [], + copts = [], + **kargs): + cc_binary( + linkopts = linkopts, + copts = copts + _psi_copts(), + **kargs + ) + +def psi_cc_library( + linkopts = [], + copts = [], + deps = [], + local_defines = [], + **kargs): + cc_library( + linkopts = linkopts, + copts = _psi_copts() + copts, + deps = deps + [ + "@com_github_gabime_spdlog//:spdlog", + ], + local_defines = local_defines + [ + "PSI_BUILD", + ], + **kargs + ) + +def psi_cmake_external(**attrs): + if "generate_args" not in attrs: + attrs["generate_args"] = ["-GNinja"] + return cmake(**attrs) + +def psi_configure_make(**attrs): + if "args" not in attrs: + attrs["args"] = ["-j 4"] + return configure_make(**attrs) + +def _psi_version_file_impl(ctx): + out = ctx.actions.declare_file(ctx.attr.filename) + ctx.actions.write( + output = out, + content = "__version__ = \"{}\"\n".format(ctx.attr.version), + ) + return [DefaultInfo(files = depset([out]))] + +psi_version_file = rule( + implementation = _psi_version_file_impl, + attrs = { + "version": attr.string(), + "filename": attr.string(), + }, +) + +def psi_cc_test( + linkopts = [], + copts = [], + deps = [], + local_defines = [], + **kwargs): + cc_test( + # -lm for tcmalloc + linkopts = linkopts + ["-lm"], + copts = _psi_copts() + copts, + deps = deps + [ + "@com_google_googletest//:gtest_main", + ], + local_defines = local_defines + [ + "PSI_BUILD", + ], + **kwargs + ) diff --git a/bazel/rapidjson.BUILD b/bazel/rapidjson.BUILD new file mode 100644 index 00000000..86748d0d --- /dev/null +++ b/bazel/rapidjson.BUILD @@ -0,0 +1,30 @@ +# Copyright 2023 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# copied from https://github.com/tensorflow/io/blob/v0.25.0/third_party/rapidjson.BUILD + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # MIT/JSON license + +cc_library( + name = "rapidjson", + srcs = glob([ + "include/**/*.h", + ]), + copts = [], + includes = [ + "include", + ], +) diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl new file mode 100644 index 00000000..283faf02 --- /dev/null +++ b/bazel/repositories.bzl @@ -0,0 +1,423 @@ +# Copyright 2021 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") +load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") + +SECRETFLOW_GIT = "https://github.com/secretflow" + +YACL_COMMIT_ID = "3baea619ae3f67911d7f072ff7dd39bc6a00ec28" + +def psi_deps(): + _com_github_nelhage_rules_boost() + _bazel_platform() + _upb() + _com_github_emptoolkit_emp_tool() + _com_github_emptoolkit_emp_ot() + _com_github_emptoolkit_emp_zk() + _com_github_facebook_zstd() + _com_github_microsoft_seal() + _com_github_eigenteam_eigen() + _com_github_microsoft_apsi() + _com_github_microsoft_gsl() + _com_github_microsoft_kuku() + _com_google_flatbuffers() + _org_apache_arrow() + _com_github_grpc_grpc() + _com_github_tencent_rapidjson() + _com_github_xtensor_xsimd() + _brotli() + _com_github_lz4_lz4() + _org_apache_thrift() + _com_google_double_conversion() + _bzip2() + _com_github_google_snappy() + _com_github_google_perfetto() + _com_github_floodyberry_curve25519_donna() + _com_github_ridiculousfish_libdivide() + _com_github_sparsehash_sparsehash() + + maybe( + git_repository, + name = "yacl", + commit = YACL_COMMIT_ID, + remote = "{}/yacl.git".format(SECRETFLOW_GIT), + ) + + # Add homebrew openmp for macOS, somehow..homebrew installs to different location on Apple Silcon/Intel macs.. so we need two rules here + native.new_local_repository( + name = "local_homebrew_x64", + build_file = "@psi//bazel:local_openmp_macos.BUILD", + path = "/usr/local/opt/libomp", + ) + + native.new_local_repository( + name = "local_homebrew_arm64", + build_file = "@psi//bazel:local_openmp_macos.BUILD", + path = "/opt/homebrew/opt/libomp/", + ) + +def _bazel_platform(): + http_archive( + name = "platforms", + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/platforms/releases/download/0.0.7/platforms-0.0.7.tar.gz", + "https://github.com/bazelbuild/platforms/releases/download/0.0.7/platforms-0.0.7.tar.gz", + ], + sha256 = "3a561c99e7bdbe9173aa653fd579fe849f1d8d67395780ab4770b1f381431d51", + ) + +def _com_github_facebook_zstd(): + maybe( + http_archive, + name = "com_github_facebook_zstd", + build_file = "@psi//bazel:zstd.BUILD", + strip_prefix = "zstd-1.5.5", + sha256 = "98e9c3d949d1b924e28e01eccb7deed865eefebf25c2f21c702e5cd5b63b85e1", + type = ".tar.gz", + urls = [ + "https://github.com/facebook/zstd/archive/refs/tags/v1.5.5.tar.gz", + ], + ) + +def _upb(): + maybe( + http_archive, + name = "upb", + sha256 = "017a7e8e4e842d01dba5dc8aa316323eee080cd1b75986a7d1f94d87220e6502", + strip_prefix = "upb-e4635f223e7d36dfbea3b722a4ca4807a7e882e2", + urls = [ + "https://storage.googleapis.com/grpc-bazel-mirror/github.com/protocolbuffers/upb/archive/e4635f223e7d36dfbea3b722a4ca4807a7e882e2.tar.gz", + "https://github.com/protocolbuffers/upb/archive/e4635f223e7d36dfbea3b722a4ca4807a7e882e2.tar.gz", + ], + ) + +def _com_github_emptoolkit_emp_tool(): + maybe( + http_archive, + name = "com_github_emptoolkit_emp_tool", + sha256 = "b9ab2380312e78020346b5d2db3d0244c7bd8098cb50f8b3620532ef491808d0", + strip_prefix = "emp-tool-0.2.5", + type = "tar.gz", + patch_args = ["-p1"], + patches = [ + "@psi//bazel:patches/emp-tool.patch", + "@psi//bazel:patches/emp-tool-cmake.patch", + "@psi//bazel:patches/emp-tool-sse2neon.patch", + ], + urls = [ + "https://github.com/emp-toolkit/emp-tool/archive/refs/tags/0.2.5.tar.gz", + ], + build_file = "@psi//bazel:emp-tool.BUILD", + ) + +def _com_github_emptoolkit_emp_ot(): + maybe( + http_archive, + name = "com_github_emptoolkit_emp_ot", + sha256 = "358036e5d18143720ee17103f8172447de23014bcfc1f8e7d5849c525ca928ac", + strip_prefix = "emp-ot-0.2.4", + type = "tar.gz", + patch_args = ["-p1"], + patches = ["@psi//bazel:patches/emp-ot.patch"], + urls = [ + "https://github.com/emp-toolkit/emp-ot/archive/refs/tags/0.2.4.tar.gz", + ], + build_file = "@psi//bazel:emp-ot.BUILD", + ) + +def _com_github_emptoolkit_emp_zk(): + maybe( + http_archive, + name = "com_github_emptoolkit_emp_zk", + sha256 = "e02e6abc6ee14ca0e69e6f5f0efe24cab7da1bc905fc7c86a3e5a529114e489a", + strip_prefix = "emp-zk-0.2.1", + type = "tar.gz", + patch_args = ["-p1"], + patches = ["@psi//bazel:patches/emp-zk.patch"], + urls = [ + "https://github.com/emp-toolkit/emp-zk/archive/refs/tags/0.2.1.tar.gz", + ], + build_file = "@psi//bazel:emp-zk.BUILD", + ) + +def _com_github_microsoft_seal(): + maybe( + http_archive, + name = "com_github_microsoft_seal", + sha256 = "af9bf0f0daccda2a8b7f344f13a5692e0ee6a45fea88478b2b90c35648bf2672", + strip_prefix = "SEAL-4.1.1", + type = "tar.gz", + patch_args = ["-p1"], + patches = ["@psi//bazel:patches/seal.patch"], + urls = [ + "https://github.com/microsoft/SEAL/archive/refs/tags/v4.1.1.tar.gz", + ], + build_file = "@psi//bazel:seal.BUILD", + ) + +def _com_github_eigenteam_eigen(): + maybe( + http_archive, + name = "com_github_eigenteam_eigen", + sha256 = "c1b115c153c27c02112a0ecbf1661494295d9dcff6427632113f2e4af9f3174d", + build_file = "@psi//bazel:eigen.BUILD", + strip_prefix = "eigen-3.4", + urls = [ + "https://gitlab.com/libeigen/eigen/-/archive/3.4/eigen-3.4.tar.gz", + ], + ) + +def _com_github_microsoft_apsi(): + maybe( + http_archive, + name = "com_github_microsoft_apsi", + sha256 = "82c0f9329c79222675109d4a3682d204acd3ea9a724bcd98fa58eabe53851333", + strip_prefix = "APSI-0.11.0", + urls = [ + "https://github.com/microsoft/APSI/archive/refs/tags/v0.11.0.tar.gz", + ], + build_file = "@psi//bazel:microsoft_apsi.BUILD", + patch_args = ["-p1"], + patches = [ + "@psi//bazel:patches/apsi.patch", + "@psi//bazel:patches/apsi-gen.patch", + "@psi//bazel:patches/apsi_bin_bundle.patch", + ], + ) + +def _com_github_microsoft_gsl(): + maybe( + http_archive, + name = "com_github_microsoft_gsl", + sha256 = "f0e32cb10654fea91ad56bde89170d78cfbf4363ee0b01d8f097de2ba49f6ce9", + strip_prefix = "GSL-4.0.0", + type = "tar.gz", + urls = [ + "https://github.com/microsoft/GSL/archive/refs/tags/v4.0.0.tar.gz", + ], + build_file = "@psi//bazel:microsoft_gsl.BUILD", + ) + +def _com_github_microsoft_kuku(): + maybe( + http_archive, + name = "com_github_microsoft_kuku", + sha256 = "96ed5fad82ea8c8a8bb82f6eaf0b5dce744c0c2566b4baa11d8f5443ad1f83b7", + strip_prefix = "Kuku-2.1.0", + type = "tar.gz", + urls = [ + "https://github.com/microsoft/Kuku/archive/refs/tags/v2.1.0.tar.gz", + ], + build_file = "@psi//bazel:microsoft_kuku.BUILD", + ) + +def _com_google_flatbuffers(): + maybe( + http_archive, + name = "com_google_flatbuffers", + sha256 = "8aff985da30aaab37edf8e5b02fda33ed4cbdd962699a8e2af98fdef306f4e4d", + strip_prefix = "flatbuffers-23.3.3", + urls = [ + "https://github.com/google/flatbuffers/archive/refs/tags/v23.3.3.tar.gz", + ], + ) + +def _org_apache_arrow(): + maybe( + http_archive, + name = "org_apache_arrow", + urls = [ + "https://github.com/apache/arrow/archive/apache-arrow-10.0.0.tar.gz", + ], + sha256 = "2852b21f93ee84185a9d838809c9a9c41bf6deca741bed1744e0fdba6cc19e3f", + strip_prefix = "arrow-apache-arrow-10.0.0", + build_file = "@psi//bazel:arrow.BUILD", + ) + +def _com_github_grpc_grpc(): + maybe( + http_archive, + name = "com_github_grpc_grpc", + sha256 = "7f42363711eb483a0501239fd5522467b31d8fe98d70d7867c6ca7b52440d828", + strip_prefix = "grpc-1.51.0", + type = "tar.gz", + patch_args = ["-p1"], + # Set grpc to use local go toolchain + patches = ["@psi//bazel:patches/grpc.patch"], + urls = [ + "https://github.com/grpc/grpc/archive/refs/tags/v1.51.0.tar.gz", + ], + ) + +def _com_github_nelhage_rules_boost(): + # use boost 1.83 + RULES_BOOST_COMMIT = "cfa585b1b5843993b70aa52707266dc23b3282d0" + maybe( + http_archive, + name = "com_github_nelhage_rules_boost", + sha256 = "a7c42df432fae9db0587ff778d84f9dc46519d67a984eff8c79ae35e45f277c1", + strip_prefix = "rules_boost-%s" % RULES_BOOST_COMMIT, + urls = [ + "https://github.com/nelhage/rules_boost/archive/%s.tar.gz" % RULES_BOOST_COMMIT, + ], + ) + +def _com_github_tencent_rapidjson(): + maybe( + http_archive, + name = "com_github_tencent_rapidjson", + urls = [ + "https://github.com/Tencent/rapidjson/archive/refs/tags/v1.1.0.tar.gz", + ], + sha256 = "bf7ced29704a1e696fbccf2a2b4ea068e7774fa37f6d7dd4039d0787f8bed98e", + strip_prefix = "rapidjson-1.1.0", + build_file = "@psi//bazel:rapidjson.BUILD", + ) + +def _com_github_xtensor_xsimd(): + maybe( + http_archive, + name = "com_github_xtensor_xsimd", + urls = [ + "https://codeload.github.com/xtensor-stack/xsimd/tar.gz/refs/tags/8.1.0", + ], + sha256 = "d52551360d37709675237d2a0418e28f70995b5b7cdad7c674626bcfbbf48328", + type = "tar.gz", + strip_prefix = "xsimd-8.1.0", + build_file = "@psi//bazel:xsimd.BUILD", + ) + +def _brotli(): + maybe( + http_archive, + name = "brotli", + build_file = "@psi//bazel:brotli.BUILD", + sha256 = "f9e8d81d0405ba66d181529af42a3354f838c939095ff99930da6aa9cdf6fe46", + strip_prefix = "brotli-1.0.9", + urls = [ + "https://github.com/google/brotli/archive/refs/tags/v1.0.9.tar.gz", + ], + ) + +def _com_github_lz4_lz4(): + maybe( + http_archive, + name = "com_github_lz4_lz4", + urls = [ + "https://codeload.github.com/lz4/lz4/tar.gz/refs/tags/v1.9.3", + ], + sha256 = "030644df4611007ff7dc962d981f390361e6c97a34e5cbc393ddfbe019ffe2c1", + type = "tar.gz", + strip_prefix = "lz4-1.9.3", + build_file = "@psi//bazel:lz4.BUILD", + ) + +def _org_apache_thrift(): + maybe( + http_archive, + name = "org_apache_thrift", + build_file = "@psi//bazel:thrift.BUILD", + sha256 = "5da60088e60984f4f0801deeea628d193c33cec621e78c8a43a5d8c4055f7ad9", + strip_prefix = "thrift-0.13.0", + urls = [ + "https://github.com/apache/thrift/archive/v0.13.0.tar.gz", + ], + ) + +def _com_google_double_conversion(): + maybe( + http_archive, + name = "com_google_double_conversion", + sha256 = "a63ecb93182134ba4293fd5f22d6e08ca417caafa244afaa751cbfddf6415b13", + strip_prefix = "double-conversion-3.1.5", + build_file = "@psi//bazel:double-conversion.BUILD", + urls = [ + "https://github.com/google/double-conversion/archive/refs/tags/v3.1.5.tar.gz", + ], + ) + +def _bzip2(): + maybe( + http_archive, + name = "bzip2", + build_file = "@psi//bazel:bzip2.BUILD", + sha256 = "ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269", + strip_prefix = "bzip2-1.0.8", + urls = [ + "https://sourceware.org/pub/bzip2/bzip2-1.0.8.tar.gz", + ], + ) + +def _com_github_google_snappy(): + maybe( + http_archive, + name = "com_github_google_snappy", + urls = [ + "https://github.com/google/snappy/archive/refs/tags/1.1.9.tar.gz", + ], + sha256 = "75c1fbb3d618dd3a0483bff0e26d0a92b495bbe5059c8b4f1c962b478b6e06e7", + strip_prefix = "snappy-1.1.9", + build_file = "@psi//bazel:snappy.BUILD", + ) + +def _com_github_google_perfetto(): + maybe( + http_archive, + name = "com_github_google_perfetto", + urls = [ + "https://github.com/google/perfetto/archive/refs/tags/v37.0.tar.gz", + ], + sha256 = "39d7b3635834398828cfd189bd61afb0657ca2a3a08efbfd9866bfbcd440810b", + strip_prefix = "perfetto-37.0", + build_file = "@psi//bazel:perfetto.BUILD", + ) + +def _com_github_floodyberry_curve25519_donna(): + maybe( + http_archive, + name = "com_github_floodyberry_curve25519_donna", + strip_prefix = "curve25519-donna-2fe66b65ea1acb788024f40a3373b8b3e6f4bbb2", + sha256 = "ba57d538c241ad30ff85f49102ab2c8dd996148456ed238a8c319f263b7b149a", + type = "tar.gz", + build_file = "@psi//bazel:curve25519-donna.BUILD", + urls = [ + "https://github.com/floodyberry/curve25519-donna/archive/2fe66b65ea1acb788024f40a3373b8b3e6f4bbb2.tar.gz", + ], + ) + +def _com_github_ridiculousfish_libdivide(): + maybe( + http_archive, + name = "com_github_ridiculousfish_libdivide", + urls = [ + "https://github.com/ridiculousfish/libdivide/archive/refs/tags/5.0.tar.gz", + ], + sha256 = "01ffdf90bc475e42170741d381eb9cfb631d9d7ddac7337368bcd80df8c98356", + strip_prefix = "libdivide-5.0", + build_file = "@psi//bazel:libdivide.BUILD", + ) + +def _com_github_sparsehash_sparsehash(): + maybe( + http_archive, + name = "com_github_sparsehash_sparsehash", + urls = [ + "https://github.com/sparsehash/sparsehash/archive/refs/tags/sparsehash-2.0.4.tar.gz", + ], + sha256 = "8cd1a95827dfd8270927894eb77f62b4087735cbede953884647f16c521c7e58", + strip_prefix = "sparsehash-sparsehash-2.0.4", + build_file = "@psi//bazel:sparsehash.BUILD", + ) diff --git a/bazel/seal.BUILD b/bazel/seal.BUILD new file mode 100644 index 00000000..acf4cfc1 --- /dev/null +++ b/bazel/seal.BUILD @@ -0,0 +1,63 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@psi//bazel:psi.bzl", "psi_cmake_external") + +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "all", + srcs = glob(["**"]), +) + +config_setting( + name = "can_use_hexl", + constraint_values = [ + "@platforms//cpu:x86_64", + ], + values = {"compilation_mode": "opt"}, +) + +default_config = { + "SEAL_USE_MSGSL": "OFF", + "SEAL_BUILD_DEPS": "OFF", + "SEAL_USE_ZLIB": "OFF", + "SEAL_USE_INTEL_HEXL": "OFF", + "SEAL_THROW_ON_TRANSPARENT_CIPHERTEXT": "OFF", #NOTE(juhou) required by apsi + "SEAL_USE_ZSTD": "ON", + "CMAKE_INSTALL_LIBDIR": "lib", +} + +x64_hexl_config = { + "SEAL_USE_MSGSL": "OFF", + "SEAL_BUILD_DEPS": "OFF", + "SEAL_USE_ZLIB": "OFF", + "SEAL_THROW_ON_TRANSPARENT_CIPHERTEXT": "OFF", #NOTE(juhou) required by apsi + "CMAKE_INSTALL_LIBDIR": "lib", + "CpuFeatures_DIR": "$EXT_BUILD_DEPS/cpu_features/lib/cmake/CpuFeatures/", + "EXT_BUILD_DEPS": "$EXT_BUILD_DEPS", + "SEAL_USE_ZSTD": "ON", + "SEAL_USE_INTEL_HEXL": "ON", +} + +psi_cmake_external( + name = "seal", + cache_entries = default_config, + lib_source = "@com_github_microsoft_seal//:all", + out_include_dir = "include/SEAL-4.1", + out_static_libs = ["libseal-4.1.a"], + deps = [ + "@com_github_facebook_zstd//:zstd", + ], +) diff --git a/bazel/snappy.BUILD b/bazel/snappy.BUILD new file mode 100644 index 00000000..419b6947 --- /dev/null +++ b/bazel/snappy.BUILD @@ -0,0 +1,39 @@ +# Copyright 2023 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_foreign_cc//foreign_cc:defs.bzl", "cmake") + +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "all_srcs", + srcs = glob(["**"]), +) + +cmake( + name = "snappy", + cache_entries = { + "SNAPPY_BUILD_TESTS": "OFF", + "SNAPPY_BUILD_BENCHMARKS": "OFF", + "CMAKE_INSTALL_LIBDIR": "lib", + }, + generate_crosstool_file = False, + install_args = [ + "--prefix $${INSTALLDIR}", + ], + lib_source = ":all_srcs", + out_static_libs = [ + "libsnappy.a", + ], +) diff --git a/bazel/sparsehash.BUILD b/bazel/sparsehash.BUILD new file mode 100644 index 00000000..baa8d805 --- /dev/null +++ b/bazel/sparsehash.BUILD @@ -0,0 +1,24 @@ +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "sparsehash", + hdrs = glob([ + "src/google/**/*", + "src/sparsehash/**/*", + ]), + includes = ["src"], + visibility = ["//visibility:public"], + deps = [ + "@psi//psi/psi/core/vole_psi:sparsehash_config", + ], +) diff --git a/bazel/thrift.BUILD b/bazel/thrift.BUILD new file mode 100644 index 00000000..f3d12eb8 --- /dev/null +++ b/bazel/thrift.BUILD @@ -0,0 +1,75 @@ +# Copyright 2023 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# copied from https://github.com/tensorflow/io/blob/v0.25.0/third_party/thrift.BUILD +# Description: +# Apache Thrift library + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # Apache 2.0 + +exports_files(["LICENSE"]) + +cc_library( + name = "thrift", + srcs = glob([ + "lib/cpp/src/thrift/**/*.h", + ]) + [ + "lib/cpp/src/thrift/protocol/TProtocol.cpp", + "lib/cpp/src/thrift/transport/TBufferTransports.cpp", + "lib/cpp/src/thrift/transport/TTransportException.cpp", + ], + hdrs = [ + "compiler/cpp/src/thrift/version.h", + "lib/cpp/src/thrift/config.h", + ], + includes = [ + "lib/cpp/src", + ], + textual_hdrs = [ + "lib/cpp/src/thrift/protocol/TBinaryProtocol.tcc", + "lib/cpp/src/thrift/protocol/TCompactProtocol.tcc", + ], + deps = [ + "@boost//:units", + ], +) + +genrule( + name = "version_h", + srcs = [ + "compiler/cpp/src/thrift/version.h.in", + ], + outs = [ + "compiler/cpp/src/thrift/version.h", + ], + cmd = "sed 's/@PACKAGE_VERSION@/0.12.0/g' $< > $@", +) + +genrule( + name = "config_h", + srcs = ["build/cmake/config.h.in"], + outs = ["lib/cpp/src/thrift/config.h"], + cmd = ("sed " + + "-e 's/cmakedefine/define/g' " + + "-e 's/$${PACKAGE}/thrift/g' " + + "-e 's/$${PACKAGE_BUGREPORT}//g' " + + "-e 's/$${PACKAGE_NAME}/thrift/g' " + + "-e 's/$${PACKAGE_TARNAME}/thrift/g' " + + "-e 's/$${PACKAGE_URL}//g' " + + "-e 's/$${PACKAGE_VERSION}/0.12.0/g' " + + "-e 's/$${PACKAGE_STRING}/thrift 0.12.0/g' " + + "$< >$@"), +) diff --git a/bazel/xsimd.BUILD b/bazel/xsimd.BUILD new file mode 100644 index 00000000..4c3361c8 --- /dev/null +++ b/bazel/xsimd.BUILD @@ -0,0 +1,47 @@ +# Copyright 2023 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# copied from https://github.com/tensorflow/io/blob/v0.25.0/third_party/xsimd.BUILD + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # BSD 3-Clause + +exports_files(["LICENSE"]) + +cc_library( + name = "xsimd", + srcs = [], + hdrs = glob( + [ + "include/xsimd/*.hpp", + "include/xsimd/config/*.hpp", + "include/xsimd/math/*.hpp", + "include/xsimd/memory/*.hpp", + "include/xsimd/stl/*.hpp", + "include/xsimd/types/*.hpp", + ], + exclude = [ + ], + ), + copts = [], + defines = [], + includes = [ + "include", + ], + linkopts = [], + visibility = ["//visibility:public"], + deps = [ + ], +) diff --git a/bazel/zstd.BUILD b/bazel/zstd.BUILD new file mode 100644 index 00000000..769b9af2 --- /dev/null +++ b/bazel/zstd.BUILD @@ -0,0 +1,38 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@psi//bazel:psi.bzl", "psi_cmake_external") + +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "all", + srcs = glob(["**"]), +) + +psi_cmake_external( + name = "zstd", + cache_entries = { + "ZSTD_BUILD_PROGRAMS": "OFF", + "ZSTD_BUILD_SHARED": "OFF", + "ZLIB_BUILD_STATIC": "ON", + "ZSTD_BUILD_TESTS": "OFF", + "ZSTD_MULTITHREAD_SUPPORT": "OFF", + "CMAKE_INSTALL_LIBDIR": "lib", + }, + lib_source = "@com_github_facebook_zstd//:all", + out_include_dir = "include/", + out_static_libs = ["libzstd.a"], + working_directory = "build/cmake", +) diff --git a/docker/.gitignore b/docker/.gitignore new file mode 100644 index 00000000..eb249455 --- /dev/null +++ b/docker/.gitignore @@ -0,0 +1,2 @@ +# psi binary +main diff --git a/docker/.nsjail/README.md b/docker/.nsjail/README.md new file mode 100644 index 00000000..7b5620e2 --- /dev/null +++ b/docker/.nsjail/README.md @@ -0,0 +1 @@ +**NOTE: this folder is still experimental.** diff --git a/docker/.nsjail/nsjail.cfg b/docker/.nsjail/nsjail.cfg new file mode 100644 index 00000000..ffa8e7ce --- /dev/null +++ b/docker/.nsjail/nsjail.cfg @@ -0,0 +1,130 @@ +cwd: "/home/kuscia" +log_file: "/root/nsjail.log" +log_level: INFO +mount: { + src: "/bin" + dst: "/bin" + is_bind: true + rw: false + is_dir: true +} +mount: { + src: "/usr/bin" + dst: "/usr/bin" + is_bind: true + rw: false + is_dir: true +} +mount: { + src: "/lib64" + dst: "/lib64" + is_bind: true + rw: false + is_dir: true +} +mount: { + src: "/usr/lib64" + dst: "/usr/lib64" + is_bind: true + rw: false + is_dir: true +} +mount: { + src: "/usr/local/bin" + dst: "/usr/local/bin" + is_bind: true + rw: false + is_dir: true +} +mount: { + src: "/usr/local/lib" + dst: "/usr/local/lib" + is_bind: true + rw: false + is_dir: true +} +mount: { + src: "/usr/share/zoneinfo" + dst: "/usr/share/zoneinfo" + is_bind: true + rw: false + is_dir: true +} +mount: { + src: "/dev" + dst: "/dev" + is_bind: true + rw: false + is_dir: true +} +mount: { + src: "/lib" + dst: "/lib" + is_bind: true + rw: false + is_dir: true +} +mount: { + src: "/etc/ld.so.cache" + dst: "/etc/ld.so.cache" + is_bind: true + rw: false + is_dir: false +} +mount: { + src: "/etc/resolv.conf" + dst: "/etc/resolv.conf" + is_bind: true + rw: false + is_dir: false +} +mount: { + src: "/sys" + dst: "/sys" + is_bind: true + rw: false + is_dir: true +} +mount: { + src: "/etc/localtime" + dst: "/etc/localtime" + is_bind: true + rw: false + is_dir: false +} +mount: { + src: "/tmp" + dst: "/tmp" + is_bind: true + rw: true + is_dir: true +} +mount: { + src: "/root/sandbox/work" + dst: "/home/kuscia" + is_bind: true + rw: true + is_dir: true +} +mount: { + src: "/home/kuscia/var/storage" + dst: "/home/kuscia/var/storage" + is_bind: true + rw: true + is_dir: true +} +mount: { + src: "/etc/kuscia/certs" + dst: "/etc/kuscia/certs" + is_bind: true + rw: false + is_dir: true +} +mount: { + src: "/etc/kuscia/task-config.conf" + dst: "/etc/kuscia/task-config.conf" + is_bind: true + rw: false + is_dir: false +} +seccomp_policy_file: ".nsjail/nsjail_seccomp.policy" \ No newline at end of file diff --git a/docker/.nsjail/nsjail_seccomp.policy b/docker/.nsjail/nsjail_seccomp.policy new file mode 100644 index 00000000..d928e8db --- /dev/null +++ b/docker/.nsjail/nsjail_seccomp.policy @@ -0,0 +1,12 @@ +POLICY rules { + KILL{ +acct, add_key, adjtimex, bpf, clock_adjtime, clock_settime, create_module, delete_module, finit_module, get_kernel_syms, +get_mempolicy, init_module, ioperm, iopl, kcmp, kexec_file_load, kexec_load, keyctl, lookup_dcookie, mbind, mount, +move_pages, name_to_handle_at, nfsservctl, open_by_handle_at, perf_event_open, personality, pivot_root, +process_vm_readv, process_vm_writev, ptrace, query_module, quotactl, reboot, request_key, set_mempolicy, setns, +settimeofday, symlinkat, swapon, swapoff, sysfs, sysctl, umount, unshare, uselib, userfaultfd, ustat, +socket(domain, type, proto){(domain!=1)&&(domain!=2)&&(domain!=10)&&(domain!=16)} + } +} + +USE rules DEFAULT ALLOW diff --git a/docker/.nsjail/run.sh b/docker/.nsjail/run.sh new file mode 100644 index 00000000..465538dd --- /dev/null +++ b/docker/.nsjail/run.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +mkdir -p sandbox/rootfs sandbox/work + +nsjail --config .nsjail/nsjail.cfg --chroot /root/sandbox/rootfs -Mo --rlimit_fsize max --hostname APP \ + --disable_no_new_privs --rlimit_nofile max --disable_clone_newuser --disable_clone_newnet --skip_setsid \ + --rlimit_as max --rlimit_nproc max --pass_fd 256 --keep_env --keep_cap --proc_path /proc \ + -- /root/main --kuscia /etc/kuscia/task-config.conf \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..7e54b5d7 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,38 @@ +FROM secretflow/base-ci:latest as builder + +# build nsjail +RUN yum install -y \ + wget autoconf bison flex git protobuf-devel libnl3-devel \ + libtool make pkg-config protobuf-compiler \ + && yum clean all + +RUN cd / && git clone https://github.com/google/nsjail.git \ + && cd /nsjail && git checkout 3.3 -b v3.3 \ + && make && mv /nsjail/nsjail /bin + +FROM openanolis/anolisos:8.8 + +ENV TZ="Asia/Shanghai" + +RUN yum install -y libgomp && yum clean all + +COPY --from=builder /bin/nsjail /usr/local/bin/ +COPY .nsjail /root/.nsjail + +COPY --chown=root:root main /root/main + +LABEL maintainer="secretflow-contact@service.alipay.com" + +ARG version +ENV version $version + +ARG config_templates="" +LABEL kuscia.secretflow.config-templates=$config_templates + +ARG deploy_templates="" +LABEL kuscia.secretflow.deploy-templates=$deploy_templates + +# run as root for now +WORKDIR /root + +CMD ["/bin/bash"] diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000..0186cff1 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,13 @@ +# build psi binary with release-ci docker + +```bash +docker run -it --rm --mount type=bind,source="$(pwd)/../../psi",target=/home/admin/dev/src -w /home/admin/dev --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --cap-add=NET_ADMIN --privileged=true secretflow/release-ci:1.2 /home/admin/dev/src/docker/entry.sh +``` + +# build psi dev docker + +```bash +sh build.sh -v <version> -u -l +``` +- *-u* means upload docker to reg. +- *-l* means tag docker as *latest* as well. diff --git a/docker/build.sh b/docker/build.sh new file mode 100644 index 00000000..cbd5aedd --- /dev/null +++ b/docker/build.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +set -e + +show_help() { + echo "Usage: bash build.sh [OPTION]... -v {the_version}" + echo " -v --version" + echo " the version to build with." + echo " -l --latest" + echo " tag this version as latest." + echo " -u --upload" + echo " upload to docker registry." +} + +source_file="../psi/version.h" +PSI_VERSION_MAJOR=$(grep "#define PSI_VERSION_MAJOR" $source_file | cut -d' ' -f3) +PSI_VERSION_MINOR=$(grep "#define PSI_VERSION_MINOR" $source_file | cut -d' ' -f3) +PSI_VERSION_PATCH=$(grep "#define PSI_VERSION_PATCH" $source_file | cut -d' ' -f3) +PSI_DEV_IDENTIFIER=$(grep "#define PSI_DEV_IDENTIFIER" $source_file | cut -d' ' -f3 | sed 's/"//g') + +PSI_VERSION="${PSI_VERSION_MAJOR}.${PSI_VERSION_MINOR}.${PSI_VERSION_PATCH}${PSI_DEV_IDENTIFIER}" +echo "Current PSI version is $PSI_VERSION." + + +while [[ "$#" -ge 1 ]]; do + case $1 in + -v|--version) + VERSION="$2" + shift + if [[ "$#" -eq 0 ]]; then + show_help + exit 1 + fi + shift + ;; + -l|--latest) + LATEST=1 + shift + ;; + -u|--upload) + UPLOAD=1 + shift + ;; + *) + echo "Unknown argument passed: $1" + exit 1 + ;; + esac +done + +if [[ -z ${VERSION} ]]; then + echo "Verison is not provided. Use PSI_VERSION." + VERSION=$PSI_VERSION +fi + +GREEN="\033[32m" +NO_COLOR="\033[0m" + +DOCKER_REG="secretflow" + +IMAGE_TAG=${DOCKER_REG}/secretflow-psi-anolis8:${VERSION} +LATEST_TAG=${DOCKER_REG}/secretflow-psi-anolis8:latest + +echo -e "Build psi binary ${GREEN}PSI ${PSI_VERSION}${NO_COLOR}..." +docker run -it --rm --mount type=bind,source="$(pwd)/../../psi",target=/home/admin/dev/src -w /home/admin/dev --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --cap-add=NET_ADMIN --privileged=true secretflow/release-ci:1.2 /home/admin/dev/src/docker/entry.sh +echo -e "Finish building psi binary ${GREEN}${IMAGE_LITE_TAG}${NO_COLOR}" + +echo -e "Building docker image ${GREEN}${IMAGE_TAG}${NO_COLOR}..." +docker build . -f Dockerfile -t ${IMAGE_TAG} --build-arg version=${VERSION} --build-arg config_templates="$(cat config_templates.yml)" --build-arg deploy_templates="$(cat deploy_templates.yml)" +echo -e "Finish building docker image ${GREEN}${IMAGE_LITE_TAG}${NO_COLOR}" + +if [[ UPLOAD -eq 1 ]]; then + docker push ${IMAGE_TAG} +fi + + +if [[ LATEST -eq 1 ]]; then + echo -e "Tag and push ${GREEN}${LATEST_TAG}${NO_COLOR} ..." + docker tag ${IMAGE_TAG} ${LATEST_TAG} + if [[ UPLOAD -eq 1 ]]; then + docker push ${LATEST_TAG} + fi +fi diff --git a/docker/config_templates.yml b/docker/config_templates.yml new file mode 100644 index 00000000..ce208448 --- /dev/null +++ b/docker/config_templates.yml @@ -0,0 +1,7 @@ +task-config.conf: | + { + "task_id": "{{.TASK_ID}}", + "task_input_config": "{{.TASK_INPUT_CONFIG}}", + "task_cluster_def": "{{.TASK_CLUSTER_DEFINE}}", + "allocated_ports": "{{.ALLOCATED_PORTS}}" + } diff --git a/docker/deploy_templates.yml b/docker/deploy_templates.yml new file mode 100644 index 00000000..d6038728 --- /dev/null +++ b/docker/deploy_templates.yml @@ -0,0 +1,20 @@ +- name: psi + replicas: 1 + spec: + containers: + - command: + - sh + args: + - -c + - "/root/main --kuscia /etc/kuscia/task-config.conf" + configVolumeMounts: + - mountPath: /etc/kuscia/task-config.conf + subPath: task-config.conf + name: psi + ports: + - name: psi + port: 54509 + protocol: GRPC + scope: Cluster + workingDir: /work + restartPolicy: Never diff --git a/docker/entry.sh b/docker/entry.sh new file mode 100644 index 00000000..14583fae --- /dev/null +++ b/docker/entry.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -ex + +cp -r src src_copied +cd src_copied + + + + +bazel build psi/psi:main -c opt --config=linux-release --repository_cache=/tmp/bazel_repo_cache +chmod 777 bazel-bin/psi/psi/main +cp bazel-bin/psi/psi/main ../src/docker/ diff --git a/psi/BUILD.bazel b/psi/BUILD.bazel new file mode 100644 index 00000000..6526de97 --- /dev/null +++ b/psi/BUILD.bazel @@ -0,0 +1,22 @@ +# Copyright 2023 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//bazel:psi.bzl", "psi_cc_library") + +package(default_visibility = ["//visibility:public"]) + +psi_cc_library( + name = "version", + hdrs = ["version.h"], +) diff --git a/psi/pir/BUILD.bazel b/psi/pir/BUILD.bazel new file mode 100644 index 00000000..a6bfdf25 --- /dev/null +++ b/psi/pir/BUILD.bazel @@ -0,0 +1,118 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//bazel:psi.bzl", "psi_cc_library", "psi_cc_test") + +package(default_visibility = ["//visibility:public"]) + +psi_cc_library( + name = "seal_pir", + srcs = ["seal_pir.cc"], + hdrs = ["seal_pir.h"], + linkopts = [ + "-ldl", + "-lm", + ], + deps = [ + ":seal_pir_utils", + ":serializable_cc_proto", + "@com_github_microsoft_seal//:seal", + "@com_github_openssl_openssl//:openssl", + "@yacl//yacl/base:byte_container_view", + "@yacl//yacl/base:exception", + "@yacl//yacl/link", + "@yacl//yacl/utils:parallel", + ], +) + +psi_cc_library( + name = "seal_pir_utils", + srcs = ["seal_pir_utils.cc"], + hdrs = ["seal_pir_utils.h"], + deps = [ + "@com_github_microsoft_seal//:seal", + "@yacl//yacl/base:exception", + ], +) + +psi_cc_library( + name = "seal_mpir", + srcs = ["seal_mpir.cc"], + hdrs = ["seal_mpir.h"], + deps = [ + ":seal_pir", + "//psi/psi/core:cuckoo_index", + "@yacl//yacl/crypto/base:symmetric_crypto", + ], +) + +proto_library( + name = "serializable_proto", + srcs = ["serializable.proto"], +) + +cc_proto_library( + name = "serializable_cc_proto", + deps = [":serializable_proto"], +) + +psi_cc_test( + name = "seal_pir_test", + srcs = ["seal_pir_test.cc"], + deps = [ + ":seal_pir", + ], +) + +psi_cc_test( + name = "seal_mpir_test", + srcs = ["seal_mpir_test.cc"], + deps = [ + ":seal_mpir", + "//psi/psi/cryptor:sodium_curve25519_cryptor", + "@com_google_absl//absl/strings", + ], +) + +proto_library( + name = "pir_proto", + srcs = ["pir.proto"], +) + +cc_proto_library( + name = "pir_cc_proto", + deps = [":pir_proto"], +) + +psi_cc_library( + name = "pir", + srcs = ["pir.cc"], + hdrs = ["pir.h"], + deps = [ + ":pir_cc_proto", + "//psi/psi/core:cuckoo_index", + "//psi/psi/core/labeled_psi", + "@yacl//yacl/crypto/base:symmetric_crypto", + ], +) + +psi_cc_test( + name = "pir_test", + srcs = ["pir_test.cc"], + deps = [ + ":pir", + "//psi/psi/io", + "@yacl//yacl/utils:scope_guard", + ], +) diff --git a/psi/pir/pir.cc b/psi/pir/pir.cc new file mode 100644 index 00000000..bb5999e4 --- /dev/null +++ b/psi/pir/pir.cc @@ -0,0 +1,741 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/pir/pir.h" + +#include <algorithm> +#include <filesystem> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/crypto/utils/rand.h" +#include "yacl/io/kv/leveldb_kvstore.h" +#include "yacl/io/rw/csv_writer.h" + +#include "psi/psi/core/ecdh_oprf/ecdh_oprf_selector.h" +#include "psi/psi/core/labeled_psi/receiver.h" +#include "psi/psi/core/labeled_psi/sender.h" +#include "psi/psi/core/labeled_psi/sender_db.h" +#include "psi/psi/core/labeled_psi/sender_kvdb.h" +#include "psi/psi/core/labeled_psi/sender_memdb.h" +#include "psi/psi/cryptor/ecc_cryptor.h" +#include "psi/psi/io/io.h" +#include "psi/psi/utils/batch_provider.h" +#include "psi/psi/utils/serialize.h" +#include "psi/psi/utils/utils.h" +namespace psi::pir { + +namespace { + +std::vector<uint8_t> ReadEcSecretKeyFile(const std::string &file_path) { + size_t file_byte_size = 0; + try { + file_byte_size = std::filesystem::file_size(file_path); + } catch (std::filesystem::filesystem_error &e) { + YACL_THROW("ReadEcSecretKeyFile {} Error: {}", file_path, e.what()); + } + YACL_ENFORCE(file_byte_size == ::psi::psi::kEccKeySize, + "error format: key file bytes is not {}", + ::psi::psi::kEccKeySize); + + std::vector<uint8_t> secret_key(::psi::psi::kEccKeySize); + + auto in = ::psi::psi::io::BuildInputStream( + ::psi::psi::io::FileIoOptions(file_path)); + in->Read(secret_key.data(), ::psi::psi::kEccKeySize); + in->Close(); + + return secret_key; +} + +size_t CsvFileDataCount(const std::string &file_path, + const std::vector<std::string> &ids) { + size_t data_count = 0; + + std::shared_ptr<::psi::psi::IBasicBatchProvider> batch_provider = + std::make_shared<::psi::psi::CsvBatchProvider>(file_path, ids, 4096); + + while (true) { + auto batch = batch_provider->ReadNextBatch(); + if (batch.empty()) { + break; + } + data_count += batch.size(); + } + + return data_count; +} + +constexpr char kMetaInfoStoreName[] = "pir_meta_info"; +constexpr char kServerDataCount[] = "server_data_count"; +constexpr char kCountPerQuery[] = "count_per_query"; +constexpr char kLabelByteCount[] = "label_byte_count"; +constexpr char kLabelColumns[] = "label_columns"; +constexpr char kPsiParams[] = "psi_params"; +constexpr char kBucketCount[] = "bucket_count"; +constexpr char kBucketSize[] = "bucket_size"; +constexpr char kCompressed[] = "compressed"; + +constexpr size_t kNonceByteCount = 16; + +void WriteMetaInfo(const std::string &setup_path, size_t server_data_count, + size_t count_per_query, size_t label_byte_count, + const std::vector<std::string> &label_cloumns, + const apsi::PSIParams &psi_params, size_t bucket_count, + size_t bucket_size = 1000000, bool compressed = false) { + std::string meta_store_name = + fmt::format("{}/{}", setup_path, kMetaInfoStoreName); + + std::shared_ptr<yacl::io::KVStore> meta_info_store = + std::make_shared<yacl::io::LeveldbKVStore>(false, meta_store_name); + + meta_info_store->Put(kServerDataCount, fmt::format("{}", server_data_count)); + meta_info_store->Put(kCountPerQuery, fmt::format("{}", count_per_query)); + meta_info_store->Put(kLabelByteCount, fmt::format("{}", label_byte_count)); + meta_info_store->Put(kBucketSize, fmt::format("{}", bucket_size)); + meta_info_store->Put(kCompressed, fmt::format("{}", compressed ? 1 : 0)); + + ::psi::psi::proto::StrItemsProto proto; + for (const auto &label_cloumn : label_cloumns) { + proto.add_items(label_cloumn); + } + yacl::Buffer buf(proto.ByteSizeLong()); + proto.SerializeToArray(buf.data(), buf.size()); + + meta_info_store->Put(kLabelColumns, buf); + + yacl::Buffer params_buffer = ::psi::psi::PsiParamsToBuffer(psi_params); + meta_info_store->Put(kPsiParams, params_buffer); + + meta_info_store->Put(kBucketCount, fmt::format("{}", bucket_count)); +} + +size_t GetSizeFromStore( + const std::shared_ptr<yacl::io::KVStore> &meta_info_store, + const std::string &key_name) { + yacl::Buffer temp_value; + meta_info_store->Get(key_name, &temp_value); + size_t key_value = std::stoul(std::string(std::string_view( + reinterpret_cast<char *>(temp_value.data()), temp_value.size()))); + + return key_value; +} + +apsi::PSIParams ReadMetaInfo(const std::string &setup_path, + size_t *server_data_count, size_t *count_per_query, + size_t *label_byte_count, + std::vector<std::string> *label_cloumns, + size_t *bucket_count, size_t *bucket_size, + bool *compressed) { + std::string meta_store_name = + fmt::format("{}/{}", setup_path, kMetaInfoStoreName); + std::shared_ptr<yacl::io::KVStore> meta_info_store = + std::make_shared<yacl::io::LeveldbKVStore>(false, meta_store_name); + + *server_data_count = GetSizeFromStore(meta_info_store, kServerDataCount); + *count_per_query = GetSizeFromStore(meta_info_store, kCountPerQuery); + *label_byte_count = GetSizeFromStore(meta_info_store, kLabelByteCount); + + yacl::Buffer label_columns_buf; + meta_info_store->Get(kLabelColumns, &label_columns_buf); + ::psi::psi::proto::StrItemsProto proto; + proto.ParseFromArray(label_columns_buf.data(), label_columns_buf.size()); + (*label_cloumns).reserve(proto.items_size()); + for (auto item : proto.items()) { + (*label_cloumns).emplace_back(item); + } + + yacl::Buffer params_buffer; + meta_info_store->Get(kPsiParams, &params_buffer); + apsi::PSIParams psi_params = ::psi::psi::ParsePsiParamsProto(params_buffer); + + *bucket_count = GetSizeFromStore(meta_info_store, kBucketCount); + *bucket_size = GetSizeFromStore(meta_info_store, kBucketSize); + size_t compress_flag = GetSizeFromStore(meta_info_store, kCompressed); + + if (compress_flag == 1) { + *compressed = true; + } else if (compress_flag == 0) { + *compressed = false; + } else { + YACL_THROW("error compress value:{}", compress_flag); + } + + return psi_params; +} + +} // namespace + +PirResultReport LabeledPirSetup(const PirSetupConfig &config) { + std::vector<std::string> key_columns; + key_columns.insert(key_columns.end(), config.key_columns().begin(), + config.key_columns().end()); + + std::vector<std::string> label_columns; + label_columns.insert(label_columns.end(), config.label_columns().begin(), + config.label_columns().end()); + + size_t server_data_count = CsvFileDataCount(config.input_path(), key_columns); + size_t count_per_query = config.num_per_query(); + + size_t bucket_size = config.bucket_size(); + + size_t bucket_count = (server_data_count + bucket_size - 1) / bucket_size; + + std::vector<uint8_t> oprf_key = ReadEcSecretKeyFile(config.oprf_key_path()); + + size_t label_byte_count = config.label_max_len(); + size_t nonce_byte_count = kNonceByteCount; + + std::string kv_store_path = config.setup_path(); + // delete store path + { + std::error_code ec; + std::filesystem::remove_all(kv_store_path, ec); + if (ec.value() != 0) { + SPDLOG_WARN("can not remove tmp dir: {}, msg: {}", kv_store_path, + ec.message()); + } + } + std::filesystem::create_directory(kv_store_path); + + apsi::PSIParams psi_params = ::psi::psi::GetPsiParams( + count_per_query, bucket_size, config.max_items_per_bin()); + + SPDLOG_INFO("table_params hash_func_count:{}", + psi_params.table_params().hash_func_count); + SPDLOG_INFO("table_params max_items_per_bin:{}", + psi_params.table_params().max_items_per_bin); + + SPDLOG_INFO("seal_params poly_modulus_degree:{}", + psi_params.seal_params().poly_modulus_degree()); + SPDLOG_INFO("query_params query_powers size:{}", + psi_params.query_params().query_powers.size()); + + WriteMetaInfo(kv_store_path, server_data_count, count_per_query, + label_byte_count, label_columns, psi_params, bucket_count, + config.bucket_size(), config.compressed()); + + std::shared_ptr<::psi::psi::ILabeledBatchProvider> batch_provider = + std::make_shared<::psi::psi::CsvBatchProvider>( + config.input_path(), key_columns, bucket_size, label_columns); + for (size_t i = 0; i < bucket_count; i++) { + std::string bucket_setup_path = + fmt::format("{}/bucket_{}", kv_store_path, i); + + SPDLOG_INFO("bucket:{} bucket_setup_path:{}", i, bucket_setup_path); + + std::vector<std::string> batch_ids; + std::vector<std::string> batch_labels; + std::tie(batch_ids, batch_labels) = batch_provider->ReadNextLabeledBatch(); + + if (batch_ids.empty()) { + SPDLOG_INFO("Finish read data"); + break; + } + + std::filesystem::create_directory(bucket_setup_path); + + apsi::PSIParams bucket_psi_params = ::psi::psi::GetPsiParams( + count_per_query, std::min<size_t>(bucket_size, batch_ids.size()), + config.max_items_per_bin()); + + std::shared_ptr<::psi::psi::ISenderDB> sender_db = + std::make_shared<::psi::psi::SenderKvDB>( + bucket_psi_params, oprf_key, bucket_setup_path, label_byte_count, + nonce_byte_count, config.compressed()); + + std::shared_ptr<::psi::psi::IBatchProvider> bucket_batch_provider = + std::make_shared<::psi::psi::MemoryBatchProvider>( + batch_ids, bucket_size, batch_labels); + + sender_db->SetData(bucket_batch_provider); + SPDLOG_INFO("finish bucket:{}", i); + } + + PirResultReport report; + report.set_data_count(server_data_count); + + return report; +} + +PirResultReport LabeledPirServer( + const std::shared_ptr<yacl::link::Context> &link_ctx, + const std::shared_ptr<::psi::psi::ISenderDB> &sender_db, + const std::vector<uint8_t> &oprf_key, const apsi::PSIParams &psi_params, + const std::vector<std::string> &label_columns, size_t bucket_count, + size_t /*server_data_count*/, size_t count_per_query, + size_t /*label_byte_count*/, uint32_t /*bucket_size*/) { + // send count_per_query + link_ctx->SendAsync(link_ctx->NextRank(), + ::psi::psi::utils::SerializeSize(count_per_query), + fmt::format("count_per_query:{}", count_per_query)); + + yacl::Buffer labels_buffer = + ::psi::psi::utils::SerializeStrItems(label_columns); + // send labels column name + link_ctx->SendAsync(link_ctx->NextRank(), labels_buffer, + fmt::format("send label columns name")); + + // send psi params + yacl::Buffer params_buffer = ::psi::psi::PsiParamsToBuffer(psi_params); + link_ctx->SendAsync(link_ctx->NextRank(), params_buffer, + fmt::format("send psi params")); + + // bucket_count + link_ctx->SendAsync(link_ctx->NextRank(), + ::psi::psi::utils::SerializeSize(bucket_count), + fmt::format("bucket_count:{}", bucket_count)); + + // const auto total_query_start = std::chrono::system_clock::now(); + + size_t query_count = 0; + size_t data_count = 0; + + ::psi::psi::LabelPsiSender sender(sender_db); + + while (true) { + // recv current batch_size + size_t batch_data_size = ::psi::psi::utils::DeserializeSize( + link_ctx->Recv(link_ctx->NextRank(), fmt::format("batch_data_size"))); + + SPDLOG_INFO("client data size: {}", batch_data_size); + if (batch_data_size == 0) { + break; + } + data_count += batch_data_size; + + // oprf + std::unique_ptr<::psi::psi::IEcdhOprfServer> oprf_server = + ::psi::psi::CreateEcdhOprfServer(oprf_key, ::psi::psi::OprfType::Basic, + ::psi::psi::CurveType::CURVE_FOURQ); + + // const auto oprf_start = std::chrono::system_clock::now(); + sender.RunOPRF(std::move(oprf_server), link_ctx); + + // const auto oprf_end = std::chrono::system_clock::now(); + // const DurationMillis oprf_duration = oprf_end - oprf_start; + // SPDLOG_INFO("*** server oprf duration:{}", oprf_duration.count()); + + // const auto query_start = std::chrono::system_clock::now(); + + sender.RunQuery(link_ctx); + + // const auto query_end = std::chrono::system_clock::now(); + // const DurationMillis query_duration = query_end - query_start; + // SPDLOG_INFO("*** server query duration:{}", query_duration.count()); + + query_count++; + } + SPDLOG_INFO("query_count:{},data_count:{}", query_count, data_count); + + PirResultReport report; + report.set_data_count(data_count); + + return report; +} + +PirResultReport LabeledPirServer( + const std::shared_ptr<yacl::link::Context> &link_ctx, + const PirServerConfig &config) { + size_t server_data_count; + size_t count_per_query; + size_t label_byte_count; + + std::vector<std::string> label_columns; + size_t bucket_count; + size_t bucket_size; + bool compressed; + + apsi::PSIParams psi_params = + ReadMetaInfo(config.setup_path(), &server_data_count, &count_per_query, + &label_byte_count, &label_columns, &bucket_count, + &bucket_size, &compressed); + + YACL_ENFORCE(label_columns.size() > 0); + + std::vector<uint8_t> oprf_key = ReadEcSecretKeyFile(config.oprf_key_path()); + + // server and client sync + auto run_f = std::async([&] { return 0; }); + ::psi::psi::SyncWait(link_ctx, &run_f); + + SPDLOG_INFO("table_params hash_func_count:{}", + psi_params.table_params().hash_func_count); + + size_t nonce_byte_count = kNonceByteCount; + + std::vector<std::shared_ptr<::psi::psi::ISenderDB>> sender_db(bucket_count); + std::vector<std::shared_ptr<::psi::psi::LabelPsiSender>> sender(bucket_count); + + std::unique_ptr<::psi::psi::IEcdhOprfServer> oprf_server = + ::psi::psi::CreateEcdhOprfServer(oprf_key, ::psi::psi::OprfType::Basic, + ::psi::psi::CurveType::CURVE_FOURQ); + + for (size_t bucket_idx = 0; bucket_idx < bucket_count; ++bucket_idx) { + std::string bucket_setup_path = + fmt::format("{}/bucket_{}", config.setup_path(), bucket_idx); + sender_db[bucket_idx] = std::make_shared<::psi::psi::SenderKvDB>( + psi_params, oprf_key, bucket_setup_path, label_byte_count, + nonce_byte_count, compressed); + + sender[bucket_idx] = + std::make_shared<::psi::psi::LabelPsiSender>(sender_db[bucket_idx]); + } + + SPDLOG_INFO("db GetItemCount:{}", sender_db[0]->GetItemCount()); + + // send count_per_query + link_ctx->SendAsync(link_ctx->NextRank(), + ::psi::psi::utils::SerializeSize(count_per_query), + fmt::format("count_per_query:{}", count_per_query)); + + yacl::Buffer labels_buffer = + ::psi::psi::utils::SerializeStrItems(label_columns); + // send labels column name + link_ctx->SendAsync(link_ctx->NextRank(), labels_buffer, + fmt::format("send label columns name")); + + // send psi params + yacl::Buffer params_buffer = ::psi::psi::PsiParamsToBuffer(psi_params); + link_ctx->SendAsync(link_ctx->NextRank(), params_buffer, + fmt::format("send psi params")); + + // send bucket_count + link_ctx->SendAsync(link_ctx->NextRank(), + ::psi::psi::utils::SerializeSize(bucket_count), + fmt::format("bucket_count:{}", bucket_count)); + + // const auto total_query_start = std::chrono::system_clock::now(); + + size_t query_count = 0; + size_t data_count = 0; + + while (true) { + // recv current batch_size + size_t batch_data_size = ::psi::psi::utils::DeserializeSize( + link_ctx->Recv(link_ctx->NextRank(), fmt::format("batch_data_size"))); + + SPDLOG_INFO("client data size: {}", batch_data_size); + if (batch_data_size == 0) { + break; + } + data_count += batch_data_size; + + // oprf + std::unique_ptr<::psi::psi::IEcdhOprfServer> oprf_server = + ::psi::psi::CreateEcdhOprfServer(oprf_key, ::psi::psi::OprfType::Basic, + ::psi::psi::CurveType::CURVE_FOURQ); + + // const auto oprf_start = std::chrono::system_clock::now(); + sender[0]->RunOPRF(std::move(oprf_server), link_ctx); + + for (size_t bucket_idx = 0; bucket_idx < bucket_count; ++bucket_idx) { + // const auto oprf_end = std::chrono::system_clock::now(); + // const DurationMillis oprf_duration = oprf_end - oprf_start; + // SPDLOG_INFO("*** server oprf duration:{}", oprf_duration.count()); + + // const auto query_start = std::chrono::system_clock::now(); + + sender[bucket_idx]->RunQuery(link_ctx); + + // const auto query_end = std::chrono::system_clock::now(); + // const DurationMillis query_duration = query_end - query_start; + // SPDLOG_INFO("*** server query duration:{}", query_duration.count()); + } + + query_count++; + } + SPDLOG_INFO("query_count:{},data_count:{}", query_count, data_count); + + PirResultReport report; + report.set_data_count(data_count); + + return report; +} + +PirResultReport LabeledPirMemoryServer( + const std::shared_ptr<yacl::link::Context> &link_ctx, + const PirSetupConfig &config) { + std::vector<std::string> key_columns; + key_columns.insert(key_columns.end(), config.key_columns().begin(), + config.key_columns().end()); + + std::vector<std::string> label_columns; + label_columns.insert(label_columns.end(), config.label_columns().begin(), + config.label_columns().end()); + + size_t server_data_count = CsvFileDataCount(config.input_path(), key_columns); + size_t count_per_query = config.num_per_query(); + SPDLOG_INFO("server_data_count:{}", server_data_count); + + YACL_ENFORCE(server_data_count <= config.bucket_size(), + "data_count:{} bucket_size:{}", config.bucket_size()); + + apsi::PSIParams psi_params = ::psi::psi::GetPsiParams( + count_per_query, server_data_count, config.max_items_per_bin()); + + std::vector<uint8_t> oprf_key = + yacl::crypto::RandBytes(::psi::psi::kEccKeySize); + + size_t label_byte_count = config.label_max_len(); + size_t nonce_byte_count = kNonceByteCount; + + std::shared_ptr<::psi::psi::ISenderDB> sender_db = + std::make_shared<::psi::psi::SenderMemDB>( + psi_params, oprf_key, label_byte_count, nonce_byte_count, + config.compressed()); + + // server and client sync + auto run_f = std::async([&] { + std::shared_ptr<::psi::psi::IBatchProvider> batch_provider = + std::make_shared<::psi::psi::CsvBatchProvider>( + config.input_path(), key_columns, 500000, label_columns); + + sender_db->SetData(batch_provider); + + return 0; + }); + ::psi::psi::SyncWait(link_ctx, &run_f); + + SPDLOG_INFO("sender_db->GetItemCount:{}", sender_db->GetItemCount()); + + size_t bucket_count = 1; + + PirResultReport report = + LabeledPirServer(link_ctx, sender_db, oprf_key, psi_params, label_columns, + bucket_count, sender_db->GetItemCount(), count_per_query, + label_byte_count, config.bucket_size()); + + return report; +} + +PirResultReport LabeledPirClient( + const std::shared_ptr<yacl::link::Context> &link_ctx, + const PirClientConfig &config) { + std::vector<std::string> key_columns; + key_columns.insert(key_columns.end(), config.key_columns().begin(), + config.key_columns().end()); + + // server and client sync + auto run_f = std::async([&] { return 0; }); + ::psi::psi::SyncWait(link_ctx, &run_f); + + // recv count_per_query + size_t count_per_query = ::psi::psi::utils::DeserializeSize( + link_ctx->Recv(link_ctx->NextRank(), fmt::format("count_per_query"))); + + YACL_ENFORCE(count_per_query > 0, "Invalid nr:{}", count_per_query); + + // recv label columns + yacl::Buffer label_columns_buffer = link_ctx->Recv( + link_ctx->NextRank(), fmt::format("recv label columns name")); + + std::vector<std::string> label_columns_name; + ::psi::psi::utils::DeserializeStrItems(label_columns_buffer, + &label_columns_name); + + yacl::io::Schema s; + for (size_t i = 0; i < key_columns.size(); ++i) { + s.feature_types.push_back(yacl::io::Schema::STRING); + } + for (size_t i = 0; i < label_columns_name.size(); ++i) { + s.feature_types.push_back(yacl::io::Schema::STRING); + } + + s.feature_names = key_columns; + s.feature_names.insert(s.feature_names.end(), label_columns_name.begin(), + label_columns_name.end()); + + yacl::io::WriterOptions w_op; + w_op.file_schema = s; + + auto out = ::psi::psi::io::BuildOutputStream( + ::psi::psi::io::FileIoOptions(config.output_path())); + yacl::io::CsvWriter writer(w_op, std::move(out)); + writer.Init(); + + // recv psi params + yacl::Buffer params_buffer = + link_ctx->Recv(link_ctx->NextRank(), fmt::format("recv psi params")); + + apsi::PSIParams psi_params = ::psi::psi::ParsePsiParamsProto(params_buffer); + + SPDLOG_INFO("table_params hash_func_count:{}", + psi_params.table_params().hash_func_count); + SPDLOG_INFO("table_params max_items_per_bin:{}", + psi_params.table_params().max_items_per_bin); + + SPDLOG_INFO("seal_params poly_modulus_degree:{}", + psi_params.seal_params().poly_modulus_degree()); + SPDLOG_INFO("query_params query_powers size:{}", + psi_params.query_params().query_powers.size()); + + size_t bucket_count = ::psi::psi::utils::DeserializeSize( + link_ctx->Recv(link_ctx->NextRank(), fmt::format("bucket_count"))); + SPDLOG_INFO("bucket_count:{}", bucket_count); + + ::psi::psi::LabelPsiReceiver receiver(psi_params, true); + + // const auto total_query_start = std::chrono::system_clock::now(); + + size_t query_count = 0; + size_t data_count = 0; + size_t table_items = psi_params.table_params().table_size / 2; + if (psi_params.table_params().hash_func_count == 1) { + table_items = 1; + } + size_t query_result_count = 0; + size_t batch_read_count = std::max<size_t>(table_items, count_per_query); + SPDLOG_INFO("batch_read_count:{}", batch_read_count); + + std::shared_ptr<::psi::psi::IBasicBatchProvider> query_batch_provider = + std::make_shared<::psi::psi::CsvBatchProvider>( + config.input_path(), key_columns, batch_read_count); + + while (true) { + auto query_batch_items = query_batch_provider->ReadNextBatch(); + + // send count_batch_size + link_ctx->SendAsync( + link_ctx->NextRank(), + ::psi::psi::utils::SerializeSize(query_batch_items.size()), + fmt::format("count_batch_size:{}", query_batch_items.size())); + + if (query_batch_items.empty()) { + break; + } + data_count += query_batch_items.size(); + + // const auto oprf_start = std::chrono::system_clock::now(); + std::pair<std::vector<apsi::HashedItem>, std::vector<apsi::LabelKey>> + items_oprf = receiver.RequestOPRF(query_batch_items, link_ctx); + + // const auto oprf_end = std::chrono::system_clock::now(); + // const DurationMillis oprf_duration = oprf_end - oprf_start; + // SPDLOG_INFO("*** server oprf duration:{}", oprf_duration.count()); + + for (size_t bucket_idx = 0; bucket_idx < bucket_count; bucket_idx++) { + SPDLOG_INFO("bucket_idx: {}", bucket_idx); + + // const auto query_start = std::chrono::system_clock::now(); + std::pair<std::vector<size_t>, std::vector<std::string>> query_result = + receiver.RequestQuery(items_oprf.first, items_oprf.second, link_ctx); + + // const auto query_end = std::chrono::system_clock::now(); + // const DurationMillis query_duration = query_end - query_start; + // SPDLOG_INFO("*** server query duration:{}", query_duration.count()); + + SPDLOG_INFO("query_result size:{}", query_result.first.size()); + query_result_count += query_result.first.size(); + + yacl::io::ColumnVectorBatch batch; + + std::vector<std::vector<std::string>> query_id_results( + key_columns.size()); + std::vector<std::vector<std::string>> query_label_results( + label_columns_name.size()); + + for (size_t i = 0; i < query_result.first.size(); ++i) { + std::vector<std::string> result_ids = + absl::StrSplit(query_batch_items[query_result.first[i]], ","); + + YACL_ENFORCE(result_ids.size() == key_columns.size()); + + std::vector<std::string> result_labels = + absl::StrSplit(query_result.second[i], ","); + + if (result_labels.size() != label_columns_name.size()) { + SPDLOG_INFO("query_result: {}", query_result.second[i]); + } + + YACL_ENFORCE(result_labels.size() == label_columns_name.size(), + fmt::format("{}!={}", result_labels.size(), + label_columns_name.size())); + + for (size_t j = 0; j < result_ids.size(); ++j) { + query_id_results[j].push_back(result_ids[j]); + } + for (size_t j = 0; j < result_labels.size(); ++j) { + query_label_results[j].push_back(result_labels[j]); + } + } + + for (size_t i = 0; i < key_columns.size(); ++i) { + batch.AppendCol(query_id_results[i]); + } + for (size_t i = 0; i < label_columns_name.size(); ++i) { + batch.AppendCol(query_label_results[i]); + } + + writer.Add(batch); + } + + query_count++; + } + + writer.Close(); + SPDLOG_INFO("query_count:{}, data_count:{}, result_count:{}", query_count, + data_count, query_result_count); + + PirResultReport report; + report.set_data_count(data_count); + + return report; +} + +PirResultReport PirSetup(const PirSetupConfig &config) { + if (config.pir_protocol() != KEYWORD_PIR_LABELED_PSI) { + YACL_THROW("Unsupported pir protocol {}", + PirProtocol_Name(config.pir_protocol())); + } + + return LabeledPirSetup(config); +} + +PirResultReport PirServer(const std::shared_ptr<yacl::link::Context> &link_ctx, + const PirServerConfig &config) { + if (config.pir_protocol() != KEYWORD_PIR_LABELED_PSI) { + YACL_THROW("Unsupported pir protocol {}", + PirProtocol_Name(config.pir_protocol())); + } + + return LabeledPirServer(link_ctx, config); +} + +PirResultReport PirMemoryServer( + const std::shared_ptr<yacl::link::Context> &link_ctx, + const PirSetupConfig &config) { + if (config.pir_protocol() != KEYWORD_PIR_LABELED_PSI) { + YACL_THROW("Unsupported pir protocol {}", + PirProtocol_Name(config.pir_protocol())); + } + + return LabeledPirMemoryServer(link_ctx, config); +} + +PirResultReport PirClient(const std::shared_ptr<yacl::link::Context> &link_ctx, + const PirClientConfig &config) { + if (config.pir_protocol() != KEYWORD_PIR_LABELED_PSI) { + YACL_THROW("Unsupported pir protocol {}", + PirProtocol_Name(config.pir_protocol())); + } + + return LabeledPirClient(link_ctx, config); +} + +} // namespace psi::pir diff --git a/psi/pir/pir.h b/psi/pir/pir.h new file mode 100644 index 00000000..212ed008 --- /dev/null +++ b/psi/pir/pir.h @@ -0,0 +1,54 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <memory> + +#include "yacl/link/link.h" + +#include "psi/psi/core/labeled_psi/sender_db.h" + +#include "psi/pir/pir.pb.h" + +namespace psi::pir { + +PirResultReport PirSetup(const PirSetupConfig &config); + +PirResultReport PirServer(const std::shared_ptr<yacl::link::Context> &link_ctx, + const PirServerConfig &config); + +PirResultReport PirClient(const std::shared_ptr<yacl::link::Context> &link_ctx, + const PirClientConfig &config); + +PirResultReport PirMemoryServer( + const std::shared_ptr<yacl::link::Context> &link_ctx, + const PirSetupConfig &config); + +// pir protocol based on labeled psi +PirResultReport LabeledPirSetup(const PirSetupConfig &config); + +PirResultReport LabeledPirServer( + const std::shared_ptr<yacl::link::Context> &link_ctx, + const PirServerConfig &config); + +PirResultReport LabeledPirMemoryServer( + const std::shared_ptr<yacl::link::Context> &link_ctx, + const PirServerConfig &config); + +PirResultReport LabeledPirClient( + const std::shared_ptr<yacl::link::Context> &link_ctx, + const PirClientConfig &config); + +} // namespace psi::pir diff --git a/psi/pir/pir.proto b/psi/pir/pir.proto new file mode 100644 index 00000000..b4540170 --- /dev/null +++ b/psi/pir/pir.proto @@ -0,0 +1,116 @@ +// +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +package psi.pir; + +// The kv-store type of pir. +enum KvStoreType { + INVALID_STORE_TYPE = 0; + + // based on memory + MEMORY_KV_STORE = 1; + + // based on leveldb + LEVELDB_KV_STORE = 2; +} + +// The algorithm type of pir. +enum PirProtocol { + INVALID_PIR_TYPE = 0; + + // based on labeled PSI + KEYWORD_PIR_LABELED_PSI = 1; + + // based on SealPIR + INDEX_PIR_SEALPIR = 10; +} + +// The Cfg Params of setup phase. +message PirSetupConfig { + // The pir protocol. + PirProtocol pir_protocol = 1; + + // type of kv-store + KvStoreType store_type = 2; + + // The input csv file path of pir. + string input_path = 3; + + // The key columns name of input data. + repeated string key_columns = 4; + + // The label columns name of input data. + repeated string label_columns = 5; + + // The number of per query. + uint32 num_per_query = 6; + + // The max number bytes of label. + uint32 label_max_len = 7; + + // The path of oprf_key file path. + string oprf_key_path = 8; + + // The path of setup output leveldb path. + string setup_path = 9; + + // compressed Seal ciphertext + bool compressed = 10; + + // split data bucket to do pir query + uint32 bucket_size = 11; + + // max items per bin, i.e. Interpolate polynomial max degree + uint32 max_items_per_bin = 12; +} + +// The Server's Cfg Params of query phase. +message PirServerConfig { + // The pir algorithm. + PirProtocol pir_protocol = 1; + + // type of kv-store + KvStoreType store_type = 2; + + // The path of setup output leveldb path. + string setup_path = 3; + + // The path of oprf_key file path. + string oprf_key_path = 4; +} + +// The Client's Cfg Params of query phase. +message PirClientConfig { + // The pir algorithm. + PirProtocol pir_protocol = 1; + + // The input csv file path of pir. + string input_path = 2; + + // The key columns name of input data. + repeated string key_columns = 3; + + // The path of query output csv file path. + string output_path = 4; +} + +// The report of pir result. +message PirResultReport { + // The data count of input/query. + int64 data_count = 1; +} diff --git a/psi/pir/pir_test.cc b/psi/pir/pir_test.cc new file mode 100644 index 00000000..0880b4b6 --- /dev/null +++ b/psi/pir/pir_test.cc @@ -0,0 +1,310 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/pir/pir.h" + +#include <filesystem> +#include <fstream> +#include <string> +#include <utility> +#include <vector> + +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/crypto/tools/prg.h" +#include "yacl/crypto/utils/rand.h" +#include "yacl/link/test_util.h" +#include "yacl/utils/scope_guard.h" + +#include "psi/psi/core/labeled_psi/psi_params.h" +#include "psi/psi/io/io.h" + +namespace { + +constexpr size_t kPsiStartPos = 100; + +struct TestParams { + size_t nr; + size_t ns; + size_t label_bytes = 16; + bool use_filedb = true; + bool compressed = false; + size_t bucket_size = 1000000; +}; + +void WriteCsvFile(const std::string &file_name, const std::string &id_name, + + const std::vector<std::string> &items) { + auto out = + psi::psi::io::BuildOutputStream(psi::psi::io::FileIoOptions(file_name)); + out->Write(fmt::format("{}\n", id_name)); + for (size_t i = 0; i < items.size(); ++i) { + out->Write(fmt::format("{}\n", items[i])); + } + out->Close(); +} + +void WriteCsvFile(const std::string &file_name, const std::string &id_name, + const std::string &label_name, + const std::vector<std::string> &items, + const std::vector<std::string> &labels) { + auto out = + psi::psi::io::BuildOutputStream(psi::psi::io::FileIoOptions(file_name)); + out->Write(fmt::format("{},{}\n", id_name, label_name)); + for (size_t i = 0; i < items.size(); ++i) { + out->Write(fmt::format("{},{}\n", items[i], labels[i])); + } + out->Close(); +} + +void WriteSecretKey(const std::string &ecdh_secret_key_path) { + std::ofstream wf(ecdh_secret_key_path, std::ios::out | std::ios::binary); + + std::vector<uint8_t> oprf_key = yacl::crypto::RandBytes(32); + + wf.write((const char *)oprf_key.data(), oprf_key.size()); + wf.close(); +} + +std::vector<std::string> GenerateData(size_t seed, size_t item_count) { + yacl::crypto::Prg<uint128_t> prg(seed); + + std::vector<std::string> items; + + for (size_t i = 0; i < item_count; ++i) { + std::string item(16, '\0'); + prg.Fill(absl::MakeSpan(item.data(), item.length())); + items.emplace_back(absl::BytesToHexString(item)); + } + + return items; +} + +std::pair<std::vector<std::string>, std::vector<std::string>> +GenerateSenderData(size_t seed, size_t item_count, size_t label_byte_count, + const absl::Span<std::string> &receiver_items, + std::vector<size_t> *intersection_idx, + std::vector<std::string> *intersection_label) { + std::vector<std::string> sender_items; + std::vector<std::string> sender_labels; + + yacl::crypto::Prg<uint128_t> prg(seed); + + for (size_t i = 0; i < item_count; ++i) { + std::string item(16, '\0'); + std::string label((label_byte_count - 1) / 2, '\0'); + + prg.Fill(absl::MakeSpan(item.data(), item.length())); + prg.Fill(absl::MakeSpan(label.data(), label.length())); + sender_items.emplace_back(absl::BytesToHexString(item)); + sender_labels.emplace_back(absl::BytesToHexString(label)); + } + + for (size_t i = 0; i < receiver_items.size(); i += 3) { + if ((kPsiStartPos + i * 5) >= sender_items.size()) { + break; + } + sender_items[kPsiStartPos + i * 5] = receiver_items[i]; + (*intersection_idx).emplace_back(i); + + (*intersection_label).emplace_back(sender_labels[kPsiStartPos + i * 5]); + } + + return std::make_pair(sender_items, sender_labels); +} + +} // namespace + +namespace psi::pir { + +class PirTest : public testing::TestWithParam<TestParams> {}; + +TEST_P(PirTest, Works) { + auto params = GetParam(); + auto ctxs = yacl::link::test::SetupWorld(2); + apsi::PSIParams psi_params = ::psi::psi::GetPsiParams(params.nr, params.ns); + + std::string tmp_store_path = + fmt::format("data_{}_{}", yacl::crypto::RandU64(), params.ns); + + std::filesystem::create_directory(tmp_store_path); + + // register remove of temp dir. + ON_SCOPE_EXIT([&] { + if (!tmp_store_path.empty()) { + std::error_code ec; + std::filesystem::remove_all(tmp_store_path, ec); + if (ec.value() != 0) { + SPDLOG_WARN("can not remove tmp dir: {}, msg: {}", tmp_store_path, + ec.message()); + } + } + }); + + std::string client_csv_path = fmt::format("{}/client.csv", tmp_store_path); + std::string server_csv_path = fmt::format("{}/server.csv", tmp_store_path); + std::string id_cloumn_name = "id"; + std::string label_cloumn_name = "label"; + + std::vector<size_t> intersection_idx; + std::vector<std::string> intersection_label; + + // generate test csv data + { + std::vector<std::string> receiver_items = + GenerateData(yacl::crypto::RandU64(), params.nr); + + std::vector<std::string> sender_keys; + std::vector<std::string> sender_labels; + + std::tie(sender_keys, sender_labels) = GenerateSenderData( + yacl::crypto::RandU64(), params.ns, params.label_bytes - 4, + absl::MakeSpan(receiver_items), &intersection_idx, &intersection_label); + + WriteCsvFile(client_csv_path, id_cloumn_name, receiver_items); + WriteCsvFile(server_csv_path, id_cloumn_name, label_cloumn_name, + sender_keys, sender_labels); + } + + // generate 32 bytes oprf_key + std::string oprf_key_path = fmt::format("{}/oprf_key.bin", tmp_store_path); + WriteSecretKey(oprf_key_path); + + std::string setup_path = fmt::format("{}/setup_path", tmp_store_path); + std::string pir_result_path = + fmt::format("{}/pir_result.csv", tmp_store_path); + + std::vector<std::string> ids = {id_cloumn_name}; + std::vector<std::string> labels = {label_cloumn_name}; + + if (params.use_filedb) { + PirSetupConfig config; + + config.set_pir_protocol(PirProtocol::KEYWORD_PIR_LABELED_PSI); + config.set_store_type(KvStoreType::LEVELDB_KV_STORE); + config.set_input_path(server_csv_path); + + config.mutable_key_columns()->Add(ids.begin(), ids.end()); + config.mutable_label_columns()->Add(labels.begin(), labels.end()); + + config.set_num_per_query(params.nr); + config.set_label_max_len(params.label_bytes); + config.set_oprf_key_path(oprf_key_path); + config.set_setup_path(setup_path); + config.set_compressed(params.compressed); + config.set_bucket_size(params.bucket_size); + + PirResultReport setup_report = PirSetup(config); + + EXPECT_EQ(setup_report.data_count(), params.ns); + + std::future<PirResultReport> f_server = std::async([&] { + PirServerConfig config; + + config.set_pir_protocol(PirProtocol::KEYWORD_PIR_LABELED_PSI); + config.set_store_type(KvStoreType::LEVELDB_KV_STORE); + + config.set_oprf_key_path(oprf_key_path); + config.set_setup_path(setup_path); + + PirResultReport report = PirServer(ctxs[0], config); + return report; + }); + + std::future<PirResultReport> f_client = std::async([&] { + PirClientConfig config; + + config.set_pir_protocol(PirProtocol::KEYWORD_PIR_LABELED_PSI); + + config.set_input_path(client_csv_path); + config.mutable_key_columns()->Add(ids.begin(), ids.end()); + config.set_output_path(pir_result_path); + + PirResultReport report = PirClient(ctxs[1], config); + return report; + }); + + PirResultReport server_report = f_server.get(); + PirResultReport client_report = f_client.get(); + + EXPECT_EQ(server_report.data_count(), params.nr); + EXPECT_EQ(client_report.data_count(), params.nr); + + } else { + std::future<PirResultReport> f_server = std::async([&] { + PirSetupConfig config; + + config.set_pir_protocol(PirProtocol::KEYWORD_PIR_LABELED_PSI); + config.set_store_type(KvStoreType::LEVELDB_KV_STORE); + config.set_input_path(server_csv_path); + + config.mutable_key_columns()->Add(ids.begin(), ids.end()); + config.mutable_label_columns()->Add(labels.begin(), labels.end()); + + config.set_num_per_query(params.nr); + config.set_label_max_len(params.label_bytes); + config.set_oprf_key_path(""); + config.set_setup_path("::memory"); + config.set_compressed(params.compressed); + config.set_bucket_size(params.bucket_size); + + PirResultReport report = PirMemoryServer(ctxs[0], config); + return report; + }); + + std::future<PirResultReport> f_client = std::async([&] { + PirClientConfig config; + + config.set_pir_protocol(PirProtocol::KEYWORD_PIR_LABELED_PSI); + + config.set_input_path(client_csv_path); + config.mutable_key_columns()->Add(ids.begin(), ids.end()); + config.set_output_path(pir_result_path); + + PirResultReport report = PirClient(ctxs[1], config); + return report; + }); + + PirResultReport server_report = f_server.get(); + PirResultReport client_report = f_client.get(); + + EXPECT_EQ(server_report.data_count(), params.nr); + EXPECT_EQ(client_report.data_count(), params.nr); + } + + std::shared_ptr<::psi::psi::ILabeledBatchProvider> pir_result_provider = + std::make_shared<::psi::psi::CsvBatchProvider>( + pir_result_path, ids, intersection_idx.size(), labels); + + // read pir_result from csv + std::vector<std::string> pir_result_ids; + std::vector<std::string> pir_result_labels; + std::tie(pir_result_ids, pir_result_labels) = + pir_result_provider->ReadNextLabeledBatch(); + + // check pir result correct + EXPECT_EQ(pir_result_ids.size(), intersection_idx.size()); + + EXPECT_EQ(pir_result_labels, intersection_label); +} + +INSTANTIATE_TEST_SUITE_P(Works_Instances, PirTest, + testing::Values( // + TestParams{10, 10000, 32}, // 10-10K-32 + TestParams{2048, 10000, 32}, // 2048-10K-32 + TestParams{100, 100000, 32, false}) // 100-100K-32 + +); + +} // namespace psi::pir diff --git a/psi/pir/seal_mpir.cc b/psi/pir/seal_mpir.cc new file mode 100644 index 00000000..92ba5d93 --- /dev/null +++ b/psi/pir/seal_mpir.cc @@ -0,0 +1,346 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/pir/seal_mpir.h" + +#include <algorithm> +#include <memory> +#include <random> +#include <string> +#include <vector> + +#include "spdlog/spdlog.h" + +#include "psi/pir/serializable.pb.h" + +namespace psi::pir { + +void MultiQueryServer::GenerateSimpleHash() { + std::vector<uint128_t> query_index_hash( + query_options_.seal_options.element_number); + + // generate item hash, server and client use same seed + yacl::parallel_for(0, query_options_.seal_options.element_number, 1, + [&](int64_t begin, int64_t end) { + for (int idx = begin; idx < end; ++idx) { + query_index_hash[idx] = HashItemIndex(idx); + } + }); + + size_t num_bins = cuckoo_params_.NumBins(); + + SPDLOG_INFO("element_number:{}", query_options_.seal_options.element_number); + + for (size_t idx = 0; idx < query_options_.seal_options.element_number; + ++idx) { + ::psi::psi::CuckooIndex::HashRoom itemHash(query_index_hash[idx]); + + std::vector<uint64_t> bin_idx(query_options_.cuckoo_hash_number); + for (size_t j = 0; j < query_options_.cuckoo_hash_number; ++j) { + bin_idx[j] = itemHash.GetHash(j) % num_bins; + size_t k = 0; + for (; k < j; ++k) { + if (bin_idx[j] == bin_idx[k]) { + break; + } + } + if (k < j) { + continue; + } + + simple_hash_[bin_idx[j]].push_back(idx); + } + } + + for (const auto &hash_ : simple_hash_) { + max_bin_item_size_ = std::max(max_bin_item_size_, hash_.size()); + } +} + +void MultiQueryServer::SetDatabase(yacl::ByteContainerView db_bytes) { + std::vector<uint8_t> zero_bytes(query_options_.seal_options.element_size); + std::memset(zero_bytes.data(), 0, query_options_.seal_options.element_size); + + for (size_t idx = 0; idx < cuckoo_params_.NumBins(); ++idx) { + std::vector<yacl::ByteContainerView> db_vec; + + for (size_t j : simple_hash_[idx]) { + db_vec.emplace_back( + &db_bytes[j * query_options_.seal_options.element_size], + query_options_.seal_options.element_size); + } + for (size_t j = simple_hash_[idx].size(); j < max_bin_item_size_; ++j) { + db_vec.emplace_back(zero_bytes); + } + + pir_server_[idx]->SetDatabase(db_vec); + } +} + +void MultiQueryServer::RecvGaloisKeys( + const std::shared_ptr<yacl::link::Context> &link_ctx) { + yacl::Buffer galkey_buffer = link_ctx->Recv( + link_ctx->NextRank(), + fmt::format("recv galios key from rank-{}", link_ctx->Rank())); + + std::string galkey_str(galkey_buffer.size(), '\0'); + std::memcpy(galkey_str.data(), galkey_buffer.data(), galkey_buffer.size()); + auto galkey = + pir_server_[0]->DeSerializeSealObject<seal::GaloisKeys>(galkey_str); + SetGaloisKeys(galkey); +} + +void MultiQueryServer::DoMultiPirAnswer( + const std::shared_ptr<yacl::link::Context> &link_ctx) { + yacl::Buffer multi_query_buffer = + link_ctx->Recv(link_ctx->NextRank(), fmt::format("recv multi pir query")); + + SealMultiPirQueryProto multi_query_proto; + multi_query_proto.ParseFromArray(multi_query_buffer.data(), + multi_query_buffer.size()); + + YACL_ENFORCE((uint64_t)multi_query_proto.querys().size() == + cuckoo_params_.NumBins()); + + std::vector<yacl::Buffer> reply_cipher_buffers( + multi_query_proto.querys().size()); + + yacl::parallel_for( + 0, multi_query_proto.querys().size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + std::vector<std::vector<seal::Ciphertext>> query_ciphers = + pir_server_[idx]->DeSerializeQuery(multi_query_proto.querys(idx)); + + std::vector<seal::Ciphertext> query_reply = + pir_server_[idx]->GenerateReply(query_ciphers); + reply_cipher_buffers[idx] = + pir_server_[idx]->SerializeCiphertexts(query_reply); + } + }); + + SealMultiPirAnswerProto mpir_answer_reply_proto; + for (int idx = 0; idx < multi_query_proto.querys().size(); ++idx) { + SealPirAnswerProto *answer = mpir_answer_reply_proto.add_answers(); + answer->set_query_size(0); + answer->set_start_pos(0); + answer->mutable_answer()->ParseFromArray(reply_cipher_buffers[idx].data(), + reply_cipher_buffers[idx].size()); + } + + yacl::Buffer mpir_answer_buffer(mpir_answer_reply_proto.ByteSizeLong()); + mpir_answer_reply_proto.SerializePartialToArray(mpir_answer_buffer.data(), + mpir_answer_buffer.size()); + + link_ctx->SendAsync( + link_ctx->NextRank(), mpir_answer_buffer, + fmt::format("send mpir reply buffer size:{}", mpir_answer_buffer.size())); +} + +void MultiQueryClient::GenerateSimpleHashMap() { + std::vector<uint128_t> query_index_hash( + query_options_.seal_options.element_number); + + // generate item hash, server and client use same seed + yacl::parallel_for(0, query_options_.seal_options.element_number, 1, + [&](int64_t begin, int64_t end) { + for (int idx = begin; idx < end; ++idx) { + query_index_hash[idx] = HashItemIndex(idx); + } + }); + + size_t num_bins = cuckoo_params_.NumBins(); + simple_hash_map_.resize(num_bins); + + std::vector<size_t> simple_hash_counter(num_bins); + for (size_t idx = 0; idx < num_bins; ++idx) { + simple_hash_counter[idx] = 0; + } + + for (size_t idx = 0; idx < query_options_.seal_options.element_number; + ++idx) { + ::psi::psi::CuckooIndex::HashRoom itemHash(query_index_hash[idx]); + + std::vector<uint64_t> bin_idx(query_options_.cuckoo_hash_number); + for (size_t j = 0; j < query_options_.cuckoo_hash_number; ++j) { + bin_idx[j] = itemHash.GetHash(j) % num_bins; + size_t k = 0; + for (; k < j; ++k) { + if (bin_idx[j] == bin_idx[k]) { + break; + } + } + if (k < j) { + continue; + } + // SPDLOG_INFO("bin index[{}]:{}", j, bin_idx[j]); + simple_hash_map_[bin_idx[j]].emplace(query_index_hash[idx], + simple_hash_counter[bin_idx[j]]); + simple_hash_counter[bin_idx[j]]++; + } + } + for (const auto &simple_hash_ : simple_hash_map_) { + max_bin_item_size_ = std::max(max_bin_item_size_, simple_hash_.size()); + } +} + +std::vector<MultiQueryItem> MultiQueryClient::GenerateBatchQueryIndex( + const std::vector<size_t> &multi_query_index) { + std::vector<MultiQueryItem> multi_query(cuckoo_params_.NumBins()); + std::vector<uint128_t> query_index_hash(multi_query_index.size()); + + yacl::parallel_for( + 0, multi_query_index.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + uint128_t item_hash = HashItemIndex(multi_query_index[idx]); + + query_index_hash[idx] = item_hash; + } + }); + + ::psi::psi::CuckooIndex cuckoo_index(cuckoo_params_); + cuckoo_index.Insert(query_index_hash); + + auto ck_bins = cuckoo_index.bins(); + + std::random_device rd; + + std::mt19937 gen(rd()); + + for (size_t idx = 0; idx < ck_bins.size(); ++idx) { + if (ck_bins[idx].IsEmpty()) { + // pad empty bin with random index + multi_query[idx].db_index = 0; + multi_query[idx].item_hash = 0; + multi_query[idx].bin_item_index = gen() % simple_hash_map_[idx].size(); + + continue; + } + size_t item_input_index = ck_bins[idx].InputIdx(); + + uint128_t item_hash = query_index_hash[item_input_index]; + + auto it = simple_hash_map_[idx].find(item_hash); + if (it == simple_hash_map_[idx].end()) { + continue; + } + + multi_query[idx].db_index = multi_query_index[item_input_index]; + multi_query[idx].item_hash = item_hash; + multi_query[idx].bin_item_index = it->second; + } + return multi_query; +} + +void MultiQueryClient::SendGaloisKeys( + const std::shared_ptr<yacl::link::Context> &link_ctx) { + seal::GaloisKeys galkey = pir_client_->GenerateGaloisKeys(); + + std::string galkey_str = + pir_client_->SerializeSealObject<seal::GaloisKeys>(galkey); + yacl::Buffer galkey_buffer(galkey_str.data(), galkey_str.length()); + + link_ctx->SendAsync( + link_ctx->NextRank(), galkey_buffer, + fmt::format("send galios key to rank-{}", link_ctx->Rank())); +} + +std::vector<std::vector<uint8_t>> MultiQueryClient::DoMultiPirQuery( + const std::shared_ptr<yacl::link::Context> &link_ctx, + const std::vector<size_t> &multi_query_index) { + std::vector<MultiQueryItem> multi_query = + GenerateBatchQueryIndex(multi_query_index); + + SealMultiPirQueryProto multi_query_proto; + + std::vector<SealPirQueryProto *> query_proto_vec(multi_query.size()); + for (size_t idx = 0; idx < multi_query.size(); ++idx) { + query_proto_vec[idx] = multi_query_proto.add_querys(); + } + + for (size_t idx = 0; idx < multi_query.size(); ++idx) { + std::vector<std::vector<seal::Ciphertext>> query_ciphers = + pir_client_->GenerateQuery(multi_query[idx].bin_item_index); + + query_proto_vec[idx]->set_query_size(0); + query_proto_vec[idx]->set_start_pos(0); + for (auto &query_cipher : query_ciphers) { + ::psi::pir::CiphertextsProto *ciphers_proto = + query_proto_vec[idx]->add_query_cipher(); + + for (size_t k = 0; k < query_cipher.size(); ++k) { + std::string cipher_bytes = + pir_client_->SerializeSealObject<seal::Ciphertext>(query_cipher[k]); + + ciphers_proto->add_ciphers(cipher_bytes.data(), cipher_bytes.length()); + } + } + } + + auto s = multi_query_proto.SerializeAsString(); + yacl::Buffer multi_query_buffer(s.data(), s.size()); + link_ctx->SendAsync( + link_ctx->NextRank(), multi_query_buffer, + fmt::format("send multi pir query number:{}", multi_query.size())); + + yacl::Buffer reply_buffer = + link_ctx->Recv(link_ctx->NextRank(), fmt::format("recv pir answer")); + + SealMultiPirAnswerProto multi_answer_proto; + multi_answer_proto.ParseFromArray(reply_buffer.data(), reply_buffer.size()); + // SPDLOG_INFO("multi_answer_proto size:{}", + // multi_answer_proto.answers_size()); + + YACL_ENFORCE((uint64_t)multi_answer_proto.answers_size() == + multi_query.size()); + + std::vector<std::vector<uint8_t>> answers(multi_query_index.size()); + size_t answer_count = 0; + + for (int idx = 0; idx < multi_answer_proto.answers_size(); ++idx) { + if (multi_query[idx].item_hash == 0) { + continue; + } + for (size_t j = 0; j < multi_query_index.size(); ++j) { + if (multi_query_index[j] != multi_query[idx].db_index) { + continue; + } + CiphertextsProto answer_proto = multi_answer_proto.answers(idx).answer(); + std::vector<seal::Ciphertext> reply_ciphers = + pir_client_->DeSerializeCiphertexts(answer_proto); + + seal::Plaintext query_plain = pir_client_->DecodeReply(reply_ciphers); + + std::vector<uint8_t> plaintext_bytes = + pir_client_->PlaintextToBytes(query_plain); + + answers[j].resize(query_options_.seal_options.element_size); + + size_t offset = + pir_client_->GetQueryOffset(multi_query[idx].bin_item_index); + + answer_count++; + + std::memcpy( + answers[j].data(), + &plaintext_bytes[offset * query_options_.seal_options.element_size], + query_options_.seal_options.element_size); + break; + } + } + YACL_ENFORCE(answer_count == multi_query_index.size()); + + return answers; +} + +} // namespace psi::pir diff --git a/psi/pir/seal_mpir.h b/psi/pir/seal_mpir.h new file mode 100644 index 00000000..52674e32 --- /dev/null +++ b/psi/pir/seal_mpir.h @@ -0,0 +1,167 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <map> +#include <memory> +#include <vector> + +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" +#include "yacl/crypto/base/symmetric_crypto.h" +#include "yacl/utils/parallel.h" + +#include "psi/pir/seal_pir.h" +#include "psi/psi/core/cuckoo_index.h" + +namespace psi::pir { + +struct MultiQueryOptions { + ::psi::pir::SealPirOptions seal_options; + size_t batch_number = 0; + size_t cuckoo_hash_number = 3; +}; + +struct HashItem { + uint8_t seed[32]; + size_t index; +}; + +struct MultiQueryItem { + size_t db_index; + uint128_t item_hash; + size_t bin_item_index; +}; + +class MultiQuery { + public: + MultiQuery(const MultiQueryOptions &query_options, + const psi::CuckooIndex::Options &cuckoo_params, + yacl::ByteContainerView seed) + : query_options_(query_options), cuckoo_params_(cuckoo_params) { + YACL_ENFORCE(seed.size() <= oracle_seed_.size()); + + std::memcpy(oracle_seed_.data(), seed.data(), seed.size()); + uint128_t crypto_key, crypto_iv = 0; + + std::memcpy(&crypto_key, seed.data(), sizeof(uint128_t)); + + crypto_ = std::make_unique<yacl::crypto::SymmetricCrypto>( + yacl::crypto::SymmetricCrypto::CryptoType::AES128_ECB, crypto_key, + crypto_iv); + } + + size_t GetMaxBinItemSize() const { return max_bin_item_size_; } + + protected: + uint128_t HashItemIndex(size_t index) { + uint128_t plaintext = yacl::MakeUint128(0, index); + + // aes(x) xor x + return crypto_->Encrypt(plaintext) ^ plaintext; + } + + MultiQueryOptions query_options_; + ::psi::psi::CuckooIndex::Options cuckoo_params_; + std::array<uint8_t, 32> oracle_seed_; + + size_t max_bin_item_size_ = 0; + std::unique_ptr<yacl::crypto::SymmetricCrypto> crypto_; +}; + +class MultiQueryServer : public MultiQuery { + public: + MultiQueryServer(const MultiQueryOptions &options, + const psi::CuckooIndex::Options &cuckoo_params, + yacl::ByteContainerView seed) + : MultiQuery(options, cuckoo_params, seed) { + simple_hash_.resize(cuckoo_params_.NumBins()); + + GenerateSimpleHash(); + + ::psi::pir::SealPirOptions pir_options{ + query_options_.seal_options.poly_modulus_degree, max_bin_item_size_, + query_options_.seal_options.element_size}; + + for (size_t idx = 0; idx < cuckoo_params.NumBins(); ++idx) { + std::shared_ptr<IDbPlaintextStore> plaintext_store = + std::make_shared<MemoryDbPlaintextStore>(); + pir_server_.push_back( + std::make_unique<SealPirServer>(pir_options, plaintext_store)); + } + } + + void GenerateSimpleHash(); + + std::vector<size_t> &GetBin(size_t index) { return simple_hash_[index]; } + + size_t GetBinNum() const { return simple_hash_.size(); } + + void SetDatabase(yacl::ByteContainerView db_bytes); + + void SetGaloisKeys(const seal::GaloisKeys &galkey) { + for (size_t idx = 0; idx < pir_server_.size(); ++idx) { + pir_server_[idx]->SetGaloisKeys(galkey); + } + } + + void RecvGaloisKeys(const std::shared_ptr<yacl::link::Context> &link_ctx); + + void DoMultiPirAnswer(const std::shared_ptr<yacl::link::Context> &link_ctx); + + private: + std::vector<std::vector<size_t>> simple_hash_; + + std::vector<std::unique_ptr<SealPirServer>> pir_server_; +}; + +class MultiQueryClient : public MultiQuery { + public: + MultiQueryClient(const MultiQueryOptions &options, + const psi::CuckooIndex::Options &cuckoo_params, + yacl::ByteContainerView seed) + : MultiQuery(options, cuckoo_params, seed) { + GenerateSimpleHashMap(); + + // seal pir client + SealPirOptions client_options{ + query_options_.seal_options.poly_modulus_degree, max_bin_item_size_, + query_options_.seal_options.element_size}; + + pir_client_ = std::make_unique<SealPirClient>(client_options); + } + + std::vector<MultiQueryItem> GenerateBatchQueryIndex( + const std::vector<size_t> &multi_query_index); + + seal::GaloisKeys GenerateGaloisKeys() { + return pir_client_->GenerateGaloisKeys(); + } + + void SendGaloisKeys(const std::shared_ptr<yacl::link::Context> &link_ctx); + + std::vector<std::vector<uint8_t>> DoMultiPirQuery( + const std::shared_ptr<yacl::link::Context> &link_ctx, + const std::vector<size_t> &multi_query_index); + + private: + void GenerateSimpleHashMap(); + + std::vector<std::map<uint128_t, size_t>> simple_hash_map_; + + std::unique_ptr<SealPirClient> pir_client_; +}; + +} // namespace psi::pir diff --git a/psi/pir/seal_mpir_test.cc b/psi/pir/seal_mpir_test.cc new file mode 100644 index 00000000..e8ba3b7c --- /dev/null +++ b/psi/pir/seal_mpir_test.cc @@ -0,0 +1,180 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/pir/seal_mpir.h" + +#include <chrono> +#include <random> +#include <set> + +#include "absl/strings/escaping.h" +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/link/test_util.h" + +#include "psi/psi/cryptor/sodium_curve25519_cryptor.h" + +namespace psi::pir { +namespace { +struct TestParams { + size_t batch_number; + size_t element_number; + size_t element_size = 288; + size_t poly_degree = 8192; // now only support 8192 +}; + +std::vector<uint8_t> GenerateDbData(TestParams params) { + std::vector<uint8_t> db_data(params.element_number * params.element_size); + + std::random_device rd; + + std::mt19937 gen(rd()); + + for (uint64_t i = 0; i < params.element_number; i++) { + for (uint64_t j = 0; j < params.element_size; j++) { + auto val = gen() % 256; + db_data[(i * params.element_size) + j] = val; + } + } + return db_data; +} + +std::vector<size_t> GenerateQueryIndex(size_t batch_number, + size_t element_number) { + std::random_device rd; + std::mt19937 gen(rd()); + + std::set<size_t> query_index_set; + + while (true) { + query_index_set.insert(gen() % element_number); + + if (query_index_set.size() == batch_number) { + break; + } + } + + std::vector<size_t> query_index; + query_index.assign(query_index_set.begin(), query_index_set.end()); + return query_index; +} + +using DurationMillis = std::chrono::duration<double, std::milli>; +} // namespace + +class SealMultiPirTest : public testing::TestWithParam<TestParams> {}; + +TEST_P(SealMultiPirTest, Works) { + auto params = GetParam(); + size_t element_number = params.element_number; + size_t element_size = params.element_size; + size_t batch_number = params.batch_number; + // size_t batch_number = 256; + double factor = 1.5; + size_t hash_num = 3; + ::psi::psi::CuckooIndex::Options cuckoo_params{batch_number, 0, hash_num, + factor}; + + std::vector<size_t> query_index = + GenerateQueryIndex(batch_number, element_number); + + std::vector<uint8_t> db_bytes = GenerateDbData(params); + + auto ctxs = yacl::link::test::SetupWorld(2); + + // use dh key exchange get shared oracle seed + psi::SodiumCurve25519Cryptor c25519_cryptor0; + psi::SodiumCurve25519Cryptor c25519_cryptor1; + + std::future<std::vector<uint8_t>> ke_func_server = + std::async([&] { return c25519_cryptor0.KeyExchange(ctxs[0]); }); + std::future<std::vector<uint8_t>> ke_func_client = + std::async([&] { return c25519_cryptor1.KeyExchange(ctxs[1]); }); + + std::vector<uint8_t> seed_server = ke_func_server.get(); + std::vector<uint8_t> seed_client = ke_func_client.get(); + + EXPECT_EQ(seed_server, seed_client); + + ::psi::pir::MultiQueryOptions options{ + {params.poly_degree, element_number, element_size}, batch_number}; + + SPDLOG_INFO("element_number:{}", options.seal_options.element_number); + + ::psi::pir::MultiQueryServer mpir_server(options, cuckoo_params, seed_server); + + ::psi::pir::MultiQueryClient mpir_client(options, cuckoo_params, seed_client); + + // server setup data + mpir_server.SetDatabase(db_bytes); + + // online send galoiskey(28MB) cause: pipeline check unittest timeout + /* + // client send galois keys to server + std::future<void> client_galkey_func = + std::async([&] { return mpir_client.SendGaloisKeys(ctxs[0]); }); + std::future<void> server_galkey_func = + std::async([&] { return mpir_server.RecvGaloisKeys(ctxs[1]); }); + + client_galkey_func.get(); + server_galkey_func.get(); + */ + + seal::GaloisKeys galkey = mpir_client.GenerateGaloisKeys(); + mpir_server.SetGaloisKeys(galkey); + + // do pir query/answer + std::future<void> pir_service_func = + std::async([&] { return mpir_server.DoMultiPirAnswer(ctxs[0]); }); + std::future<std::vector<std::vector<uint8_t>>> pir_client_func = std::async( + [&] { return mpir_client.DoMultiPirQuery(ctxs[1], query_index); }); + + pir_service_func.get(); + std::vector<std::vector<uint8_t>> query_reply_bytes = pir_client_func.get(); + + EXPECT_EQ(query_reply_bytes.size(), query_index.size()); + + for (size_t idx = 0; idx < query_reply_bytes.size(); ++idx) { + std::vector<uint8_t> query_db_bytes(params.element_size); + std::memcpy(query_db_bytes.data(), + &db_bytes[query_index[idx] * params.element_size], + params.element_size); + + if ((query_db_bytes.size() != query_reply_bytes[idx].size()) || + (std::memcmp(query_db_bytes.data(), query_reply_bytes[idx].data(), + query_reply_bytes[idx].size()) != 0)) { + SPDLOG_INFO( + "idx:{} query_index:{} query_db_bytes:{}", idx, query_index[idx], + absl::BytesToHexString(absl::string_view( + (const char *)query_db_bytes.data(), query_db_bytes.size()))); + SPDLOG_INFO("query_reply_bytes[{}]:{}", idx, + absl::BytesToHexString(absl::string_view( + (const char *)query_reply_bytes[idx].data(), + query_reply_bytes[idx].size()))); + } + + EXPECT_EQ(query_db_bytes, query_reply_bytes[idx]); + } +} + +INSTANTIATE_TEST_SUITE_P( + Works_Instances, SealMultiPirTest, + testing::Values(TestParams{32, 1000}, // element size default 288B + TestParams{32, 1000, 10}, // + TestParams{32, 1000, 400}, // + TestParams{64, 10000}, // element size default 288B + TestParams{64, 10000, 20}) // +); + +} // namespace psi::pir diff --git a/psi/pir/seal_pir.cc b/psi/pir/seal_pir.cc new file mode 100644 index 00000000..a3e83254 --- /dev/null +++ b/psi/pir/seal_pir.cc @@ -0,0 +1,1171 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/pir/seal_pir.h" + +#include <cmath> +#include <cstddef> +#include <utility> + +#include "openssl/bn.h" +#include "seal/util/polyarithsmallmod.h" +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" +#include "yacl/utils/parallel.h" + +namespace psi::pir { + +namespace { +// Number of coefficients needed to represent a database element +uint64_t CoefficientsPerElement(uint32_t logtp, uint64_t ele_size) { + return std::ceil(8 * ele_size / static_cast<double>(logtp)); +} + +// Number of database elements that can fit in a single FV plaintext +uint64_t ElementsPerPtxt(uint32_t logt, uint64_t n, uint64_t ele_size) { + uint64_t coeff_per_ele = CoefficientsPerElement(logt, ele_size); + uint64_t ele_per_ptxt = n / coeff_per_ele; + YACL_ENFORCE(ele_per_ptxt > 0); + return ele_per_ptxt; +} + +// Number of FV plaintexts needed to represent the database +uint64_t PlaintextsPerDb(uint32_t logtp, uint64_t n, uint64_t ele_num, + uint64_t ele_size) { + uint64_t ele_per_ptxt = ElementsPerPtxt(logtp, n, ele_size); + return std::ceil(static_cast<double>(ele_num) / ele_per_ptxt); +} + +std::vector<uint64_t> BytesToCoeffs(uint32_t limit, const uint8_t *bytes, + uint64_t size) { + uint64_t size_out = CoefficientsPerElement(limit, size); + std::vector<uint64_t> output(size_out); + + uint32_t room = limit; + uint64_t *target = output.data(); + + for (uint32_t i = 0; i < size; i++) { + uint8_t src = bytes[i]; + uint32_t rest = 8; + while (rest != 0) { + if (room == 0) { + target++; + room = limit; + } + uint32_t shift = rest; + if (room < rest) { + shift = room; + } + *target = *target << shift; + *target = *target | (src >> (8 - shift)); + src = src << shift; + room -= shift; + rest -= shift; + } + } + + *target = *target << room; + return output; +} + +void CoeffsToBytes(uint32_t limit, const seal::Plaintext &coeffs, + uint8_t *output, uint32_t size_out) { + uint32_t room = 8; + uint32_t j = 0; + uint8_t *target = output; + + for (uint32_t i = 0; i < coeffs.coeff_count(); i++) { + uint64_t src = coeffs[i]; + uint32_t rest = limit; + while ((rest != 0) && j < size_out) { + uint32_t shift = rest; + if (room < rest) { + shift = room; + } + target[j] = target[j] << shift; + target[j] = target[j] | (src >> (limit - shift)); + src = src << shift; + room -= shift; + rest -= shift; + if (room == 0) { + j++; + room = 8; + } + } + } +} + +void VectorToPlaintext(const std::vector<uint64_t> &coeffs, + seal::Plaintext *plain) { + uint32_t coeff_count = coeffs.size(); + plain->resize(coeff_count); + seal::util::set_uint(coeffs.data(), coeff_count, plain->data()); +} + +std::vector<uint64_t> GetDimensions(uint64_t plaintext_num, uint32_t d) { + YACL_ENFORCE(d > 0); + YACL_ENFORCE(plaintext_num > 0); + + std::vector<uint64_t> dimensions(d); + + for (uint32_t i = 0; i < d; i++) { + dimensions[i] = std::max( + static_cast<uint32_t>(2), + static_cast<uint32_t>(std::floor(std::pow(plaintext_num, 1.0 / d)))); + } + + uint32_t product = 1; + uint32_t j = 0; + + // if plaintext_num is not a d-power + if (std::fabs(static_cast<double>(dimensions[0]) - + std::pow(plaintext_num, 1.0 / d)) > + std::numeric_limits<double>::epsilon()) { + while (product < plaintext_num && j < d) { + product = 1; + dimensions[j++]++; + for (uint32_t i = 0; i < d; i++) { + product *= dimensions[i]; + } + } + } + + return dimensions; +} + +std::vector<uint64_t> ComputeIndices(uint64_t query_index, + std::vector<uint64_t> nvec) { + uint32_t num = nvec.size(); + uint64_t product = 1; + + for (uint32_t i = 0; i < num; i++) { + product *= nvec[i]; + } + + uint64_t j = query_index; + std::vector<uint64_t> result; + + for (uint32_t i = 0; i < num; i++) { + product /= nvec[i]; + uint64_t ji = j / product; + + result.push_back(ji); + j -= ji * product; + } + + return result; +} + +} // namespace + +void SealPir::SetPolyModulusDegree(size_t degree) { + seal_params_ = + std::make_unique<seal::EncryptionParameters>(seal::scheme_type::bfv); + + seal_params_->set_poly_modulus_degree(degree); + seal_params_->set_coeff_modulus(seal::CoeffModulus::BFVDefault(degree)); + + if (degree == 8192) { + seal_params_->set_plain_modulus(seal::PlainModulus::Batching(degree, 17)); + //} //else if (degree == 4096) { + // seal_params_->set_plain_modulus(seal::PlainModulus::Batching(degree, + // 38)); + } else { + YACL_THROW("poly_modulus_degree {} is not support.", degree); + } + + context_ = std::make_unique<seal::SEALContext>(*(seal_params_)); +} + +void SealPir::SetPirParams(size_t element_number, size_t element_size) { + uint32_t logt = std::floor(std::log2(seal_params_->plain_modulus().value())); + uint32_t N = seal_params_->poly_modulus_degree(); + + // number of FV plaintexts needed to represent all elements + uint64_t plaintext_num = + PlaintextsPerDb(logt, N, element_number, element_size); + + size_t d = 1; + if (element_number > 8192) { + d = 2; + } + std::vector<uint64_t> nvec = GetDimensions(plaintext_num, d); + + uint32_t expansion_ratio = 0; + for (const auto &modulus : seal_params_->coeff_modulus()) { + double logqi = std::log2(modulus.value()); + expansion_ratio += ceil(logqi / logt); + } + + // dimension + pir_params_.d = d; + pir_params_.dbc = 6; + pir_params_.n = plaintext_num; + pir_params_.nvec = nvec; + pir_params_.expansion_ratio = expansion_ratio + << 1; // because one ciphertext = two polys +} + +std::string SealPir::SerializePlaintexts( + const std::vector<seal::Plaintext> &plains) { + psi::pir::PlaintextsProto plains_proto; + + for (const auto &plain : plains) { + std::string plain_bytes = SerializeSealObject<seal::Plaintext>(plain); + + plains_proto.add_data(plain_bytes.data(), plain_bytes.length()); + } + return plains_proto.SerializeAsString(); +} + +std::vector<seal::Plaintext> SealPir::DeSerializePlaintexts( + const std::string &plaintext_bytes, bool safe_load) { + psi::pir::PlaintextsProto plains_proto; + plains_proto.ParseFromArray(plaintext_bytes.data(), plaintext_bytes.length()); + + std::vector<seal::Plaintext> plains(plains_proto.data_size()); + + yacl::parallel_for(0, plains_proto.data_size(), 1, + [&](int64_t begin, int64_t end) { + for (int i = begin; i < end; ++i) { + plains[i] = DeSerializeSealObject<seal::Plaintext>( + plains_proto.data(i), safe_load); + } + }); + return plains; +} + +yacl::Buffer SealPir::SerializeCiphertexts( + const std::vector<seal::Ciphertext> &ciphers) { + psi::pir::CiphertextsProto ciphers_proto; + + for (const auto &cipher : ciphers) { + std::string cipher_bytes = SerializeSealObject<seal::Ciphertext>(cipher); + + ciphers_proto.add_ciphers(cipher_bytes.data(), cipher_bytes.length()); + } + + yacl::Buffer b(ciphers_proto.ByteSizeLong()); + ciphers_proto.SerializePartialToArray(b.data(), b.size()); + return b; +} + +std::vector<seal::Ciphertext> SealPir::DeSerializeCiphertexts( + const CiphertextsProto &ciphers_proto, bool safe_load) { + std::vector<seal::Ciphertext> ciphers(ciphers_proto.ciphers_size()); + + yacl::parallel_for(0, ciphers_proto.ciphers_size(), 1, + [&](int64_t begin, int64_t end) { + for (int i = begin; i < end; ++i) { + ciphers[i] = DeSerializeSealObject<seal::Ciphertext>( + ciphers_proto.ciphers(i), safe_load); + } + }); + return ciphers; +} + +std::vector<seal::Ciphertext> SealPir::DeSerializeCiphertexts( + const yacl::Buffer &ciphers_buffer, bool safe_load) { + CiphertextsProto ciphers_proto; + ciphers_proto.ParseFromArray(ciphers_buffer.data(), ciphers_buffer.size()); + + return DeSerializeCiphertexts(ciphers_proto, safe_load); +} + +yacl::Buffer SealPir::SerializeQuery( + SealPirQueryProto *query_proto, + const std::vector<std::vector<seal::Ciphertext>> &query_ciphers) { + for (const auto &query_cipher : query_ciphers) { + CiphertextsProto *ciphers_proto = query_proto->add_query_cipher(); + for (const auto &ciphertext : query_cipher) { + std::string cipher_bytes = + SerializeSealObject<seal::Ciphertext>(ciphertext); + + ciphers_proto->add_ciphers(cipher_bytes.data(), cipher_bytes.length()); + } + } + + yacl::Buffer b(query_proto->ByteSizeLong()); + query_proto->SerializePartialToArray(b.data(), b.size()); + return b; +} + +yacl::Buffer SealPir::SerializeQuery( + const std::vector<std::vector<seal::Ciphertext>> &query_ciphers) { + SealPirQueryProto query_proto; + + query_proto.set_query_size(0); + query_proto.set_start_pos(0); + + return SerializeQuery(&query_proto, query_ciphers); +} + +std::vector<std::vector<seal::Ciphertext>> SealPir::DeSerializeQuery( + const SealPirQueryProto &query_proto, bool safe_load) { + std::vector<std::vector<seal::Ciphertext>> pir_query( + query_proto.query_cipher_size()); + + yacl::parallel_for( + 0, query_proto.query_cipher_size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t i = begin; i < end; ++i) { + const auto &ciphers = query_proto.query_cipher(i); + + pir_query[i].resize(ciphers.ciphers_size()); + for (int j = 0; j < ciphers.ciphers_size(); ++j) { + pir_query[i][j] = DeSerializeSealObject<seal::Ciphertext>( + ciphers.ciphers(j), safe_load); + } + } + }); + return pir_query; +} + +std::vector<std::vector<seal::Ciphertext>> SealPir::DeSerializeQuery( + const yacl::Buffer &query_buffer, bool safe_load) { + SealPirQueryProto query_proto; + query_proto.ParseFromArray(query_buffer.data(), query_buffer.size()); + + return DeSerializeQuery(query_proto, safe_load); +} + +#ifdef DEC_DEBUG_ +// use client decrypt key get noise budget +SealPirServer::SealPirServer( + const SealPirOptions &options, + std::shared_ptr<IDbPlaintextStore> &plaintext_store, SealPirClient &client) + : SealPir(options), plaintext_store_(plaintext_store), client_(client) {} +#else +SealPirServer::SealPirServer(const SealPirOptions &options, + std::shared_ptr<IDbPlaintextStore> plaintext_store) + : SealPir(options), plaintext_store_(std::move(plaintext_store)) {} +#endif + +// set database + +void SealPirServer::SetDatabase( + const std::shared_ptr<IDbElementProvider> &db_provider) { + uint64_t db_size = options_.element_number * options_.element_size; + + YACL_ENFORCE(db_provider->GetDbByteSize() == db_size); + + uint32_t logt = std::floor(std::log2(seal_params_->plain_modulus().value())); + uint32_t N = seal_params_->poly_modulus_degree(); + + // number of FV plaintexts needed to represent all elements + uint64_t plaintext_num; + uint64_t db_num; + if (options_.query_size == 0) { + plaintext_num = PlaintextsPerDb(logt, N, options_.element_number, + options_.element_size); + db_num = 1; + } else { + plaintext_num = + PlaintextsPerDb(logt, N, options_.query_size, options_.element_size); + db_num = (options_.element_number + options_.query_size - 1) / + options_.query_size; + } + + // number of FV plaintexts needed to create the d-dimensional matrix + uint64_t prod = 1; + for (auto n : pir_params_.nvec) { + prod *= n; + } + uint64_t matrix_plaintexts = prod; + YACL_ENFORCE(plaintext_num <= matrix_plaintexts); + + uint64_t ele_per_ptxt = ElementsPerPtxt(logt, N, options_.element_size); + uint64_t bytes_per_ptxt = ele_per_ptxt * options_.element_size; + + uint64_t coeff_per_ptxt = + ele_per_ptxt * CoefficientsPerElement(logt, options_.element_size); + YACL_ENFORCE(coeff_per_ptxt <= N); + + plaintext_store_->SetSubDbNumber(db_num); + + for (size_t idx = 0; idx < db_num; ++idx) { + std::vector<seal::Plaintext> db_vec; + db_vec.reserve(matrix_plaintexts); + + uint32_t offset = idx * options_.query_size * options_.element_size; + + for (uint64_t i = 0; i < plaintext_num; i++) { + uint64_t process_bytes = 0; + + if (db_size <= offset) { + break; + } else if (db_size < offset + bytes_per_ptxt) { + process_bytes = db_size - offset; + } else { + process_bytes = bytes_per_ptxt; + } + + // Get the coefficients of the elements that will be packed in plaintext i + std::vector<uint8_t> element_bytes = + db_provider->ReadElement(offset, process_bytes); + std::vector<uint64_t> coefficients = + BytesToCoeffs(logt, element_bytes.data(), process_bytes); + offset += process_bytes; + + uint64_t used = coefficients.size(); + + YACL_ENFORCE(used <= coeff_per_ptxt); + + // Pad the rest with 1s + for (uint64_t j = 0; j < (N - used); j++) { + coefficients.push_back(1); + } + + seal::Plaintext plain; + VectorToPlaintext(coefficients, &plain); + db_vec.push_back(std::move(plain)); + } + + // Add padding to make database a matrix + uint64_t current_plaintexts = db_vec.size(); + YACL_ENFORCE(current_plaintexts <= plaintext_num); + +#ifdef DEC_DEBUG_ + SPDLOG_INFO( + "adding: {} FV plaintexts of padding (equivalent to: {} elements", + (matrix_plaintexts - current_plaintexts), + (matrix_plaintexts - current_plaintexts) * + elements_per_ptxt(logt, N, options_.element_size)); +#endif + + std::vector<uint64_t> padding(N, 1); + + for (uint64_t i = 0; i < (matrix_plaintexts - current_plaintexts); i++) { + seal::Plaintext plain; + VectorToPlaintext(padding, &plain); + db_vec.push_back(plain); + } + + // pre process db + yacl::parallel_for(0, db_vec.size(), 1, [&](int64_t begin, int64_t end) { + for (uint32_t i = begin; i < end; i++) { + evaluator_->transform_to_ntt_inplace(db_vec[i], + context_->first_parms_id()); + } + }); + plaintext_store_->SavePlaintexts(db_vec, idx); + } +} + +void SealPirServer::SetDatabase( + const std::vector<yacl::ByteContainerView> &db_vec) { + std::vector<uint8_t> db_flatten_bytes(db_vec.size() * options_.element_size); + for (size_t idx = 0; idx < db_vec.size(); ++idx) { + std::memcpy(&db_flatten_bytes[idx * options_.element_size], + db_vec[idx].data(), db_vec[idx].size()); + } + + std::shared_ptr<IDbElementProvider> db_provider = + std::make_shared<MemoryDbElementProvider>(db_flatten_bytes, + options_.element_size); + + return SetDatabase(db_provider); +} + +std::vector<seal::Ciphertext> SealPirServer::ExpandQuery( + const seal::Ciphertext &encrypted, std::uint32_t m) { + uint64_t plain_mod = seal_params_->plain_modulus().value(); + + seal::GaloisKeys &galkey = galois_key_; + + // Assume that m is a power of 2. If not, round it to the next power of 2. + uint32_t logm = std::ceil(std::log2(m)); + + std::vector<int> galois_elts; + auto n = seal_params_->poly_modulus_degree(); + YACL_ENFORCE(logm <= std::ceil(std::log2(n)), "m > n is not allowed."); + + galois_elts.reserve(std::ceil(std::log2(n))); + for (int i = 0; i < std::ceil(std::log2(n)); i++) { + galois_elts.push_back((n + seal::util::exponentiate_uint(2, i)) / + seal::util::exponentiate_uint(2, i)); + } + + std::vector<seal::Ciphertext> results(1); + results[0] = encrypted; + seal::Plaintext tempPt; + for (size_t j = 0; j < logm - 1; j++) { + std::vector<seal::Ciphertext> results2(1 << (j + 1)); + int step = 1 << j; + seal::Plaintext pt0(n); + seal::Plaintext pt1(n); + + pt0.set_zero(); + pt0[n - step] = plain_mod - 1; + + int index_raw = (n << 1) - (1 << j); + int index = (index_raw * galois_elts[j]) % (n << 1); + pt1.set_zero(); + pt1[index] = 1; + + // int nstep = -step; + yacl::parallel_for(0, step, 1, [&](int64_t begin, int64_t end) { + for (int k = begin; k < end; k++) { + seal::Ciphertext c0; + seal::Ciphertext c1; + seal::Ciphertext t0; + seal::Ciphertext t1; + + c0 = results[k]; + + // SPDLOG_INFO("apply_galois j:{} k:{}", j, k); + evaluator_->apply_galois(c0, galois_elts[j], galkey, t0); + evaluator_->add(c0, t0, results2[k]); + + evaluator_->multiply_plain(c0, pt0, c1); + evaluator_->multiply_plain(t0, pt1, t1); + evaluator_->add(c1, t1, results2[k + step]); + } + }); + results = results2; + } + + // Last step of the loop + std::vector<seal::Ciphertext> results2(results.size() << 1); + seal::Plaintext two("2"); + + seal::Plaintext pt0(n); + seal::Plaintext pt1(n); + + pt0.set_zero(); + pt0[n - results.size()] = plain_mod - 1; + + int index_raw = (n << 1) - (1 << (logm - 1)); + int index = (index_raw * galois_elts[logm - 1]) % (n << 1); + pt1.set_zero(); + pt1[index] = 1; + + for (uint32_t k = 0; k < results.size(); k++) { + if (k >= (m - (1 << (logm - 1)))) { // corner case. + evaluator_->multiply_plain(results[k], two, + results2[k]); // plain multiplication by 2. + } else { + seal::Ciphertext c0; + seal::Ciphertext c1; + seal::Ciphertext t0; + seal::Ciphertext t1; + + c0 = results[k]; + evaluator_->apply_galois(c0, galois_elts[logm - 1], galkey, t0); + evaluator_->add(c0, t0, results2[k]); + + evaluator_->multiply_plain(c0, pt0, c1); + evaluator_->multiply_plain(t0, pt1, t1); + evaluator_->add(c1, t1, results2[k + results.size()]); + } + } + + auto first = results2.begin(); + auto last = results2.begin() + m; + std::vector<seal::Ciphertext> new_vec(first, last); + return new_vec; +} + +std::vector<seal::Ciphertext> SealPirServer::GenerateReply( + const std::vector<std::vector<seal::Ciphertext>> &query_ciphers, + size_t start_pos) { + std::vector<uint64_t> nvec = pir_params_.nvec; + uint64_t product = 1; + + for (auto n : nvec) { + product *= n; + } + + auto coeff_count = seal_params_->poly_modulus_degree(); + + size_t sub_db_index = 0; + if (options_.query_size > 0) { + sub_db_index = start_pos / options_.query_size; + } + + std::vector<seal::Plaintext> db_plaintexts = + plaintext_store_->ReadPlaintexts(sub_db_index); + std::vector<seal::Plaintext> *cur = &db_plaintexts; + + std::vector<seal::Plaintext> intermediate_plain; // decompose.... + + auto pool = seal::MemoryManager::GetPool(); + + int N = seal_params_->poly_modulus_degree(); + + int logt = std::floor(std::log2(seal_params_->plain_modulus().value())); + + for (uint32_t i = 0; i < nvec.size(); i++) { + std::vector<seal::Ciphertext> expanded_query; + + uint64_t n_i = nvec[i]; + + for (uint32_t j = 0; j < query_ciphers[i].size(); j++) { + uint64_t total = N; + if (j == query_ciphers[i].size() - 1) { + total = n_i % N; + } + + std::vector<seal::Ciphertext> expanded_query_part = + ExpandQuery(query_ciphers[i][j], total); + + expanded_query.insert( + expanded_query.end(), + std::make_move_iterator(expanded_query_part.begin()), + std::make_move_iterator(expanded_query_part.end())); + expanded_query_part.clear(); + } + if (expanded_query.size() != n_i) { + SPDLOG_INFO(" size mismatch!!! {}-{}", expanded_query.size(), n_i); + } + +#ifdef DEC_DEBUG_ + SPDLOG_INFO("Checking expanded query, size = {}", expanded_query.size()); + + seal::Plaintext tempPt; + for (size_t h = 0; h < expanded_query.size(); h++) { + client_.decryptor_->decrypt(expanded_query[h], tempPt); + + SPDLOG_INFO("h:{} noise budget = {}, tempPt: {}", h, + client_.decryptor_->invariant_noise_budget(expanded_query[h]), + tempPt.to_string()); + } + +#endif + + // Transform expanded query to NTT, and ... + yacl::parallel_for( + 0, expanded_query.size(), 1, [&](int64_t begin, int64_t end) { + for (uint32_t jj = begin; jj < end; jj++) { + evaluator_->transform_to_ntt_inplace(expanded_query[jj]); + } + }); + + // Transform plaintext to NTT. If database is pre-processed, can skip + if (i > 0) { + yacl::parallel_for(0, (*cur).size(), 1, [&](int64_t begin, int64_t end) { + for (uint32_t jj = begin; jj < end; jj++) { + evaluator_->transform_to_ntt_inplace((*cur)[jj], + context_->first_parms_id()); + } + }); + } + +#ifdef DEC_DEBUG_ + for (uint64_t k = 0; k < product; k++) { + if ((*cur)[k].is_zero()) { + SPDLOG_INFO("k: {}, product: {}-th ptxt = 0 ", (k + 1), product); + } + } +#endif + + product /= n_i; + std::vector<seal::Ciphertext> intermediateCtxts(product); + + yacl::parallel_for(0, product, 1, [&](int64_t begin, int64_t end) { + for (int k = begin; k < end; k++) { + uint64_t j = 0; + while ((*cur)[k + j * product].is_zero()) { + j++; + } + + evaluator_->multiply_plain(expanded_query[j], (*cur)[k + j * product], + intermediateCtxts[k]); + + seal::Ciphertext temp; + for (j += 1; j < n_i; j++) { + if ((*cur)[k + j * product].is_zero()) { + SPDLOG_INFO("cur[{}] is zero, k:{}, j:{}", (k + j * product), k, j); + continue; + } + evaluator_->multiply_plain(expanded_query[j], (*cur)[k + j * product], + temp); + evaluator_->add_inplace(intermediateCtxts[k], + temp); // Adds to first component. + } + } + }); + + yacl::parallel_for( + 0, intermediateCtxts.size(), 1, [&](int64_t begin, int64_t end) { + for (uint32_t jj = begin; jj < end; jj++) { + evaluator_->transform_from_ntt_inplace(intermediateCtxts[jj]); + } + }); + + if (i == nvec.size() - 1) { + return intermediateCtxts; + } else { + intermediate_plain.clear(); + intermediate_plain.reserve(pir_params_.expansion_ratio * product); + cur = &intermediate_plain; + + auto tempplain = seal::util::allocate<seal::Plaintext>( + pir_params_.expansion_ratio * product, pool, coeff_count); + + for (uint64_t rr = 0; rr < product; rr++) { + DecomposeToPlaintextsPtr( + intermediateCtxts[rr], + tempplain.get() + rr * pir_params_.expansion_ratio, logt); + + for (uint32_t jj = 0; jj < pir_params_.expansion_ratio; jj++) { + auto offset = rr * pir_params_.expansion_ratio + jj; + intermediate_plain.emplace_back(tempplain[offset]); + } + } + product *= pir_params_.expansion_ratio; // multiply by expansion rate. + } + SPDLOG_INFO("Server: {}-th recursion level finished", (i + 1)); + } + SPDLOG_INFO("reply generated! "); + // This should never get here + + std::vector<seal::Ciphertext> fail(1); + return fail; +} + +inline void SealPirServer::DecomposeToPlaintextsPtr( + const seal::Ciphertext &encrypted, seal::Plaintext *plain_ptr, int logt) { + std::vector<seal::Plaintext> result; + auto coeff_count = seal_params_->poly_modulus_degree(); + auto coeff_mod_count = seal_params_->coeff_modulus().size(); + auto encrypted_count = encrypted.size(); + + uint64_t t1 = 1ULL << logt; // t1 <= t. + + uint64_t t1minusone = t1 - 1; + // A triple for loop. Going over polys, moduli, and decomposed index. + + for (size_t i = 0; i < encrypted_count; i++) { + const uint64_t *encrypted_pointer = encrypted.data(i); + for (size_t j = 0; j < coeff_mod_count; j++) { + // populate one poly at a time. + // create a polynomial to store the current decomposition value + // which will be copied into the array to populate it at the current + // index. + double logqj = std::log2(seal_params_->coeff_modulus()[j].value()); + // int expansion_ratio = ceil(logqj + exponent - 1) / exponent; + int expansion_ratio = std::ceil(logqj / logt); + + uint64_t curexp = 0; + for (int k = 0; k < expansion_ratio; k++) { + // Decompose here + for (size_t m = 0; m < coeff_count; m++) { + plain_ptr[i * coeff_mod_count * expansion_ratio + + j * expansion_ratio + k][m] = + (*(encrypted_pointer + m + (j * coeff_count)) >> curexp) & + t1minusone; + } + curexp += logt; + } + } + } +} + +std::vector<seal::Plaintext> SealPirServer::DecomposeToPlaintexts( + const seal::Ciphertext &encrypted) { + std::vector<seal::Plaintext> result; + auto coeff_count = seal_params_->poly_modulus_degree(); + auto coeff_mod_count = seal_params_->coeff_modulus().size(); + // auto plain_bit_count = seal_params_->plain_modulus().bit_count(); + auto encrypted_count = encrypted.size(); + + // Generate powers of t. + uint64_t plain_mod = seal_params_->plain_modulus().value(); + + // A triple for loop. Going over polys, moduli, and decomposed index. + for (size_t i = 0; i < encrypted_count; i++) { + const uint64_t *encrypted_pointer = encrypted.data(i); + for (size_t j = 0; j < coeff_mod_count; j++) { + // populate one poly at a time. + // create a polynomial to store the current decomposition value + // which will be copied into the array to populate it at the current + // index. + int logqj = std::log2(seal_params_->coeff_modulus()[j].value()); + int expansion_ratio = std::ceil(logqj / std::log2(plain_mod)); + + uint64_t cur = 1; + for (int k = 0; k < expansion_ratio; k++) { + // Decompose here + seal::Plaintext temp(coeff_count); + std::transform( + encrypted_pointer + (j * coeff_count), + encrypted_pointer + ((j + 1) * coeff_count), temp.data(), + [cur, &plain_mod](auto &in) { return (in / cur) % plain_mod; }); + + result.emplace_back(std::move(temp)); + cur *= plain_mod; + } + } + } + + return result; +} + +std::string SealPirServer::SerializeDbPlaintext(int db_index) { + return SerializePlaintexts(*db_vec_[db_index].get()); +} + +void SealPirServer::DeSerializeDbPlaintext( + const std::string &db_serialize_bytes, int db_index) { + std::vector<seal::Plaintext> plaintext_vec = + DeSerializePlaintexts(db_serialize_bytes); + + db_vec_[db_index] = + std::make_unique<std::vector<seal::Plaintext>>(plaintext_vec); +} + +void SealPirServer::RecvGaloisKeys( + const std::shared_ptr<yacl::link::Context> &link_ctx) { + yacl::Buffer galkey_buffer = link_ctx->Recv( + link_ctx->NextRank(), + fmt::format("recv galios key from rank-{}", link_ctx->Rank())); + + std::string galkey_str(galkey_buffer.size(), '\0'); + std::memcpy(galkey_str.data(), galkey_buffer.data(), galkey_buffer.size()); + + auto galkey = DeSerializeSealObject<seal::GaloisKeys>(galkey_str); + SetGaloisKeys(galkey); +} + +void SealPirServer::DoPirAnswer( + const std::shared_ptr<yacl::link::Context> &link_ctx) { + yacl::Buffer query_buffer = + link_ctx->Recv(link_ctx->NextRank(), fmt::format("recv query ciphers")); + + SealPirQueryProto query_proto; + query_proto.ParseFromArray(query_buffer.data(), query_buffer.size()); + + std::vector<std::vector<seal::Ciphertext>> query_ciphers = + DeSerializeQuery(query_proto); + std::vector<seal::Ciphertext> reply_ciphers = + GenerateReply(query_ciphers, query_proto.start_pos()); + + yacl::Buffer reply_buffer = SerializeCiphertexts(reply_ciphers); + link_ctx->SendAsync( + link_ctx->NextRank(), reply_buffer, + fmt::format("send query reply size:{}", reply_buffer.size())); +} + +// SealPirClient +SealPirClient::SealPirClient(const SealPirOptions &options) : SealPir(options) { + keygen_ = std::make_unique<seal::KeyGenerator>(*context_); + + seal::PublicKey public_key_; + keygen_->create_public_key(public_key_); + + encryptor_ = std::make_unique<seal::Encryptor>(*context_, public_key_); + + seal::SecretKey secret_key = keygen_->secret_key(); + + decryptor_ = std::make_unique<seal::Decryptor>(*context_, secret_key); +} + +seal::GaloisKeys SealPirClient::GenerateGaloisKeys() { + // Generate the Galois keys needed for coeff_select. + std::vector<uint32_t> galois_elts; + int N = seal_params_->poly_modulus_degree(); + int logN = seal::util::get_power_of_two(N); + + galois_elts.reserve(logN); + for (int i = 0; i < logN; i++) { + galois_elts.push_back((N + seal::util::exponentiate_uint(2, i)) / + seal::util::exponentiate_uint(2, i)); + } + + seal::GaloisKeys galois_keys; + keygen_->create_galois_keys(galois_elts, galois_keys); + + return galois_keys; +} + +std::vector<std::vector<seal::Ciphertext>> SealPirClient::GenerateQuery( + size_t index) { + size_t query_indx = GetQueryIndex(index); + + indices_ = ComputeIndices(query_indx, pir_params_.nvec); + + ComputeInverseScales(); + + std::vector<std::vector<seal::Ciphertext>> result(pir_params_.d); + int N = seal_params_->poly_modulus_degree(); + + seal::Plaintext pt(seal_params_->poly_modulus_degree()); + for (uint32_t i = 0; i < indices_.size(); i++) { + uint32_t num_ptxts = ceil((pir_params_.nvec[i] + 0.0) / N); + + for (uint32_t j = 0; j < num_ptxts; j++) { + pt.set_zero(); + if (indices_[i] > N * (j + 1) || indices_[i] < N * j) { + // just encrypt zero + } else { + uint64_t real_index = indices_[i] - N * j; + pt[real_index] = 1; + } + seal::Ciphertext dest; + encryptor_->encrypt(pt, dest); + + result[i].push_back(dest); + } + } + + return result; +} + +seal::Plaintext SealPirClient::DecodeReply( + const std::vector<seal::Ciphertext> &reply) { + uint32_t exp_ratio = pir_params_.expansion_ratio; + uint32_t recursion_level = pir_params_.d; + + std::vector<seal::Ciphertext> temp = reply; + + uint64_t t = seal_params_->plain_modulus().value(); + + for (uint32_t i = 0; i < recursion_level; i++) { + std::vector<seal::Ciphertext> newtemp; + std::vector<seal::Plaintext> tempplain; + + for (uint32_t j = 0; j < temp.size(); j++) { + seal::Plaintext ptxt; + decryptor_->decrypt(temp[j], ptxt); +#ifdef DEC_DEBUG_ + // SPDLOG_INFO("Client: reply noise budget = {}", + // decryptor_->invariant_noise_budget(temp[j])); + // SPDLOG_INFO("ptxt to_string: {}", ptxt.to_string()); +#endif + // multiply by inverse_scale for every coefficient of ptxt + for (size_t h = 0; h < ptxt.coeff_count(); h++) { + ptxt[h] *= inverse_scales_[recursion_level - 1 - i]; + + ptxt[h] %= t; + } + tempplain.push_back(ptxt); + +#ifdef DEC_DEBUG_ + // SPDLOG_INFO("recursion level : {} noise budget : {}", i, + // decryptor_->invariant_noise_budget(temp[j])); +#endif + + if ((j + 1) % exp_ratio == 0 && j > 0) { + // Combine into one ciphertext. + seal::Ciphertext combined = ComposeToCiphertext(tempplain); + newtemp.push_back(combined); + tempplain.clear(); + } + } + + if (i == recursion_level - 1) { + assert(temp.size() == 1); + + return tempplain[0]; + } else { + tempplain.clear(); + temp = newtemp; + } + } + + // This should never be called + assert(0); + seal::Plaintext fail; + return fail; +} + +uint64_t SealPirClient::GetQueryIndex(uint64_t element_idx) { + auto N = seal_params_->poly_modulus_degree(); + auto logt = std::floor(std::log2(seal_params_->plain_modulus().value())); + + auto ele_per_ptxt = ElementsPerPtxt(logt, N, options_.element_size); + return static_cast<uint64_t>(element_idx / ele_per_ptxt); +} + +uint64_t SealPirClient::GetQueryOffset(uint64_t element_idx) { + uint32_t N = seal_params_->poly_modulus_degree(); + uint32_t logt = std::floor(std::log2(seal_params_->plain_modulus().value())); + + uint64_t ele_per_ptxt = ElementsPerPtxt(logt, N, options_.element_size); + return element_idx % ele_per_ptxt; +} + +std::vector<uint8_t> SealPirClient::PlaintextToBytes( + const seal::Plaintext &plain) { + uint32_t N = seal_params_->poly_modulus_degree(); + uint32_t logt = std::floor(std::log2(seal_params_->plain_modulus().value())); + + // Convert from FV plaintext (polynomial) to database element at the client + std::vector<uint8_t> elements(N * logt / 8); + + CoeffsToBytes(logt, plain, elements.data(), (N * logt) / 8); + + return elements; +} + +void SealPirClient::ComputeInverseScales() { + YACL_ENFORCE(indices_.size() == pir_params_.nvec.size(), "size mismatch"); + + int logt = std::floor(std::log2(seal_params_->plain_modulus().value())); + + uint64_t N = seal_params_->poly_modulus_degree(); + uint64_t t = seal_params_->plain_modulus().value(); + int logN = std::log2(N); + int logm = logN; + + inverse_scales_.clear(); + + for (size_t i = 0; i < pir_params_.nvec.size(); i++) { + // uint64_t index_modN = indices_[i] % N; + uint64_t numCtxt = + ceil((pir_params_.nvec[i] + 0.0) / N); // number of query ciphertexts. + uint64_t batchId = indices_[i] / N; + if (batchId == numCtxt - 1) { + logm = std::ceil(std::log2((pir_params_.nvec[i] % N))); + } + + uint64_t inverse_scale; + + int quo = logm / logt; + int mod = logm % logt; + inverse_scale = std::pow(2, logt - mod); + + if ((quo + 1) % 2 != 0) { + inverse_scale = + seal_params_->plain_modulus().value() - pow(2, logt - mod); + } + // get mod inverse + { + BN_CTX *bn_ctx = BN_CTX_new(); + BIGNUM *bn_t = BN_new(); + BIGNUM *bn_m = BN_new(); + BIGNUM *ret = BN_new(); + BN_set_word(bn_t, t); + BN_set_word(bn_m, 1 << logm); + + BN_mod_inverse(ret, bn_m, bn_t, bn_ctx); + inverse_scale = BN_get_word(ret); + + BN_free(bn_t); + BN_free(bn_m); + BN_free(ret); + BN_CTX_free(bn_ctx); + } + inverse_scales_.push_back(inverse_scale); + if ((inverse_scale << logm) % t != 1) { + YACL_THROW("get inverse wrong"); + } + } +} + +seal::Ciphertext SealPirClient::ComposeToCiphertext( + const std::vector<seal::Plaintext> &plains) { + size_t encrypted_count = 2; + auto coeff_count = seal_params_->poly_modulus_degree(); + auto coeff_mod_count = seal_params_->coeff_modulus().size(); + uint64_t plainMod = seal_params_->plain_modulus().value(); + int logt = std::floor(std::log2(plainMod)); + + seal::Ciphertext result(*context_); + result.resize(encrypted_count); + + // A triple for loop. Going over polys, moduli, and decomposed index. + for (size_t i = 0; i < encrypted_count; i++) { + uint64_t *encrypted_pointer = result.data(i); + + for (size_t j = 0; j < coeff_mod_count; j++) { + // populate one poly at a time. + // create a polynomial to store the current decomposition value + // which will be copied into the array to populate it at the current + // index. + double logqj = std::log2(seal_params_->coeff_modulus()[j].value()); + int expansion_ratio = std::ceil(logqj / logt); + uint64_t cur = 1; + + for (int k = 0; k < expansion_ratio; k++) { + // Compose here + const uint64_t *plain_coeff = + plains[k + j * (expansion_ratio) + + i * (coeff_mod_count * expansion_ratio)] + .data(); + + for (size_t m = 0; m < coeff_count; m++) { + if (k == 0) { + *(encrypted_pointer + m + j * coeff_count) = + *(plain_coeff + m) * cur; + } else { + *(encrypted_pointer + m + j * coeff_count) += + *(plain_coeff + m) * cur; + } + } + + cur <<= logt; + } + } + } + + return result; +} + +void SealPirClient::SendGaloisKeys( + const std::shared_ptr<yacl::link::Context> &link_ctx) { + seal::GaloisKeys galkey = GenerateGaloisKeys(); + + std::string galkey_str = SerializeSealObject<seal::GaloisKeys>(galkey); + yacl::Buffer galkey_buffer(galkey_str.data(), galkey_str.length()); + + link_ctx->SendAsync( + link_ctx->NextRank(), galkey_buffer, + fmt::format("send galios key to rank-{}", link_ctx->Rank())); +} + +std::vector<uint8_t> SealPirClient::DoPirQuery( + const std::shared_ptr<yacl::link::Context> &link_ctx, size_t db_index) { + size_t query_index = db_index; + size_t start_pos = 0; + // + if (options_.query_size != 0) { + query_index = db_index % options_.query_size; + start_pos = db_index - query_index; + } + + std::vector<std::vector<seal::Ciphertext>> query_ciphers = + GenerateQuery(query_index); + + SealPirQueryProto query_proto; + query_proto.set_query_size(options_.query_size); + query_proto.set_start_pos(start_pos); + + yacl::Buffer query_buffer = SerializeQuery(&query_proto, query_ciphers); + link_ctx->SendAsync( + link_ctx->NextRank(), query_buffer, + fmt::format("send query message({})", query_buffer.size())); + + yacl::Buffer reply_buffer = + link_ctx->Recv(link_ctx->NextRank(), fmt::format("send query message")); + + std::vector<seal::Ciphertext> reply_ciphers = + DeSerializeCiphertexts(reply_buffer); + + seal::Plaintext query_plain = DecodeReply(reply_ciphers); + + std::vector<uint8_t> plaintext_bytes = PlaintextToBytes(query_plain); + + std::vector<uint8_t> query_reply_data(options_.element_size); + + size_t offset = GetQueryOffset(query_index); + + std::memcpy(query_reply_data.data(), + &plaintext_bytes[offset * options_.element_size], + options_.element_size); + + return query_reply_data; +} + +} // namespace psi::pir diff --git a/psi/pir/seal_pir.h b/psi/pir/seal_pir.h new file mode 100644 index 00000000..2c3a90b7 --- /dev/null +++ b/psi/pir/seal_pir.h @@ -0,0 +1,273 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <cmath> +#include <memory> +#include <string> +#include <vector> + +#include "seal/seal.h" +#include "yacl/base/byte_container_view.h" +#include "yacl/link/link.h" + +#include "psi/pir/seal_pir_utils.h" + +#include "psi/pir/serializable.pb.h" + +namespace psi::pir { + +// +// SealPIR paper: +// PIR with compressed queries and amortized query processing +// https://eprint.iacr.org/2017/1142.pdf +// code reference microsoft opensource implement: +// https://github.com/microsoft/SealPIR + +struct SealPirOptions { + // RLWE polynomial degree + size_t poly_modulus_degree; + // db element number + size_t element_number; + // byte size of per element + size_t element_size; + // number of real query data + size_t query_size = 0; +}; + +struct PirParams { + std::uint64_t n; // number of plaintexts in database + std::uint32_t d; // number of dimensions for the database (1 or 2) + std::uint32_t expansion_ratio; // ratio of ciphertext to plaintext + std::uint32_t dbc; // decomposition bit count (used by relinearization) + std::vector<std::uint64_t> nvec; // size of each of the d dimensions +}; + +class SealPir { + public: + explicit SealPir(const SealPirOptions &options) : options_(options) { + SetPolyModulusDegree(options.poly_modulus_degree); + + evaluator_ = std::make_unique<seal::Evaluator>(*context_); + + if (options.query_size > 0) { + SetPirParams(options.query_size, options.element_size); + } else { + SetPirParams(options.element_number, options.element_size); + } + } + + /** + * @brief Set the seal parameter Poly Modulus Degree + * + * @param degree seal Poly degree 2048/4096/8192 + */ + void SetPolyModulusDegree(size_t degree); + + /** + * @brief Set the Pir Params object + * + * @param element_number db element_number + * @param element_size db element bytes + */ + void SetPirParams(size_t element_number, size_t element_size); + + template <typename T> + std::string SerializeSealObject(const T &object) { + std::ostringstream output; + object.save(output); + return output.str(); + } + + template <typename T> + T DeSerializeSealObject(const std::string &object_bytes, + bool safe_load = false) { + T seal_object; + std::istringstream object_input(object_bytes); + if (safe_load) { + seal_object.load(*context_, object_input); + } else { + seal_object.unsafe_load(*context_, object_input); + } + return seal_object; + } + + std::string SerializePlaintexts(const std::vector<seal::Plaintext> &plains); + + std::vector<seal::Plaintext> DeSerializePlaintexts( + const std::string &plaintext_bytes, bool safe_load = false); + + yacl::Buffer SerializeCiphertexts( + const std::vector<seal::Ciphertext> &ciphers); + + std::vector<seal::Ciphertext> DeSerializeCiphertexts( + const CiphertextsProto &ciphers_proto, bool safe_load = false); + + std::vector<seal::Ciphertext> DeSerializeCiphertexts( + const yacl::Buffer &ciphers_buffer, bool safe_load = false); + + yacl::Buffer SerializeQuery( + SealPirQueryProto *query_proto, + const std::vector<std::vector<seal::Ciphertext>> &query_ciphers); + + yacl::Buffer SerializeQuery( + const std::vector<std::vector<seal::Ciphertext>> &query_ciphers); + + std::vector<std::vector<seal::Ciphertext>> DeSerializeQuery( + const yacl::Buffer &query_buffer, bool safe_load = false); + + std::vector<std::vector<seal::Ciphertext>> DeSerializeQuery( + const SealPirQueryProto &query_proto, bool safe_load = false); + + protected: + SealPirOptions options_; + PirParams pir_params_; + std::unique_ptr<seal::EncryptionParameters> seal_params_; + + std::unique_ptr<seal::SEALContext> context_; + std::unique_ptr<seal::Evaluator> evaluator_; +}; + +// +// general single server PIR protocol +// +// PirClient PirServer +// SetupDB offline +// ====================================== +// query online +// ------------> +// answer +// <------------ +// extract result +// + +// +// SealPIR protocol +// +// SealPirClient SealPirServer +// SetupDB offline +// ================================== +// SendGaloisKeys online +// ------------> SetGaloisKeys +// ---------------------------------- +// DoPirQuery DoPirAnswer +// GenerateQuery +// ------------> ExpandQuery +// GenerateReply +// <------------ +// DecodeReply +// + +class SealPirClient; + +class SealPirServer : public SealPir { + public: +#ifdef DEC_DEBUG_ + SealPirServer(const SealPirOptions &options, SealPirClient &client); + +#else + SealPirServer(const SealPirOptions &options, + std::shared_ptr<IDbPlaintextStore> plaintext_store); +#endif + + // read db data, convert to Seal::Plaintext + void SetDatabase(const std::shared_ptr<IDbElementProvider> &db_provider); + void SetDatabase(const std::vector<yacl::ByteContainerView> &db_vec); + + // set client GaloisKeys + void SetGaloisKeys(const seal::GaloisKeys &galkey) { galois_key_ = galkey; } + + // expand one query Seal:Ciphertext + std::vector<seal::Ciphertext> ExpandQuery(const seal::Ciphertext &encrypted, + std::uint32_t m); + + // GenerateReply for query_ciphers + std::vector<seal::Ciphertext> GenerateReply( + const std::vector<std::vector<seal::Ciphertext>> &query_ciphers, + size_t start_pos = 0); + + std::string SerializeDbPlaintext(int db_index = 0); + void DeSerializeDbPlaintext(const std::string &db_serialize_bytes, + int db_index = 0); + + // receive, deserialize, and set client GaloisKeys + void RecvGaloisKeys(const std::shared_ptr<yacl::link::Context> &link_ctx); + + // receive client query, and answer + void DoPirAnswer(const std::shared_ptr<yacl::link::Context> &link_ctx); + + private: + // for debug use, get noise budget +#ifdef DEC_DEBUG_ + SealPirClient &client_; +#endif + std::vector<std::unique_ptr<std::vector<seal::Plaintext>>> db_vec_; + std::shared_ptr<IDbPlaintextStore> plaintext_store_; + + seal::GaloisKeys galois_key_; + + void DecomposeToPlaintextsPtr(const seal::Ciphertext &encrypted, + seal::Plaintext *plain_ptr, int logt); + std::vector<seal::Plaintext> DecomposeToPlaintexts( + const seal::Ciphertext &encrypted); +}; + +class SealPirClient : public SealPir { + public: + explicit SealPirClient(const SealPirOptions &options); + + // db_index to seal::Plaintexts index and offset + uint64_t GetQueryIndex(uint64_t element_idx); + uint64_t GetQueryOffset(uint64_t element_idx); + + // get Seal::Ciphertext + std::vector<std::vector<seal::Ciphertext>> GenerateQuery(size_t index); + + // decode server's answer reply + seal::Plaintext DecodeReply(const std::vector<seal::Ciphertext> &reply); + + // send GaloisKeys to server + void SendGaloisKeys(const std::shared_ptr<yacl::link::Context> &link_ctx); + + // generate GaloisKeys + seal::GaloisKeys GenerateGaloisKeys(); + + void ComputeInverseScales(); + + // when Dimension > 1 + // Compose plaintexts to ciphertext + seal::Ciphertext ComposeToCiphertext( + const std::vector<seal::Plaintext> &plains); + + // plaintext coefficient to bytes + std::vector<uint8_t> PlaintextToBytes(const seal::Plaintext &plain); + + // PirQuery + std::vector<uint8_t> DoPirQuery( + const std::shared_ptr<yacl::link::Context> &link_ctx, size_t db_index); + + private: + std::unique_ptr<seal::KeyGenerator> keygen_; + + std::unique_ptr<seal::Encryptor> encryptor_; + std::unique_ptr<seal::Decryptor> decryptor_; + + std::vector<uint64_t> indices_; // the indices for retrieval. + std::vector<uint64_t> inverse_scales_; + + // set friend class + friend class SealPirServer; +}; +} // namespace psi::pir diff --git a/psi/pir/seal_pir_test.cc b/psi/pir/seal_pir_test.cc new file mode 100644 index 00000000..a8849711 --- /dev/null +++ b/psi/pir/seal_pir_test.cc @@ -0,0 +1,137 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/pir/seal_pir.h" + +#include <chrono> +#include <memory> +#include <random> + +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/link/test_util.h" + +namespace psi::pir { +namespace { +struct TestParams { + size_t element_number; + size_t element_size = 288; + size_t query_size = 0; +}; + +std::vector<uint8_t> GenerateDbData(TestParams params) { + std::vector<uint8_t> db_data(params.element_number * params.element_size); + + std::random_device rd; + + std::mt19937 gen(rd()); + + for (uint64_t i = 0; i < params.element_number; i++) { + for (uint64_t j = 0; j < params.element_size; j++) { + auto val = gen() % 256; + db_data[(i * params.element_size) + j] = val; + } + } + return db_data; +} + +using DurationMillis = std::chrono::duration<double, std::milli>; +} // namespace + +class SealPirTest : public testing::TestWithParam<TestParams> {}; + +TEST_P(SealPirTest, Works) { + size_t n = 8192; + auto params = GetParam(); + auto ctxs = yacl::link::test::SetupWorld(2); + + std::vector<uint8_t> db_data = GenerateDbData(params); + + psi::pir::SealPirOptions options{n, params.element_number, + params.element_size, params.query_size}; + + psi::pir::SealPirClient client(options); + + std::shared_ptr<IDbPlaintextStore> plaintext_store = + std::make_shared<MemoryDbPlaintextStore>(); +#ifdef DEC_DEBUG_ + psi::pir::SealPirServer server(options, client, plaintext_store); +#else + psi::pir::SealPirServer server(options, plaintext_store); +#endif + + // === server setup + + std::shared_ptr<IDbElementProvider> db_provider = + std::make_shared<MemoryDbElementProvider>(db_data, params.element_size); + server.SetDatabase(db_provider); + + /* galkey data 28MB, cause pipeline unittest timeout + // client send galois keys to server + std::future<void> client_galkey_func = + std::async([&] { return client.SendGaloisKeys(ctxs[0]); }); + std::future<void> server_galkey_func = + std::async([&] { return server.RecvGaloisKeys(ctxs[1]); }); + + client_galkey_func.get(); + server_galkey_func.get(); + */ + // use offline + seal::GaloisKeys galkey = client.GenerateGaloisKeys(); + server.SetGaloisKeys(galkey); + + std::random_device rd; + + std::mt19937 gen(rd()); + // size_t index = 40; + size_t index = gen() % params.element_number; + + // do pir query/answer + const auto pir_start_time = std::chrono::system_clock::now(); + + std::future<std::vector<uint8_t>> pir_client_func = + std::async([&] { return client.DoPirQuery(ctxs[0], index); }); + std::future<void> pir_service_func = + std::async([&] { return server.DoPirAnswer(ctxs[1]); }); + + pir_service_func.get(); + std::vector<uint8_t> query_reply_bytes = pir_client_func.get(); + + const auto pir_end_time = std::chrono::system_clock::now(); + const DurationMillis pir_time = pir_end_time - pir_start_time; + + SPDLOG_INFO("pir time : {} ms", pir_time.count()); + + EXPECT_EQ( + std::memcmp(query_reply_bytes.data(), + &db_data[index * params.element_size], params.element_size), + 0); +} + +INSTANTIATE_TEST_SUITE_P( + Works_Instances, SealPirTest, + testing::Values(TestParams{1000}, // element size default 288B + TestParams{1000, 10}, // + TestParams{1000, 20}, // + TestParams{1000, 400}, // + TestParams{3000}, // element size default 288B + TestParams{3000, 10}, // + TestParams{3000, 20}, // + TestParams{3000, 400}, // + // + TestParams{203, 288, 100}, // + TestParams{3000, 288, 1000}) // +); + +} // namespace psi::pir diff --git a/psi/pir/seal_pir_utils.cc b/psi/pir/seal_pir_utils.cc new file mode 100644 index 00000000..ecf4830f --- /dev/null +++ b/psi/pir/seal_pir_utils.cc @@ -0,0 +1,62 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/pir/seal_pir_utils.h" + +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" + +namespace psi::pir { + +std::vector<uint8_t> MemoryDbElementProvider::ReadElement(size_t index) { + YACL_ENFORCE(index < items_.size()); + + std::vector<uint8_t> element(element_size_); + + std::memcpy(element.data(), &items_[index], element_size_); + return element; +} + +std::vector<uint8_t> MemoryDbElementProvider::ReadElement(size_t index, + size_t size) { + YACL_ENFORCE((index + size) <= items_.size()); + + std::vector<uint8_t> element(size); + + std::memcpy(element.data(), &items_[index], size); + return element; +} + +void MemoryDbPlaintextStore::SetSubDbNumber(size_t sub_db_num) { + db_vec_.resize(sub_db_num); +} + +void MemoryDbPlaintextStore::SavePlaintext(const seal::Plaintext& plaintext, + size_t sub_db_index) { + db_vec_[sub_db_index].push_back(plaintext); +} + +void MemoryDbPlaintextStore::SavePlaintexts( + const std::vector<seal::Plaintext>& plaintexts, size_t sub_db_index) { + for (const auto& plaintext : plaintexts) { + db_vec_[sub_db_index].push_back(plaintext); + } +} + +std::vector<seal::Plaintext> MemoryDbPlaintextStore::ReadPlaintexts( + size_t sub_db_index) { + return std::move(db_vec_[sub_db_index]); +} + +} // namespace psi::pir diff --git a/psi/pir/seal_pir_utils.h b/psi/pir/seal_pir_utils.h new file mode 100644 index 00000000..be1c8c67 --- /dev/null +++ b/psi/pir/seal_pir_utils.h @@ -0,0 +1,89 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <string> +#include <utility> +#include <vector> + +#include "seal/seal.h" + +namespace psi::pir { +// Interface which read db data. +class IDbElementProvider { + public: + virtual ~IDbElementProvider() = default; + + // Read at `index` item and return data. An empty returned vector + // is treated as the end of stream. + virtual std::vector<uint8_t> ReadElement(size_t index) = 0; + virtual std::vector<uint8_t> ReadElement(size_t index, size_t size) = 0; + + virtual size_t GetDbSize() = 0; + virtual size_t GetDbByteSize() = 0; +}; + +// Interface which read batch of db data. +class IDbPlaintextStore { + public: + virtual ~IDbPlaintextStore() = default; + + virtual void SetSubDbNumber(size_t sub_db_num) = 0; + + virtual void SavePlaintext(const seal::Plaintext& plaintext, + size_t sub_db_index) = 0; + + virtual void SavePlaintexts(const std::vector<seal::Plaintext>& plaintext, + size_t sub_db_index) = 0; + virtual std::vector<seal::Plaintext> ReadPlaintexts(size_t sub_db_index) = 0; +}; + +class MemoryDbElementProvider : public IDbElementProvider { + public: + explicit MemoryDbElementProvider(const std::vector<uint8_t>& items, + size_t element_size) + : items_(std::move(items)), element_size_(element_size) {} + + std::vector<uint8_t> ReadElement(size_t index) override; + std::vector<uint8_t> ReadElement(size_t index, size_t size) override; + + size_t GetDbSize() override { return items_.size() / element_size_; } + size_t GetDbByteSize() override { return items_.size(); } + + const std::vector<uint8_t>& items() const; + + private: + const std::vector<uint8_t> items_; + size_t element_size_; +}; + +class MemoryDbPlaintextStore : public IDbPlaintextStore { + public: + virtual ~MemoryDbPlaintextStore() = default; + + void SetSubDbNumber(size_t sub_db_num) override; + + void SavePlaintext(const seal::Plaintext& plaintext, + size_t sub_db_index) override; + + void SavePlaintexts(const std::vector<seal::Plaintext>& plaintexts, + size_t sub_db_index) override; + std::vector<seal::Plaintext> ReadPlaintexts(size_t sub_db_index) override; + + private: + std::vector<std::vector<seal::Plaintext>> db_vec_; +}; + +} // namespace psi::pir diff --git a/psi/pir/serializable.proto b/psi/pir/serializable.proto new file mode 100644 index 00000000..e0e19d45 --- /dev/null +++ b/psi/pir/serializable.proto @@ -0,0 +1,47 @@ +// +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +package psi.pir; + +message PlaintextsProto { + repeated bytes data = 1; +} + +message CiphertextsProto { + repeated bytes ciphers = 1; +} + +message SealPirQueryProto { + int32 query_size = 1; + int32 start_pos = 2; + repeated CiphertextsProto query_cipher = 3; +} + +message SealPirAnswerProto { + int32 query_size = 1; + int32 start_pos = 2; + CiphertextsProto answer = 3; +} + +message SealMultiPirQueryProto { + repeated SealPirQueryProto querys = 1; +} + +message SealMultiPirAnswerProto { + repeated SealPirAnswerProto answers = 1; +} diff --git a/psi/proto/BUILD.bazel b/psi/proto/BUILD.bazel new file mode 100644 index 00000000..ec272568 --- /dev/null +++ b/psi/proto/BUILD.bazel @@ -0,0 +1,44 @@ +# Copyright 2023 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_proto//proto:defs.bzl", "proto_library") + +package(default_visibility = ["//visibility:public"]) + +proto_library( + name = "psi_proto", + srcs = ["psi.proto"], + deps = [ + "//psi/psi:psi_proto", + "@yacl//yacl/link:link_proto", + ], +) + +cc_proto_library( + name = "psi_cc_proto", + deps = [":psi_proto"], +) + +proto_library( + name = "kuscia_proto", + srcs = ["kuscia.proto"], + deps = [ + ":psi_proto", + ], +) + +cc_proto_library( + name = "kuscia_cc_proto", + deps = [":kuscia_proto"], +) diff --git a/psi/proto/kuscia.proto b/psi/proto/kuscia.proto new file mode 100644 index 00000000..d8666105 --- /dev/null +++ b/psi/proto/kuscia.proto @@ -0,0 +1,80 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// NOTE(junfeng): This file is copied from +// https://github.com/secretflow/kuscia/blob/main/proto/api/v1alpha1/appconfig/app_config.proto +// Use git submodule or move this app_config proto to SecretFlow Open Spec. + +syntax = "proto3"; + +package kuscia; + +import "psi/proto/psi.proto"; + +option go_package = "github.com/secretflow/kuscia/proto/api/v1alpha1/appconfig"; +option java_package = "com.secretflow.v1alpha1.appconfig"; + +// Service represents the service address corresponding to the port. +message Service { + // Name of port. + string port_name = 1; + // Endpoint list corresponding to the port. + repeated string endpoints = 2; +} + +// Party represents the basic information of the party. +message Party { + // Name of party. + string name = 1; + // role carried by party. Examples: client, server... + string role = 2; + // List of services exposed by pod. + repeated Service services = 3; +} + +// ClusterDefine represents the information of all parties. +message ClusterDefine { + // Basic information of all parties. + repeated Party parties = 1; + // index of self party. + int32 self_party_idx = 2; + // index of self endpoint. + int32 self_endpoint_idx = 3; +} + +// Port represents an allocated port for pod. +message Port { + // Each named port in a pod must have a unique name. + string name = 1; + // Number of port allocated for pod. + int32 port = 2; + // Scope of port. Must be Cluster,Domain,Local. + // Defaults to "Local". + // +optional + string scope = 3; + // Protocol for port. Must be HTTP,GRPC. + // Defaults to "HTTP". + // +optional + string protocol = 4; +} + +// AllocatedPorts represents allocated ports for pod. +message AllocatedPorts { + // Allocated ports. + repeated Port ports = 1; +} + +message TaskInputConfig { + map<string, psi.psi.v2.PsiConfig> sf_psi_config_map = 1; +} diff --git a/psi/proto/psi.proto b/psi/proto/psi.proto new file mode 100644 index 00000000..9a05fcb9 --- /dev/null +++ b/psi/proto/psi.proto @@ -0,0 +1,391 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +import "yacl/link/link.proto"; +import "psi/psi/psi.proto"; + +package psi.psi.v2; + +// Role of parties. +enum Role { + ROLE_UNSPECIFIED = 0; + + // receiver + // In 2P symmetric PSI, receiver would always receive the result. + ROLE_RECEIVER = 1; + + // sender + // In 2P symmetric PSI, sender is the other participant apart from receiver. + ROLE_SENDER = 2; +} + +// PSI protocols. +enum Protocol { + PROTOCOL_UNSPECIFIED = 0; + + // Semi-Honest Secure + + // [Mea86]C. Meadows, "A More Efficient Cryptographic Matchmaking Protocol + // for Use in the Absence of a Continuously Available Third Party," 1986 IEEE + // Symposium on Security and Privacy, Oakland, CA, USA, 1986, pp. 134-134, + // doi: 10.1109/SP.1986.10022. + PROTOCOL_ECDH = 1; + + // Efficient Batched Oblivious PRF with Applications to Private Set + // Intersection https://eprint.iacr.org/2016/799.pdf + PROTOCOL_KKRT = 2; + + // Blazing Fast PSI https://eprint.iacr.org/2022/320.pdf + PROTOCOL_RR22 = 3; +} + +// Configs for ECDH protocol. +message EcdhConfig { + .psi.psi.CurveType curve = 1; +} + +// Configs for KKRT protocol +message KkrtConfig { + // Since the total input may not fit in memory, the input may be splitted into + // buckets. bucket_size indicate the number of items in each bucket. + // + // If the memory of host is limited, you should set a smaller bucket size. + // Otherwise, you should use a larger one. + // + // If not set, use default value: 1 << 20. + uint64 bucket_size = 1; +} + +// Configs for RR22 protocol. +message Rr22Config { + // Since the total input may not fit in memory, the input may be splitted into + // buckets. bucket_size indicate the number of items in each bucket. + // + // If the memory of host is limited, you should set a smaller bucket size. + // Otherwise, you should use a larger one. + // + // If not set, use default value: 1 << 20. + uint64 bucket_size = 1; + + bool low_comm_mode = 2; +} + +// Any items related to PSI protocols. +message ProtocolConfig { + Protocol protocol = 1; + + Role role = 2; + + // Reveal result to sender. + bool broadcast_result = 3; + + // For ECDH protocol. + EcdhConfig ecdh_config = 4; + + // For KKRT protocol. + KkrtConfig kkrt_config = 5; + + // For RR22 protocol. + Rr22Config rr22_config = 6; +} + +// Stores input or output data. +// For IoType::IO_TYPE_MEM_RAW. +message Table { + message Row { + repeated string values = 1; + } + + Row header = 1; + repeated Row data = 2; +} + +enum IoType { + IO_TYPE_UNSPECIFIED = 0; + + // Local csv file. + IO_TYPE_FILE_CSV = 1; + + // With Table pb msg. + IO_TYPE_MEM_RAW = 2; +} + +// Input configuration. +message InputConfig { + IoType type = 1; + + // Required for FILE. + string path = 2; + // Required for RAW. + Table raw = 3; +} + +// Output configuration. +message OutputConfig { + // If true, type of output would be the same as input type. And type would be + // ngelected. + bool input_type_followed = 1; + + IoType type = 2; + + // Required for FILE. + string path = 3; +} + +// Configuration for recovery. +// If a PSI task failed unexpectedly, e.g. network failures and restart, the +// task can resume to the latest checkpoint to save time. +// However, enabling recovery would due in extra disk IOs and disk space +// occupation. +message RecoveryConfig { + bool enabled = 1; + + // Stores status and checkpoint files. + string folder = 2; +} + +message DebugOptions { + // Logging level for default logger. + // Default to info. + // Supports: + // trace - SPDLOG_LEVEL_TRACE, + // debug - SPDLOG_LEVEL_DEBUG, + // info - SPDLOG_LEVEL_INFO, + // warn - SPDLOG_LEVEL_WARN, + // err - SPDLOG_LEVEL_ERROR, + // critical - SPDLOG_LEVEL_CRITICAL, + // off - SPDLOG_LEVEL_OFF, + string logging_level = 1; + + // The path of trace. + // Deafult to /tmp/psi.trace + string trace_path = 2; +} + +// The top level of Configs. +// run(PsiConfig)->PsiReport +message PsiConfig { + // Configs for protocols. + ProtocolConfig protocol_config = 1; + + // Configs for input. + InputConfig input_config = 2; + + // Configs for output. + OutputConfig output_config = 3; + + // Configs for network. + yacl.link.ContextDescProto link_config = 4; + + string self_link_party = 5; + + // keys for intersection. + repeated string keys = 6; + + // Logging level. + DebugOptions debug_options = 7; + + // If true, a precheck of duplicated items will be conducted. + // An early exception would be throw before PSI. + bool check_duplicates = 8; + + // It true, output defference instead of intersection. + bool output_difference = 9; + + // It true, output is sorted according to keys in InputConfig. + bool sort_output = 10; + + // Configs for recovery. + RecoveryConfig recovery_config = 11; + + // Advanced modes which allow duplicate keys. + enum AdvancedJoinType { + ADVANCED_JOIN_TYPE_UNSPECIFIED = 0; + + // Advanced mode: Inner Join + // If selected, check_duplicates is invalid. + // If selected, both parties could have duplicate keys. + // e.g. If input of receiver is + // | key1 | value1| + // |------|-------| + // | x | 1 | + // | x | 2 | + // | x | 3 | + // | y | 4 | + // and input of sender is + // | key2 | value2| + // |------|-------| + // | x | a | + // | x | b | + // | z | c | + // + // After inner join. + // The ourput of receiver is: + // | key1 | value1| + // |------|-------| + // | x | 1 | + // | x | 2 | + // | x | 3 | + // | x | 1 | + // | x | 2 | + // | x | 3 | + // The output of sender is + // | key2 | value2| + // |------|-------| + // | x | a | + // | x | b | + // | x | a | + // | x | b | + // | x | a | + // | x | b | + // + ADVANCED_JOIN_TYPE_INNER_JOIN = 1; + + // Advanced mode: Left Join + // If selected, check_duplicates is invalid. + // If selected, both parties could have duplicate keys. + // e.g. If input of left side is | key1 | value1| + // |------|-------| + // | x | 1 | + // | x | 2 | + // | x | 3 | + // | y | 4 | + // and input of right side is + // | key2 | value2| + // |------|-------| + // | x | a | + // | x | b | + // | z | c | + // + // After inner join. + // The ourput of left side is: + // | key1 | value1| + // |------|-------| + // | x | 1 | + // | x | 2 | + // | x | 3 | + // | x | 1 | + // | x | 2 | + // | x | 3 | + // | y | 4 | + // The output of right side is + // | key2 | value2| + // |------|-------| + // | x | a | + // | x | b | + // | x | a | + // | x | b | + // | x | a | + // | x | b | + // | n/a | n/a | + ADVANCED_JOIN_TYPE_LEFT_JOIN = 2; + } + + AdvancedJoinType advanced_join_type = 12; + + // Required if advanced_join_type is ADVANCED_JOIN_TYPE_INNER_JOIN. + Role left_side = 13; + + // Check if hash digest of keys from parties are equal to determine whether to + // early-stop. + bool check_hash_digest = 14; +} + +// Execution Report. +message PsiReport { + // The data count of input. + int64 original_count = 1; + + // The count of intersection. Get `-1` when self party can not get result. + int64 intersection_count = 2; + + // Maybe used if output type is RAW. + Table output = 3; +} + +// Save some critical information for future recovery. +message RecoveryCheckpoint { + enum Stage { + STAGE_UNSPECIFIED = 0; + + STAGE_INIT_END = 1; + + STAGE_PRE_PROCESS_END = 2; + + STAGE_ONLINE_START = 3; + + STAGE_ONLINE_END = 4; + + STAGE_POST_PROCESS_END = 5; + } + + // Stage of PSI. + Stage stage = 1; + + // A copy of origin PSI config. + PsiConfig config = 2; + + // Hash digest of input keys. + string input_hash_digest = 3; + + // Saved dual masked item count from self originally. + // PROTOCOL_ECDH only. + uint64 ecdh_dual_masked_item_self_count = 4; + + // Saved dual masked item count from peer originally. + // PROTOCOL_ECDH only. + uint64 ecdh_dual_masked_item_peer_count = 5; + + // Saved parsed bucket count. + // PROTOCOL_KKRT and PROTOCOL_RR22 only. + uint64 parsed_bucket_count = 6; +} + +// Internal usage only at this moment. +message InnerJoinConfig { + // Path of origin input. + string input_path = 1; + + // The role of party, + Role role = 2; + + // Keys for PSI. + repeated string keys = 3; + + // Path of sorted input depending on keys. + string sorted_input_path = 4; + + // Path of unique keys and cnt + string unique_input_keys_cnt_path = 5; + + // Path of PSI output with unique_input_keys_cnt_path as input. + string self_intersection_cnt_path = 6; + + // Path of received peer intersection count. + string peer_intersection_cnt_path = 7; + + // The path of output for inner join. + string output_path = 8; +} + +message InternalRecoveryRecord { + RecoveryCheckpoint.Stage stage = 1; + + uint64 ecdh_dual_masked_item_peer_count = 2; + + uint64 parsed_bucket_count = 3; +} diff --git a/psi/psi/BUILD.bazel b/psi/psi/BUILD.bazel new file mode 100644 index 00000000..1ea1a238 --- /dev/null +++ b/psi/psi/BUILD.bazel @@ -0,0 +1,250 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//bazel:psi.bzl", "psi_cc_binary", "psi_cc_library", "psi_cc_test") +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@rules_cc//cc:defs.bzl", "cc_proto_library") + +package(default_visibility = ["//visibility:public"]) + +proto_library( + name = "psi_proto", + srcs = ["psi.proto"], +) + +cc_proto_library( + name = "psi_cc_proto", + deps = [":psi_proto"], +) + +psi_cc_library( + name = "memory_psi", + srcs = ["memory_psi.cc"], + hdrs = [ + "memory_psi.h", + ], + deps = [ + ":prelude", + ":psi_cc_proto", + "//psi/psi/core:ecdh_psi", + "//psi/psi/operator", + "//psi/psi/operator:factory", + "//psi/psi/utils", + ], +) + +psi_cc_library( + name = "prelude", + hdrs = [ + "prelude.h", + ], + deps = [ + ":psi_cc_proto", + "//psi/proto:psi_cc_proto", + ], +) + +psi_cc_test( + name = "memory_psi_test", + srcs = ["memory_psi_test.cc"], + deps = [ + ":memory_psi", + "//psi/psi/utils:test_utils", + ], +) + +psi_cc_library( + name = "bucket_ub_psi", + srcs = ["bucket_ub_psi.cc"], + hdrs = [ + "bucket_ub_psi.h", + ], + deps = [ + ":prelude", + ":psi_cc_proto", + "//psi/psi/core:ecdh_oprf_psi", + "//psi/psi/utils:batch_provider", + "//psi/psi/utils:csv_checker", + "//psi/psi/utils:csv_header_analyzer", + "//psi/psi/utils:ec_point_store", + "//psi/psi/utils:progress", + "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/time", + "@yacl//yacl/utils:scope_guard", + ], +) + +psi_cc_library( + name = "bucket_psi", + srcs = ["bucket_psi.cc"], + hdrs = [ + "bucket_psi.h", + ], + deps = [ + ":bucket_ub_psi", + ":memory_psi", + ":prelude", + ":psi_cc_proto", + "//psi/psi/utils:batch_provider", + "//psi/psi/utils:csv_checker", + "//psi/psi/utils:csv_header_analyzer", + "//psi/psi/utils:ec_point_store", + "//psi/psi/utils:file", + ], +) + +psi_cc_test( + name = "bucket_psi_test", + srcs = ["bucket_psi_test.cc"], + deps = [ + ":bucket_psi", + "@yacl//yacl/utils:scope_guard", + ], +) + +psi_cc_test( + name = "bucket_ub_psi_test", + srcs = ["bucket_ub_psi_test.cc"], + deps = [ + ":bucket_psi", + "//psi/psi/utils:test_utils", + ], +) + +psi_cc_library( + name = "interface", + srcs = ["interface.cc"], + hdrs = ["interface.h"], + deps = [ + ":recovery", + ":trace_categories", + "//psi/proto:psi_cc_proto", + "//psi/psi:bucket_psi", + "//psi/psi/utils:index_store", + "//psi/psi/utils:inner_join", + "@com_github_google_perfetto//:perfetto", + "@com_google_absl//absl/status", + "@yacl//yacl/link", + ], +) + +psi_cc_library( + name = "factory", + srcs = ["factory.cc"], + hdrs = ["factory.h"], + deps = [ + "//psi/psi/ecdh:receiver", + "//psi/psi/ecdh:sender", + "//psi/psi/kkrt:receiver", + "//psi/psi/kkrt:sender", + "//psi/psi/rr22:receiver", + "//psi/psi/rr22:sender", + "@yacl//yacl/base:exception", + ], +) + +psi_cc_binary( + name = "main", + srcs = ["main.cc"], + deps = [ + ":factory", + ":kuscia_adapter", + ":trace_categories", + "//psi:version", + "@boost//:algorithm", + "@com_github_gflags_gflags//:gflags", + ], +) + +psi_cc_library( + name = "trace_categories", + srcs = ["trace_categories.cc"], + hdrs = ["trace_categories.h"], + deps = [ + "@com_github_google_perfetto//:perfetto", + ], +) + +psi_cc_library( + name = "recovery", + srcs = [ + "recovery.cc", + ], + hdrs = [ + "recovery.h", + ], + deps = [ + "//psi/proto:psi_cc_proto", + "//psi/psi/cryptor:ecc_cryptor", + "//psi/psi/io", + "@yacl//yacl/base:exception", + "@yacl//yacl/link", + ], +) + +psi_cc_test( + name = "recovery_test", + srcs = ["recovery_test.cc"], + deps = [ + ":recovery", + "//psi/psi/cryptor:cryptor_selector", + ], +) + +psi_cc_library( + name = "kuscia_adapter", + srcs = [ + "kuscia_adapter.cc", + ], + hdrs = [ + "kuscia_adapter.h", + ], + deps = [ + "//psi/proto:kuscia_cc_proto", + "//psi/proto:psi_cc_proto", + "@com_github_tencent_rapidjson//:rapidjson", + "@yacl//yacl/base:exception", + ], +) + +psi_cc_test( + name = "kuscia_adapter_test", + srcs = ["kuscia_adapter_test.cc"], + deps = [ + ":kuscia_adapter", + ], +) + +psi_cc_test( + name = "psi_test", + srcs = ["psi_test.cc"], + deps = [ + ":factory", + "//psi/psi/utils:arrow_csv_batch_provider", + "@yacl//yacl/utils:scope_guard", + ], +) + +psi_cc_library( + name = "bucket", + srcs = ["bucket.cc"], + hdrs = ["bucket.h"], + deps = [ + "//psi/proto:psi_cc_proto", + "//psi/psi:prelude", + "//psi/psi:recovery", + "//psi/psi/utils", + "//psi/psi/utils:hash_bucket_cache", + ], +) diff --git a/psi/psi/benchmark/BUILD.bazel b/psi/psi/benchmark/BUILD.bazel new file mode 100644 index 00000000..93e850be --- /dev/null +++ b/psi/psi/benchmark/BUILD.bazel @@ -0,0 +1,62 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//bazel:psi.bzl", "psi_cc_binary") + +package(default_visibility = ["//visibility:public"]) + +psi_cc_binary( + name = "standalone_bench", + srcs = [ + "standalone_bench.cc", + "standalone_bench.h", + ], + deps = [ + "//psi/psi/core:ecdh_oprf_psi", + "//psi/psi/core:ecdh_psi", + "//psi/psi/core:kkrt_psi", + "//psi/psi/core:mini_psi", + "//psi/psi/core/bc22_psi", + "//psi/psi/utils:test_utils", + "@com_github_google_benchmark//:benchmark", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", + "@yacl//yacl/link:test_util", + "@yacl//yacl/utils:scope_guard", + ], +) + +psi_cc_binary( + name = "mparty_bench", + srcs = [ + "mparty_bench.cc", + "mparty_bench.h", + ], + deps = [ + "//psi/psi:psi_cc_proto", + "//psi/psi/core:ecdh_oprf_psi", + "//psi/psi/core:ecdh_psi", + "//psi/psi/core:kkrt_psi", + "//psi/psi/core:mini_psi", + "//psi/psi/core/bc22_psi", + "//psi/psi/utils:test_utils", + "@com_github_google_benchmark//:benchmark", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", + "@yacl//yacl/base:int128", + "@yacl//yacl/crypto/base/hash:hash_utils", + "@yacl//yacl/link:factory", + "@yacl//yacl/utils:scope_guard", + ], +) diff --git a/psi/psi/benchmark/mparty_bench.cc b/psi/psi/benchmark/mparty_bench.cc new file mode 100644 index 00000000..4b8a3872 --- /dev/null +++ b/psi/psi/benchmark/mparty_bench.cc @@ -0,0 +1,102 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/benchmark/mparty_bench.h" + +#include <string> +#include <vector> + +#include "gflags/gflags.h" + +DEFINE_uint32(rank, 0, "self rank, starts with 0"); + +DEFINE_string(parties, "", + "server list, format: host1:port1[,host2:port2, ...]"); + +namespace psi::psi::bench { + +void DefaultPsiArguments(benchmark::internal::Benchmark* b) { + b->Args({1 << 18}) + ->Args({1 << 20}) + ->Args({1 << 22}) + ->Args({1 << 24}) + ->Args({1000000}) + ->Args({5000000}) + ->Args({10000000}) + ->Iterations(1) + ->Unit(benchmark::kSecond); +} + +// register benchmarks with arguments +BM_REGISTER_ALL_PSI(DefaultPsiArguments); +// +// Equivalent to the following: +// +// BM_REGISTER_ECDH_PSI(DefaultPsiArguments); +// BM_REGISTER_ECDH_OPRF_PSI(DefaultPsiArguments); +// BM_REGISTER_KKRT_PSI(DefaultPsiArguments); +// BM_REGISTER_BC22_PSI(DefaultPsiArguments); +// BM_REGISTER_MINI_PSI(DefaultPsiArguments); + +} // namespace psi::psi::bench + +namespace { +void PreparePsiBench(const uint32_t rank, const std::string& parties) { + std::vector<std::string> host_ips; + if (parties.empty()) { + // default ips for semi2k + host_ips = absl::StrSplit(psi::psi::bench::kTwoPartyHosts, ','); + } else { + host_ips = absl::StrSplit(parties, ','); + } + YACL_ENFORCE(host_ips.size() == 2); + + yacl::link::ContextDesc lctx_desc; + for (size_t i = 0; i < 2; i++) { + const std::string id = fmt::format("party{}", i); + lctx_desc.parties.push_back({id, host_ips[i]}); + benchmark::AddCustomContext(fmt::format("Benchmark Party-{} IP", i), + host_ips[i]); + } + + // setup bench_lctx and link + yacl::link::FactoryBrpc factory; + psi::psi::bench::PsiBench::bench_lctx = + factory.CreateContext(lctx_desc, rank); + psi::psi::bench::PsiBench::bench_lctx->ConnectToMesh(); +} +} // namespace + +// the main function +int main(int argc, char** argv) { + gflags::AllowCommandLineReparsing(); + gflags::ParseCommandLineFlags(&argc, &argv, true); + + try { + PreparePsiBench(FLAGS_rank, FLAGS_parties); + + // these entries are from BENCHMARK_MAIN + // ::benchmark::Initialize(&argc, argv); // remove all benchmark flags + // if (::benchmark::ReportUnrecognizedArguments(argc, argv)) return 1; + ::benchmark::RunSpecifiedBenchmarks(); + ::benchmark::Shutdown(); + + // sync close + psi::psi::bench::PsiBench::bench_lctx->WaitLinkTaskFinish(); + } catch (std::exception& e) { + exit(EXIT_FAILURE); + } + + return 0; +} diff --git a/psi/psi/benchmark/mparty_bench.h b/psi/psi/benchmark/mparty_bench.h new file mode 100644 index 00000000..0bb42aa2 --- /dev/null +++ b/psi/psi/benchmark/mparty_bench.h @@ -0,0 +1,308 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <algorithm> +#include <future> +#include <iostream> +#include <memory> +#include <optional> +#include <random> +#include <string> +#include <vector> + +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "benchmark/benchmark.h" +#include "yacl/base/exception.h" +#include "yacl/base/int128.h" +#include "yacl/utils/scope_guard.h" + +#include "psi/psi/core/bc22_psi/bc22_psi.h" +#include "psi/psi/core/ecdh_oprf_psi.h" +#include "psi/psi/core/ecdh_psi.h" +#include "psi/psi/core/kkrt_psi.h" +#include "psi/psi/core/mini_psi.h" +#include "psi/psi/cryptor/cryptor_selector.h" +#include "psi/psi/io/io.h" +#include "psi/psi/utils/batch_provider.h" +#include "psi/psi/utils/ec_point_store.h" +#include "psi/psi/utils/test_utils.h" + +namespace psi::psi::bench { + +namespace { + +void WriteCsvFile(const std::string& file_name, + const std::vector<std::string>& items) { + auto out = io::BuildOutputStream(io::FileIoOptions(file_name)); + out->Write("id\n"); + for (const auto& data : items) { + out->Write(fmt::format("{}\n", data)); + } + out->Close(); +} + +} // namespace + +const char kTwoPartyHosts[] = "127.0.0.1:9540,127.0.0.1:9541"; + +class PsiBench : public benchmark::Fixture { + public: + static std::shared_ptr<yacl::link::Context> bench_lctx; + PsiBench() { + spdlog::set_level(spdlog::level::off); // turn off spdlog + } +}; + +std::shared_ptr<yacl::link::Context> PsiBench::bench_lctx = nullptr; + +#define PSI_BM_DEFINE_ECDH_TYPE(CurveType) \ + BENCHMARK_DEFINE_F(PsiBench, EcdhPsi_##CurveType) \ + (benchmark::State & state) { \ + for (auto _ : state) { \ + state.PauseTiming(); \ + size_t numel = state.range(0); \ + auto items = psi::test::CreateRangeItems(bench_lctx->Rank(), numel); \ + const auto curve = psi::test::GetOverrideCurveType(); \ + \ + state.ResumeTiming(); \ + \ + psi::RunEcdhPsi(bench_lctx, items, 0, \ + curve.has_value() ? *curve : (CurveType)); \ + } \ + } + +#define PSI_BM_DEFINE_ECDH() \ + PSI_BM_DEFINE_ECDH_TYPE(CURVE_25519); \ + PSI_BM_DEFINE_ECDH_TYPE(CURVE_FOURQ); \ + PSI_BM_DEFINE_ECDH_TYPE(CURVE_SM2); \ + PSI_BM_DEFINE_ECDH_TYPE(CURVE_SECP256K1); + +PSI_BM_DEFINE_ECDH() + +#define PSI_BM_DEFINE_ECDH_OPRF_FULL(CurveType) \ + BENCHMARK_DEFINE_F(PsiBench, EcdhPsiOprf_##CurveType) \ + (benchmark::State & state) { \ + for (auto _ : state) { \ + state.PauseTiming(); \ + size_t numel = state.range(0); \ + auto items = psi::test::CreateRangeItems(bench_lctx->Rank(), numel); \ + \ + /* We let bob obtains the final result */ \ + state.ResumeTiming(); \ + if (bench_lctx->Rank() == 0) { \ + EcdhOprfPsiOptions options; \ + options.curve_type = (CurveType); \ + options.link0 = bench_lctx; \ + options.link1 = bench_lctx->Spawn(); \ + auto offline_proc = EcdhOprfPsiServer(options); \ + const auto sk = offline_proc.GetPrivateKey(); \ + auto online_proc = EcdhOprfPsiServer(options, sk); \ + \ + /* offline: init */ \ + auto timestamp_str = std::to_string(absl::ToUnixNanos(absl::Now())); \ + /* server input */ \ + auto server_input_path = std::filesystem::path( \ + fmt::format("server-input-{}", timestamp_str)); \ + \ + /* server output */ \ + auto server_tmp_cache_path = \ + std::filesystem::path(fmt::format("tmp-cache-{}", timestamp_str)); \ + /* register remove of temp file. */ \ + ON_SCOPE_EXIT([&] { \ + std::error_code ec; \ + std::filesystem::remove(server_input_path, ec); \ + if (ec.value() != 0) { \ + SPDLOG_WARN("can not remove tmp file: {}, msg: {}", \ + server_input_path.c_str(), ec.message()); \ + } \ + std::filesystem::remove(server_tmp_cache_path, ec); \ + if (ec.value() != 0) { \ + SPDLOG_WARN("can not remove tmp file: {}, msg: {}", \ + server_tmp_cache_path.c_str(), ec.message()); \ + } \ + }); \ + \ + WriteCsvFile(server_input_path.string(), items); \ + std::vector<std::string> cloumn_ids = {"id"}; \ + std::shared_ptr<CachedCsvBatchProvider> item_provider = \ + std::make_shared<CachedCsvBatchProvider>( \ + server_input_path.string(), cloumn_ids, kEcdhOprfPsiBatchSize, \ + 100000, true); \ + \ + std::shared_ptr<IUbPsiCache> ub_cache = std::make_shared<UbPsiCache>( \ + server_tmp_cache_path.string(), offline_proc.GetCompareLength(), \ + cloumn_ids); \ + \ + offline_proc.FullEvaluate(item_provider, ub_cache); \ + \ + /* offline: finalize */ \ + std::shared_ptr<IBasicBatchProvider> batch_provider = \ + std::make_shared<UbPsiCacheProvider>( \ + server_tmp_cache_path.string(), kEcdhOprfPsiBatchSize, \ + offline_proc.GetCompareLength()); \ + offline_proc.SendFinalEvaluatedItems(batch_provider); \ + \ + /* online */ \ + online_proc.RecvBlindAndSendEvaluate(); \ + \ + } else { \ + EcdhOprfPsiOptions options; \ + options.curve_type = (CurveType); \ + options.link0 = bench_lctx; \ + options.link1 = bench_lctx->Spawn(); \ + auto self_ec_point_store = std::make_shared<MemoryEcPointStore>(); \ + auto peer_ec_point_store = std::make_shared<MemoryEcPointStore>(); \ + auto offline_proc = EcdhOprfPsiClient(options); \ + auto online_proc = EcdhOprfPsiClient(options); \ + \ + /* offline: recv and evaluate */ \ + offline_proc.RecvFinalEvaluatedItems(peer_ec_point_store); \ + \ + /* online */ \ + auto proc_send = std::async([&] { \ + auto item_provider = std::make_shared<MemoryBatchProvider>( \ + items, kEcdhOprfPsiBatchSize); \ + online_proc.SendBlindedItems(item_provider); \ + }); \ + \ + auto proc_recv = std::async( \ + [&] { online_proc.RecvEvaluatedItems(self_ec_point_store); }); \ + \ + proc_send.get(); \ + proc_recv.get(); \ + \ + /* online: finalize */ \ + auto& peer_results = peer_ec_point_store->content(); \ + auto& self_results = self_ec_point_store->content(); \ + std::sort(peer_results.begin(), peer_results.end()); \ + \ + std::vector<std::string> final_result; \ + for (size_t i = 0; i < self_results.size(); i++) { \ + if (std::binary_search(peer_results.begin(), peer_results.end(), \ + self_results[i])) { \ + final_result.push_back(std::to_string(i + 1)); \ + } \ + } \ + } \ + } \ + } + +#define PSI_BM_DEFINE_ECDH_OPRF() \ + PSI_BM_DEFINE_ECDH_OPRF_FULL(CURVE_25519); \ + PSI_BM_DEFINE_ECDH_OPRF_FULL(CURVE_FOURQ); \ + PSI_BM_DEFINE_ECDH_OPRF_FULL(CURVE_SM2); \ + PSI_BM_DEFINE_ECDH_OPRF_FULL(CURVE_SECP256K1); + +PSI_BM_DEFINE_ECDH_OPRF() + +BENCHMARK_DEFINE_F(PsiBench, KkrtPsi) +(benchmark::State& state) { + for (auto _ : state) { + state.PauseTiming(); + size_t numel = state.range(0); + auto items = psi::test::CreateItemHashes(bench_lctx->Rank(), numel); + + state.ResumeTiming(); + + if (bench_lctx->Rank() == 0) { /* Sender */ + auto ot_recv = psi::GetKkrtOtSenderOptions(bench_lctx, 512); + psi::KkrtPsiSend(bench_lctx, ot_recv, items); + } else { /* Receiver */ + auto ot_send = psi::GetKkrtOtReceiverOptions(bench_lctx, 512); + psi::KkrtPsiRecv(bench_lctx, ot_send, items); + } + } +} + +BENCHMARK_DEFINE_F(PsiBench, Bc22Psi) +(benchmark::State& state) { + for (auto _ : state) { + state.PauseTiming(); + size_t numel = state.range(0); + auto items = psi::test::CreateRangeItems(bench_lctx->Rank(), numel); + + state.ResumeTiming(); + + if (bench_lctx->Rank() == 0) { /* Sender */ + Bc22PcgPsi party(bench_lctx, PsiRoleType::Sender); + party.RunPsi(items); + } else { /* Receiver */ + Bc22PcgPsi party(bench_lctx, PsiRoleType::Receiver); + party.RunPsi(items); + party.GetIntersection(); + } + } +} + +#define PSI_BM_DEFINE_MINI_TYPE(IsBatch) \ + BENCHMARK_DEFINE_F(PsiBench, MiniPsi##_##IsBatch) \ + (benchmark::State & state) { \ + for (auto _ : state) { \ + state.PauseTiming(); \ + size_t numel = state.range(0); \ + auto items = psi::test::CreateRangeItems(bench_lctx->Rank(), numel); \ + \ + state.ResumeTiming(); \ + if (bench_lctx->Rank() == 0) { /* Sender */ \ + psi::MiniPsiSend(bench_lctx, items); \ + } else { /* Receiver */ \ + psi::MiniPsiRecv(bench_lctx, items); \ + } \ + } \ + } + +#define PSI_BM_DEFINE_MINI() \ + PSI_BM_DEFINE_MINI_TYPE(NoBatch) \ + PSI_BM_DEFINE_MINI_TYPE(Batch) +PSI_BM_DEFINE_MINI() + +#define PSI_BM_REGISTER_CURVE_PSI_TYPE(PsiType, CurveType, Arguments) \ + BENCHMARK_REGISTER_F(PsiBench, PsiType##_##CurveType)->Apply(Arguments); + +#define PSI_BM_REGISTER_CURVE_PSI(PsiType, Arguments) \ + PSI_BM_REGISTER_CURVE_PSI_TYPE(PsiType, CURVE_25519, Arguments); \ + PSI_BM_REGISTER_CURVE_PSI_TYPE(PsiType, CURVE_FOURQ, Arguments); \ + PSI_BM_REGISTER_CURVE_PSI_TYPE(PsiType, CURVE_SM2, Arguments); \ + PSI_BM_REGISTER_CURVE_PSI_TYPE(PsiType, CURVE_SECP256K1, Arguments); + +#define BM_REGISTER_ECDH_PSI(Arguments) \ + PSI_BM_REGISTER_CURVE_PSI(EcdhPsi, Arguments) + +#define BM_REGISTER_ECDH_OPRF_PSI(Arguments) \ + /* Currently, ECDH OPRF does not support Curve25518 donna */ \ + PSI_BM_REGISTER_CURVE_PSI_TYPE(EcdhPsiOprf, CURVE_FOURQ, Arguments); \ + PSI_BM_REGISTER_CURVE_PSI_TYPE(EcdhPsiOprf, CURVE_SM2, Arguments); \ + PSI_BM_REGISTER_CURVE_PSI_TYPE(EcdhPsiOprf, CURVE_SECP256K1, Arguments); + +#define BM_REGISTER_KKRT_PSI(Arguments) \ + BENCHMARK_REGISTER_F(PsiBench, KkrtPsi)->Apply(Arguments); + +#define BM_REGISTER_BC22_PSI(Arguments) \ + BENCHMARK_REGISTER_F(PsiBench, Bc22Psi)->Apply(Arguments); + +#define BM_REGISTER_MINI_PSI(Arguments) \ + BENCHMARK_REGISTER_F(PsiBench, MiniPsi_NoBatch)->Apply(Arguments); \ + BENCHMARK_REGISTER_F(PsiBench, MiniPsi_Batch)->Apply(Arguments); + +#define BM_REGISTER_ALL_PSI(Arguments) \ + BM_REGISTER_ECDH_PSI(Arguments); \ + BM_REGISTER_ECDH_OPRF_PSI(Arguments); \ + BM_REGISTER_KKRT_PSI(Arguments); \ + BM_REGISTER_BC22_PSI(Arguments); \ + BM_REGISTER_MINI_PSI(Arguments); + +} // namespace psi::psi::bench diff --git a/psi/psi/benchmark/standalone_bench.cc b/psi/psi/benchmark/standalone_bench.cc new file mode 100644 index 00000000..232c8b3b --- /dev/null +++ b/psi/psi/benchmark/standalone_bench.cc @@ -0,0 +1,45 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/benchmark/standalone_bench.h" + +namespace psi::psi::bench { + +void DefaultPsiArguments(benchmark::internal::Benchmark* b) { + b->Args({1 << 18}) + ->Args({1 << 20}) + ->Args({1 << 22}) + ->Args({1 << 24}) + ->Args({1000000}) + ->Args({5000000}) + ->Args({10000000}) + ->Iterations(1) + ->Unit(benchmark::kSecond); +} + +// register benchmarks with arguments +BM_REGISTER_ALL_PSI(DefaultPsiArguments); +// +// Equivalent to the following: +// +// BM_REGISTER_ECDH_PSI(DefaultPsiArguments); +// BM_REGISTER_ECDH_OPRF_PSI(DefaultPsiArguments); +// BM_REGISTER_KKRT_PSI(DefaultPsiArguments); +// BM_REGISTER_BC22_PSI(DefaultPsiArguments); +// BM_REGISTER_MINI_PSI(DefaultPsiArguments); + +} // namespace psi::psi::bench + +// the main function +BENCHMARK_MAIN(); diff --git a/psi/psi/benchmark/standalone_bench.h b/psi/psi/benchmark/standalone_bench.h new file mode 100644 index 00000000..392d298c --- /dev/null +++ b/psi/psi/benchmark/standalone_bench.h @@ -0,0 +1,378 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <future> +#include <iostream> +#include <memory> +#include <optional> +#include <random> +#include <string> +#include <vector> + +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "benchmark/benchmark.h" +#include "yacl/base/exception.h" +#include "yacl/base/int128.h" +#include "yacl/link/test_util.h" +#include "yacl/utils/scope_guard.h" + +#include "psi/psi/core/bc22_psi/bc22_psi.h" +#include "psi/psi/core/ecdh_oprf_psi.h" +#include "psi/psi/core/ecdh_psi.h" +#include "psi/psi/core/kkrt_psi.h" +#include "psi/psi/core/mini_psi.h" +#include "psi/psi/cryptor/cryptor_selector.h" +#include "psi/psi/utils/batch_provider.h" +#include "psi/psi/utils/ec_point_store.h" +#include "psi/psi/utils/test_utils.h" + +namespace psi::psi::bench { + +namespace { + +void WriteCsvFile(const std::string& file_name, + const std::vector<std::string>& items) { + auto out = io::BuildOutputStream(io::FileIoOptions(file_name)); + out->Write("id\n"); + for (const auto& data : items) { + out->Write(fmt::format("{}\n", data)); + } + out->Close(); +} + +} // namespace + +class PsiBench : public benchmark::Fixture { + public: + PsiBench() { + spdlog::set_level(spdlog::level::off); // turn off spdlog + } +}; + +#define PSI_BM_DEFINE_ECDH_TYPE(CurveType) \ + BENCHMARK_DEFINE_F(PsiBench, EcdhPsi_##CurveType) \ + (benchmark::State & state) { \ + for (auto _ : state) { \ + state.PauseTiming(); \ + size_t numel = state.range(0); \ + auto a_items = psi::test::CreateRangeItems(1, numel); \ + auto b_items = psi::test::CreateRangeItems(2, numel); \ + auto ctxs = yacl::link::test::SetupWorld(2); \ + auto proc = [](const std::shared_ptr<yacl::link::Context>& ctx, \ + const std::vector<std::string>& items, \ + size_t target_rank) -> std::vector<std::string> { \ + const auto curve = psi::test::GetOverrideCurveType(); \ + return psi::RunEcdhPsi(ctx, items, target_rank, \ + curve.has_value() ? *curve : (CurveType)); \ + }; \ + \ + state.ResumeTiming(); \ + \ + auto fa = std::async(proc, ctxs[0], a_items, 0); \ + auto fb = std::async(proc, ctxs[1], b_items, 0); \ + \ + auto results_a = fa.get(); \ + auto results_b = fb.get(); \ + } \ + } + +#define PSI_BM_DEFINE_ECDH() \ + PSI_BM_DEFINE_ECDH_TYPE(CURVE_25519); \ + PSI_BM_DEFINE_ECDH_TYPE(CURVE_FOURQ); \ + PSI_BM_DEFINE_ECDH_TYPE(CURVE_SM2); \ + PSI_BM_DEFINE_ECDH_TYPE(CURVE_SECP256K1); + +PSI_BM_DEFINE_ECDH() + +#define ECDH_OPRF_SENDER_OFFLINE() \ + { \ + /* offline: init */ \ + auto timestamp_str = std::to_string(absl::ToUnixNanos(absl::Now())); \ + /* server input */ \ + auto server_input_path = \ + std::filesystem::path(fmt::format("server-input-{}", timestamp_str)); \ + \ + /* server output */ \ + auto server_tmp_cache_path = \ + std::filesystem::path(fmt::format("tmp-cache-{}", timestamp_str)); \ + /* register remove of temp file. */ \ + ON_SCOPE_EXIT([&] { \ + std::error_code ec; \ + std::filesystem::remove(server_input_path, ec); \ + if (ec.value() != 0) { \ + SPDLOG_WARN("can not remove tmp file: {}, msg: {}", \ + server_input_path.c_str(), ec.message()); \ + } \ + std::filesystem::remove(server_tmp_cache_path, ec); \ + if (ec.value() != 0) { \ + SPDLOG_WARN("can not remove tmp file: {}, msg: {}", \ + server_tmp_cache_path.c_str(), ec.message()); \ + } \ + }); \ + \ + WriteCsvFile(server_input_path.string(), items); \ + std::vector<std::string> cloumn_ids = {"id"}; \ + std::shared_ptr<CachedCsvBatchProvider> item_provider = \ + std::make_shared<CachedCsvBatchProvider>( \ + server_input_path.string(), cloumn_ids, kEcdhOprfPsiBatchSize, \ + 100000, true); \ + \ + std::shared_ptr<IUbPsiCache> ub_cache = std::make_shared<UbPsiCache>( \ + server_tmp_cache_path.string(), offline_proc.GetCompareLength(), \ + cloumn_ids); \ + offline_proc.FullEvaluate(item_provider, ub_cache); \ + \ + /* offline: finalize */ \ + std::shared_ptr<IBasicBatchProvider> batch_provider = \ + std::make_shared<UbPsiCacheProvider>(server_tmp_cache_path.string(), \ + kEcdhOprfPsiBatchSize, \ + offline_proc.GetCompareLength()); \ + offline_proc.SendFinalEvaluatedItems(batch_provider); \ + } + +#define ECDH_OPRF_SENDER_ONLINE() \ + { /* online */ \ + online_proc.RecvBlindAndSendEvaluate(); \ + } + +#define ECDH_OPRF_RECEIVER_OFFLINE() \ + { \ + /* offline */ \ + auto peer_ec_point_store = std::make_shared<MemoryEcPointStore>(); \ + offline_proc.RecvFinalEvaluatedItems(peer_ec_point_store); \ + } + +#define ECDH_OPRF_RECEIVER_ONLINE() \ + { /* online */ \ + auto self_ec_point_store = std::make_shared<MemoryEcPointStore>(); \ + auto proc_send = std::async([&] { \ + auto item_provider = \ + std::make_shared<MemoryBatchProvider>(items, kEcdhOprfPsiBatchSize); \ + online_proc.SendBlindedItems(item_provider); \ + }); \ + \ + auto proc_recv = std::async( \ + [&] { online_proc.RecvEvaluatedItems(self_ec_point_store); }); \ + \ + proc_send.get(); \ + proc_recv.get(); \ + } + +#define PSI_BM_DEFINE_ECDH_OPRF_FULL(CurveType) \ + BENCHMARK_DEFINE_F(PsiBench, EcdhPsiOprf_##CurveType) \ + (benchmark::State & state) { \ + for (auto _ : state) { \ + state.PauseTiming(); \ + size_t numel = state.range(0); \ + auto a_items = psi::test::CreateRangeItems(1, numel); \ + auto b_items = psi::test::CreateRangeItems(2, numel); \ + auto ctxs = yacl::link::test::SetupWorld(2); \ + \ + /* We let bob obtains the final result */ \ + auto a_proc = [](const std::shared_ptr<yacl::link::Context>& ctx, \ + const std::vector<std::string>& items) { \ + EcdhOprfPsiOptions options; \ + options.curve_type = (CurveType); \ + options.link0 = ctx; \ + options.link1 = ctx->Spawn(); \ + \ + /* Offline Phase */ \ + auto offline_proc = EcdhOprfPsiServer(options); \ + ECDH_OPRF_SENDER_OFFLINE() \ + \ + /* Online Phase */ \ + const auto sk = offline_proc.GetPrivateKey(); \ + auto online_proc = EcdhOprfPsiServer(options, sk); \ + ECDH_OPRF_SENDER_ONLINE() \ + }; \ + \ + auto b_proc = [](const std::shared_ptr<yacl::link::Context>& ctx, \ + const std::vector<std::string>& items) { \ + EcdhOprfPsiOptions options; \ + options.curve_type = (CurveType); \ + options.link0 = ctx; \ + options.link1 = ctx->Spawn(); \ + \ + /* Offline Phase */ \ + auto offline_proc = EcdhOprfPsiClient(options); \ + ECDH_OPRF_RECEIVER_OFFLINE() \ + \ + /* Online Phase */ \ + auto online_proc = EcdhOprfPsiClient(options); \ + ECDH_OPRF_RECEIVER_ONLINE() \ + }; \ + \ + state.ResumeTiming(); \ + \ + auto fa = std::async(a_proc, ctxs[0], a_items); \ + auto fb = std::async(b_proc, ctxs[1], b_items); \ + fa.get(); \ + fb.get(); \ + } \ + } + +#define PSI_BM_DEFINE_ECDH_OPRF() \ + PSI_BM_DEFINE_ECDH_OPRF_FULL(CURVE_25519); \ + PSI_BM_DEFINE_ECDH_OPRF_FULL(CURVE_FOURQ); \ + PSI_BM_DEFINE_ECDH_OPRF_FULL(CURVE_SM2); \ + PSI_BM_DEFINE_ECDH_OPRF_FULL(CURVE_SECP256K1); + +PSI_BM_DEFINE_ECDH_OPRF() + +BENCHMARK_DEFINE_F(PsiBench, KkrtPsi) +(benchmark::State& state) { + for (auto _ : state) { + state.PauseTiming(); + size_t numel = state.range(0); + auto a_items = psi::test::CreateItemHashes(1, numel); + auto b_items = psi::test::CreateItemHashes(2, numel); + auto ctxs = yacl::link::test::SetupWorld(2); + + /* Sender */ + auto a_proc = [](const std::shared_ptr<yacl::link::Context>& ctx, + const std::vector<uint128_t>& items) { + auto ot_recv = psi::GetKkrtOtSenderOptions(ctx, 512); + psi::KkrtPsiSend(ctx, ot_recv, items); + }; + + /* Receiver */ + auto b_proc = [](const std::shared_ptr<yacl::link::Context>& ctx, + const std::vector<uint128_t>& items) { + auto ot_send = psi::GetKkrtOtReceiverOptions(ctx, 512); + return psi::KkrtPsiRecv(ctx, ot_send, items); + }; + + state.ResumeTiming(); + + auto fa = std::async(a_proc, ctxs[0], a_items); + auto fb = std::async(b_proc, ctxs[1], b_items); + + fa.get(); + auto results = fb.get(); + } +} + +BENCHMARK_DEFINE_F(PsiBench, Bc22Psi) +(benchmark::State& state) { + for (auto _ : state) { + state.PauseTiming(); + size_t numel = state.range(0); + auto a_items = psi::test::CreateRangeItems(1, numel); + auto b_items = psi::test::CreateRangeItems(2, numel); + auto ctxs = yacl::link::test::SetupWorld(2); + + Bc22PcgPsi sender(ctxs[0], PsiRoleType::Sender); + Bc22PcgPsi receiver(ctxs[1], PsiRoleType::Receiver); + + /* Sender */ + auto a_proc = [](const std::shared_ptr<yacl::link::Context>& ctx, + const std::vector<std::string>& items) { + Bc22PcgPsi party(ctx, PsiRoleType::Sender); + party.RunPsi(items); + }; + + /* Receiver */ + auto b_proc = [](const std::shared_ptr<yacl::link::Context>& ctx, + const std::vector<std::string>& items) { + Bc22PcgPsi party(ctx, PsiRoleType::Receiver); + party.RunPsi(items); + return party.GetIntersection(); + }; + + state.ResumeTiming(); + + auto fa = std::async(a_proc, ctxs[0], a_items); + auto fb = std::async(b_proc, ctxs[1], b_items); + + fa.get(); + auto results = fb.get(); + } +} + +#define PSI_BM_DEFINE_MINI_TYPE(IsBatch) \ + BENCHMARK_DEFINE_F(PsiBench, MiniPsi##_##IsBatch) \ + (benchmark::State & state) { \ + for (auto _ : state) { \ + state.PauseTiming(); \ + size_t numel = state.range(0); \ + auto a_items = psi::test::CreateRangeItems(1, numel); \ + auto b_items = psi::test::CreateRangeItems(2, numel); \ + auto ctxs = yacl::link::test::SetupWorld(2); \ + \ + /* Sender */ \ + auto a_proc = [](const std::shared_ptr<yacl::link::Context>& ctx, \ + const std::vector<std::string>& items) { \ + psi::MiniPsiSend(ctx, items); \ + }; \ + \ + /* Receiver */ \ + auto b_proc = [](const std::shared_ptr<yacl::link::Context>& ctx, \ + const std::vector<std::string>& items) { \ + psi::MiniPsiRecv(ctx, items); \ + }; \ + \ + state.ResumeTiming(); \ + \ + auto fa = std::async(a_proc, ctxs[0], a_items); \ + auto fb = std::async(b_proc, ctxs[1], b_items); \ + \ + fa.get(); \ + fb.get(); \ + } \ + } + +#define PSI_BM_DEFINE_MINI() \ + PSI_BM_DEFINE_MINI_TYPE(NoBatch) \ + PSI_BM_DEFINE_MINI_TYPE(Batch) +PSI_BM_DEFINE_MINI() + +#define PSI_BM_REGISTER_CURVE_PSI_TYPE(PsiType, CurveType, Arguments) \ + BENCHMARK_REGISTER_F(PsiBench, PsiType##_##CurveType)->Apply(Arguments); + +#define PSI_BM_REGISTER_CURVE_PSI(PsiType, Arguments) \ + PSI_BM_REGISTER_CURVE_PSI_TYPE(PsiType, CURVE_25519, Arguments); \ + PSI_BM_REGISTER_CURVE_PSI_TYPE(PsiType, CURVE_FOURQ, Arguments); \ + PSI_BM_REGISTER_CURVE_PSI_TYPE(PsiType, CURVE_SM2, Arguments); \ + PSI_BM_REGISTER_CURVE_PSI_TYPE(PsiType, CURVE_SECP256K1, Arguments); + +#define BM_REGISTER_ECDH_PSI(Arguments) \ + PSI_BM_REGISTER_CURVE_PSI(EcdhPsi, Arguments) + +#define BM_REGISTER_ECDH_OPRF_PSI(Arguments) \ + /* Currently, ECDH OPRF does not support Curve25518 donna */ \ + PSI_BM_REGISTER_CURVE_PSI_TYPE(EcdhPsiOprf, CURVE_FOURQ, Arguments); \ + PSI_BM_REGISTER_CURVE_PSI_TYPE(EcdhPsiOprf, CURVE_SM2, Arguments); \ + PSI_BM_REGISTER_CURVE_PSI_TYPE(EcdhPsiOprf, CURVE_SECP256K1, Arguments); + +#define BM_REGISTER_KKRT_PSI(Arguments) \ + BENCHMARK_REGISTER_F(PsiBench, KkrtPsi)->Apply(Arguments); + +#define BM_REGISTER_BC22_PSI(Arguments) \ + BENCHMARK_REGISTER_F(PsiBench, Bc22Psi)->Apply(Arguments); + +#define BM_REGISTER_MINI_PSI(Arguments) \ + BENCHMARK_REGISTER_F(PsiBench, MiniPsi_NoBatch)->Apply(Arguments); \ + BENCHMARK_REGISTER_F(PsiBench, MiniPsi_Batch)->Apply(Arguments); + +#define BM_REGISTER_ALL_PSI(Arguments) \ + BM_REGISTER_ECDH_PSI(Arguments); \ + BM_REGISTER_ECDH_OPRF_PSI(Arguments); \ + BM_REGISTER_KKRT_PSI(Arguments); \ + BM_REGISTER_BC22_PSI(Arguments); \ + BM_REGISTER_MINI_PSI(Arguments); + +} // namespace psi::psi::bench diff --git a/psi/psi/bucket.cc b/psi/psi/bucket.cc new file mode 100644 index 00000000..dfcd50cd --- /dev/null +++ b/psi/psi/bucket.cc @@ -0,0 +1,108 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/bucket.h" + +#include "psi/psi/prelude.h" +#include "psi/psi/utils/utils.h" + +namespace psi::psi { + +std::optional<std::vector<HashBucketCache::BucketItem>> PrepareBucketData( + v2::Protocol protocol, size_t bucket_idx, + const std::shared_ptr<yacl::link::Context>& lctx, + HashBucketCache* input_bucket_store) { + std::vector<HashBucketCache::BucketItem> bucket_items_list; + auto load_bucket_f = std::async([&] { + bucket_items_list = input_bucket_store->LoadBucketItems(bucket_idx); + }); + + SyncWait(lctx, &load_bucket_f); + + size_t min_inputs_size = bucket_items_list.size(); + std::vector<size_t> inputs_size_list = + AllGatherItemsSize(lctx, bucket_items_list.size()); + for (size_t idx = 0; idx < inputs_size_list.size(); idx++) { + SPDLOG_INFO("psi protocol={}, rank={}, inputs_size={}", protocol, idx, + inputs_size_list[idx]); + min_inputs_size = std::min(min_inputs_size, inputs_size_list[idx]); + } + + if (min_inputs_size == 0) { + SPDLOG_INFO( + "psi protocol={}, min_inputs_size=0, " + "no need do intersection", + protocol); + return {}; + } + + SPDLOG_INFO("run psi bucket_idx={}, bucket_item_size={} ", bucket_idx, + bucket_items_list.size()); + + return bucket_items_list; +} + +void HandleBucketResultBySender( + bool broadcast_result, const std::shared_ptr<yacl::link::Context>& lctx, + const std::vector<HashBucketCache::BucketItem>& bucket_items_list, + IndexWriter* writer) { + if (broadcast_result) { + std::vector<std::string> result_list; + BroadcastResult(lctx, &result_list); + + if (result_list.empty()) { + return; + } + + if (result_list.size() == bucket_items_list.size()) { + for (const auto& item : bucket_items_list) { + writer->WriteCache(item.index); + } + } else { + std::sort(result_list.begin(), result_list.end()); + for (const auto& item : bucket_items_list) { + if (std::binary_search(result_list.begin(), result_list.end(), + item.base64_data)) { + writer->WriteCache(item.index); + } + } + } + + writer->Commit(); + } +} + +void HandleBucketResultByReceiver( + bool broadcast_result, const std::shared_ptr<yacl::link::Context>& lctx, + const std::vector<HashBucketCache::BucketItem>& result_list, + IndexWriter* writer) { + if (broadcast_result) { + std::vector<std::string> item_data_list; + item_data_list.reserve(result_list.size()); + + for (const auto& item : result_list) { + item_data_list.emplace_back(item.base64_data); + } + + BroadcastResult(lctx, &item_data_list); + } + + for (const auto& item : result_list) { + writer->WriteCache(item.index); + } + + writer->Commit(); +} + +} // namespace psi::psi diff --git a/psi/psi/bucket.h b/psi/psi/bucket.h new file mode 100644 index 00000000..439bfe08 --- /dev/null +++ b/psi/psi/bucket.h @@ -0,0 +1,44 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include <optional> + +#include "yacl/link/link.h" + +#include "psi/psi/recovery.h" +#include "psi/psi/utils/hash_bucket_cache.h" +#include "psi/psi/utils/index_store.h" + +namespace psi::psi { + +// Default bucket size when not provided. +constexpr uint64_t kDefaultBucketSize = 1 << 20; + +std::optional<std::vector<HashBucketCache::BucketItem>> PrepareBucketData( + v2::Protocol protocol, size_t bucket_idx, + const std::shared_ptr<yacl::link::Context>& lctx, + HashBucketCache* input_bucket_store); + +void HandleBucketResultBySender( + bool broadcast_result, const std::shared_ptr<yacl::link::Context>& lctx, + const std::vector<HashBucketCache::BucketItem>& bucket_items_list, + IndexWriter* writer); + +void HandleBucketResultByReceiver( + bool broadcast_result, const std::shared_ptr<yacl::link::Context>& lctx, + const std::vector<HashBucketCache::BucketItem>& result_list, + IndexWriter* writer); + +} // namespace psi::psi diff --git a/psi/psi/bucket_psi.cc b/psi/psi/bucket_psi.cc new file mode 100644 index 00000000..f004dae8 --- /dev/null +++ b/psi/psi/bucket_psi.cc @@ -0,0 +1,482 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/bucket_psi.h" + +#include <omp.h> + +#include <algorithm> +#include <cstddef> +#include <filesystem> +#include <fstream> +#include <numeric> +#include <thread> +#include <type_traits> +#include <unordered_set> +#include <utility> + +#include "absl/synchronization/mutex.h" +#include "absl/synchronization/notification.h" +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/crypto/utils/rand.h" +#include "yacl/utils/serialize.h" + +#include "psi/psi/bucket_ub_psi.h" +#include "psi/psi/core/ecdh_oprf_psi.h" +#include "psi/psi/core/ecdh_psi.h" +#include "psi/psi/cryptor/cryptor_selector.h" +#include "psi/psi/io/io.h" +#include "psi/psi/prelude.h" +#include "psi/psi/utils/batch_provider.h" +#include "psi/psi/utils/csv_header_analyzer.h" +#include "psi/psi/utils/ec_point_store.h" +#include "psi/psi/utils/file.h" +#include "psi/psi/utils/serialize.h" + +namespace psi::psi { + +namespace { + +constexpr size_t kBucketSize = 1 << 20; + +class ProgressLoop { + public: + // Starts the background thread which will be calling the function + ProgressLoop(const std::shared_ptr<Progress>& progress, + ProgressCallbacks function, int64_t interval_ms) + : progress_(progress), + function_(function), + interval_ms_(std::max(static_cast<int64_t>(1), interval_ms)) { + thread_.reset(new std::thread([this]() { RunLoop(); })); + } + + ~ProgressLoop() { + NotifyStop(); + // wait for thread_ to complete and cleanup. + thread_->join(); + thread_.reset(); + } + + private: + // Notifies the background thread to stop + void NotifyStop() { + if (!stop_event_.HasBeenNotified()) { + stop_event_.Notify(); + } + } + + // Loops forever calling `function_` every `interval_ms_`. + void RunLoop() { + while (!stop_event_.HasBeenNotified()) { + const int64_t begin = absl::ToUnixMillis(absl::Now()); + function_(progress_->Get()); + const int64_t end = absl::ToUnixMillis(absl::Now()); + const int64_t deadline = begin + interval_ms_; + if (deadline > end) { + if (stop_event_.WaitForNotificationWithTimeout( + absl::Milliseconds(deadline - end))) { + // notify end + break; + } + } + } + // last progress callback + function_(progress_->Get()); + } + + const std::shared_ptr<Progress> progress_; + const ProgressCallbacks function_; + const int64_t interval_ms_; + + // Protects state below. + mutable absl::Mutex mutex_; + absl::Notification stop_event_; + + std::unique_ptr<std::thread> thread_ = nullptr; +}; + +} // namespace + +bool HashListEqualTest(const std::vector<yacl::Buffer>& hash_list) { + YACL_ENFORCE(!hash_list.empty(), "unsupported hash_list size={}", + hash_list.size()); + for (size_t idx = 1; idx < hash_list.size(); idx++) { + if (hash_list[idx] == hash_list[0]) { + continue; + } + return false; + } + return true; +} + +std::unique_ptr<CsvChecker> CheckInput( + std::shared_ptr<yacl::link::Context> lctx, const std::string& input_path, + const std::string& output_path, + const std::vector<std::string>& selected_fields, bool precheck_required, + bool ic_mode) { + // input dataset pre check + SPDLOG_INFO("Begin sanity check for input file: {}, precheck_switch:{}", + input_path, precheck_required); + std::unique_ptr<CsvChecker> checker; + auto csv_check_f = std::async([&] { + checker = std::make_unique<CsvChecker>( + input_path, selected_fields, + std::filesystem::path(output_path).parent_path().string(), + !precheck_required); + }); + // keep alive + if (ic_mode) { + csv_check_f.get(); + } else { + SyncWait(lctx, &csv_check_f); + } + SPDLOG_INFO("End sanity check for input file: {}, size={}", input_path, + checker->data_count()); + + return checker; +} + +BucketPsi::BucketPsi(BucketPsiConfig config, + std::shared_ptr<yacl::link::Context> lctx, bool ic_mode) + : config_(std::move(config)), ic_mode_(ic_mode), lctx_(std::move(lctx)) { + if (config_.psi_type() != PsiType::ECDH_OPRF_UB_PSI_2PC_GEN_CACHE) { + Init(); + } + + // prepare fields vec + selected_fields_.insert(selected_fields_.end(), + config_.input_params().select_fields().begin(), + config_.input_params().select_fields().end()); +} + +PsiResultReport BucketPsi::Run(ProgressCallbacks progress_callbacks, + int64_t callbacks_interval_ms) { + // init progress + auto progress = std::make_shared<Progress>(); + progress->SetWeights({15, 65, 20}); + + // begin loop thread + std::unique_ptr<ProgressLoop> p_loop = nullptr; + if (progress_callbacks) { + SPDLOG_INFO("begin progress callback loop thread, interval:{}", + callbacks_interval_ms); + p_loop = std::make_unique<ProgressLoop>(progress, progress_callbacks, + callbacks_interval_ms); + } + + PsiResultReport report; + std::vector<uint64_t> indices; + bool digest_equal = false; + + if (config_.psi_type() != PsiType::ECDH_OPRF_UB_PSI_2PC_OFFLINE && + config_.psi_type() != PsiType::ECDH_OPRF_UB_PSI_2PC_GEN_CACHE && + config_.psi_type() != PsiType::ECDH_OPRF_UB_PSI_2PC_TRANSFER_CACHE && + config_.psi_type() != PsiType::ECDH_OPRF_UB_PSI_2PC_ONLINE && + config_.psi_type() != PsiType::ECDH_OPRF_UB_PSI_2PC_SHUFFLE_ONLINE) { + progress->NextSubProgress("Precheck"); + auto checker = CheckInput(lctx_, config_.input_params().path(), + config_.output_params().path(), selected_fields_, + config_.input_params().precheck(), ic_mode_); + report.set_original_count(checker->data_count()); + + // gather others hash digest + if (!ic_mode_) { + std::vector<yacl::Buffer> digest_buf_list = yacl::link::AllGather( + lctx_, checker->hash_digest(), "PSI:SYNC_DIGEST"); + digest_equal = HashListEqualTest(digest_buf_list); + } + + // run psi + auto psi_progress = progress->NextSubProgress("RunPsi"); + if (!digest_equal) { + uint64_t items_count = checker->data_count(); + indices = RunPsi(psi_progress, items_count); + } else { + SPDLOG_INFO("Skip doing psi, because dataset has been aligned!"); + indices.resize(checker->data_count()); + std::iota(indices.begin(), indices.end(), 0); + } + + } else { + progress->NextSubProgress("UB Precheck"); + + if (config_.input_params().precheck() && + (config_.psi_type() == PsiType::ECDH_OPRF_UB_PSI_2PC_SHUFFLE_ONLINE) && + (lctx_->Rank() != config_.receiver_rank())) { + SPDLOG_INFO( + "Begin sanity check for input file: {}, precheck_switch: true", + config_.input_params().path()); + std::shared_ptr<CsvChecker> checker; + auto csv_check_f = std::async([&] { + checker = std::make_shared<CsvChecker>( + config_.input_params().path(), selected_fields_, + std::filesystem::path(config_.input_params().path()) + .parent_path() + .string(), + false); + }); + + csv_check_f.get(); + + SPDLOG_INFO("End sanity check for input file: {}, size={}", + config_.input_params().path(), checker->data_count()); + } + + auto psi_progress = progress->NextSubProgress("UB RunPsi"); + uint64_t items_count = 0; + indices = RunPsi(psi_progress, items_count); + report.set_original_count(items_count); + } + + progress->NextSubProgress("ProduceOutput"); + ProduceOutput(digest_equal, indices, report); + + progress->Done(); + + return report; +} + +void BucketPsi::ProduceOutput(bool digest_equal, std::vector<uint64_t>& indices, + PsiResultReport& report) { + if ((config_.psi_type() == PsiType::ECDH_OPRF_UB_PSI_2PC_OFFLINE) || + (config_.psi_type() == PsiType::ECDH_OPRF_UB_PSI_2PC_GEN_CACHE) || + (config_.psi_type() == PsiType::ECDH_OPRF_UB_PSI_2PC_TRANSFER_CACHE) || + (static_cast<size_t>(config_.receiver_rank()) != lctx_->Rank() && + !config_.broadcast_result())) { + report.set_intersection_count(-1); + // no generate output file; + return; + } else { + report.set_intersection_count(indices.size()); + } + + // filter dataset + SPDLOG_INFO("Begin post filtering, indices.size={}, should_sort={}", + indices.size(), config_.output_params().need_sort()); + + std::sort(indices.begin(), indices.end()); + GenerateResult(config_.input_params().path(), config_.output_params().path(), + selected_fields_, indices, config_.output_params().need_sort(), + digest_equal); + + SPDLOG_INFO("End post filtering, in={}, out={}", + config_.input_params().path(), config_.output_params().path()); +} + +void BucketPsi::Init() { + // TODO: deal input_params data_type + + if (config_.bucket_size() == 0) { + config_.set_bucket_size(kBucketSize); + } + SPDLOG_INFO("bucket size set to {}", config_.bucket_size()); + + MemoryPsiConfig config; + config.set_psi_type(config_.psi_type()); + config.set_curve_type(config_.curve_type()); + config.set_receiver_rank(config_.receiver_rank()); + config.set_broadcast_result(config_.broadcast_result()); + // set dppsi parameters + if (config_.has_dppsi_params()) { + DpPsiParams* dppsi_params = config.mutable_dppsi_params(); + dppsi_params->set_bob_sub_sampling( + config_.dppsi_params().bob_sub_sampling()); + dppsi_params->set_epsilon(config_.dppsi_params().epsilon()); + } + mem_psi_ = std::make_unique<MemoryPsi>(config, lctx_); + + // create output folder. + CreateOutputFolder(config_.output_params().path()); +} + +std::vector<uint64_t> BucketPsi::RunPsi(std::shared_ptr<Progress>& progress, + uint64_t& self_items_count) { + SPDLOG_INFO("Run psi protocol={}, self_items_count={}", config_.psi_type(), + self_items_count); + + if (config_.psi_type() == PsiType::ECDH_PSI_2PC) { + EcdhPsiOptions psi_options; + if (config_.curve_type() == CurveType::CURVE_INVALID_TYPE) { + YACL_THROW("Unsupported curve type"); + } + psi_options.ecc_cryptor = CreateEccCryptor(config_.curve_type()); + psi_options.link_ctx = lctx_; + psi_options.target_rank = static_cast<size_t>(config_.receiver_rank()); + if (config_.broadcast_result()) { + psi_options.target_rank = yacl::link::kAllRank; + } + psi_options.ic_mode = ic_mode_; + + auto batch_provider = std::make_shared<CsvBatchProvider>( + config_.input_params().path(), selected_fields_, + psi_options.batch_size); + auto self_ec_point_store = std::make_shared<HashBucketEcPointStore>( + std::filesystem::path(config_.output_params().path()).parent_path(), + 64); + + auto peer_ec_point_store = std::make_shared<HashBucketEcPointStore>( + std::filesystem::path(config_.output_params().path()).parent_path(), + 64); + + // Hook progress-> + if (progress) { + psi_options.on_batch_finished = [progress, self_items_count, + psi_options](size_t batch_count) { + size_t last_percent = progress->Get().percentage; + + size_t completed = batch_count * psi_options.batch_size; + size_t total = std::max<size_t>(1, self_items_count); + size_t curr_percent = 100 * completed / total; + progress->Update(curr_percent); + + // Log something. + constexpr size_t kLogEveryNPercent = 5; + if (curr_percent != last_percent && + curr_percent % kLogEveryNPercent == 0) { + SPDLOG_INFO("ECDH progress {}%", curr_percent); + } + }; + } + + // Launch ECDH-PSI core. + RunEcdhPsi(psi_options, batch_provider, self_ec_point_store, + peer_ec_point_store); + + std::vector<uint64_t> results; + results = + FinalizeAndComputeIndices(self_ec_point_store, peer_ec_point_store); + + return results; + } else if ((config_.psi_type() == PsiType::ECDH_OPRF_UB_PSI_2PC_GEN_CACHE) || + (config_.psi_type() == + PsiType::ECDH_OPRF_UB_PSI_2PC_TRANSFER_CACHE) || + (config_.psi_type() == + PsiType::ECDH_OPRF_UB_PSI_2PC_SHUFFLE_ONLINE) || + (config_.psi_type() == PsiType::ECDH_OPRF_UB_PSI_2PC_OFFLINE) || + (config_.psi_type() == PsiType::ECDH_OPRF_UB_PSI_2PC_ONLINE)) { + std::vector<uint64_t> results; + + std::tie(results, self_items_count) = UbPsi(config_, lctx_); + + return results; + } else { + return RunBucketPsi(progress, self_items_count); + } +} + +size_t NegotiateBucketNum(const std::shared_ptr<yacl::link::Context>& lctx, + size_t self_items_count, size_t self_bucket_size, + int psi_type) { + std::vector<size_t> items_size_list = + AllGatherItemsSize(lctx, self_items_count); + + std::vector<size_t> bucket_count_list(items_size_list.size()); + size_t max_bucket_count = 0; + size_t min_item_size = self_items_count; + + for (size_t idx = 0; idx < items_size_list.size(); idx++) { + bucket_count_list[idx] = + (items_size_list[idx] + self_bucket_size - 1) / self_bucket_size; + max_bucket_count = std::max(max_bucket_count, bucket_count_list[idx]); + min_item_size = std::min(min_item_size, items_size_list[idx]); + + SPDLOG_INFO("psi protocol={}, rank={} item_size={}", psi_type, idx, + items_size_list[idx]); + } + + // one party item_size is 0, no need to do intersection + if (min_item_size == 0) { + SPDLOG_INFO("psi protocol={}, min_item_size=0", psi_type); + return 0; + } + + return max_bucket_count; +} + +std::vector<uint64_t> BucketPsi::RunBucketPsi( + std::shared_ptr<Progress>& progress, uint64_t self_items_count) { + std::vector<uint64_t> ret; + + size_t max_bucket_count = NegotiateBucketNum( + lctx_, self_items_count, config_.bucket_size(), config_.psi_type()); + + // one party item_size is 0, no need to do intersection + if (max_bucket_count == 0) { + return ret; + } + + SPDLOG_INFO("psi protocol={}, bucket_count={}", config_.psi_type(), + max_bucket_count); + + // hash bucket items + auto bucket_store = CreateCacheFromCsv( + config_.input_params().path(), selected_fields_, + std::filesystem::path(config_.output_params().path()).parent_path(), + max_bucket_count); + for (size_t bucket_idx = 0; bucket_idx < bucket_store->BucketNum(); + bucket_idx++) { + auto bucket_items_list = bucket_store->LoadBucketItems(bucket_idx); + + SPDLOG_INFO("run psi bucket_idx={}, bucket_item_size={} ", bucket_idx, + bucket_items_list.size()); + + std::vector<std::string> item_data_list; + item_data_list.reserve(bucket_items_list.size()); + for (const auto& item : bucket_items_list) { + item_data_list.push_back(item.base64_data); + } + + auto result_list = mem_psi_->Run(item_data_list); + + SPDLOG_INFO("psi protocol={}, result_size={}", config_.psi_type(), + result_list.size()); + + // get result item indices + GetResultIndices(item_data_list, bucket_items_list, result_list, &ret); + + // count progress + if (progress) { + progress->Update(100 * (bucket_idx + 1) / bucket_store->BucketNum()); + } + } + + return ret; +} + +void GetResultIndices(const std::vector<std::string>& item_data_list, + const std::vector<HashBucketCache::BucketItem>& item_list, + std::vector<std::string>& result_list, + std::vector<uint64_t>* indices) { + indices->reserve(indices->size() + result_list.size()); + if (result_list.empty()) { + return; + } else if (result_list.size() == item_list.size()) { + for (const auto& item : item_list) { + indices->push_back(item.index); + } + return; + } + + std::sort(result_list.begin(), result_list.end()); + for (size_t i = 0; i < item_data_list.size(); ++i) { + if (std::binary_search(result_list.begin(), result_list.end(), + item_data_list[i])) { + indices->push_back(item_list[i].index); + } + } +} + +} // namespace psi::psi diff --git a/psi/psi/bucket_psi.h b/psi/psi/bucket_psi.h new file mode 100644 index 00000000..adb0c473 --- /dev/null +++ b/psi/psi/bucket_psi.h @@ -0,0 +1,139 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <cstddef> +#include <memory> +#include <string> +#include <vector> + +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "yacl/base/exception.h" +#include "yacl/link/link.h" +#include "yacl/utils/scope_guard.h" + +#include "psi/psi/memory_psi.h" +#include "psi/psi/utils/csv_checker.h" +#include "psi/psi/utils/hash_bucket_cache.h" +#include "psi/psi/utils/index_store.h" +#include "psi/psi/utils/progress.h" +#include "psi/psi/utils/utils.h" + +#include "psi/psi/psi.pb.h" + +namespace psi::psi { + +using ProgressCallbacks = std::function<void(const Progress::Data&)>; + +bool HashListEqualTest(const std::vector<yacl::Buffer>& hash_list); + +std::unique_ptr<CsvChecker> CheckInput( + std::shared_ptr<yacl::link::Context> lctx, const std::string& input_path, + const std::string& output_path, + const std::vector<std::string>& selected_fields, bool precheck_required, + bool ic_mode); + +template <typename T> +size_t GenerateResult(const std::string& input_path, + const std::string& output_path, + const std::vector<std::string>& selected_fields, + const T& indices, bool sort_output, bool digest_equal, + bool output_difference = false) { + // use tmp file to avoid `shell Injection` + auto timestamp_str = std::to_string(absl::ToUnixNanos(absl::Now())); + auto tmp_sort_in_file = + std::filesystem::path(output_path) + .parent_path() + .append(fmt::format("tmp-sort-in-{}", timestamp_str)); + auto tmp_sort_out_file = + std::filesystem::path(output_path) + .parent_path() + .append(fmt::format("tmp-sort-out-{}", timestamp_str)); + // register remove of temp file. + ON_SCOPE_EXIT([&] { + std::error_code ec; + std::filesystem::remove(tmp_sort_out_file, ec); + if (ec.value() != 0) { + SPDLOG_WARN("can not remove tmp file: {}, msg: {}", + tmp_sort_out_file.c_str(), ec.message()); + } + std::filesystem::remove(tmp_sort_in_file, ec); + if (ec.value() != 0) { + SPDLOG_WARN("can not remove tmp file: {}, msg: {}", + tmp_sort_in_file.c_str(), ec.message()); + } + }); + + size_t cnt = FilterFileByIndices(input_path, tmp_sort_in_file, indices, + output_difference); + if (sort_output && !digest_equal) { + MultiKeySort(tmp_sort_in_file, tmp_sort_out_file, selected_fields); + std::filesystem::rename(tmp_sort_out_file, output_path); + } else { + std::filesystem::rename(tmp_sort_in_file, output_path); + } + + return cnt; +} + +// the item order of `item_data_list` and `item_list` needs to be the same +void GetResultIndices(const std::vector<std::string>& item_data_list, + const std::vector<HashBucketCache::BucketItem>& item_list, + std::vector<std::string>& result_list, + std::vector<uint64_t>* indices); + +size_t NegotiateBucketNum(const std::shared_ptr<yacl::link::Context>& lctx, + size_t self_items_count, size_t self_bucket_size, + int psi_type); + +class BucketPsi { + public: + // ic_mode: 互联互通模式,对方可以是非隐语应用 + // Interconnection mode, the other side can be non-secretflow application + explicit BucketPsi(BucketPsiConfig config, + std::shared_ptr<yacl::link::Context> lctx, + bool ic_mode = false); + ~BucketPsi() = default; + + PsiResultReport Run(ProgressCallbacks progress_callbacks = nullptr, + int64_t callbacks_interval_ms = 5 * 1000); + + // unbalanced get items_count when RunPSI + // other psi use sanity check get items_count + // TODO: sanity check affects performance maybe optional + std::vector<uint64_t> RunPsi(std::shared_ptr<Progress>& progress, + uint64_t& self_items_count); + + void ProduceOutput(bool digest_equal, std::vector<uint64_t>& indices, + PsiResultReport& report); + + private: + void Init(); + + std::vector<uint64_t> RunBucketPsi(std::shared_ptr<Progress>& progress, + uint64_t self_items_count); + + BucketPsiConfig config_; + bool ic_mode_; + + std::shared_ptr<yacl::link::Context> lctx_; + + std::vector<std::string> selected_fields_; + + std::unique_ptr<MemoryPsi> mem_psi_; +}; + +} // namespace psi::psi diff --git a/psi/psi/bucket_psi_test.cc b/psi/psi/bucket_psi_test.cc new file mode 100644 index 00000000..6e2b6917 --- /dev/null +++ b/psi/psi/bucket_psi_test.cc @@ -0,0 +1,659 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/bucket_psi.h" + +#include <fstream> +#include <vector> + +#include "absl/strings/escaping.h" +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/crypto/utils/rand.h" +#include "yacl/link/test_util.h" + +#include "psi/psi/io/io.h" + +namespace psi::psi { + +namespace { +struct TestParams { + std::vector<uint32_t> item_size_list; + std::vector<std::string> in_content_list; + std::vector<std::string> out_content_list; + std::vector<std::vector<std::string>> field_names_list; + PsiType psi_protocol; + size_t num_bins; + bool should_sort; + bool run_in_ic_mode; + uint32_t expect_result_size; +}; + +size_t GetFileLineCount(const std::string& name) { + std::ifstream in(name); + return std::count(std::istreambuf_iterator<char>(in), + std::istreambuf_iterator<char>(), '\n'); +} + +std::string ReadFileToString(const std::string& name) { + auto io = io::BuildInputStream(io::FileIoOptions(name)); + std::string r; + r.resize(io->GetLength()); + io->Read(r.data(), r.size()); + return r; +} + +void WriteFile(const std::string& file_name, const std::string& content) { + auto out = io::BuildOutputStream(io::FileIoOptions(file_name)); + out->Write(content); + out->Close(); +} + +} // namespace + +class StreamTaskPsiTest : public testing::TestWithParam<TestParams> { + protected: + void SetUp() override { + tmp_dir_ = "./tmp"; + std::filesystem::create_directory(tmp_dir_); + } + void TearDown() override { + input_paths_.clear(); + output_paths_.clear(); + + if (!tmp_dir_.empty()) { + std::error_code ec; + std::filesystem::remove_all(tmp_dir_, ec); + // Leave error as it is, do nothing + } + } + + void SetupTmpfilePaths(size_t num) { + for (size_t i = 0; i < num; ++i) { + input_paths_.emplace_back(fmt::format("{}/tmp-input-{}", tmp_dir_, i)); + output_paths_.emplace_back(fmt::format("{}/tmp-output-{}", tmp_dir_, i)); + } + } + + std::string tmp_dir_; + std::vector<std::string> input_paths_; + std::vector<std::string> output_paths_; +}; + +TEST_P(StreamTaskPsiTest, Works) { + auto params = GetParam(); + + SetupTmpfilePaths(params.in_content_list.size()); + auto lctxs = yacl::link::test::SetupWorld(params.in_content_list.size()); + + auto proc = [&](int idx) -> PsiResultReport { + BucketPsiConfig config; + config.mutable_input_params()->set_path(input_paths_[idx]); + config.mutable_input_params()->mutable_select_fields()->Add( + params.field_names_list[idx].begin(), + params.field_names_list[idx].end()); + config.mutable_input_params()->set_precheck(true); + config.mutable_output_params()->set_path(output_paths_[idx]); + config.mutable_output_params()->set_need_sort(params.should_sort); + config.set_psi_type(params.psi_protocol); + config.set_broadcast_result(true); + // set small bucket size for test + config.set_bucket_size(3); + config.set_curve_type(CurveType::CURVE_25519); + + BucketPsi ctx(config, lctxs[idx], params.run_in_ic_mode); + return ctx.Run(); + }; + + size_t world_size = lctxs.size(); + std::vector<std::future<PsiResultReport>> f_links(world_size); + for (size_t i = 0; i < world_size; i++) { + WriteFile(input_paths_[i], params.in_content_list[i]); + f_links[i] = std::async(proc, i); + } + + for (size_t i = 0; i < world_size; i++) { + auto report = f_links[i].get(); + + EXPECT_EQ(report.original_count(), params.item_size_list[i]); + EXPECT_EQ(report.intersection_count(), + GetFileLineCount(output_paths_[i]) - 1); + EXPECT_EQ(params.expect_result_size, + GetFileLineCount(output_paths_[i]) - 1); + EXPECT_EQ(params.out_content_list[i], ReadFileToString(output_paths_[i])); + } +} + +TEST_P(StreamTaskPsiTest, BroadcastFalse) { + auto params = GetParam(); + size_t receiver_rank = 0; + + SetupTmpfilePaths(params.in_content_list.size()); + auto lctxs = yacl::link::test::SetupWorld(params.in_content_list.size()); + + auto proc = [&](int idx) -> PsiResultReport { + BucketPsiConfig config; + config.mutable_input_params()->set_path(input_paths_[idx]); + config.mutable_input_params()->mutable_select_fields()->Add( + params.field_names_list[idx].begin(), + params.field_names_list[idx].end()); + config.mutable_input_params()->set_precheck(true); + config.mutable_output_params()->set_path(output_paths_[idx]); + config.mutable_output_params()->set_need_sort(params.should_sort); + config.set_psi_type(params.psi_protocol); + config.set_receiver_rank(receiver_rank); + config.set_broadcast_result(false); + // set min bucket size for test + config.set_bucket_size(1); + config.set_curve_type(CurveType::CURVE_25519); + + BucketPsi ctx(config, lctxs[idx], params.run_in_ic_mode); + return ctx.Run(); + }; + + size_t world_size = lctxs.size(); + std::vector<std::future<PsiResultReport>> f_links(world_size); + for (size_t i = 0; i < world_size; i++) { + WriteFile(input_paths_[i], params.in_content_list[i]); + f_links[i] = std::async(proc, i); + } + + for (size_t i = 0; i < world_size; i++) { + auto report = f_links[i].get(); + + if (i == receiver_rank) { + EXPECT_EQ(report.original_count(), params.item_size_list[i]); + EXPECT_EQ(report.intersection_count(), + GetFileLineCount(output_paths_[i]) - 1); + EXPECT_EQ(params.expect_result_size, + GetFileLineCount(output_paths_[i]) - 1); + EXPECT_EQ(params.out_content_list[i], ReadFileToString(output_paths_[i])); + } else { + EXPECT_EQ(report.original_count(), params.item_size_list[i]); + EXPECT_EQ(report.intersection_count(), -1); + EXPECT_FALSE(std::filesystem::exists(output_paths_[i])); + } + } +} + +INSTANTIATE_TEST_SUITE_P( + Works_Instances, StreamTaskPsiTest, + testing::Values( + TestParams{ + {10, 15}, + {"id,value\ng,1\nb,2\ns,1\nh,1\ne,1\nc,1\na,1\nj,1\nk,1\nl,1\n", + "id,value\ne,1\nc,1\na,1\nj,1\nk,1\nq,1\nw,1\nn,1\nr,1\nt,1\ny," + "1\nu,1\ni,1\no,1\np,1\n"}, + {"id,value\na,1\nc,1\ne,1\nj,1\nk,1\n", + "id,value\na,1\nc,1\ne,1\nj,1\nk,1\n"}, + {{"id"}, {"id"}}, + PsiType::ECDH_PSI_2PC, + 64, + true, + false, + 5, + }, + TestParams{ + {10, 15}, + {"id,value\ng,1\nb,2\ns,1\nh,1\ne,1\nc,1\na,1\nj,1\nk,1\nl,1\n", + "id,value\ne,1\nc,1\na,1\nj,1\nk,1\nq,1\nw,1\nn,1\nr,1\nt,1\ny," + "1\nu,1\ni,1\no,1\np,1\n"}, + {"id,value\na,1\nc,1\ne,1\nj,1\nk,1\n", + "id,value\na,1\nc,1\ne,1\nj,1\nk,1\n"}, + {{"id"}, {"id"}}, + PsiType::ECDH_PSI_2PC, + 64, + true, + true, + 5, + }, + TestParams{ + {3, 3}, + {"id,value\nc测试,c\nb测试,b\na测试,a\n", + "id,value\nb测试,b\nc测试,c\na测试,a\n"}, + {"id,value\na测试,a\nb测试,b\nc测试,c\n", + "id,value\na测试,a\nb测试,b\nc测试,c\n"}, + {{"id"}, {"id"}}, + PsiType::ECDH_PSI_2PC, + 64, + true, + false, + 3, + }, + TestParams{ + {3, 3}, + {"id,value\nc测试,c\nb测试,b\na测试,a\n", + "id,value\nb测试,b\nc测试,c\na测试,a\n"}, + {"id,value\na测试,a\nb测试,b\nc测试,c\n", + "id,value\na测试,a\nb测试,b\nc测试,c\n"}, + {{"id"}, {"id"}}, + PsiType::ECDH_PSI_2PC, + 64, + true, + true, + 3, + }, + TestParams{ + {3, 3}, + {"id,value\nc测试,c\nb测试,b\na测试,a\n", + "id,value\nb测试,b\nc测试,c\na测试,a\n"}, + {"id,value\na测试,a\nb测试,b\nc测试,c\n", + "id,value\na测试,a\nb测试,b\nc测试,c\n"}, + {{"id"}, {"id"}}, + PsiType::KKRT_PSI_2PC, + 64, + true, + false, + 3, + }, + TestParams{ + {5, 6}, + {"id,value\nc测试,c\nb测试,b\na测试,a\nn测试,n\nj测试,j\n", + "id,value\nb测试,b\nc测试,c\nj测试,t\na测试,a\nk测试,k\nq测试," + "q\n"}, + {"id,value\na测试,a\nb测试,b\nc测试,c\nj测试,j\n", + "id,value\na测试,a\nb测试,b\nc测试,c\nj测试,t\n"}, + {{"id"}, {"id"}}, + PsiType::BC22_PSI_2PC, + 64, + true, + false, + 4, + }, + TestParams{ + {3, 3, 3}, + {"id,value\nc测试,c\nb测试,b\na测试,a\n", + "id,value\nb测试,b\nc测试,c\na测试,a\n", + "id,value\na测试,a\nc测试,c\nb测试,b\n"}, + {"id,value\na测试,a\nb测试,b\nc测试,c\n", + "id,value\na测试,a\nb测试,b\nc测试,c\n", + "id,value\na测试,a\nb测试,b\nc测试,c\n"}, + {{"id"}, {"id"}, {"id"}}, + PsiType::ECDH_PSI_3PC, + 64, + true, + false, + 3, + }, + TestParams{ + {3, 3, 3, 3}, + {"id,value\nc测试,c\nb测试,b\na测试,a\n", + "id,value\nb测试,b\nc测试,c\na测试,a\n", + "id,value\na测试,a\nc测试,c\nb测试,b\n", + "id,value\na测试,b\nc测试,c\nb测试,b\n"}, + {"id,value\na测试,a\nb测试,b\nc测试,c\n", + "id,value\na测试,a\nb测试,b\nc测试,c\n", + "id,value\na测试,a\nb测试,b\nc测试,c\n", + "id,value\na测试,b\nb测试,b\nc测试,c\n"}, + {{"id"}, {"id"}, {"id"}, {"id"}}, + PsiType::ECDH_PSI_NPC, + 64, + true, + false, + 3, + }, + TestParams{ + {3, 3, 3, 3}, + {"id,value\nc测试,c\nb测试,b\na测试,a\n", + "id,value\nb测试,b\nc测试,c\na测试,a\n", + "id,value\na测试,a\nc测试,c\nb测试,b\n", + "id,value\na测试,b\nc测试,c\nb测试,b\n"}, + {"id,value\na测试,a\nb测试,b\nc测试,c\n", + "id,value\na测试,a\nb测试,b\nc测试,c\n", + "id,value\na测试,a\nb测试,b\nc测试,c\n", + "id,value\na测试,b\nb测试,b\nc测试,c\n"}, + {{"id"}, {"id"}, {"id"}, {"id"}}, + PsiType::KKRT_PSI_NPC, + 64, + true, + false, + 3, + }, + + // one party empty + TestParams{ + {3, 0}, + {"id,value\nc测试,c\nb测试,b\na测试,a\n", "id,value\n"}, + {"id,value\n", "id,value\n"}, + {{"id"}, {"id"}}, + PsiType::ECDH_PSI_2PC, + 64, + true, + false, + 0, + }, + TestParams{ + {3, 0}, + {"id,value\nc测试,c\nb测试,b\na测试,a\n", "id,value\n"}, + {"id,value\n", "id,value\n"}, + {{"id"}, {"id"}}, + PsiType::KKRT_PSI_2PC, + 64, + true, + false, + 0, + }, + TestParams{ + {3, 0, 3}, + {"id,value\nc测试,c\nb测试,b\na测试,a\n", "id,value\n", + "id,value\na测试,a\nc测试,c\nb测试,b\n"}, + {"id,value\n", "id,value\n", "id,value\n"}, + {{"id"}, {"id"}, {"id"}}, + PsiType::ECDH_PSI_3PC, + 64, + true, + false, + 0, + }, + TestParams{ + {3, 0, 3, 3}, + {"id,value\nc测试,c\nb测试,b\na测试,a\n", "id,value\n", + "id,value\na测试,a\nc测试,c\nb测试,b\n", + "id,value\na测试,b\nc测试,c\nb测试,b\n"}, + {"id,value\n", "id,value\n", "id,value\n", "id,value\n"}, + {{"id"}, {"id"}, {"id"}, {"id"}}, + PsiType::ECDH_PSI_NPC, + 64, + true, + false, + 0, + }, + TestParams{ + {3, 0, 3, 3}, + {"id,value\nc测试,c\nb测试,b\na测试,a\n", "id,value\n", + "id,value\na测试,a\nc测试,c\nb测试,b\n", + "id,value\na测试,b\nc测试,c\nb测试,b\n"}, + {"id,value\n", "id,value\n", "id,value\n", "id,value\n"}, + {{"id"}, {"id"}, {"id"}, {"id"}}, + PsiType::KKRT_PSI_NPC, + 64, + true, + false, + 0, + }, + + // multi key + TestParams{ + {3, 2}, + {"f2,id\n1,a\n1,b\n6,c\n", "f1,id\n1,b\n6,c\n"}, + {"f2,id\n1,b\n6,c\n", "f1,id\n1,b\n6,c\n"}, + {{"f2", "id"}, {"f1", "id"}}, + PsiType::ECDH_PSI_2PC, + 64, + true, + false, + 2, + }, + TestParams{ + {3, 2}, + {"f2,id\n1,a\n1,b\n6,c\n", "f1,id\n1,b\n6,c\n"}, + {"f2,id\n1,b\n6,c\n", "f1,id\n1,b\n6,c\n"}, + {{"f2", "id"}, {"f1", "id"}}, + PsiType::ECDH_PSI_2PC, + 64, + true, + true, + 2, + }, + TestParams{ + {3, 2}, + {"f2,id\n1,a\n1,b\n6,c\n", "f1,id\n1,b\n6,c\n"}, + {"f2,id\n1,b\n6,c\n", "f1,id\n1,b\n6,c\n"}, + {{"f2", "id"}, {"f1", "id"}}, + PsiType::KKRT_PSI_2PC, + 64, + true, + false, + 2, + }, + TestParams{ + {3, 2, 1}, + {"f2,id\n1,a\n1,b\n6,c\n", "f1,id\n1,b\n6,c\n", "f3,id\n1,b\n"}, + {"f2,id\n1,b\n", "f1,id\n1,b\n", "f3,id\n1,b\n"}, + {{"f2", "id"}, {"f1", "id"}, {"f3", "id"}}, + PsiType::ECDH_PSI_3PC, + 64, + true, + false, + 1, + }, + TestParams{ + {3, 2, 2, 1}, + {"f2,id\n1,a\n1,b\n6,c\n", "f1,id\n1,b\n6,c\n", "f3,id\n1,b\n1,a\n", + "f4,id\n1,b\n"}, + {"f2,id\n1,b\n", "f1,id\n1,b\n", "f3,id\n1,b\n", "f4,id\n1,b\n"}, + {{"f2", "id"}, {"f1", "id"}, {"f3", "id"}, {"f4", "id"}}, + PsiType::ECDH_PSI_NPC, + 64, + true, + false, + 1, + }, + TestParams{ + {3, 2, 2, 1}, + {"f2,id\n1,a\n1,b\n6,c\n", "f1,id\n1,b\n6,c\n", "f3,id\n1,b\n1,a\n", + "f4,id\n1,b\n"}, + {"f2,id\n1,b\n", "f1,id\n1,b\n", "f3,id\n1,b\n", "f4,id\n1,b\n"}, + {{"f2", "id"}, {"f1", "id"}, {"f3", "id"}, {"f4", "id"}}, + PsiType::KKRT_PSI_NPC, + 64, + true, + false, + 1, + })); + +struct FailedTestParams { + size_t party_num; + size_t receiver_rank; + PsiType psi_protocol; +}; + +class BucketTaskPsiTestFailedTest + : public testing::TestWithParam<FailedTestParams> {}; + +TEST_P(BucketTaskPsiTestFailedTest, FailedWorks) { + auto params = GetParam(); + + auto lctxs = yacl::link::test::SetupWorld(params.party_num); + + BucketPsiConfig config; + config.set_psi_type(params.psi_protocol); + config.set_receiver_rank(params.receiver_rank); + config.set_broadcast_result(true); + config.set_curve_type(CurveType::CURVE_25519); + + ASSERT_ANY_THROW(BucketPsi ctx(config, lctxs[0])); +} + +INSTANTIATE_TEST_SUITE_P(FailedWorks_Instances, BucketTaskPsiTestFailedTest, + testing::Values( + // invalid link world size + FailedTestParams{3, 0, PsiType::KKRT_PSI_2PC}, + FailedTestParams{4, 0, PsiType::ECDH_PSI_2PC}, + FailedTestParams{1, 0, PsiType::BC22_PSI_2PC}, + FailedTestParams{2, 0, PsiType::ECDH_PSI_3PC}, + // invalid receiver_rank + FailedTestParams{3, 4, PsiType::ECDH_PSI_3PC}, + FailedTestParams{2, 5, PsiType::KKRT_PSI_2PC}, + // invalid psi_type + FailedTestParams{3, 4, + PsiType::INVALID_PSI_TYPE})); + +class UnbalancedPsiTest { + public: + UnbalancedPsiTest() { SetUp(); } + + ~UnbalancedPsiTest() { TearDown(); } + + void SetUp() { + // tmp_dir_ = "./tmp"; + tmp_dir_ = fmt::format("tmp_{}", yacl::crypto::SecureRandU64()); + std::filesystem::create_directory(tmp_dir_); + } + + void TearDown() { + input_paths_.clear(); + output_paths_.clear(); + + if (!tmp_dir_.empty()) { + std::error_code ec; + std::filesystem::remove_all(tmp_dir_, ec); + // Leave error as it is, do nothing + } + } + + void SetupTmpfilePaths(size_t num) { + for (size_t i = 0; i < num; ++i) { + input_paths_.emplace_back(fmt::format("{}/tmp-input-{}", tmp_dir_, i)); + output_paths_.emplace_back(fmt::format("{}/tmp-output-{}", tmp_dir_, i)); + } + } + + std::string tmp_dir_; + std::vector<std::string> input_paths_; + std::vector<std::string> output_paths_; +}; + +TEST(UnbalancedPsiTest, EcdhOprfUnbalanced) { + UnbalancedPsiTest unbalanced_psi_test; + TestParams params = { + {4, 15}, + {"id,value\na,1\nb,2\ne,1\nc,1\n", + "id,value\ne,1\nc,1\na,1\nj,1\nk,1\nq,1\nw,1\nn,1\nr,1\nt,1\ny," + "1\nu,1\ni,1\no,1\np,1\n"}, + {"id,value\na,1\nc,1\ne,1\n", "id,value\na,1\nc,1\ne,1\n"}, + {{"id"}, {"id"}}, + PsiType::ECDH_OPRF_UB_PSI_2PC_OFFLINE, + 64, + true, + false, + 3, + }; + size_t receiver_rank = 0; + + unbalanced_psi_test.SetupTmpfilePaths(params.in_content_list.size()); + auto lctxs = yacl::link::test::SetupWorld(params.in_content_list.size()); + + std::string preprocess_file_path = + fmt::format("{}/preprocess-cipher-store-{}.db", + unbalanced_psi_test.tmp_dir_, receiver_rank); + + std::string ecdh_secret_key_path = fmt::format( + "{}/ecdh-secret-key.bin", unbalanced_psi_test.tmp_dir_, receiver_rank); + + // write temp secret key + { + std::ofstream wf(ecdh_secret_key_path, std::ios::out | std::ios::binary); + + std::string secret_key_binary = absl::HexStringToBytes( + "000102030405060708090a0b0c0d0e0ff0e0d0c0b0a090807060504030201000"); + wf.write(secret_key_binary.data(), secret_key_binary.length()); + wf.close(); + } + + // offline phase + auto offline_proc = [&](int idx) -> PsiResultReport { + BucketPsiConfig config; + + config.mutable_input_params()->set_path( + unbalanced_psi_test.input_paths_[idx]); + config.mutable_input_params()->mutable_select_fields()->Add( + params.field_names_list[idx].begin(), + params.field_names_list[idx].end()); + config.mutable_input_params()->set_precheck(true); + config.mutable_output_params()->set_path( + unbalanced_psi_test.output_paths_[idx]); + config.mutable_output_params()->set_need_sort(params.should_sort); + config.set_psi_type(params.psi_protocol); + config.set_receiver_rank(receiver_rank); + config.set_broadcast_result(false); + config.set_bucket_size(1000000); + config.set_curve_type(CurveType::CURVE_FOURQ); + if (receiver_rank == lctxs[idx]->Rank()) { + config.set_preprocess_path(preprocess_file_path); + } else { + config.set_ecdh_secret_key_path(ecdh_secret_key_path); + } + + BucketPsi ctx(config, lctxs[idx]); + return ctx.Run(); + }; + + size_t world_size = lctxs.size(); + std::vector<std::future<PsiResultReport>> offline_f_links(world_size); + for (size_t i = 0; i < world_size; i++) { + WriteFile(unbalanced_psi_test.input_paths_[i], params.in_content_list[i]); + offline_f_links[i] = std::async(offline_proc, i); + } + + for (size_t i = 0; i < world_size; i++) { + auto report = offline_f_links[i].get(); + SPDLOG_INFO("{}", report.intersection_count()); + if (i != receiver_rank) { + EXPECT_EQ(report.original_count(), params.item_size_list[i]); + } + } + + // online phase + auto online_proc = [&](int idx) -> PsiResultReport { + std::string input_file_path = unbalanced_psi_test.input_paths_[idx]; + std::string output_file_path = unbalanced_psi_test.output_paths_[idx]; + std::vector<std::string> id_list = params.field_names_list[idx]; + + BucketPsiConfig config; + config.mutable_input_params()->set_path(input_file_path); + config.mutable_input_params()->mutable_select_fields()->Add(id_list.begin(), + id_list.end()); + config.mutable_input_params()->set_precheck(true); + config.mutable_output_params()->set_path(output_file_path); + config.mutable_output_params()->set_need_sort(params.should_sort); + config.set_psi_type(PsiType::ECDH_OPRF_UB_PSI_2PC_ONLINE); + config.set_receiver_rank(receiver_rank); + config.set_broadcast_result(false); + config.set_bucket_size(1000000); + config.set_curve_type(CurveType::CURVE_FOURQ); + + if (receiver_rank == lctxs[idx]->Rank()) { + config.set_preprocess_path(preprocess_file_path); + } else { + config.set_ecdh_secret_key_path(ecdh_secret_key_path); + } + + BucketPsi ctx(config, lctxs[idx]); + return ctx.Run(); + }; + + std::vector<std::future<PsiResultReport>> online_f_links(world_size); + for (size_t i = 0; i < world_size; i++) { + WriteFile(unbalanced_psi_test.input_paths_[i], params.in_content_list[i]); + online_f_links[i] = std::async(online_proc, i); + } + + for (size_t i = 0; i < world_size; i++) { + auto report = online_f_links[i].get(); + SPDLOG_INFO("{}", report.intersection_count()); + + if (i == receiver_rank) { + EXPECT_EQ(report.original_count(), params.item_size_list[i]); + EXPECT_EQ(report.intersection_count(), + GetFileLineCount(unbalanced_psi_test.output_paths_[i]) - 1); + EXPECT_EQ(params.expect_result_size, + GetFileLineCount(unbalanced_psi_test.output_paths_[i]) - 1); + EXPECT_EQ(params.out_content_list[i], + ReadFileToString(unbalanced_psi_test.output_paths_[i])); + } + } +} + +} // namespace psi::psi diff --git a/psi/psi/bucket_ub_psi.cc b/psi/psi/bucket_ub_psi.cc new file mode 100644 index 00000000..7b1d5c82 --- /dev/null +++ b/psi/psi/bucket_ub_psi.cc @@ -0,0 +1,505 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/bucket_ub_psi.h" + +#include <memory> +#include <unordered_set> + +#include "yacl/crypto/utils/rand.h" +#include "yacl/utils/scope_guard.h" + +#include "psi/psi/io/io.h" +#include "psi/psi/prelude.h" +#include "psi/psi/utils/serialize.h" +#include "psi/psi/utils/utils.h" + +namespace psi::psi { + +namespace { + +std::vector<uint8_t> ReadEcSecretKeyFile(const std::string& file_path) { + size_t file_byte_size = 0; + try { + file_byte_size = std::filesystem::file_size(file_path); + } catch (std::filesystem::filesystem_error& e) { + YACL_THROW("ReadEcSecretKeyFile {} Error: {}", file_path, e.what()); + } + YACL_ENFORCE(file_byte_size == kEccKeySize, + "error format: key file bytes is not {}", kEccKeySize); + + std::vector<uint8_t> secret_key(kEccKeySize); + + auto in = io::BuildInputStream(io::FileIoOptions(file_path)); + in->Read(secret_key.data(), kEccKeySize); + in->Close(); + + return secret_key; +} + +std::vector<std::string> GetItemsByIndices( + const std::string& input_path, + const std::vector<std::string>& selected_fields, + const std::vector<uint64_t>& indices, size_t batch_size = 8192) { + std::vector<std::string> items; + std::unordered_set<size_t> indices_set; + + indices_set.insert(indices.begin(), indices.end()); + + std::shared_ptr<IBasicBatchProvider> batch_provider = + std::make_shared<CsvBatchProvider>(input_path, selected_fields, + batch_size); + + size_t item_index = 0; + while (true) { + auto batch_items = batch_provider->ReadNextBatch(); + if (batch_items.empty()) { + break; + } + for (size_t i = 0; i < batch_items.size(); ++i) { + if (indices_set.find(item_index) != indices_set.end()) { + items.push_back(batch_items[i]); + } + item_index++; + } + } + return items; +} + +} // namespace + +std::pair<std::vector<uint64_t>, size_t> UbPsi( + BucketPsiConfig config, std::shared_ptr<yacl::link::Context> lctx) { + EcdhOprfPsiOptions psi_options; + psi_options.link0 = lctx; + + if (config.psi_type() == PsiType::ECDH_OPRF_UB_PSI_2PC_GEN_CACHE) { + psi_options.link1 = lctx; + } else { + psi_options.link1 = lctx->Spawn(); + } + + psi_options.curve_type = config.curve_type(); + + std::string tmp_dir = + fmt::format("bucket_tmp_{}", yacl::crypto::SecureRandU64()); + std::filesystem::create_directory(tmp_dir); + + // register remove of temp dir. + ON_SCOPE_EXIT([&] { + if (!tmp_dir.empty()) { + std::error_code ec; + std::filesystem::remove_all(tmp_dir, ec); + if (ec.value() != 0) { + SPDLOG_WARN("can not remove tmp dir: {}, msg: {}", tmp_dir, + ec.message()); + } + } + }); + + SPDLOG_INFO("input file path:{}", config.input_params().path()); + SPDLOG_INFO("output file path:{}", config.output_params().path()); + + std::pair<std::vector<uint64_t>, size_t> results; + + switch (config.psi_type()) { + case PsiType::ECDH_OPRF_UB_PSI_2PC_GEN_CACHE: + results = UbPsiServerGenCache(config, lctx, psi_options); + break; + case PsiType::ECDH_OPRF_UB_PSI_2PC_TRANSFER_CACHE: + if (lctx->Rank() == config.receiver_rank()) { + results = UbPsiClientTransferCache(config, lctx, psi_options, tmp_dir); + } else { + results = UbPsiServerTransferCache(config, lctx, psi_options, tmp_dir); + } + break; + case PsiType::ECDH_OPRF_UB_PSI_2PC_SHUFFLE_ONLINE: + if (lctx->Rank() == config.receiver_rank()) { + results = UbPsiServerShuffleOnline(config, lctx, psi_options, tmp_dir); + } else { + results = UbPsiClientShuffleOnline(config, lctx, psi_options, tmp_dir); + } + break; + case PsiType::ECDH_OPRF_UB_PSI_2PC_OFFLINE: + if (lctx->Rank() == config.receiver_rank()) { + results = UbPsiClientOffline(config, lctx, psi_options, tmp_dir); + } else { + results = UbPsiServerOffline(config, lctx, psi_options, tmp_dir); + } + break; + case PsiType::ECDH_OPRF_UB_PSI_2PC_ONLINE: + if (lctx->Rank() == config.receiver_rank()) { + results = UbPsiClientOnline(config, lctx, psi_options, tmp_dir); + } else { + results = UbPsiServerOnline(config, lctx, psi_options, tmp_dir); + } + break; + default: + YACL_THROW("Invalid unbalanced psi subprotocol: {}", config.psi_type()); + } + + if (config.psi_type() != PsiType::ECDH_OPRF_UB_PSI_2PC_GEN_CACHE) { + SPDLOG_INFO("rank:{} Start end sync", lctx->Rank()); + AllGatherItemsSize(lctx, 0); + SPDLOG_INFO("rank:{} After end sync", lctx->Rank()); + } + + return results; +} + +// generate cache +std::pair<std::vector<uint64_t>, size_t> UbPsiServerGenCache( + BucketPsiConfig config, std::shared_ptr<yacl::link::Context> /*lctx*/, + const EcdhOprfPsiOptions& psi_options) { + std::vector<uint8_t> server_private_key = + ReadEcSecretKeyFile(config.ecdh_secret_key_path()); + + std::shared_ptr<EcdhOprfPsiServer> dh_oprf_psi_server = + std::make_shared<EcdhOprfPsiServer>(psi_options, server_private_key); + + std::vector<std::string> selected_fields; + selected_fields.insert(selected_fields.end(), + config.input_params().select_fields().begin(), + config.input_params().select_fields().end()); + + std::shared_ptr<IShuffledBatchProvider> batch_provider = + std::make_shared<CachedCsvBatchProvider>( + config.input_params().path(), selected_fields, psi_options.batch_size, + config.bucket_size(), true); + + std::shared_ptr<IUbPsiCache> ub_cache = std::make_shared<UbPsiCache>( + config.output_params().path(), dh_oprf_psi_server->GetCompareLength(), + selected_fields); + + size_t self_items_count = + dh_oprf_psi_server->FullEvaluate(batch_provider, ub_cache); + + std::vector<uint64_t> results; + + return std::make_pair(results, self_items_count); +} + +// transfer cache +std::pair<std::vector<uint64_t>, size_t> UbPsiClientTransferCache( + BucketPsiConfig config, std::shared_ptr<yacl::link::Context> lctx, + const EcdhOprfPsiOptions& psi_options, const std::string& tmp_dir) { + std::shared_ptr<EcdhOprfPsiClient> ub_psi_client_transfer_cache = + std::make_shared<EcdhOprfPsiClient>(psi_options); + + std::string self_ec_point_store_path = + fmt::format("{}/tmp-self-cipher-store-{}.csv", tmp_dir, lctx->Rank()); + + auto peer_ec_point_store = std::make_shared<CachedCsvEcPointStore>( + config.preprocess_path(), false, "peer", false); + + SPDLOG_INFO("Start Sync"); + AllGatherItemsSize(lctx, 0); + SPDLOG_INFO("After Sync"); + + ub_psi_client_transfer_cache->RecvFinalEvaluatedItems(peer_ec_point_store); + + peer_ec_point_store->Flush(); + + std::vector<uint64_t> results; + + return std::make_pair(results, 0); +} + +std::pair<std::vector<uint64_t>, size_t> UbPsiServerTransferCache( + BucketPsiConfig config, std::shared_ptr<yacl::link::Context> lctx, + const EcdhOprfPsiOptions& psi_options, const std::string& /*tmp_dir*/) { + std::array<uint8_t, kEccKeySize> server_private_key; + + std::shared_ptr<EcdhOprfPsiServer> ub_psi_server_transfer_cache = + std::make_shared<EcdhOprfPsiServer>(psi_options, server_private_key); + + std::shared_ptr<IBasicBatchProvider> batch_provider = + std::make_shared<UbPsiCacheProvider>( + config.input_params().path(), psi_options.batch_size, + ub_psi_server_transfer_cache->GetCompareLength()); + + SPDLOG_INFO("Start sync"); + AllGatherItemsSize(lctx, 0); + SPDLOG_INFO("After sync"); + + size_t self_items_count = + ub_psi_server_transfer_cache->SendFinalEvaluatedItems(batch_provider); + + std::vector<uint64_t> results; + + return std::make_pair(results, self_items_count); +} + +// online with shuffling +std::pair<std::vector<uint64_t>, size_t> UbPsiClientShuffleOnline( + BucketPsiConfig config, std::shared_ptr<yacl::link::Context> lctx, + const EcdhOprfPsiOptions& psi_options, const std::string& tmp_dir) { + std::vector<uint8_t> private_key = yacl::crypto::RandBytes(kEccKeySize); + + std::shared_ptr<EcdhOprfPsiClient> ub_psi_client_shuffle_online = + std::make_shared<EcdhOprfPsiClient>(psi_options, private_key); + + std::vector<std::string> selected_fields; + selected_fields.insert(selected_fields.end(), + config.input_params().select_fields().begin(), + config.input_params().select_fields().end()); + + std::shared_ptr<IBasicBatchProvider> batch_provider = + std::make_shared<CsvBatchProvider>(config.input_params().path(), + selected_fields, + psi_options.batch_size); + + std::string ec_point_store_path1 = + fmt::format("{}/tmp-self-cipher-store-{}.csv", tmp_dir, lctx->Rank()); + + auto self_ec_point_store = std::make_shared<CachedCsvEcPointStore>( + ec_point_store_path1, true, "self", false); + + auto peer_ec_point_store = std::make_shared<CachedCsvEcPointStore>( + config.preprocess_path(), false, "peer", true); + + SPDLOG_INFO("shuffle online protocol CachedCsvCipherStore: {} {}", + ec_point_store_path1, config.preprocess_path()); + + size_t self_items_count = + ub_psi_client_shuffle_online->SendBlindedItems(batch_provider); + + ub_psi_client_shuffle_online->RecvEvaluatedItems(self_ec_point_store); + + self_ec_point_store->Flush(); + + std::vector<uint64_t> indices; + std::vector<std::string> masked_items; + std::tie(indices, masked_items) = FinalizeAndComputeIndices( + self_ec_point_store, peer_ec_point_store, config.bucket_size()); + SPDLOG_INFO("indices size:{}", indices.size()); + + std::shared_ptr<IBasicBatchProvider> intersection_masked_provider = + std::make_shared<MemoryBatchProvider>(masked_items, + psi_options.batch_size); + ub_psi_client_shuffle_online->SendIntersectionMaskedItems( + intersection_masked_provider); + + std::vector<uint64_t> results; + + return std::make_pair(results, self_items_count); +} + +std::pair<std::vector<uint64_t>, size_t> UbPsiServerShuffleOnline( + BucketPsiConfig config, std::shared_ptr<yacl::link::Context> /*lctx*/, + const EcdhOprfPsiOptions& psi_options, const std::string& /*tmp_dir*/) { + std::vector<uint8_t> server_private_key = + ReadEcSecretKeyFile(config.ecdh_secret_key_path()); + + std::shared_ptr<EcdhOprfPsiServer> ub_psi_server_shuffle_online = + std::make_shared<EcdhOprfPsiServer>(psi_options, server_private_key); + + ub_psi_server_shuffle_online->RecvBlindAndShuffleSendEvaluate(); + + std::shared_ptr<IShuffledBatchProvider> cache_provider = + std::make_shared<UbPsiCacheProvider>( + config.preprocess_path(), config.bucket_size(), + ub_psi_server_shuffle_online->GetCompareLength()); + + size_t self_items_size; + std::vector<uint64_t> results; + std::tie(results, self_items_size) = + ub_psi_server_shuffle_online->RecvIntersectionMaskedItems(cache_provider); + + return std::make_pair(results, self_items_size); +} + +// offline +std::pair<std::vector<uint64_t>, size_t> UbPsiClientOffline( + BucketPsiConfig config, std::shared_ptr<yacl::link::Context> lctx, + const EcdhOprfPsiOptions& psi_options, const std::string& tmp_dir) { + std::shared_ptr<EcdhOprfPsiClient> dh_oprf_psi_client_offline = + std::make_shared<EcdhOprfPsiClient>(psi_options); + + std::string self_ec_point_store_path = + fmt::format("{}/tmp-self-cipher-store-{}.csv", tmp_dir, lctx->Rank()); + + auto self_ec_point_store = std::make_shared<CachedCsvEcPointStore>( + self_ec_point_store_path, true, "self", false); + + auto peer_ec_point_store = std::make_shared<CachedCsvEcPointStore>( + config.preprocess_path(), false, "peer", false); + + SPDLOG_INFO("Start Sync"); + AllGatherItemsSize(lctx, 0); + SPDLOG_INFO("After Sync"); + + dh_oprf_psi_client_offline->RecvFinalEvaluatedItems(peer_ec_point_store); + + peer_ec_point_store->Flush(); + + std::vector<uint64_t> results; + + return std::make_pair(results, 0); +} + +std::pair<std::vector<uint64_t>, size_t> UbPsiServerOffline( + BucketPsiConfig config, std::shared_ptr<yacl::link::Context> lctx, + const EcdhOprfPsiOptions& psi_options, const std::string& /*tmp_dir*/) { + std::vector<uint8_t> server_private_key = + ReadEcSecretKeyFile(config.ecdh_secret_key_path()); + + std::shared_ptr<EcdhOprfPsiServer> dh_oprf_psi_server_offline = + std::make_shared<EcdhOprfPsiServer>(psi_options, server_private_key); + + std::vector<std::string> selected_fields; + selected_fields.insert(selected_fields.end(), + config.input_params().select_fields().begin(), + config.input_params().select_fields().end()); + + std::shared_ptr<IShuffledBatchProvider> batch_provider = + std::make_shared<CachedCsvBatchProvider>( + config.input_params().path(), selected_fields, psi_options.batch_size, + config.bucket_size(), true); + + SPDLOG_INFO("Start sync"); + AllGatherItemsSize(lctx, 0); + SPDLOG_INFO("After sync"); + + size_t self_items_count = + dh_oprf_psi_server_offline->FullEvaluateAndSend(batch_provider); + + std::vector<uint64_t> results; + + return std::make_pair(results, self_items_count); +} + +// online +std::pair<std::vector<uint64_t>, size_t> UbPsiClientOnline( + BucketPsiConfig config, std::shared_ptr<yacl::link::Context> lctx, + const EcdhOprfPsiOptions& psi_options, const std::string& tmp_dir) { + std::shared_ptr<EcdhOprfPsiClient> dh_oprf_psi_client_online = + std::make_shared<EcdhOprfPsiClient>(psi_options); + + std::vector<std::string> selected_fields; + selected_fields.insert(selected_fields.end(), + config.input_params().select_fields().begin(), + config.input_params().select_fields().end()); + + std::shared_ptr<IBasicBatchProvider> batch_provider = + std::make_shared<CsvBatchProvider>(config.input_params().path(), + selected_fields, + psi_options.batch_size); + + std::string ec_point_store_path1 = + fmt::format("{}/tmp-self-cipher-store-{}.csv", tmp_dir, lctx->Rank()); + + auto self_ec_point_store = std::make_shared<CachedCsvEcPointStore>( + ec_point_store_path1, true, "self", false); + + auto peer_ec_point_store = std::make_shared<CachedCsvEcPointStore>( + config.preprocess_path(), false, "peer", true); + + SPDLOG_INFO("online protocol CachedCsvCipherStore: {} {}", + ec_point_store_path1, config.preprocess_path()); + + std::future<size_t> f_client_send_blind = std::async([&] { + return dh_oprf_psi_client_online->SendBlindedItems(batch_provider); + }); + + // self_ec_point_store + dh_oprf_psi_client_online->RecvEvaluatedItems(self_ec_point_store); + + self_ec_point_store->Flush(); + + size_t self_items_count = f_client_send_blind.get(); + + std::vector<uint64_t> results; + std::vector<std::string> masked_items; + std::tie(results, masked_items) = FinalizeAndComputeIndices( + self_ec_point_store, peer_ec_point_store, config.bucket_size()); + + YACL_ENFORCE(results.size() == masked_items.size()); + SPDLOG_INFO("indices size:{}", results.size()); + + if (config.broadcast_result()) { + // send intersection size + lctx->SendAsyncThrottled( + lctx->NextRank(), utils::SerializeSize(results.size()), + fmt::format("EC-OPRF:PSI:INTERSECTION_SIZE={}", results.size())); + + SPDLOG_INFO("rank:{} begin broadcast {} intersection results", lctx->Rank(), + results.size()); + + if (results.size() > 0) { + std::vector<std::string> result_items = GetItemsByIndices( + config.input_params().path(), selected_fields, results); + + auto recv_res_buf = + yacl::link::Broadcast(lctx, utils::SerializeStrItems(result_items), + config.receiver_rank(), "broadcast psi result"); + + SPDLOG_INFO("rank:{} result size:{}", lctx->Rank(), result_items.size()); + } + SPDLOG_INFO("rank:{} end broadcast {} intersection results", lctx->Rank(), + results.size()); + } + + return std::make_pair(results, self_items_count); +} + +std::pair<std::vector<uint64_t>, size_t> UbPsiServerOnline( + BucketPsiConfig config, std::shared_ptr<yacl::link::Context> lctx, + const EcdhOprfPsiOptions& psi_options, const std::string& /*tmp_dir*/) { + std::vector<uint8_t> server_private_key = + ReadEcSecretKeyFile(config.ecdh_secret_key_path()); + + std::shared_ptr<EcdhOprfPsiServer> dh_oprf_psi_server_online = + std::make_shared<EcdhOprfPsiServer>(psi_options, server_private_key); + + dh_oprf_psi_server_online->RecvBlindAndSendEvaluate(); + + std::vector<uint64_t> results; + + if (config.broadcast_result()) { + size_t intersection_size = utils::DeserializeSize(lctx->Recv( + lctx->NextRank(), fmt::format("EC-OPRF:PSI:INTERSECTION_SIZE"))); + + SPDLOG_INFO("rank:{} begin recv broadcast {} intersection results", + lctx->Rank(), results.size(), intersection_size); + + if (intersection_size > 0) { + std::vector<std::string> result_items; + + auto recv_res_buf = + yacl::link::Broadcast(lctx, utils::SerializeStrItems(result_items), + config.receiver_rank(), "broadcast psi result"); + + utils::DeserializeStrItems(recv_res_buf, &result_items); + + std::vector<std::string> selected_fields; + selected_fields.insert(selected_fields.end(), + config.input_params().select_fields().begin(), + config.input_params().select_fields().end()); + + SPDLOG_INFO("begin GetIndicesByItems"); + results = GetIndicesByItems(config.input_params().path(), selected_fields, + result_items, config.bucket_size()); + SPDLOG_INFO("end GetIndicesByItems"); + + SPDLOG_INFO("rank:{} result size:{}", lctx->Rank(), result_items.size()); + } + SPDLOG_INFO("rank:{} end recv broadcast {} intersection results", + lctx->Rank(), results.size()); + } + + return std::make_pair(results, 0); +} + +} // namespace psi::psi diff --git a/psi/psi/bucket_ub_psi.h b/psi/psi/bucket_ub_psi.h new file mode 100644 index 00000000..b07baa90 --- /dev/null +++ b/psi/psi/bucket_ub_psi.h @@ -0,0 +1,70 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "yacl/base/exception.h" +#include "yacl/link/link.h" + +#include "psi/psi/core/ecdh_oprf_psi.h" + +#include "psi/psi/psi.pb.h" + +namespace psi::psi { + +std::pair<std::vector<uint64_t>, size_t> UbPsi( + BucketPsiConfig config, std::shared_ptr<yacl::link::Context> lctx); + +std::pair<std::vector<uint64_t>, size_t> UbPsiServerGenCache( + BucketPsiConfig config, std::shared_ptr<yacl::link::Context> lctx, + const EcdhOprfPsiOptions& psi_options); + +std::pair<std::vector<uint64_t>, size_t> UbPsiClientTransferCache( + BucketPsiConfig config, std::shared_ptr<yacl::link::Context> lctx, + const EcdhOprfPsiOptions& psi_options, const std::string& tmp_dir); + +std::pair<std::vector<uint64_t>, size_t> UbPsiServerTransferCache( + BucketPsiConfig config, std::shared_ptr<yacl::link::Context> lctx, + const EcdhOprfPsiOptions& psi_options, const std::string& tmp_dir); + +std::pair<std::vector<uint64_t>, size_t> UbPsiClientShuffleOnline( + BucketPsiConfig config, std::shared_ptr<yacl::link::Context> lctx, + const EcdhOprfPsiOptions& psi_options, const std::string& tmp_dir); + +std::pair<std::vector<uint64_t>, size_t> UbPsiServerShuffleOnline( + BucketPsiConfig config, std::shared_ptr<yacl::link::Context> lctx, + const EcdhOprfPsiOptions& psi_options, const std::string& tmp_dir); + +std::pair<std::vector<uint64_t>, size_t> UbPsiClientOffline( + BucketPsiConfig config, std::shared_ptr<yacl::link::Context> lctx, + const EcdhOprfPsiOptions& psi_options, const std::string& tmp_dir); + +std::pair<std::vector<uint64_t>, size_t> UbPsiServerOffline( + BucketPsiConfig config, std::shared_ptr<yacl::link::Context> lctx, + const EcdhOprfPsiOptions& psi_options, const std::string& tmp_dir); + +std::pair<std::vector<uint64_t>, size_t> UbPsiClientOnline( + BucketPsiConfig config, std::shared_ptr<yacl::link::Context> lctx, + const EcdhOprfPsiOptions& psi_options, const std::string& tmp_dir); + +std::pair<std::vector<uint64_t>, size_t> UbPsiServerOnline( + BucketPsiConfig config, std::shared_ptr<yacl::link::Context> lctx, + const EcdhOprfPsiOptions& psi_options, const std::string& tmp_dir); + +} // namespace psi::psi diff --git a/psi/psi/bucket_ub_psi_test.cc b/psi/psi/bucket_ub_psi_test.cc new file mode 100644 index 00000000..3447a90e --- /dev/null +++ b/psi/psi/bucket_ub_psi_test.cc @@ -0,0 +1,265 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <fstream> +#include <vector> + +#include "absl/strings/escaping.h" +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/crypto/utils/rand.h" +#include "yacl/link/test_util.h" +#include "yacl/utils/scope_guard.h" + +#include "psi/psi/bucket_psi.h" +#include "psi/psi/io/io.h" +#include "psi/psi/utils/test_utils.h" + +namespace psi::psi { + +namespace { + +std::pair<std::vector<std::string>, std::vector<std::string>> +GenerateUbPsiTestData(size_t begin, size_t items_size) { + std::vector<std::string> items_a = test::CreateRangeItems(begin, items_size); + + std::vector<std::string> items_b = test::CreateRangeItems( + begin + items_size / 4, std::max(static_cast<size_t>(1), items_size / 2)); + + return std::make_pair(items_a, items_b); +} + +void WriteCsvFile(const std::string &file_name, const std::string &column_id, + const std::vector<std::string> &items) { + auto out = io::BuildOutputStream(io::FileIoOptions(file_name)); + out->Write(fmt::format("{}\n", column_id)); + for (const auto &data : items) { + out->Write(fmt::format("{}\n", data)); + } + out->Close(); +} + +void WriteSecretKey(const std::string &ecdh_secret_key_path) { + std::ofstream wf(ecdh_secret_key_path, std::ios::out | std::ios::binary); + + std::string secret_key_binary = absl::HexStringToBytes( + "000102030405060708090a0b0c0d0e0ff0e0d0c0b0a090807060504030201000"); + wf.write(secret_key_binary.data(), secret_key_binary.length()); + wf.close(); +} + +} // namespace + +struct TestParams { + size_t items_size; + CurveType curve_type = CurveType::CURVE_FOURQ; + bool shuffle_online = false; +}; + +class UnbalancedPsiTest : public ::testing::TestWithParam<TestParams> {}; + +TEST_P(UnbalancedPsiTest, Work) { + auto params = GetParam(); + + std::string tmp_dir = fmt::format("tmp_{}", yacl::crypto::SecureRandU64()); + std::filesystem::create_directory(tmp_dir); + + // register remove of temp dir. + ON_SCOPE_EXIT([&] { + if (!tmp_dir.empty()) { + std::error_code ec; + std::filesystem::remove_all(tmp_dir, ec); + if (ec.value() != 0) { + SPDLOG_WARN("can not remove tmp dir: {}, msg: {}", tmp_dir, + ec.message()); + } + } + }); + + auto lctxs = yacl::link::test::SetupWorld(2); + + std::vector<std::string> input_paths(lctxs[0]->WorldSize()); + std::vector<std::string> output_paths(lctxs[0]->WorldSize()); + + std::vector<std::vector<std::string>> items_data(lctxs[0]->WorldSize()); + + std::tie(items_data[0], items_data[1]) = + GenerateUbPsiTestData(0, params.items_size); + + std::vector<std::string> intersection_check = + test::GetIntersection(items_data[0], items_data[1]); + + std::string column_id = "id"; + std::vector<std::string> field_names = {column_id}; + + for (size_t i = 0; i < lctxs[0]->WorldSize(); ++i) { + input_paths[i] = fmt::format("{}/ub-input-rank-{}.csv", tmp_dir, i); + + output_paths[i] = fmt::format("{}/ub-output-rank-{}.csv", tmp_dir, i); + WriteCsvFile(input_paths[i], column_id, items_data[i]); + } + + size_t server_rank = 0; + size_t client_rank = 1; + + std::string preprocess_file_path = + fmt::format("{}/preprocess-cipher-store-{}.csv", tmp_dir, client_rank); + std::string server_cache_path = + fmt::format("{}/server-cache-{}.bin", tmp_dir, server_rank); + std::string ecdh_secret_key_path = + fmt::format("{}/ecdh-secret-key.bin", tmp_dir, server_rank); + + // write temp secret key + WriteSecretKey(ecdh_secret_key_path); + + // step 1, gen cache + { + BucketPsiConfig config; + + config.mutable_input_params()->set_path(input_paths[server_rank]); + config.mutable_input_params()->mutable_select_fields()->Add( + field_names.begin(), field_names.end()); + config.mutable_input_params()->set_precheck(false); + config.mutable_output_params()->set_path(server_cache_path); + config.mutable_output_params()->set_need_sort(false); + config.set_psi_type(PsiType::ECDH_OPRF_UB_PSI_2PC_GEN_CACHE); + config.set_receiver_rank(server_rank); + config.set_broadcast_result(false); + config.set_bucket_size(10000000); + config.set_curve_type(params.curve_type); + config.set_ecdh_secret_key_path(ecdh_secret_key_path); + + BucketPsi ctx(config, nullptr); + + ctx.Run(); + } + + // step 2, transfer cache + auto transfer_cache_proc = [&](int idx) -> PsiResultReport { + BucketPsiConfig config; + + config.mutable_input_params()->set_path(input_paths[idx]); + config.mutable_input_params()->mutable_select_fields()->Add( + field_names.begin(), field_names.end()); + config.mutable_input_params()->set_precheck(false); + config.mutable_output_params()->set_path(output_paths[idx]); + config.mutable_output_params()->set_need_sort(false); + config.set_psi_type(PsiType::ECDH_OPRF_UB_PSI_2PC_TRANSFER_CACHE); + config.set_receiver_rank(client_rank); + config.set_broadcast_result(false); + // set min bucket size for test + config.set_bucket_size(10000000); + config.set_curve_type(params.curve_type); + if (client_rank == lctxs[idx]->Rank()) { + config.set_preprocess_path(preprocess_file_path); + } else { + config.set_ecdh_secret_key_path(ecdh_secret_key_path); + config.mutable_input_params()->set_path(server_cache_path); + } + + BucketPsi ctx(config, lctxs[idx]); + return ctx.Run(); + }; + + size_t world_size = lctxs.size(); + std::vector<std::future<PsiResultReport>> transfer_cache_f(world_size); + for (size_t i = 0; i < world_size; i++) { + transfer_cache_f[i] = std::async(transfer_cache_proc, i); + } + + for (size_t i = 0; i < world_size; i++) { + auto report = transfer_cache_f[i].get(); + SPDLOG_INFO("{}", report.intersection_count()); + if (i != client_rank) { + EXPECT_EQ(report.original_count(), items_data[i].size()); + } + } + + size_t receiver_rank = client_rank; + PsiType psi_protocol = PsiType::ECDH_OPRF_UB_PSI_2PC_ONLINE; + if (params.shuffle_online) { + psi_protocol = PsiType::ECDH_OPRF_UB_PSI_2PC_SHUFFLE_ONLINE; + receiver_rank = server_rank; + } + // online phase + auto online_proc = [&](int idx) -> PsiResultReport { + std::string input_file_path = input_paths[idx]; + std::string output_file_path = output_paths[idx]; + + BucketPsiConfig config; + config.mutable_input_params()->set_path(input_file_path); + config.mutable_input_params()->mutable_select_fields()->Add( + field_names.begin(), field_names.end()); + config.mutable_input_params()->set_precheck(false); + config.mutable_output_params()->set_path(output_file_path); + config.mutable_output_params()->set_need_sort(false); + config.set_psi_type(psi_protocol); + config.set_receiver_rank(receiver_rank); + config.set_broadcast_result(false); + // set min bucket size for test + config.set_bucket_size(10000000); + config.set_curve_type(params.curve_type); + + if (client_rank == lctxs[idx]->Rank()) { + config.set_preprocess_path(preprocess_file_path); + } else { + config.set_preprocess_path(server_cache_path); + config.set_ecdh_secret_key_path(ecdh_secret_key_path); + } + + BucketPsi ctx(config, lctxs[idx]); + return ctx.Run(); + }; + + std::vector<std::future<PsiResultReport>> online_f(world_size); + for (size_t i = 0; i < world_size; i++) { + online_f[i] = std::async(online_proc, i); + } + + for (size_t i = 0; i < world_size; i++) { + auto report = online_f[i].get(); + SPDLOG_INFO("{}", report.intersection_count()); + + if (params.shuffle_online) { + if (i == server_rank) { + EXPECT_EQ(report.original_count(), items_data[i].size()); + EXPECT_EQ(report.intersection_count(), intersection_check.size()); + } + } else { + if (i == client_rank) { + EXPECT_EQ(report.original_count(), items_data[i].size()); + EXPECT_EQ(report.intersection_count(), intersection_check.size()); + } + } + } +} + +INSTANTIATE_TEST_SUITE_P( + Works_Instances, UnbalancedPsiTest, + testing::Values( + // CURVE_FOURQ + TestParams{1}, // + TestParams{10}, // + TestParams{50}, // + TestParams{4095}, // less than one batch + TestParams{4096}, // exactly one batch + TestParams{10000}, // more than one batch + TestParams{10000, CurveType::CURVE_FOURQ, true}, // shuffle online + TestParams{50000, CurveType::CURVE_FOURQ, true}, // shuffle online + TestParams{1000, CurveType::CURVE_SM2}, // CURVE_SM2 + TestParams{1000, CurveType::CURVE_SECP256K1} // Curve256k1 + ) // +); + +} // namespace psi::psi diff --git a/psi/psi/core/BUILD.bazel b/psi/psi/core/BUILD.bazel new file mode 100644 index 00000000..7accbb2c --- /dev/null +++ b/psi/psi/core/BUILD.bazel @@ -0,0 +1,231 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//bazel:psi.bzl", "psi_cc_binary", "psi_cc_library", "psi_cc_test") + +package(default_visibility = ["//visibility:public"]) + +psi_cc_library( + name = "communication", + srcs = ["communication.cc"], + hdrs = ["communication.h"], + deps = [ + ":ic_protocol_psi_cc_proto", + "//psi/psi/utils:serialize", + "@yacl//yacl/base:exception", + "@yacl//yacl/link", + ], +) + +cc_proto_library( + name = "ic_protocol_psi_cc_proto", + deps = ["@org_interconnection//interconnection/runtime:ecdh_psi"], +) + +psi_cc_library( + name = "ecdh_psi", + srcs = ["ecdh_psi.cc"], + hdrs = ["ecdh_psi.h"], + deps = [ + ":communication", + "//psi/psi:recovery", + "//psi/psi/cryptor:cryptor_selector", + "//psi/psi/utils:batch_provider", + "//psi/psi/utils:ec_point_store", + "@com_google_absl//absl/strings", + "@yacl//yacl/link", + "@yacl//yacl/utils:parallel", + ], +) + +psi_cc_test( + name = "ecdh_psi_test", + srcs = ["ecdh_psi_test.cc"], + deps = [ + ":ecdh_psi", + "//psi/psi/utils:test_utils", + ], +) + +psi_cc_binary( + name = "ecdh_psi_bench", + srcs = ["ecdh_psi_bench.cc"], + deps = [ + ":ecdh_psi", + "@com_github_google_benchmark//:benchmark_main", + ], +) + +psi_cc_library( + name = "ecdh_3pc_psi", + srcs = ["ecdh_3pc_psi.cc"], + hdrs = ["ecdh_3pc_psi.h"], + deps = [ + ":ecdh_psi", + ], +) + +psi_cc_test( + name = "ecdh_3pc_psi_test", + srcs = ["ecdh_3pc_psi_test.cc"], + deps = [ + ":ecdh_3pc_psi", + "//psi/psi/utils:test_utils", + ], +) + +psi_cc_binary( + name = "ecdh_3pc_psi_bench", + srcs = ["ecdh_3pc_psi_bench.cc"], + deps = [ + ":ecdh_3pc_psi", + "//psi/psi/utils:test_utils", + "@com_github_google_benchmark//:benchmark_main", + ], +) + +psi_cc_library( + name = "cuckoo_index", + srcs = ["cuckoo_index.cc"], + hdrs = ["cuckoo_index.h"], + linkopts = ["-lm"], + deps = [ + "@com_google_absl//absl/types:span", + "@yacl//yacl/base:exception", + "@yacl//yacl/base:int128", + ], +) + +psi_cc_test( + name = "cuckoo_index_test", + srcs = ["cuckoo_index_test.cc"], + deps = [ + ":cuckoo_index", + "@yacl//yacl/crypto/utils:rand", + ], +) + +psi_cc_library( + name = "kkrt_psi", + srcs = ["kkrt_psi.cc"], + hdrs = ["kkrt_psi.h"], + deps = [ + ":communication", + ":cuckoo_index", + "//psi/psi/utils:serialize", + "@com_google_absl//absl/strings", + "@yacl//yacl/crypto/base/hash:hash_utils", + "@yacl//yacl/crypto/primitives/ot:base_ot", + "@yacl//yacl/crypto/primitives/ot:iknp_ote", + "@yacl//yacl/crypto/primitives/ot:kkrt_ote", + "@yacl//yacl/crypto/utils:rand", + "@yacl//yacl/link", + ], +) + +psi_cc_test( + name = "kkrt_psi_test", + srcs = ["kkrt_psi_test.cc"], + deps = [ + ":kkrt_psi", + "@yacl//yacl/crypto/base/hash:hash_utils", + ], +) + +psi_cc_binary( + name = "kkrt_psi_bench", + srcs = ["kkrt_psi_bench.cc"], + deps = [ + ":kkrt_psi", + "@com_github_google_benchmark//:benchmark_main", + ], +) + +psi_cc_library( + name = "ecdh_oprf_psi", + srcs = ["ecdh_oprf_psi.cc"], + hdrs = ["ecdh_oprf_psi.h"], + linkopts = select({ + "@bazel_tools//src/conditions:darwin": [], + "//conditions:default": ["-fopenmp"], + }), + deps = [ + ":communication", + "@yacl//yacl/base:exception", + "//psi/psi/core/ecdh_oprf:ecdh_oprf_selector", + "//psi/psi/utils:batch_provider", + "//psi/psi/utils:ec_point_store", + "//psi/psi/utils:ub_psi_cache", + "@com_google_absl//absl/strings", + "@yacl//yacl/link", + "@yacl//yacl/utils:parallel", + ] + select({ + "@bazel_tools//src/conditions:darwin_x86_64": ["@local_homebrew_x64//:openmp"], + "@bazel_tools//src/conditions:darwin_arm64": ["@local_homebrew_arm64//:openmp"], + "//conditions:default": [], + }), +) + +psi_cc_test( + name = "ecdh_oprf_psi_test", + srcs = ["ecdh_oprf_psi_test.cc"], + deps = [ + ":ecdh_oprf_psi", + "//psi/psi/utils:test_utils", + "@com_google_absl//absl/time", + "@yacl//yacl/crypto/tools:prg", + "@yacl//yacl/crypto/utils:rand", + "@yacl//yacl/utils:scope_guard", + ], +) + +psi_cc_library( + name = "mini_psi", + srcs = ["mini_psi.cc"], + hdrs = ["mini_psi.h"], + defines = ["CURVE25519_DONNA"], + deps = [ + ":communication", + ":cuckoo_index", + "//psi/psi/core/polynomial", + "//psi/psi/utils:batch_provider", + "//psi/psi/utils:serialize", + "//psi/psi/utils:test_utils", + "@com_github_floodyberry_curve25519_donna//:curve25519_donna", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest", + "@yacl//yacl/crypto/base/hash:hash_utils", + "@yacl//yacl/crypto/tools:prg", + "@yacl//yacl/link", + "@yacl//yacl/utils:parallel", + ], +) + +psi_cc_test( + name = "mini_psi_test", + srcs = ["mini_psi_test.cc"], + deps = [ + ":mini_psi", + ], +) + +psi_cc_binary( + name = "mini_psi_demo", + srcs = ["mini_psi_demo.cc"], + deps = [ + ":ecdh_psi", + ":kkrt_psi", + ":mini_psi", + ], +) diff --git a/psi/psi/core/bc22_psi/BUILD.bazel b/psi/psi/core/bc22_psi/BUILD.bazel new file mode 100644 index 00000000..102e9a98 --- /dev/null +++ b/psi/psi/core/bc22_psi/BUILD.bazel @@ -0,0 +1,110 @@ +# Copyright 2021 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//bazel:psi.bzl", "psi_cc_binary", "psi_cc_library", "psi_cc_test") +load("@yacl//bazel:yacl.bzl", "AES_COPT_FLAGS") + +package(default_visibility = ["//visibility:public"]) + +psi_cc_library( + name = "bc22_psi", + srcs = ["bc22_psi.cc"], + hdrs = ["bc22_psi.h"], + copts = AES_COPT_FLAGS, + deps = [ + ":emp_vole", + ":generalized_cuckoo_hash", + "//psi/psi/core:communication", + "//psi/psi/utils:serialize", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/strings", + "@yacl//yacl/base:exception", + "@yacl//yacl/base:int128", + "@yacl//yacl/crypto/utils:rand", + "@yacl//yacl/link", + "@yacl//yacl/utils:parallel", + ], +) + +psi_cc_test( + name = "bc22_psi_test", + srcs = ["bc22_psi_test.cc"], + deps = [ + ":bc22_psi", + ], +) + +psi_cc_library( + name = "emp_vole", + srcs = ["emp_vole.cc"], + hdrs = ["emp_vole.h"], + copts = AES_COPT_FLAGS, + deps = [ + "//psi/psi/core:communication", + "//psi/psi/utils:emp_io_adapter", + "//psi/psi/utils:serialize", + "@com_github_emptoolkit_emp_zk//:emp-zk", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", + "@yacl//yacl/base:exception", + "@yacl//yacl/crypto/utils:rand", + "@yacl//yacl/link", + ], +) + +psi_cc_test( + name = "emp_vole_test", + srcs = ["emp_vole_test.cc"], + copts = AES_COPT_FLAGS, + deps = [ + ":emp_vole", + "@com_google_absl//absl/container:flat_hash_set", + "@yacl//yacl/link", + ], +) + +psi_cc_library( + name = "generalized_cuckoo_hash", + srcs = ["generalized_cuckoo_hash.cc"], + hdrs = ["generalized_cuckoo_hash.h"], + deps = [ + "//psi/psi/core:cuckoo_index", + "@com_google_absl//absl/strings", + "@yacl//yacl/base:byte_container_view", + "@yacl//yacl/base:exception", + "@yacl//yacl/base:int128", + "@yacl//yacl/crypto/base/hash:hash_utils", + "@yacl//yacl/crypto/tools:prg", + "@yacl//yacl/crypto/utils:rand", + "@yacl//yacl/link", + "@yacl//yacl/utils:parallel", + ], +) + +psi_cc_test( + name = "generalized_cuckoo_hash_test", + srcs = ["generalized_cuckoo_hash_test.cc"], + deps = [ + ":generalized_cuckoo_hash", + ], +) + +psi_cc_binary( + name = "bc22_psi_bench", + srcs = ["bc22_psi_bench.cc"], + deps = [ + ":bc22_psi", + "@com_github_google_benchmark//:benchmark_main", + ], +) diff --git a/psi/psi/core/bc22_psi/bc22_psi.cc b/psi/psi/core/bc22_psi/bc22_psi.cc new file mode 100644 index 00000000..a5d716e3 --- /dev/null +++ b/psi/psi/core/bc22_psi/bc22_psi.cc @@ -0,0 +1,563 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/bc22_psi/bc22_psi.h" + +#include <algorithm> +#include <future> +#include <random> +#include <utility> +#include <vector> + +#include "absl/container/flat_hash_set.h" +#include "absl/strings/escaping.h" +#include "openssl/rand.h" +#include "spdlog/spdlog.h" +#include "yacl/crypto/utils/rand.h" +#include "yacl/utils/parallel.h" + +#include "psi/psi/core/bc22_psi/emp_vole.h" +#include "psi/psi/utils/serialize.h" + +#include "psi/psi/utils/serializable.pb.h" + +namespace psi::psi { + +namespace { + +constexpr size_t kDefaultHashNum = 2; +constexpr size_t kMaxItemsPerBin = 3; + +constexpr size_t kPcgPsiBatchSize = 4096; +constexpr size_t kPcgPsiLogBatchSize = kPcgPsiBatchSize * 100; + +constexpr size_t kMaxCompareBytes = 13; + +// BC22 section 2.3 +// H(i, vi) = H(i, wi-delta*ui) +std::vector<uint8_t> BaRKOPRFHash(size_t bin_idx, + WolverineVoleFieldType value) { + std::string hash_input(sizeof(WolverineVoleFieldType) + sizeof(size_t), '\0'); + std::memcpy(hash_input.data(), &bin_idx, sizeof(size_t)); + std::memcpy(hash_input.data() + sizeof(size_t), &value, + sizeof(WolverineVoleFieldType)); + auto hash_res = yacl::crypto::Blake3(hash_input); + return {hash_res.begin(), hash_res.end()}; +} + +} // namespace + +Bc22PcgPsi::Bc22PcgPsi(std::shared_ptr<yacl::link::Context> link_ctx, + PsiRoleType role) + : link_ctx_(std::move(link_ctx)), + role_(role), + batch_size_(kPcgPsiBatchSize) {} + +void Bc22PcgPsi::ExchangeItemsNumber(size_t self_item_num) { + // exchange items number, compute compare bytes size + // oprf compare bits: 40 + log2(n1) + log2(n2) + + yacl::Buffer self_count_buffer = utils::SerializeSize(self_item_num); + link_ctx_->SendAsyncThrottled( + link_ctx_->NextRank(), self_count_buffer, + fmt::format("send items count: {}", self_item_num)); + + yacl::Buffer peer_items_num_buffer = + link_ctx_->Recv(link_ctx_->NextRank(), fmt::format("peer items number")); + peer_items_num_ = utils::DeserializeSize(peer_items_num_buffer); +} + +void Bc22PcgPsi::RunPsi(absl::Span<const std::string> items) { + ExchangeItemsNumber(items.size()); + + size_t compare_bytes_size = + utils::GetCompareBytesLength(items.size(), peer_items_num_); + + SPDLOG_INFO("self size:{}, peer size:{} compare_bytes_size:{}", items.size(), + peer_items_num_, compare_bytes_size); + + if (role_ == PsiRoleType::Sender) { + cuckoo_options_ = + GetCuckooHashOption(kMaxItemsPerBin, kDefaultHashNum, peer_items_num_); + // sender alice + + // call mBaRK-OPRF + std::string oprfs = RunmBaRKOprfSender(items, compare_bytes_size); + + // send sender's oprfs to receiver + PcgPsiSendOprf(items, oprfs, compare_bytes_size); + } else if (role_ == PsiRoleType::Receiver) { + cuckoo_options_ = + GetCuckooHashOption(kMaxItemsPerBin, kDefaultHashNum, items.size()); + // receiver bob + + // call mBaRK-OPRF + std::vector<std::string> oprf_encode_vec = + RunmBaRKOprfReceiver(items, compare_bytes_size); + + // receive sender's oprfs, and compute intersection + PcgPsiRecvOprf(items, oprf_encode_vec, compare_bytes_size); + } else { + YACL_THROW("wrong psi role type: {}", static_cast<int>(role_)); + } +} + +// mBaRK-OPRF +std::string Bc22PcgPsi::RunmBaRKOprfSender(absl::Span<const std::string> items, + size_t compare_bytes_size) { + WolverineVole vole(role_, link_ctx_); + + SimpleHashTable simple_table(cuckoo_options_); + + // insert simple hash table + std::future<void> table_thread = std::async([&] { + SPDLOG_INFO("begin insert simple hash table"); + + simple_table.Insert(items); + + SPDLOG_INFO("after insert simple hash table"); + }); + + uint64_t vole_count_needed = cuckoo_options_.NumBins() * kMaxItemsPerBin; + + // vole extension + SPDLOG_INFO("begin pcg vole extension"); + // wi = delta * ui + vi + // alice : delta wi + // bob : ui || vi as one __uint128_t + std::vector<WolverineVoleFieldType> vole_blocks = + vole.Extend(vole_count_needed); + SPDLOG_INFO("after pcg vole extension"); + + WolverineVoleFieldType delta = vole.Delta(); + + const size_t coeff_byte_size = + kMaxItemsPerBin * sizeof(WolverineVoleFieldType); + + // sender alice + // recv masked polynomial coeff + size_t recv_bin_idx = 0; + size_t bins_num = cuckoo_options_.NumBins(); + SPDLOG_INFO("cuckoo_options_.NumBins: {}", bins_num); + + SPDLOG_INFO("begin recv receiver's masked coeff"); + + std::vector<std::array<WolverineVoleFieldType, kMaxItemsPerBin>> + masked_coeffs(bins_num); + + while (true) { + yacl::Buffer masked_coeff_buffer = link_ctx_->Recv( + link_ctx_->NextRank(), fmt::format("recv {} bin", recv_bin_idx)); + YACL_ENFORCE((masked_coeff_buffer.size() % coeff_byte_size) == 0); + + size_t num_bin = masked_coeff_buffer.size() / coeff_byte_size; + + std::memcpy(&masked_coeffs[recv_bin_idx], + reinterpret_cast<uint8_t *>(masked_coeff_buffer.data()), + masked_coeff_buffer.size()); + + recv_bin_idx += num_bin; + // every kPcgPsiLogBatchSize bin print percentage + if (recv_bin_idx % kPcgPsiLogBatchSize == 0) { + SPDLOG_INFO( + "recv receiver's masked coeff, recv_bin_idx: {} Bins_Num:{} " + "percentage:{}", + recv_bin_idx, bins_num, (double)recv_bin_idx / bins_num); + } + + if (recv_bin_idx == bins_num) { + break; + } + } + + SPDLOG_INFO("after recv receiver's masked coeff, recv_bin_idx: {}", + recv_bin_idx); + + table_thread.get(); + + const std::vector<std::vector<CuckooIndex::Bin>> &bins = simple_table.bins(); + + const std::vector<uint64_t> &items_hash_low64 = + simple_table.GetItemsHashLow64(); + + SPDLOG_INFO("role:{} items:{} bins size: {},items_hash_low64 size: {}", + (role_ == PsiRoleType::Sender) ? "sender" : "receiver", + items.size(), bins.size(), items_hash_low64.size()); + + std::string oprfs; + oprfs.resize(items.size() * cuckoo_options_.num_hash * kMaxCompareBytes); + + // shuffle sender's oprfs + std::mt19937 rng(yacl::crypto::SecureRandU64()); + + std::vector<size_t> shuffled_idx_vec(items.size()); + std::iota(shuffled_idx_vec.begin(), shuffled_idx_vec.end(), 0); + std::shuffle(shuffled_idx_vec.begin(), shuffled_idx_vec.end(), rng); + + // randomize conflict oprfs + const std::vector<size_t> &conflict_idx = simple_table.GetConflictIdx(); + + for (auto i : conflict_idx) { + size_t shuffled_idx = shuffled_idx_vec[i]; + + YACL_ENFORCE( + RAND_bytes(reinterpret_cast<unsigned char *>( + &oprfs[((shuffled_idx * cuckoo_options_.num_hash)) * + compare_bytes_size]), + 2 * compare_bytes_size) == 1); + } + + // compute sender's oprf + yacl::parallel_for(0, bins.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t bin_idx = begin; bin_idx < end; ++bin_idx) { + std::vector<WolverineVoleFieldType> oprf_key(kMaxItemsPerBin); + + size_t vole_start = bin_idx * kMaxItemsPerBin; + + std::array<WolverineVoleFieldType, kMaxItemsPerBin> masked_coeff = + masked_coeffs[bin_idx]; + + // delta * masked_coeff_i + w_i + // = delta * coeff_i - delta * u_i + w_i + // = delta * coeff_i + v_i + for (size_t j = 0; j < kMaxItemsPerBin; ++j) { + WolverineVoleFieldType tmp = mod(delta * masked_coeff[j], pr); + + oprf_key[j] = mod(vole_blocks[vole_start + j] + tmp, pr); + } + + for (size_t j = 0; j < bins[bin_idx].size(); ++j) { + size_t item_idx = bins[bin_idx][j].InputIdx(); + size_t hash_idx = bins[bin_idx][j].HashIdx(); + + absl::string_view item_hash_str = absl::string_view( + reinterpret_cast<const char *>(&items_hash_low64[item_idx]), + sizeof(uint64_t)); + + // delta * Poly(x) + vi_0 + vi_1*x+vi_1*x^2 + WolverineVoleFieldType eval = + EvaluatePolynomial(absl::MakeSpan(oprf_key), item_hash_str, delta); + + std::vector<uint8_t> hash_res = BaRKOPRFHash(bin_idx, eval); + + // copy to shuffled pos + size_t shuffled_idx = shuffled_idx_vec[item_idx]; + + std::memcpy( + &oprfs[((shuffled_idx * cuckoo_options_.num_hash) + hash_idx) * + compare_bytes_size], + hash_res.data(), compare_bytes_size); + } + } + }); + + SPDLOG_INFO("after compute sender's oprf"); + + return oprfs; +} + +std::vector<std::string> Bc22PcgPsi::RunmBaRKOprfReceiver( + absl::Span<const std::string> items, size_t compare_bytes_size) { + WolverineVole vole(role_, link_ctx_); + + GeneralizedCuckooHashTable cuckoo_table(cuckoo_options_, kMaxItemsPerBin); + + uint64_t vole_count_needed = cuckoo_options_.NumBins() * kMaxItemsPerBin; + + std::future<void> table_thread = std::async([&] { + SPDLOG_INFO("begin insert hash table"); + + cuckoo_table.Insert(items); + + SPDLOG_INFO("after insert hash table"); + }); + + // emp vole extension + + SPDLOG_INFO("begin pcg vole extension"); + // wi = delta * ui + vi + // alice : delta wi + // bob : ui || vi as one __uint128_t + std::vector<WolverineVoleFieldType> vole_blocks = + vole.Extend(vole_count_needed); + SPDLOG_INFO("after pcg vole extension"); + + table_thread.get(); + + const std::vector<std::vector<CuckooIndex::Bin>> &bins = cuckoo_table.bins(); + + const std::vector<uint64_t> &items_hash_low64 = + cuckoo_table.GetItemsHashLow64(); + + SPDLOG_INFO("role:{} items:{} bins size: {}, items_hash_low64 size: {}", + (role_ == PsiRoleType::Sender) ? "sender" : "receiver", + items.size(), bins.size(), items_hash_low64.size()); + + const size_t coeff_byte_size = + kMaxItemsPerBin * sizeof(WolverineVoleFieldType); + + // receiver bob + // send mask + std::vector<std::string> oprf_encode_vec(items.size()); + + SPDLOG_INFO("begin compute and send receiver's masked coeff"); + + for (size_t idx = 0; idx < bins.size(); idx += batch_size_) { + size_t current_batch_size = std::min(batch_size_, bins.size() - idx); + + yacl::Buffer masked_coeff_buffer(coeff_byte_size * current_batch_size); + + std::vector<std::string> oprf_blocks_batch(current_batch_size); + + yacl::parallel_for( + 0, current_batch_size, 1, [&](int64_t begin, int64_t end) { + for (int64_t j = begin; j < end; ++j) { + size_t bin_idx = idx + j; + + std::vector<std::string> bin_data(kMaxItemsPerBin); + size_t pos = j * coeff_byte_size; + + size_t k = 0; + for (; k < bins[bin_idx].size(); ++k) { + bin_data[k] = absl::string_view( + reinterpret_cast<const char *>( + &items_hash_low64[bins[bin_idx][k].InputIdx()]), + sizeof(uint64_t)); // use 64 bit + } + + for (; k < kMaxItemsPerBin; ++k) { + std::string buf(sizeof(uint64_t), '\0'); + YACL_ENFORCE(RAND_bytes(reinterpret_cast<uint8_t *>(buf.data()), + buf.length()) == 1); + bin_data[k] = buf; + } + std::vector<WolverineVoleFieldType> coeff_blocks = + GetPolynomialCoefficients(bin_data); + + // use vole mask polynomial coefficient + // coeff_i - ui + size_t vole_start = (idx + j) * kMaxItemsPerBin; + for (k = 0; k < coeff_blocks.size(); ++k) { + coeff_blocks[k] = mod( + coeff_blocks[k] + (vole_blocks[vole_start + k] >> 64), pr); + } + + // copy to masked_coeff send buffer + std::memcpy( + reinterpret_cast<uint8_t *>(masked_coeff_buffer.data()) + pos, + coeff_blocks.data(), coeff_byte_size); + + // get vi_0, vi_1, vi_2, i: bin index + std::vector<WolverineVoleFieldType> coeff_vole(kMaxItemsPerBin); + for (k = 0; k < coeff_vole.size(); ++k) { + coeff_vole[k] = + vole_blocks[vole_start + k] & 0xFFFFFFFFFFFFFFFFLL; + } + + for (k = 0; k < bins[bin_idx].size(); ++k) { + size_t item_index = bins[bin_idx][k].InputIdx(); + + // get item_hash as x, + absl::string_view item_hash_str = absl::string_view( + reinterpret_cast<const char *>(&items_hash_low64[item_index]), + sizeof(uint64_t)); + + // compute vi_0 + vi_1*x +vi_2*x^2, i: bin index + WolverineVoleFieldType eval = + EvaluatePolynomial(coeff_vole, item_hash_str, 0); + + // compute oprf, H(i, vi_0 + vi_1*x +vi_2*x^2), i: bin index + std::vector<uint8_t> hash_res = BaRKOPRFHash(bin_idx, eval); + + oprf_encode_vec[item_index] = + absl::string_view( + reinterpret_cast<const char *>(hash_res.data()), + hash_res.size()) + .substr(0, compare_bytes_size); + } + } + }); + + link_ctx_->SendAsyncThrottled( + link_ctx_->NextRank(), masked_coeff_buffer, + fmt::format("send {} bin", current_batch_size)); + } + SPDLOG_INFO("after send receiver's masked coeff"); + + return oprf_encode_vec; +} + +void Bc22PcgPsi::PcgPsiSendOprf(absl::Span<const std::string> items, + const std::string &oprfs, + size_t compare_bytes_size) { + SPDLOG_INFO("begin send sender's oprf"); + // send oprf + // batch send oprf + for (size_t i = 0; i < items.size(); i += batch_size_) { + size_t current_batch_size = std::min(batch_size_, items.size() - i); + bool is_last_batch = false; + + if ((i + current_batch_size) == items.size()) { + is_last_batch = true; + } + + proto::PsiDataBatchProto proto; + proto.set_item_num(current_batch_size); + std::string flatten_bytes = oprfs.substr( + i * cuckoo_options_.num_hash * compare_bytes_size, + current_batch_size * cuckoo_options_.num_hash * compare_bytes_size); + + proto.set_flatten_bytes(flatten_bytes); + proto.set_is_last_batch(is_last_batch); + yacl::Buffer oprf_buffer(proto.ByteSizeLong()); + proto.SerializeToArray(oprf_buffer.data(), oprf_buffer.size()); + + link_ctx_->SendAsyncThrottled( + link_ctx_->NextRank(), oprf_buffer, + fmt::format("send oprf buffer, bytes: {}", oprf_buffer.size())); + } + + SPDLOG_INFO("after send sender's oprf"); +} + +void Bc22PcgPsi::PcgPsiRecvOprf(absl::Span<const std::string> items, + const std::vector<std::string> &oprf_encode_vec, + size_t compare_bytes_size) { + SPDLOG_INFO("begin recv sender's oprf"); + + // recv oprf + std::string sender_oprf( + peer_items_num_ * cuckoo_options_.num_hash * compare_bytes_size, '\0'); + + size_t oprf_count = 0; + while (true) { + yacl::Buffer oprf_buffer = + link_ctx_->Recv(link_ctx_->NextRank(), fmt::format("recv oprf buffer")); + + proto::PsiDataBatchProto proto; + proto.ParseFromArray(oprf_buffer.data(), oprf_buffer.size()); + + size_t current_batch_size = proto.item_num(); + const std::string &flatten_bytes = proto.flatten_bytes(); + bool is_last_batch = proto.is_last_batch(); + + SPDLOG_DEBUG( + "recv oprf_buffer size:{} item_num:{} " + "is_last_batch:{} " + "flatten_bytes:{}", + oprf_buffer.size(), current_batch_size, is_last_batch, + flatten_bytes.length()); + + std::memcpy(&sender_oprf[oprf_count * cuckoo_options_.num_hash * + compare_bytes_size], + flatten_bytes.data(), flatten_bytes.length()); + + oprf_count += current_batch_size; + + // every kPcgPsiLogBatchSize bin print percentage + if (oprf_count % kPcgPsiLogBatchSize == 0) { + SPDLOG_INFO( + "recv sender's oprf, oprf_count: {} peer_items_num_:{} " + "percentage:{}", + oprf_count, peer_items_num_, (double)oprf_count / peer_items_num_); + } + + SPDLOG_DEBUG(" oprf_count:{}", oprf_count); + + if (is_last_batch) { + break; + } + } + + SPDLOG_INFO("after recv sender's oprf"); + + // https://abseil.io/docs/cpp/guides/container#abslflat_hash_map-and-abslflat_hash_set + // absl flat_hash_set faster than std::unordered_set + std::vector<absl::flat_hash_set<std::string>> sender_oprf_set( + cuckoo_options_.num_hash); + + auto sender_oprf_insert_proc = [&](size_t hash_idx) -> void { + size_t oprf_idx = 0; + for (size_t i = 0; i < peer_items_num_; ++i) { + std::string oprf = sender_oprf.substr( + (oprf_idx + hash_idx) * compare_bytes_size, compare_bytes_size); + sender_oprf_set[hash_idx].insert(std::move(oprf)); + + oprf_idx += cuckoo_options_.num_hash; + } + }; + + std::vector<std::future<void>> f_set; + for (size_t i = 0; i < cuckoo_options_.num_hash; ++i) { + f_set.push_back(std::async(sender_oprf_insert_proc, i)); + } + + for (size_t i = 0; i < cuckoo_options_.num_hash; ++i) { + f_set[i].get(); + } + + SPDLOG_INFO("after insert unordered_set"); + + SPDLOG_INFO("begin compute intersection"); + + std::vector<std::string> results2; + + std::vector<size_t> result_by_hash0(cuckoo_options_.num_hash); + std::vector<size_t> result_by_hash1(cuckoo_options_.num_hash); + + auto intersection_proc = [&](size_t begin, size_t end, + std::vector<std::string> *results_vec, + std::vector<size_t> *result_by_hash) -> void { + for (size_t i = begin; i < end; ++i) { + for (size_t j = 0; j < cuckoo_options_.num_hash; ++j) { + auto it = sender_oprf_set[j].find(oprf_encode_vec[i]); + if (it != sender_oprf_set[j].end()) { + (*results_vec).emplace_back(items[i]); + (*result_by_hash)[j]++; + break; + } + } + } + }; + + std::future<void> f_intersection = + std::async(intersection_proc, items.size() / 2, items.size(), &results2, + &result_by_hash1); + + intersection_proc(0, items.size() / 2, &results_, &result_by_hash0); + + f_intersection.get(); + + SPDLOG_INFO("result:{}, result2:{}", results_.size(), results2.size()); + + results_.insert(results_.end(), results2.begin(), results2.end()); + + result_by_hash0[0] += result_by_hash1[0]; + result_by_hash0[1] += result_by_hash1[1]; + SPDLOG_INFO("hash_count0:{}, hash_count1:{}", result_by_hash0[0], + result_by_hash0[1]); + + // sender use only one cuckooHash to insert SimpleHash + // may leak some information about receiver's items hash + if ((result_by_hash0[0] == 0) || (result_by_hash0[1] == 0)) { + SPDLOG_WARN("*** may be attacked"); + } + + SPDLOG_INFO("oprf_count:{} intersection size:{}", oprf_count, + results_.size()); + + SPDLOG_INFO("after compute intersection"); +} + +} // namespace psi::psi diff --git a/psi/psi/core/bc22_psi/bc22_psi.h b/psi/psi/core/bc22_psi/bc22_psi.h new file mode 100644 index 00000000..007e7959 --- /dev/null +++ b/psi/psi/core/bc22_psi/bc22_psi.h @@ -0,0 +1,91 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <memory> +#include <string> +#include <vector> + +#include "absl/types/span.h" +#include "yacl/base/int128.h" + +#include "psi/psi/core/bc22_psi/generalized_cuckoo_hash.h" +#include "psi/psi/core/communication.h" +#include "psi/psi/utils/serialize.h" + +namespace psi::psi { + +// PSI from Pseudorandom Correlation Generators +// https://eprint.iacr.org/2022/334 +// Journées C2 2022 – Hendaye April 10-15 2022 (inria.fr) + +// VOLE +// Wolverine: Fast, Scalable, and Communication-Efficient +// Zero-Knowledge Proofs for Boolean and Arithmetic Circuits +// https://eprint.iacr.org/2020/925 +// https://github.com/emp-toolkit/emp-zk + +class Bc22PcgPsi { + public: + Bc22PcgPsi(std::shared_ptr<yacl::link::Context> link_ctx, PsiRoleType role); + + void RunPsi(absl::Span<const std::string> items); + + std::vector<std::string> GetIntersection() { + if (role_ == PsiRoleType::Receiver) { + return results_; + } else { + YACL_THROW("Bc22PcgPsi only Receiver get intersection"); + } + } + + private: + // exchange items number, compute compare bytes size + void ExchangeItemsNumber(size_t self_item_num); + + // mBaRK-OPRF sender/receiver + std::string RunmBaRKOprfSender(absl::Span<const std::string> items, + size_t compare_bytes_size); + + std::vector<std::string> RunmBaRKOprfReceiver( + absl::Span<const std::string> items, size_t compare_bytes_size); + + // send/recv oprf + void PcgPsiSendOprf(absl::Span<const std::string> items, + const std::string &oprfs, size_t compare_bytes_size); + + void PcgPsiRecvOprf(absl::Span<const std::string> items, + const std::vector<std::string> &oprf_encode_vec, size_t); + + // cuckoo_options + CuckooIndex::Options cuckoo_options_; + + // Provides the link for the rank world. + std::shared_ptr<yacl::link::Context> link_ctx_; + + // psi role sender/receiver + PsiRoleType role_; + + // batch send/recv data size + size_t batch_size_; + + // peer's item size + size_t peer_items_num_ = 0; + + // intersection result + std::vector<std::string> results_; +}; + +} // namespace psi::psi diff --git a/psi/psi/core/bc22_psi/bc22_psi_bench.cc b/psi/psi/core/bc22_psi/bc22_psi_bench.cc new file mode 100644 index 00000000..167114db --- /dev/null +++ b/psi/psi/core/bc22_psi/bc22_psi_bench.cc @@ -0,0 +1,147 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <algorithm> +#include <future> +#include <random> +#include <string> + +#include "absl/strings/escaping.h" +#include "absl/strings/str_split.h" +#include "benchmark/benchmark.h" +#include "spdlog/spdlog.h" +#include "yacl/link/test_util.h" + +#include "psi/psi/core/bc22_psi/bc22_psi.h" + +namespace psi::psi { + +namespace { + +std::vector<std::string> CreateRangeItems(size_t start_pos, size_t size) { + std::vector<std::string> ret(size); + + auto gen_items_proc = [&](size_t begin, size_t end) -> void { + for (size_t i = begin; i < end; ++i) { + ret[i] = std::to_string(start_pos + i); + } + }; + + std::future<void> f_gen = std::async(gen_items_proc, size / 2, size); + + gen_items_proc(0, size / 2); + + f_gen.get(); + + return ret; +} + +std::shared_ptr<yacl::link::Context> CreateContext( + int self_rank, yacl::link::ContextDesc& lctx_desc) { + std::shared_ptr<yacl::link::Context> link_ctx; + + yacl::link::FactoryBrpc factory; + link_ctx = factory.CreateContext(lctx_desc, self_rank); + link_ctx->ConnectToMesh(); + + return link_ctx; +} + +std::vector<std::shared_ptr<yacl::link::Context>> CreateLinks( + const std::string& host_str) { + std::vector<std::string> hosts = absl::StrSplit(host_str, ','); + yacl::link::ContextDesc lctx_desc; + for (size_t rank = 0; rank < hosts.size(); rank++) { + const std::string id = fmt::format("party{}", rank); + lctx_desc.parties.push_back({id, hosts[rank]}); + } + + auto proc = [&](int self_rank) -> std::shared_ptr<yacl::link::Context> { + return CreateContext(self_rank, lctx_desc); + }; + + size_t world_size = hosts.size(); + std::vector<std::future<std::shared_ptr<yacl::link::Context>>> f_links( + world_size); + for (size_t i = 0; i < world_size; i++) { + f_links[i] = std::async(proc, i); + } + + std::vector<std::shared_ptr<yacl::link::Context>> links(world_size); + for (size_t i = 0; i < world_size; i++) { + links[i] = f_links[i].get(); + } + + return links; +} + +constexpr char kLinkAddrAB[] = "127.0.0.1:9532,127.0.0.1:9533"; +constexpr uint32_t kLinkRecvTimeout = 30 * 60 * 1000; +constexpr uint32_t kLinkWindowSize = 16; + +} // namespace + +static void BM_PcgPsi(benchmark::State& state) { + for (auto _ : state) { + state.PauseTiming(); + size_t n = state.range(0); + auto alice_data = CreateRangeItems(1, n); + auto bob_data = CreateRangeItems(2, n); + + auto ctxs = CreateLinks(kLinkAddrAB); + + ctxs[0]->SetThrottleWindowSize(kLinkWindowSize); + ctxs[1]->SetThrottleWindowSize(kLinkWindowSize); + + ctxs[0]->SetRecvTimeout(kLinkRecvTimeout); + ctxs[1]->SetRecvTimeout(kLinkRecvTimeout); + + state.ResumeTiming(); + + Bc22PcgPsi pcg_psi_send(ctxs[0], PsiRoleType::Sender); + Bc22PcgPsi pcg_psi_recv(ctxs[1], PsiRoleType::Receiver); + + std::future<void> send_thread = + std::async([&] { pcg_psi_send.RunPsi(alice_data); }); + + std::future<void> recv_thread = + std::async([&] { return pcg_psi_recv.RunPsi(bob_data); }); + + send_thread.get(); + recv_thread.get(); + + std::vector<std::string> intersection = pcg_psi_recv.GetIntersection(); + + SPDLOG_INFO("intersection:{}", intersection.size()); + auto stats0 = ctxs[0]->GetStats(); + auto stats1 = ctxs[1]->GetStats(); + SPDLOG_INFO("sender ctx0 sent_bytes:{} recv_bytes:{}", + stats0->sent_bytes.load(), stats0->recv_bytes.load()); + SPDLOG_INFO("receiver ctx1 sent_bytes:{} recv_bytes:{}", + stats1->sent_bytes.load(), stats1->recv_bytes.load()); + } +} + +// [256k, 512k, 1m, 2m, 4m, 8m, 16m] +BENCHMARK(BM_PcgPsi) + ->Unit(benchmark::kMillisecond) + ->Arg(256 << 10) + ->Arg(512 << 10) + ->Arg(1 << 20) + ->Arg(2 << 20) + ->Arg(4 << 20) + ->Arg(8 << 20) + ->Arg(16 << 20); + +} // namespace psi::psi diff --git a/psi/psi/core/bc22_psi/bc22_psi_test.cc b/psi/psi/core/bc22_psi/bc22_psi_test.cc new file mode 100644 index 00000000..3b22bcf1 --- /dev/null +++ b/psi/psi/core/bc22_psi/bc22_psi_test.cc @@ -0,0 +1,121 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/bc22_psi/bc22_psi.h" + +#include <algorithm> +#include <future> +#include <random> +#include <string> + +#include "absl/container/flat_hash_set.h" +#include "absl/strings/escaping.h" +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/link/test_util.h" +#include "yacl/utils/parallel.h" + +namespace psi::psi { + +namespace { + +std::vector<std::string> CreateRangeItems(size_t start_pos, size_t size) { + std::vector<std::string> ret(size); + + auto gen_items_proc = [&](size_t begin, size_t end) -> void { + for (size_t i = begin; i < end; ++i) { + ret[i] = std::to_string(start_pos + i); + } + }; + + std::future<void> f_gen = std::async(gen_items_proc, size / 2, size); + + gen_items_proc(0, size / 2); + + f_gen.get(); + + return ret; +} + +std::vector<std::string> GetIntersection( + const std::vector<std::string>& items_a, + const std::vector<std::string>& items_b) { + absl::flat_hash_set<std::string> set(items_a.begin(), items_a.end()); + std::vector<std::string> ret; + for (const auto& s : items_b) { + if (set.count(s) != 0) { + ret.push_back(s); + } + } + return ret; +} + +// Special test data +// [20000000, ] [20000001, ] +// 5560969 和 21906809 2-hash simple-hash conflict +// constexpr size_t kTestItemsSize = 9000000; +// +// constexpr size_t kTestItemsSize = 10000000; + +} // namespace + +class PcgPsiTest : public testing::TestWithParam<size_t> {}; + +TEST_P(PcgPsiTest, Works) { + auto params = GetParam(); + size_t items_size = params; + auto ctxs = yacl::link::test::SetupWorld(2); + + std::vector<std::string> alice_data = CreateRangeItems(20000000, items_size); + std::vector<std::string> bob_data = CreateRangeItems(20000001, items_size); + + std::vector<std::string> intersection_std = + GetIntersection(alice_data, bob_data); + + std::sort(intersection_std.begin(), intersection_std.end()); + + Bc22PcgPsi pcg_psi_send(ctxs[0], PsiRoleType::Sender); + Bc22PcgPsi pcg_psi_recv(ctxs[1], PsiRoleType::Receiver); + + std::future<void> send_thread = + std::async([&] { pcg_psi_send.RunPsi(alice_data); }); + + std::future<void> recv_thread = + std::async([&] { return pcg_psi_recv.RunPsi(bob_data); }); + + send_thread.get(); + recv_thread.get(); + std::vector<std::string> intersection = pcg_psi_recv.GetIntersection(); + + std::sort(intersection.begin(), intersection.end()); + + size_t std_size = intersection_std.size(); + size_t size2 = intersection.size(); + SPDLOG_INFO("intersection_std size:{} intersection_size:{} diff: {}", + std_size, size2, (std_size - size2)); + + EXPECT_EQ(intersection, intersection_std); + + auto stats0 = ctxs[0]->GetStats(); + auto stats1 = ctxs[1]->GetStats(); + SPDLOG_INFO("sender ctx0 sent_bytes:{} recv_bytes:{}", + stats0->sent_bytes.load(), stats0->recv_bytes.load()); + SPDLOG_INFO("receiver ctx1 sent_bytes:{} recv_bytes:{}", + stats1->sent_bytes.load(), stats1->recv_bytes.load()); +} + +INSTANTIATE_TEST_SUITE_P(Works_Instances, PcgPsiTest, + testing::Values(10000, 100000, 1000000)); + +} // namespace psi::psi diff --git a/psi/psi/core/bc22_psi/emp_vole.cc b/psi/psi/core/bc22_psi/emp_vole.cc new file mode 100644 index 00000000..a387ece0 --- /dev/null +++ b/psi/psi/core/bc22_psi/emp_vole.cc @@ -0,0 +1,146 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/bc22_psi/emp_vole.h" + +#include <utility> + +#include "spdlog/spdlog.h" +#include "yacl/crypto/utils/rand.h" + +namespace psi::psi { + +WolverineVole::WolverineVole(PsiRoleType psi_role, + std::shared_ptr<yacl::link::Context> link_ctx) + : party_((psi_role == PsiRoleType::Sender) ? emp::ALICE : emp::BOB), + link_ctx_(std::move(link_ctx)) { + // set EmpIoAdapter + for (size_t i = 0; i < kVoleSilentOTThreads; ++i) { + silent_ios_[i] = std::make_unique<EmpIoAdapter>(link_ctx_); + ios_[i] = silent_ios_[i].get(); + } + + emp_zk_vole_ = std::make_unique<VoleTriple<EmpIoAdapter>>( + party_, kVoleSilentOTThreads, ios_); + + SPDLOG_INFO("party {}, begin svole setup", + (party_ == emp::ALICE) ? "alice" : "bob"); + Setup(); + SPDLOG_INFO("party {}, after svole setup", + (party_ == emp::ALICE) ? "alice" : "bob"); +} + +void WolverineVole::Setup() { + if (party_ == emp::ALICE) { + delta_ = yacl::crypto::RandSeed(); + delta_ = + delta_ & (static_cast<WolverineVoleFieldType>(0xFFFFFFFFFFFFFFFFLL)); + delta_ = mod(delta_, pr); + emp_zk_vole_->setup(delta_); + } else { + emp_zk_vole_->setup(); + } +} + +std::vector<WolverineVoleFieldType> WolverineVole::Extend(size_t vole_num) { + std::vector<WolverineVoleFieldType> vole_blocks(vole_num); + + emp_zk_vole_->extend(vole_blocks.data(), vole_blocks.size()); + + return vole_blocks; +} + +std::vector<WolverineVoleFieldType> GetPolynomialCoefficients( + const std::vector<std::string>& bin_data) { + YACL_ENFORCE(bin_data.size() <= 3); + + std::vector<WolverineVoleFieldType> block_coeffs(bin_data.size()); + + std::vector<WolverineVoleFieldType> t(bin_data.size()); + for (size_t i = 0; i < bin_data.size(); ++i) { + YACL_ENFORCE(bin_data[0].length() <= sizeof(WolverineVoleFieldType), + "{}>{}", bin_data[0].length(), sizeof(WolverineVoleFieldType)); + t[i] = 0; + std::memcpy(&t[i], bin_data[i].data(), bin_data[i].length()); + t[i] = mod(t[i], pr); + } + + if (bin_data.size() == 1) { + block_coeffs[0] = pr - mod(t[0], pr); + } else if (bin_data.size() == 2) { + block_coeffs[0] = mult_mod(t[0], t[1]); + block_coeffs[1] = pr - mod(t[0] + t[1], pr); + } else if (bin_data.size() == 3) { + WolverineVoleFieldType d01; + WolverineVoleFieldType d02; + WolverineVoleFieldType d12; + + d01 = mult_mod(t[0], t[1]); + d02 = mult_mod(t[0], t[2]); + d12 = mult_mod(t[1], t[2]); + + WolverineVoleFieldType tmp = mult_mod(d01, t[2]); + while (tmp > pr) { + tmp -= pr; + } + + block_coeffs[0] = pr - tmp; + + block_coeffs[1] = mod(d01 + d02 + d12, pr); + block_coeffs[2] = pr - mod(t[0] + t[1] + t[2], pr); + } + + return block_coeffs; +} + +WolverineVoleFieldType EvaluatePolynomial( + absl::Span<const WolverineVoleFieldType> coeffs, WolverineVoleFieldType x, + WolverineVoleFieldType high_coeff) { + std::vector<WolverineVoleFieldType> xp(coeffs.size() + 1); + + std::vector<WolverineVoleFieldType> block_coeffs; + for (unsigned __int128 coeff : coeffs) { + block_coeffs.push_back(static_cast<WolverineVoleFieldType>(coeff)); + } + + WolverineVoleFieldType xx = mod(x, pr); + + xp[0] = 1; + block_coeffs.push_back(high_coeff); + + for (size_t i = 1; i < xp.size(); ++i) { + xp[i] = mult_mod(xp[i - 1], xx); + } + + WolverineVoleFieldType result = 0; + for (size_t i = 0; i < xp.size(); ++i) { + result += mult_mod(xp[i], block_coeffs[i]); + result = mod(result, pr); + } + + return result; +} + +WolverineVoleFieldType EvaluatePolynomial( + absl::Span<const WolverineVoleFieldType> coeffs, std::string_view x, + WolverineVoleFieldType high_coeff) { + WolverineVoleFieldType block_x = 0; + + YACL_ENFORCE(x.length() <= sizeof(WolverineVoleFieldType)); + std::memcpy(&block_x, x.data(), x.length()); + + return EvaluatePolynomial(coeffs, block_x, high_coeff); +} + +} // namespace psi::psi diff --git a/psi/psi/core/bc22_psi/emp_vole.h b/psi/psi/core/bc22_psi/emp_vole.h new file mode 100644 index 00000000..2d14cbba --- /dev/null +++ b/psi/psi/core/bc22_psi/emp_vole.h @@ -0,0 +1,92 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <memory> +#include <string> +#include <vector> + +#include "absl/types/span.h" +#include "emp-tool/utils/block.h" +#include "emp-tool/utils/f2k.h" +#include "emp-zk/emp-vole/emp-vole.h" +#include "yacl/base/exception.h" +#include "yacl/link/link.h" + +#include "psi/psi/core/communication.h" +#include "psi/psi/utils/emp_io_adapter.h" +#include "psi/psi/utils/serialize.h" + +namespace psi::psi { + +inline constexpr size_t kVoleSilentOTThreads = 1; + +// VOLE +// Wolverine: Fast, Scalable, and Communication-Efficient +// Zero-Knowledge Proofs for Boolean and Arithmetic Circuits +// https://eprint.iacr.org/2020/925 +// https://github.com/emp-toolkit/emp-zk + +using WolverineVoleFieldType = __uint128_t; + +class WolverineVole { + public: + WolverineVole(PsiRoleType psi_role, + std::shared_ptr<yacl::link::Context> link_ctx); + + // extend baseVole get vole_num voles + // Filed: mersenne prime 2^61 - 1 + // wi = delta * ui + vi + // alice : delta, wi + // bob : ui || vi as one __uint128_t + std::vector<WolverineVoleFieldType> Extend(size_t vole_num); + + // get delta + WolverineVoleFieldType Delta() { + if (party_ == emp::ALICE) { + return delta_; + } else { + YACL_THROW("party: {} without delta", party_); + } + } + + private: + // setup + // alice set delta + // call baseVole + void Setup(); + + int party_; + std::shared_ptr<yacl::link::Context> link_ctx_; + + WolverineVoleFieldType delta_; + + std::array<std::unique_ptr<EmpIoAdapter>, kVoleSilentOTThreads> silent_ios_; + EmpIoAdapter *ios_[kVoleSilentOTThreads]; + std::unique_ptr<VoleTriple<EmpIoAdapter>> emp_zk_vole_; +}; + +std::vector<WolverineVoleFieldType> GetPolynomialCoefficients( + const std::vector<std::string> &bin_data); + +WolverineVoleFieldType EvaluatePolynomial( + absl::Span<const WolverineVoleFieldType> coeffs, std::string_view x, + WolverineVoleFieldType high_coeff = 1); + +WolverineVoleFieldType EvaluatePolynomial( + absl::Span<const WolverineVoleFieldType> coeffs, WolverineVoleFieldType x, + WolverineVoleFieldType high_coeff); + +} // namespace psi::psi diff --git a/psi/psi/core/bc22_psi/emp_vole_test.cc b/psi/psi/core/bc22_psi/emp_vole_test.cc new file mode 100644 index 00000000..b7314d7e --- /dev/null +++ b/psi/psi/core/bc22_psi/emp_vole_test.cc @@ -0,0 +1,112 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/bc22_psi/emp_vole.h" + +#include <random> +#include <string> +#include <utility> +#include <vector> + +#include "absl/strings/escaping.h" +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/crypto/utils/rand.h" +#include "yacl/link/test_util.h" + +#include "psi/psi/utils/serialize.h" + +namespace psi::psi { + +class EmpVoleTest : public testing::TestWithParam<size_t> {}; + +TEST_P(EmpVoleTest, Works) { + auto params = GetParam(); + + auto ctxs = yacl::link::test::SetupWorld(2); + + uint64_t vole_need = params; + + std::vector<WolverineVoleFieldType> vole_alice; + std::vector<WolverineVoleFieldType> vole_bob; + + WolverineVoleFieldType delta = 0; + + std::future<void> vole_alice_thread = std::async([&] { + WolverineVole vole(PsiRoleType::Sender, ctxs[0]); + + vole_alice = vole.Extend(vole_need); + delta = vole.Delta(); + }); + + std::future<void> vole_bob_thread = std::async([&] { + WolverineVole vole(PsiRoleType::Receiver, ctxs[1]); + + vole_bob = vole.Extend(vole_need); + }); + + vole_alice_thread.get(); + vole_bob_thread.get(); + + EXPECT_EQ(vole_alice.size(), vole_need); + EXPECT_EQ(vole_bob.size(), vole_need); + + auto stats0 = ctxs[0]->GetStats(); + auto stats1 = ctxs[1]->GetStats(); + SPDLOG_INFO("sender/alice ctx0 sent_bytes:{} recv_bytes:{}", + stats0->sent_bytes.load(), stats0->recv_bytes.load()); + SPDLOG_INFO("receiver/bob ctx1 sent_bytes:{} recv_bytes:{}", + stats1->sent_bytes.load(), stats1->recv_bytes.load()); + + // check vole + // wi = delta * ui + vi + // sender/alice : delta wi + // receiver/bob : ui || vi as one __uint128_t + for (size_t i = 0; i < vole_need; ++i) { + // delta * ui + WolverineVoleFieldType tmp = mod(delta * (vole_bob[i] >> 64), pr); + // delta * ui + vi + tmp = mod(tmp + vole_alice[i], pr); + // check wi = delta * ui + vi + EXPECT_EQ(tmp, (vole_bob[i] & 0xFFFFFFFFFFFFFFFFLL)); + } +} + +INSTANTIATE_TEST_SUITE_P(Works_Instances, EmpVoleTest, + testing::Values(10000, 100000, 1000000)); + +TEST(EmpVoleTest, PolynomialTest) { + std::mt19937 rng(yacl::crypto::SecureRandU64()); + + for (size_t idx = 1; idx < 4; ++idx) { + std::vector<std::string> points(idx); + + for (auto& point : points) { + point = std::to_string(rng()); + } + + std::vector<__uint128_t> coeffs = GetPolynomialCoefficients(points); + + for (auto& point : points) { + WolverineVoleFieldType result = + EvaluatePolynomial(absl::MakeSpan(coeffs), point); + WolverineVoleFieldType tt; + memcpy(&tt, &result, sizeof(tt)); + + EXPECT_EQ(result, 0); + } + } +} + +} // namespace psi::psi diff --git a/psi/psi/core/bc22_psi/generalized_cuckoo_hash.cc b/psi/psi/core/bc22_psi/generalized_cuckoo_hash.cc new file mode 100644 index 00000000..215e1855 --- /dev/null +++ b/psi/psi/core/bc22_psi/generalized_cuckoo_hash.cc @@ -0,0 +1,262 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/bc22_psi/generalized_cuckoo_hash.h" + +#include <set> +#include <utility> + +#include "absl/strings/escaping.h" +#include "spdlog/spdlog.h" +#include "yacl/base/int128.h" +#include "yacl/crypto/utils/rand.h" +#include "yacl/utils/parallel.h" + +namespace psi::psi { + +namespace { + +constexpr size_t kDefaultHashNum = 2; + +// Permutation-Based Hashing +// [PSSZ15] In USENIX Security 2015 +// Phasing: Private set intersection using permutation-based hashing +// [BC22] Section 2.4 +// h0(x) = hash(x), +// h1(x) = h0(x) xor fingerprint(x). +std::vector<uint64_t> GetBinIdx(const CuckooIndex::Options &options, + uint128_t item_hash, uint64_t item_hash_u64) { + YACL_ENFORCE(options.num_hash == kDefaultHashNum); + size_t num_bins = options.NumBins(); + + CuckooIndex::HashRoom hash_room(item_hash); + + size_t bin_idx = hash_room.GetHash(0); + + std::vector<uint64_t> hash_bin_idx(options.num_hash); + + hash_bin_idx[0] = bin_idx % num_bins; + hash_bin_idx[1] = (bin_idx ^ item_hash_u64) % num_bins; + + return hash_bin_idx; +} + +} // namespace + +CuckooIndex::Options GetCuckooHashOption(size_t bin_size, size_t hash_num, + size_t items_size) { + CuckooIndex::Options options; + + options.num_input = items_size; + options.num_stash = 0; + options.num_hash = hash_num; + + YACL_ENFORCE(hash_num == kDefaultHashNum, "just support 2 hash"); + + if (hash_num == kDefaultHashNum) { + if (bin_size == 2) { + options.scale_factor = 1; + } else if (bin_size == 3) { + options.scale_factor = 0.6; + } else { + YACL_THROW("unsupported"); + } + } else { + YACL_THROW("unsupported"); + } + return options; +} + +GeneralizedCuckooHashTable::GeneralizedCuckooHashTable( + CuckooIndex::Options options, size_t bin_data_num, uint128_t seed) + : gch_options_(options), + max_items_per_bin_(bin_data_num), + seed_(seed), + gen_(yacl::crypto::SecureRandU64()) { + size_t table_size = gch_options_.NumBins(); + bins_.resize(table_size); + + uniform_hash_idx_ = + std::uniform_int_distribution<uint32_t>(0, gch_options_.num_hash - 1); + uniform_data_idx_ = + std::uniform_int_distribution<uint32_t>(0, max_items_per_bin_ - 1); +} + +void GeneralizedCuckooHashTable::Insert(yacl::ByteContainerView item_data, + size_t input_offset) { + CuckooIndex::Bin candidate; + candidate.set_encoded(input_offset); + + int64_t level = gch_options_.max_try_count; + size_t bin_idx; + while ((level--) != 0) { + size_t rand_hash_idx = uniform_hash_idx_(gen_); + + for (uint32_t i = 0; i < gch_options_.num_hash; i++) { + size_t hash_idx = (rand_hash_idx + i) % gch_options_.num_hash; + + bin_idx = hashes_[candidate.InputIdx()][hash_idx]; + + if (bins_[bin_idx].size() < max_items_per_bin_) { + uint64_t next_candid = + CuckooIndex::Bin::Encode(candidate.InputIdx(), hash_idx); + candidate.set_encoded(next_candid); + + bins_[bin_idx].push_back(candidate); + inserted_items_++; + + return; + } + } + + // random select bin_idx and idx in bin, swap candidate + size_t rand_data_idx = uniform_data_idx_(gen_); + + rand_hash_idx = uniform_hash_idx_(gen_); + + bin_idx = hashes_[candidate.InputIdx()][rand_hash_idx]; + + uint64_t next_candid = + CuckooIndex::Bin::Encode(candidate.InputIdx(), rand_hash_idx); + candidate.set_encoded(next_candid); + + candidate = CuckooIndex::Bin( + bins_[bin_idx][rand_data_idx].Swap(candidate.encoded())); + } + + YACL_THROW( + "Error insert, level:{} insert item_data:{}", level, + absl::BytesToHexString(absl::string_view( + reinterpret_cast<const char *>(item_data.data()), item_data.size()))); +} + +void GeneralizedCuckooHashTable::Insert(yacl::ByteContainerView item) { + uint128_t item_hash = yacl::crypto::Blake3_128(item); + size_t input_offset = hashes_.size(); + + // hash_bin_idx + std::pair<uint64_t, uint64_t> items_hash_u64 = + yacl::DecomposeUInt128(item_hash); + std::vector<uint64_t> hash_bin_idx = + GetBinIdx(gch_options_, item_hash, items_hash_u64.first); + hashes_.push_back(hash_bin_idx); + + items_hash_low64_.push_back(items_hash_u64.second); + + Insert(item, input_offset); +} + +void GeneralizedCuckooHashTable::Insert(absl::Span<const std::string> items) { + size_t input_offset = items_hash_low64_.size(); + items_hash_low64_.resize(input_offset + items.size()); + hashes_.resize(input_offset + items.size()); + + yacl::parallel_for(0, items.size(), 1, [&](int64_t begin, int64_t end) { + for (int i = begin; i < end; ++i) { + uint128_t item_hash = yacl::crypto::Blake3_128(items[i]); + + std::pair<uint64_t, uint64_t> items_hash_u64 = + yacl::DecomposeUInt128(item_hash); + + items_hash_low64_[input_offset + i] = items_hash_u64.second; + hashes_[input_offset + i] = + GetBinIdx(gch_options_, item_hash, items_hash_u64.first); + } + }); + + for (size_t i = 0; i < items.size(); ++i) { + Insert(items[i], input_offset + i); + } +} + +SimpleHashTable::SimpleHashTable(CuckooIndex::Options options, uint128_t seed) + : gch_options_(options), seed_(seed) { + size_t table_size = gch_options_.NumBins(); + + bins_.resize(table_size); +} + +void SimpleHashTable::Insert(yacl::ByteContainerView item_data, + const std::vector<uint64_t> &hash_bin_idx) { + CuckooIndex::Bin candidate; + candidate.set_encoded(inserted_items_); + + std::set<size_t> idx_set(hash_bin_idx.begin(), hash_bin_idx.end()); + + size_t bin_idx; + + if (idx_set.size() < hash_bin_idx.size()) { + SPDLOG_WARN("hash conflict: bin_idx:{}, data:{}", hash_bin_idx[0], + absl::BytesToHexString(absl::string_view( + reinterpret_cast<const char *>(item_data.data()), + item_data.size()))); + + conflict_idx_.push_back(candidate.InputIdx()); + + bin_idx = hash_bin_idx[0]; + uint64_t next_candid = CuckooIndex::Bin::Encode(candidate.InputIdx(), 0); + candidate.set_encoded(next_candid); + bins_[bin_idx].push_back(candidate); + + } else { + for (size_t i = 0; i < gch_options_.num_hash; i++) { + bin_idx = hash_bin_idx[i]; + + uint64_t next_candid = CuckooIndex::Bin::Encode(candidate.InputIdx(), i); + candidate.set_encoded(next_candid); + bins_[bin_idx].push_back(candidate); + } + } + + inserted_items_++; +} + +void SimpleHashTable::Insert(yacl::ByteContainerView item) { + uint128_t item_hash = yacl::crypto::Blake3_128(item); + + std::vector<uint64_t> hash_bin_idx; + std::pair<uint64_t, uint64_t> item_hash_u64 = + yacl::DecomposeUInt128(item_hash); + + items_hash_low64_.push_back(item_hash_u64.second); + + hash_bin_idx = GetBinIdx(gch_options_, item_hash, item_hash_u64.first); + + Insert(item, hash_bin_idx); +} + +void SimpleHashTable::Insert(absl::Span<const std::string> items) { + size_t input_offset = items_hash_low64_.size(); + items_hash_low64_.resize(input_offset + items.size()); + + std::vector<std::vector<uint64_t>> hash_bin_idx(items.size()); + + yacl::parallel_for(0, items.size(), 1, [&](int64_t begin, int64_t end) { + for (int i = begin; i < end; ++i) { + uint128_t item_hash = yacl::crypto::Blake3_128(items[i]); + + std::pair<uint64_t, uint64_t> item_hash_u64 = + yacl::DecomposeUInt128(item_hash); + hash_bin_idx[i] = GetBinIdx(gch_options_, item_hash, item_hash_u64.first); + + items_hash_low64_[input_offset + i] = item_hash_u64.second; + } + }); + + for (size_t i = 0; i < items.size(); ++i) { + Insert(items[i], hash_bin_idx[i]); + } +} + +} // namespace psi::psi diff --git a/psi/psi/core/bc22_psi/generalized_cuckoo_hash.h b/psi/psi/core/bc22_psi/generalized_cuckoo_hash.h new file mode 100644 index 00000000..d025432c --- /dev/null +++ b/psi/psi/core/bc22_psi/generalized_cuckoo_hash.h @@ -0,0 +1,123 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <cmath> +#include <random> +#include <string> +#include <vector> + +#include "absl/types/span.h" +#include "yacl/base/byte_container_view.h" +#include "yacl/base/exception.h" +#include "yacl/base/int128.h" +#include "yacl/crypto/base/hash/hash_utils.h" + +#include "psi/psi/core/cuckoo_index.h" + +namespace psi::psi { + +// GeneralizedCuckooHash options +// now support (2,2), (3,2) gch +// Reference: +// DW07. M. Dietzfelbinger and C. Weidling. +// Balanced allocation and dictionaries with tightly packed constant size bins +// +CuckooIndex::Options GetCuckooHashOption(size_t bin_size, size_t hash_num, + size_t items_size); + +// abstract interface for psi hash table +class IPsiHashTable { + public: + virtual ~IPsiHashTable() {} + + virtual void Insert(absl::Span<const std::string> items) = 0; +}; + +class GeneralizedCuckooHashTable : public IPsiHashTable { + public: + explicit GeneralizedCuckooHashTable(CuckooIndex::Options options, + size_t bin_data_num, uint128_t seed = 0); + + void Insert(yacl::ByteContainerView item_data, size_t input_offset); + void Insert(yacl::ByteContainerView item); + void Insert(absl::Span<const std::string> items) override; + + const std::vector<std::vector<CuckooIndex::Bin>> &bins() const { + return bins_; + } + + const std::vector<uint64_t> &GetItemsHashLow64() const { + return items_hash_low64_; + } + + // Returns the current fill rate of the hash table and stash. + inline double FillRate() const noexcept { + return static_cast<double>(inserted_items_) / + (static_cast<double>(gch_options_.NumBins()) * max_items_per_bin_); + } + + const CuckooIndex::Options &GetCuckooOptions() const { return gch_options_; } + + size_t GetMaxItemsPerBin() const { return max_items_per_bin_; } + + protected: + CuckooIndex::Options gch_options_; + // max data number per bin + size_t max_items_per_bin_; + uint128_t seed_; + std::vector<std::vector<CuckooIndex::Bin>> bins_; + std::vector<std::vector<uint64_t>> hashes_; + std::vector<uint64_t> items_hash_low64_; + size_t inserted_items_ = 0; + + // Randomness source for location function sampling. + std::mt19937_64 gen_; + + std::uniform_int_distribution<std::uint32_t> uniform_hash_idx_; + std::uniform_int_distribution<std::uint32_t> uniform_data_idx_; +}; + +class SimpleHashTable : public IPsiHashTable { + public: + explicit SimpleHashTable(CuckooIndex::Options options, uint128_t seed = 0); + + void Insert(yacl::ByteContainerView item_data, + const std::vector<uint64_t> &hash_bin_idx); + void Insert(yacl::ByteContainerView item); + void Insert(absl::Span<const std::string> items) override; + + const std::vector<std::vector<CuckooIndex::Bin>> &bins() const { + return bins_; + } + + const std::vector<uint64_t> &GetItemsHashLow64() const { + return items_hash_low64_; + } + + const CuckooIndex::Options &GetCuckooOptions() const { return gch_options_; } + + const std::vector<size_t> &GetConflictIdx() const { return conflict_idx_; } + + protected: + CuckooIndex::Options gch_options_; + uint128_t seed_; + std::vector<std::vector<CuckooIndex::Bin>> bins_; + std::vector<uint64_t> items_hash_low64_; + size_t inserted_items_ = 0; + std::vector<size_t> conflict_idx_; +}; + +} // namespace psi::psi diff --git a/psi/psi/core/bc22_psi/generalized_cuckoo_hash_test.cc b/psi/psi/core/bc22_psi/generalized_cuckoo_hash_test.cc new file mode 100644 index 00000000..a31c2b6a --- /dev/null +++ b/psi/psi/core/bc22_psi/generalized_cuckoo_hash_test.cc @@ -0,0 +1,128 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/bc22_psi/generalized_cuckoo_hash.h" + +#include <random> +#include <string> + +#include "absl/strings/escaping.h" +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" + +namespace psi::psi { + +namespace { + +std::vector<std::string> CreateRangeItems(size_t begin, size_t size) { + std::vector<std::string> ret; + for (size_t i = 0; i < size; i++) { + ret.push_back(std::to_string(begin + i)); + } + return ret; +} + +} // namespace + +class GchTest : public testing::TestWithParam<size_t> {}; + +TEST(GchTest, BasicTest) { + std::random_device rd; + std::mt19937 rng(rd()); + + std::vector<std::string> items(1000); + for (size_t idx = 1; idx < items.size(); ++idx) { + items[idx] = std::to_string(rng()); + } + + size_t bin_size = 2; + size_t hash_num = 2; + CuckooIndex::Options cuckoo_options = + GetCuckooHashOption(bin_size, hash_num, items.size()); + + GeneralizedCuckooHashTable gch(cuckoo_options, bin_size, 0); + SimpleHashTable simple_table(cuckoo_options); + + gch.Insert(absl::MakeSpan(items)); + simple_table.Insert(absl::MakeSpan(items)); + + const std::vector<std::vector<CuckooIndex::Bin>> &bins = gch.bins(); + const std::vector<std::vector<CuckooIndex::Bin>> &simple_table_bins = + simple_table.bins(); + + SPDLOG_INFO("bin size: {}, simple_table_bins size: {}", bins.size(), + simple_table_bins.size()); + + size_t bin_items[4] = { + 0, + }; + size_t c1 = 0; + size_t c2 = 0; + std::vector<size_t> hash_data_num(hash_num); + memset(hash_data_num.data(), 0, hash_data_num.size() * sizeof(size_t)); + + for (size_t i = 0; i < bins.size(); ++i) { + if (simple_table_bins[i].size() < bins[i].size()) { + SPDLOG_INFO("****{}****", i); + } + for (const auto &bin : bins[i]) { + // size_t item_idx = bins[i][j].InputIdx(); + size_t hash_idx = bin.HashIdx(); + hash_data_num[hash_idx]++; + } + bin_items[bins[i].size()]++; + c1 += bins[i].size(); + c2 += simple_table_bins[i].size(); + } + SPDLOG_INFO("bin0:{}, bin1:{}, bin2:{}", bin_items[0], bin_items[1], + bin_items[2]); + SPDLOG_INFO("hash_data_num[0]:{} hash_data_num[1]:{}", hash_data_num[0], + hash_data_num[1]); + SPDLOG_INFO("c1:{} c2:{}", c1, c2); + SPDLOG_INFO("FillRate:{}", gch.FillRate()); + + // conflict index + const std::vector<size_t> conflict_idx = simple_table.GetConflictIdx(); + SPDLOG_INFO("conflict_idx: {}", conflict_idx.size()); +} + +TEST(GchTest, CuckooHashTest) { + std::vector<std::string> alice_data = CreateRangeItems(20000000, 30000); + + size_t bin_size = 3; + size_t hash_num = 2; + CuckooIndex::Options cuckoo_options = + GetCuckooHashOption(bin_size, hash_num, alice_data.size()); + + GeneralizedCuckooHashTable gch(cuckoo_options, bin_size, 0); + SimpleHashTable simple_table(cuckoo_options); + + gch.Insert(absl::MakeSpan(alice_data)); + simple_table.Insert(absl::MakeSpan(alice_data)); + + const std::vector<std::vector<CuckooIndex::Bin>> &bins = gch.bins(); + + const std::vector<uint64_t> &items_hash = gch.GetItemsHashLow64(); + + SPDLOG_INFO("items size:{} bins:{} items:{}", alice_data.size(), bins.size(), + items_hash.size()); + + EXPECT_EQ(items_hash.size(), alice_data.size()); + + // conflict index + const std::vector<size_t> conflict_idx = simple_table.GetConflictIdx(); + SPDLOG_INFO("conflict_idx: {}", conflict_idx.size()); +} + +} // namespace psi::psi diff --git a/psi/psi/core/communication.cc b/psi/psi/core/communication.cc new file mode 100644 index 00000000..aad4bd91 --- /dev/null +++ b/psi/psi/core/communication.cc @@ -0,0 +1,76 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/communication.h" + +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" + +#include "interconnection/runtime/ecdh_psi.pb.h" + +namespace psi::psi { + +std::shared_ptr<yacl::link::Context> CreateP2PLinkCtx( + const std::string& id_prefix, + const std::shared_ptr<yacl::link::Context>& link_ctx, size_t peer_rank) { + if (link_ctx->WorldSize() > 2) { + // build subworld link + auto peer_id = link_ctx->PartyIdByRank(peer_rank); + auto self_id = link_ctx->PartyIdByRank(link_ctx->Rank()); + std::vector<std::string> party_ids({peer_id, self_id}); + std::sort(party_ids.begin(), party_ids.end()); + + size_t a_rank = std::min(link_ctx->Rank(), peer_rank); + size_t b_rank = std::max(link_ctx->Rank(), peer_rank); + std::string sub_id = fmt::format("{}-{}-{}", id_prefix, a_rank, b_rank); + + auto ctx = link_ctx->SubWorld(sub_id, party_ids); + SPDLOG_INFO("create p2p link, id:{}, rank:{}", ctx->Id(), ctx->Rank()); + + return ctx; + } else { + return link_ctx; + } +} + +yacl::Buffer IcPsiBatchSerializer::Serialize(PsiDataBatch&& batch) { + org::interconnection::v2::runtime::EcdhPsiCipherBatch proto; + proto.set_type(batch.type); + proto.set_batch_index(batch.batch_index); + proto.set_is_last_batch(batch.is_last_batch); + + proto.set_count(batch.item_num); + proto.set_ciphertext(std::move(batch.flatten_bytes)); + + yacl::Buffer buf(proto.ByteSizeLong()); + proto.SerializeToArray(buf.data(), buf.size()); + return buf; +} + +PsiDataBatch IcPsiBatchSerializer::Deserialize(yacl::ByteContainerView buf) { + org::interconnection::v2::runtime::EcdhPsiCipherBatch proto; + YACL_ENFORCE(proto.ParseFromArray(buf.data(), buf.size()), + "parse EcdhPsiCipherBatch proto fail"); + + PsiDataBatch batch; + batch.item_num = proto.count(); + batch.flatten_bytes = std::move(*proto.mutable_ciphertext()); + batch.is_last_batch = proto.is_last_batch(); + + batch.type = proto.type(); + batch.batch_index = proto.batch_index(); + return batch; +} + +} // namespace psi::psi diff --git a/psi/psi/core/communication.h b/psi/psi/core/communication.h new file mode 100644 index 00000000..97d4df4b --- /dev/null +++ b/psi/psi/core/communication.h @@ -0,0 +1,104 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <memory> +#include <string> + +#include "yacl/base/buffer.h" +#include "yacl/link/link.h" + +#include "psi/psi/utils/serializable.pb.h" + +namespace psi::psi { + +// I prefer 4096. +inline constexpr size_t kEcdhPsiBatchSize = 4096; + +// Ecc256 requires 32 bytes. +inline constexpr size_t kKeySize = 32; +inline constexpr size_t kHashSize = kKeySize; + +// The final comparison bytes. +// Hongcheng suggested that 90 bits would be enough. Here we give 96 bits. +// +// The least significant bits(LSB) of g^{ab} are globally indistinguishable from +// a random bit-string, Reference: +// Optimal Randomness Extraction from a Diffie-Hellman Element +// EUROCRYPT 2009 https://link.springer.com/chapter/10.1007/978-3-642-01001-9_33 +// +inline constexpr size_t kFinalCompareBytes = 12; + +enum class PsiRoleType { + Sender, + Receiver, +}; + +struct PsiDataBatch { + // current batch item num + uint32_t item_num = 0; + + // Pack all items in a single `std::string` to save bandwidth. + std::string flatten_bytes; + + // Metadata. + // Batch index. Start from 0. + int32_t batch_index = 0; + bool is_last_batch = false; + + // The type hint for each message. + // + // "enc": the first stage ciphertext + // "dual.enc": the second stage ciphertext + std::string type; + + // deprecated. use IcPsiBatchSerializer instead. + yacl::Buffer Serialize() const { + proto::PsiDataBatchProto proto; + proto.set_item_num(item_num); + proto.set_flatten_bytes(flatten_bytes); + proto.set_is_last_batch(is_last_batch); + + yacl::Buffer buf(proto.ByteSizeLong()); + proto.SerializeToArray(buf.data(), buf.size()); + return buf; + } + + // deprecated. use IcPsiBatchSerializer instead. + static PsiDataBatch Deserialize(const yacl::Buffer& buf) { + proto::PsiDataBatchProto proto; + proto.ParseFromArray(buf.data(), buf.size()); + + PsiDataBatch batch; + batch.item_num = proto.item_num(); + batch.flatten_bytes = proto.flatten_bytes(); + batch.is_last_batch = proto.is_last_batch(); + + return batch; + } +}; + +// Serialize data using an interconnection standard protocol +class IcPsiBatchSerializer { + public: + // support interconnection standard. + static yacl::Buffer Serialize(PsiDataBatch&& batch); + static PsiDataBatch Deserialize(yacl::ByteContainerView buf); +}; + +std::shared_ptr<yacl::link::Context> CreateP2PLinkCtx( + const std::string& id_prefix, + const std::shared_ptr<yacl::link::Context>& link_ctx, size_t peer_rank); +} // namespace psi::psi diff --git a/psi/psi/core/cuckoo_index.cc b/psi/psi/core/cuckoo_index.cc new file mode 100644 index 00000000..0bce63f6 --- /dev/null +++ b/psi/psi/core/cuckoo_index.cc @@ -0,0 +1,150 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/cuckoo_index.h" + +#include <cmath> +#include <set> + +namespace psi::psi { + +CuckooIndex::CuckooIndex(const Options& options) : options_(options) { + bins_.resize(options_.NumBins()); + stash_.resize(options_.num_stash); + hashes_.reserve(options_.NumBins()); + + YACL_ENFORCE((options_.num_hash - 1) * HashRoom::kBlockSize + + sizeof(uint64_t) <= + sizeof(HashType)); +} + +void CuckooIndex::Insert(absl::Span<const HashType> codes) { + const size_t input_offset = hashes_.size(); + const size_t size = codes.size(); + const size_t num_bins = options_.NumBins(); + + // Add to hash rooms. + for (const HashType& code : codes) { + hashes_.emplace_back(code); + } + YACL_ENFORCE(hashes_.size() <= options_.num_input); + + std::vector<Bin> candidates(size); + for (size_t i = 0; i < candidates.size(); ++i) { + // Init candidates. Start from first hash function. + candidates[i].set_encoded(input_offset + i); + } + + size_t try_count = 0; + std::vector<Bin> evicted; + + while (!candidates.empty() && try_count++ < options_.max_try_count) { + size_t write_idx = 0; + for (size_t i = 0; i < candidates.size(); ++i) { + const Bin candid = candidates[i]; + size_t bin_idx = + hashes_[candid.InputIdx()].GetHash(candid.HashIdx()) % num_bins; + Bin evicted_bin = Bin(bins_[bin_idx].Swap(candid.encoded())); + if (!evicted_bin.IsEmpty()) { + // Try next hash for evicted items. + uint64_t next_candid = + Bin::Encode(evicted_bin.InputIdx(), + (evicted_bin.HashIdx() + 1) % options_.num_hash); + candidates[write_idx++].set_encoded(next_candid); + } + } + candidates.resize(write_idx); + } + + for (auto& candidate : candidates) { + PutToStash(candidate.InputIdx()); + } +} + +void CuckooIndex::PutToStash(uint64_t input_idx) { + // `stash` is small enough to do a linear search. + for (auto& s : stash_) { + if (s.IsEmpty()) { + s.set_encoded(input_idx); + return; + } + } + YACL_THROW("Cannot find empty bin in stash for input_idx={}", input_idx); +} + +void CuckooIndex::SanityCheck() const { + std::set<uint64_t> set; + for (const auto& bin : bins_) { + if (!bin.IsEmpty()) { + YACL_ENFORCE(set.insert(bin.InputIdx()).second, + "Input={} already exists.", bin.InputIdx()); + } + } + for (const auto& bin : stash_) { + if (!bin.IsEmpty()) { + YACL_ENFORCE(set.insert(bin.InputIdx()).second, + "Input={} already exists.", bin.InputIdx()); + } + } + + // All inputs should be found. + YACL_ENFORCE(set.size() == options_.num_input); + // Every input must exists. + size_t idx = 0; + for (uint64_t i : set) { + YACL_ENFORCE(idx++ == i, "Cannot find input={}", i); + } +} + +// computing method from cryptoTool +// https://github.com/ladnir/cryptoTools/blob/master/cryptoTools/Common/CuckooIndex.cpp#L133 +CuckooIndex::Options CuckooIndex::SelectParams(uint64_t n, uint64_t stash_size, + uint64_t hash_num, + uint64_t stat_sec_param) { + auto h = hash_num != 0 ? hash_num : 3; + + if (stash_size == 0 && h == 3) { + double a = 240; + double b = -std::log2(n) - 256; + + auto e = (stat_sec_param - b) / a; + + // we have the statSecParam = a e + b, where e = |cuckoo|/|set| is the + // expenation factor therefore we have that + // + // e = (statSecParam - b) / a + // + + return CuckooIndex::Options{n, 0, h, e}; + } + + YACL_THROW("not support for stash_size={} and hash_num={}", stash_size, + hash_num); +} + +uint8_t CuckooIndex::MinCollidingHashIdx(uint64_t bin_index) const { + auto bin = bins_[bin_index]; + size_t num_bins = options_.NumBins(); + uint64_t input_idx = bin.InputIdx(); + uint64_t target; + for (uint64_t i = 0; i < options_.num_hash; i++) { + target = hashes_[input_idx].GetHash(i) % num_bins; + if (target == bin_index) { + return static_cast<uint8_t>(i); + } + } + return -1; +} + +} // namespace psi::psi \ No newline at end of file diff --git a/psi/psi/core/cuckoo_index.h b/psi/psi/core/cuckoo_index.h new file mode 100644 index 00000000..8f93d629 --- /dev/null +++ b/psi/psi/core/cuckoo_index.h @@ -0,0 +1,156 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <vector> + +#include "absl/types/span.h" +#include "yacl/base/exception.h" +#include "yacl/base/int128.h" + +namespace psi::psi { + +// CuckooIndex does not want to be a container like `unordered_map` which +// provides CRUD interfaces. Instead, CuckooIndex aims to decide the location +// for each input. And it is widely used in PSI or PIR for a perfect `1 to N` +// relationships. +// +// Reference: +// https://github.com/ladnir/cryptoTools/blob/924e328071b8c0616df01a233705702a7f6df2c3/cryptoTools/Common/CuckooIndex.h +// +// This class give a slightly more cpp-style interface and it's more readable. +class CuckooIndex { + public: + // Leave a space for us to adjust hash algorithm later, say `blake3` will + // output 2356 bits. + using HashType = uint128_t; + + // Compact bin representation. + // + // Sample: this bin stores input `input_idx=2` via the second hash function + // (hash_idx=1). + // + // |----------uint64_t---------------------| + // | 8-bit | 56-bit | + // | hashidx | input idx | + // 00000001_0000000000000...0000000000010 + // |---------------------------------------| + class Bin { + public: + inline static constexpr uint64_t kEmpty = uint64_t(-1); + + explicit Bin(uint64_t encoded = kEmpty) : encoded_(encoded) {} + + uint8_t HashIdx() const { return uint8_t(encoded_ >> 56); } + uint64_t InputIdx() const { return encoded_ & (uint64_t(-1) >> 8); } + + uint64_t encoded() const { return encoded_; } + void set_encoded(uint64_t encoded) { encoded_ = encoded; } + + uint64_t Swap(uint64_t encoded) { + uint64_t prev = encoded_; + encoded_ = encoded; + return prev; + } + + bool IsEmpty() const { return encoded_ == kEmpty; } + + static uint64_t Encode(uint64_t input_idx, uint8_t hash_idx) { + return input_idx | uint64_t(hash_idx) << 56; + } + + private: + uint64_t encoded_; + }; + + // Compact hash codes store. + class HashRoom { + public: + explicit HashRoom(HashType code) : code_(code) {} + + inline static constexpr int kBlockSize = 2; + + uint64_t GetHash(size_t idx) const { + // Make it portable on non-x86 archs. + // References: + // https://blog.quarkslab.com/unaligned-accesses-in-cc-what-why-and-solutions-to-do-it-properly.html + // https://www.kernel.org/doc/Documentation/unaligned-memory-access.txt + uint64_t aligned_u64; + memcpy(&aligned_u64, (const uint8_t*)(&code_) + idx * kBlockSize, + sizeof(aligned_u64)); + return aligned_u64; + } + + private: + const HashType code_; + }; + + struct Options { + // Number of expected inputs. + uint64_t num_input; + // Stash capcity. + uint64_t num_stash; + // Number of hash functions used. + uint64_t num_hash; + // Scale factor for computing `NumBins`. + double scale_factor; + // Decide the max number of evictions allowed for each `Insert`. These + // evicted items will be put into `stash` finally. If the stash is full, an + // exception will ocurred. + uint64_t max_try_count = 128; + + uint64_t NumBins() const { + uint64_t num_bins = num_input * scale_factor; + // + // for stashless cuckooHash + // when num_input < 256, num_bins add 8 for safe + // reduce the probability of using stash + if ((num_stash == 0) && (num_input < 256)) { + num_bins += 8; + } + return num_bins; + } + }; + + public: + explicit CuckooIndex(const Options& options); + + const std::vector<Bin>& bins() const { return bins_; } + + const std::vector<Bin>& stash() const { return stash_; } + + const std::vector<HashRoom>& hashes() const { return hashes_; } + + // This interface assumes `inputs` are already cryptographic random. + void Insert(absl::Span<const HashType> codes); + + // For debug only. + void SanityCheck() const; + + static Options SelectParams(uint64_t n, uint64_t stash_size, + uint64_t hash_num, uint64_t stat_sec_param = 40); + + uint8_t MinCollidingHashIdx(uint64_t bin_index) const; + + private: + void PutToStash(uint64_t input_idx); + + const Options options_; + std::vector<Bin> bins_; + std::vector<Bin> stash_; + std::vector<HashRoom> hashes_; +}; + +} // namespace psi::psi diff --git a/psi/psi/core/cuckoo_index_test.cc b/psi/psi/core/cuckoo_index_test.cc new file mode 100644 index 00000000..f0d4e1e5 --- /dev/null +++ b/psi/psi/core/cuckoo_index_test.cc @@ -0,0 +1,85 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/cuckoo_index.h" + +#include <algorithm> +#include <random> + +#include "gtest/gtest.h" +#include "yacl/crypto/utils/rand.h" + +namespace psi::psi { + +class CuckooIndexTest : public testing::TestWithParam<CuckooIndex::Options> {}; + +TEST_P(CuckooIndexTest, Works) { + const auto& param = GetParam(); + { + // Insert all in one call. + CuckooIndex cuckoo_index(param); + + auto inputs = yacl::crypto::RandVec<uint128_t>(param.num_input); + + ASSERT_NO_THROW(cuckoo_index.Insert(absl::MakeSpan(inputs))); + ASSERT_NO_THROW(cuckoo_index.SanityCheck()); + + EXPECT_EQ(cuckoo_index.bins().size(), param.NumBins()); + EXPECT_EQ(cuckoo_index.stash().size(), param.num_stash); + EXPECT_EQ(cuckoo_index.hashes().size(), param.num_input); + } + { + // Insert by batches. + CuckooIndex cuckoo_index(param); + auto inputs = yacl::crypto::RandVec<uint128_t>(param.num_input); + + constexpr size_t kChunkSize = 1024; + for (size_t i = 0; i < inputs.size(); i += kChunkSize) { + size_t chunk_size = std::min(kChunkSize, inputs.size() - i); + absl::Span<const uint128_t> chunk(inputs.data() + i, chunk_size); + ASSERT_NO_THROW(cuckoo_index.Insert(chunk)); + } + + ASSERT_NO_THROW(cuckoo_index.SanityCheck()); + EXPECT_EQ(cuckoo_index.bins().size(), param.NumBins()); + EXPECT_EQ(cuckoo_index.stash().size(), param.num_stash); + EXPECT_EQ(cuckoo_index.hashes().size(), param.num_input); + } +} + +INSTANTIATE_TEST_SUITE_P( + Works_Instances, CuckooIndexTest, + testing::Values( + CuckooIndex::Options{(1 << 16), 8, 3, 1.2}, // 3-way, 1.2, 8 + CuckooIndex::Options{(1 << 16) + 1, 4, 2, 2.4}, // 2-way, 2.4, 4 + CuckooIndex::Options{(1 << 16) - 1, 4, 2, 2.4}, // 2-way, 2.4, 4 + CuckooIndex::Options{(1 << 20) + 17, 4, 3, 1.2}, // 3-way, 1.2, 4 + CuckooIndex::Options{(1 << 0), 0, 3, 1.2} // dummy + )); + +TEST(CuckooIndexTest, Bad_StashTooSmall) { + CuckooIndex cuckoo_index(CuckooIndex::Options{1 << 16, 0, 3, 1.1}); + auto inputs = yacl::crypto::RandVec<uint128_t>(1 << 16); + + ASSERT_THROW(cuckoo_index.Insert(absl::MakeSpan(inputs)), yacl::Exception); +} + +TEST(CuckooIndexTest, Bad_SmallScaleFactor) { + CuckooIndex cuckoo_index(CuckooIndex::Options{1 << 16, 8, 3, 1.01}); + auto inputs = yacl::crypto::RandVec<uint128_t>(1 << 16); + + ASSERT_THROW(cuckoo_index.Insert(absl::MakeSpan(inputs)), yacl::Exception); +} + +} // namespace psi::psi diff --git a/psi/psi/core/dp_psi/BUILD.bazel b/psi/psi/core/dp_psi/BUILD.bazel new file mode 100644 index 00000000..13f596a8 --- /dev/null +++ b/psi/psi/core/dp_psi/BUILD.bazel @@ -0,0 +1,71 @@ +# Copyright 2021 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//bazel:psi.bzl", "psi_cc_binary", "psi_cc_library", "psi_cc_test") + +package(default_visibility = ["//visibility:public"]) + +psi_cc_library( + name = "dp_psi", + srcs = [ + "dp_psi.cc", + "dp_psi_utils.cc", + ], + hdrs = [ + "dp_psi.h", + "dp_psi_utils.h", + ], + deps = [ + "//psi/psi/core:ecdh_3pc_psi", + "//psi/psi/core:ecdh_psi", + "//psi/psi/cryptor:cryptor_selector", + "//psi/psi/utils:batch_provider", + "//psi/psi/utils:ec_point_store", + "//psi/psi/utils:serialize", + "@com_google_absl//absl/strings", + "@yacl//yacl/base:exception", + "@yacl//yacl/crypto/utils:rand", + "@yacl//yacl/link", + "@yacl//yacl/utils:parallel", + ], +) + +psi_cc_test( + name = "dp_psi_test", + srcs = ["dp_psi_test.cc"], + deps = [ + ":dp_psi", + "@com_google_absl//absl/container:flat_hash_set", + ], +) + +psi_cc_binary( + name = "dp_psi_bench", + srcs = ["dp_psi_bench.cc"], + deps = [ + ":dp_psi", + "@com_github_google_benchmark//:benchmark_main", + "@com_google_absl//absl/container:flat_hash_set", + ], +) + +psi_cc_binary( + name = "dp_psi_payload_bench", + srcs = ["dp_psi_payload_bench.cc"], + deps = [ + ":dp_psi", + "@com_github_google_benchmark//:benchmark_main", + "@com_google_absl//absl/container:flat_hash_set", + ], +) diff --git a/psi/psi/core/dp_psi/dp_psi.cc b/psi/psi/core/dp_psi/dp_psi.cc new file mode 100644 index 00000000..c9c864f6 --- /dev/null +++ b/psi/psi/core/dp_psi/dp_psi.cc @@ -0,0 +1,313 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/dp_psi/dp_psi.h" + +#include <algorithm> +#include <future> +#include <random> + +#include "spdlog/spdlog.h" +#include "yacl/base/buffer.h" +#include "yacl/base/exception.h" +#include "yacl/crypto/utils/rand.h" +#include "yacl/utils/parallel.h" + +#include "psi/psi/core/communication.h" +#include "psi/psi/core/dp_psi/dp_psi_utils.h" +#include "psi/psi/core/ecdh_3pc_psi.h" +#include "psi/psi/cryptor/cryptor_selector.h" +#include "psi/psi/utils/batch_provider.h" +#include "psi/psi/utils/ec_point_store.h" +#include "psi/psi/utils/serialize.h" + +#include "psi/psi/utils/serializable.pb.h" + +namespace psi::psi { + +namespace { + +constexpr uint64_t kSendBatchSize = 8192; + +std::vector<size_t> BernoulliSamples(const std::vector<size_t>& items_idx, + double q) { + std::pair<uint64_t, uint64_t> seed_pair = + yacl::DecomposeUInt128(yacl::crypto::RandU128()); + std::mt19937 rand(seed_pair.first); + + SPDLOG_INFO("sample bernoulli_distribution: {}", q); + + std::bernoulli_distribution dist(q); + + std::vector<size_t> bernoulli_items_idx; + for (unsigned long idx : items_idx) { + if (dist(rand)) { + bernoulli_items_idx.push_back(idx); + } + } + + SPDLOG_INFO( + "bernoulli_items_idx:{} ratio:{} ", bernoulli_items_idx.size(), + static_cast<double>(bernoulli_items_idx.size()) / items_idx.size()); + + return bernoulli_items_idx; +} + +std::pair<std::vector<std::string>, std::vector<size_t>> BernoulliSamples( + const std::vector<std::string>& items, + const std::vector<size_t>& shuffled_idx, double p) { + std::pair<uint64_t, uint64_t> seed_pair = + yacl::DecomposeUInt128(yacl::crypto::RandU128()); + std::mt19937 rand(seed_pair.first); + + SPDLOG_INFO("sample bernoulli_distribution: {}", p); + + std::bernoulli_distribution dist(p); + + std::vector<std::string> bernoulli_items; + std::vector<size_t> bernoulli_idx; + for (size_t idx = 0; idx < items.size(); idx++) { + if (dist(rand)) { + bernoulli_items.push_back(items[shuffled_idx[idx]]); + bernoulli_idx.push_back(shuffled_idx[idx]); + } + } + + SPDLOG_INFO("bernoulli_items: {}, bernoulli_idx:{} ratio:{} ", + bernoulli_items.size(), bernoulli_idx.size(), + static_cast<double>(bernoulli_items.size()) / items.size()); + return std::make_pair(bernoulli_items, bernoulli_idx); +} + +std::vector<size_t> GetShuffledIdx(size_t items_size) { + std::pair<uint64_t, uint64_t> seed_pair = + yacl::DecomposeUInt128(yacl::crypto::RandU128()); + std::mt19937 rng(seed_pair.first); + + std::vector<size_t> shuffled_idx_vec(items_size); + std::iota(shuffled_idx_vec.begin(), shuffled_idx_vec.end(), 0); + std::shuffle(shuffled_idx_vec.begin(), shuffled_idx_vec.end(), rng); + + return shuffled_idx_vec; +} + +} // namespace + +size_t RunDpEcdhPsiAlice(const DpPsiOptions& dp_psi_options, + const std::shared_ptr<yacl::link::Context>& link_ctx, + const std::vector<std::string>& items, + size_t* sub_sample_size, size_t* up_sample_size, + CurveType curve) { + auto peer_ec_point_store = std::make_shared<MemoryEcPointStore>(); + + auto batch_provider = + std::make_shared<MemoryBatchProvider>(items, kEcdhPsiBatchSize); + + SPDLOG_INFO( + "alice items_size: {}, down_sampling_rate: {}, up_sampling_rate: {}", + items.size(), dp_psi_options.p2, dp_psi_options.q); + + EcdhPsiOptions options; + + options.link_ctx = link_ctx; + options.ecc_cryptor = CreateEccCryptor(curve); + options.target_rank = link_ctx->Rank(); + + EcdhP2PExtendCtx psi_ctx(options); + + std::future<void> f_mask_self_a = + std::async([&] { return psi_ctx.MaskSelf(batch_provider); }); + + std::future<void> f_recv_peer_a = + std::async([&] { return psi_ctx.MaskPeer(peer_ec_point_store); }); + + f_mask_self_a.get(); + f_recv_peer_a.get(); + + SPDLOG_INFO("after maskSelf maskPeer"); + + std::vector<std::string>& alice_peer_result = peer_ec_point_store->content(); + + std::vector<std::string> self_dual_mask; + + std::future<void> f_recv_dual_mask_a = + std::async([&] { return psi_ctx.RecvItems(&self_dual_mask); }); + f_recv_dual_mask_a.get(); + + // std::vector<std::string> intersection_ab; + std::vector<size_t> intersection_idx; + std::vector<size_t> non_intersection_idx; + + std::sort(self_dual_mask.begin(), self_dual_mask.end()); + + for (size_t index = 0; index < alice_peer_result.size(); index++) { + if (std::binary_search(self_dual_mask.begin(), self_dual_mask.end(), + alice_peer_result[index])) { + YACL_ENFORCE(index < alice_peer_result.size()); + + intersection_idx.push_back(index); + } else { + non_intersection_idx.push_back(index); + } + } + // check non_intersection_idx size==0 + // if size==0 report intersection 0 + if (non_intersection_idx.empty()) { + yacl::Buffer intersection_idx_size_buffer = utils::SerializeSize(0); + link_ctx->SendAsyncThrottled(link_ctx->NextRank(), + intersection_idx_size_buffer, + fmt::format("intersection_idx size: {}", 0)); + + SPDLOG_WARN("non_intersection_idx size 0"); + + return 0; + } + + std::vector<size_t> sub_sample_idx = + BernoulliSamples(intersection_idx, dp_psi_options.p2); + + std::vector<size_t> up_sample_idx = + BernoulliSamples(non_intersection_idx, dp_psi_options.q); + + *sub_sample_size = intersection_idx.size() - sub_sample_idx.size(); + *up_sample_size = up_sample_idx.size(); + + sub_sample_idx.insert(sub_sample_idx.end(), up_sample_idx.begin(), + up_sample_idx.end()); + // sort index, mix sub_sample_idx and up_sample_idx + std::sort(sub_sample_idx.begin(), sub_sample_idx.end()); + + SPDLOG_INFO("alice intersection size: {}", sub_sample_idx.size()); + + yacl::Buffer intersection_idx_size_buffer = + utils::SerializeSize(sub_sample_idx.size()); + link_ctx->SendAsyncThrottled( + link_ctx->NextRank(), intersection_idx_size_buffer, + fmt::format("intersection_idx size: {}", sub_sample_idx.size())); + + for (size_t idx = 0; idx < sub_sample_idx.size(); idx += kSendBatchSize) { + PsiDataBatch data_batch; + size_t current_batch_size; + if ((idx + kSendBatchSize) < sub_sample_idx.size()) { + data_batch.is_last_batch = false; + current_batch_size = kSendBatchSize; + } else { + data_batch.is_last_batch = true; + current_batch_size = sub_sample_idx.size() - idx; + } + std::string flatten_bytes(current_batch_size * sizeof(size_t), '\0'); + + std::memcpy(flatten_bytes.data(), &sub_sample_idx[idx], + current_batch_size * sizeof(size_t)); + + data_batch.flatten_bytes = flatten_bytes; + link_ctx->SendAsyncThrottled(link_ctx->NextRank(), data_batch.Serialize(), + "batch send idx"); + } + + return sub_sample_idx.size(); +} + +std::vector<size_t> RunDpEcdhPsiBob( + const DpPsiOptions& dp_psi_options, + const std::shared_ptr<yacl::link::Context>& link_ctx, + const std::vector<std::string>& items, size_t* sub_sample_size, + CurveType curve) { + std::vector<size_t> bob_shuffled_idx = GetShuffledIdx(items.size()); + + SPDLOG_INFO("bob items_size: {}, down_sampling_rate: {}", items.size(), + dp_psi_options.p1); + + std::pair<std::vector<std::string>, std::vector<size_t>> sub_sample_result = + BernoulliSamples(items, bob_shuffled_idx, dp_psi_options.p1); + + *sub_sample_size = items.size() - sub_sample_result.first.size(); + + auto batch_provider = std::make_shared<MemoryBatchProvider>( + sub_sample_result.first, kEcdhPsiBatchSize); + + auto peer_ec_point_store = std::make_shared<MemoryEcPointStore>(); + + EcdhPsiOptions options; + + // set options + options.ecc_cryptor = CreateEccCryptor(curve); + options.link_ctx = link_ctx; + options.target_rank = link_ctx->Rank(); + + EcdhP2PExtendCtx psi_ctx(options); + + std::future<void> f_mask_peer_b = + std::async([&] { return psi_ctx.MaskPeer(peer_ec_point_store); }); + + std::future<void> f_mask_self_b = + std::async([&] { return psi_ctx.MaskSelf(batch_provider); }); + + f_mask_peer_b.get(); + f_mask_self_b.get(); + + SPDLOG_INFO("after mask self, mask peer"); + + std::vector<std::string>& bob_peer_result = peer_ec_point_store->content(); + std::sort(bob_peer_result.begin(), bob_peer_result.end()); + + // send x^a^b to alice + std::future<void> f_bob_send_shuffle_dual_mask = + std::async([&] { return psi_ctx.SendItems(bob_peer_result); }); + + f_bob_send_shuffle_dual_mask.get(); + + SPDLOG_INFO("after send shuffled batch"); + + yacl::Buffer intersection_size_buffer = link_ctx->Recv( + link_ctx->NextRank(), fmt::format("recv intersection_size")); + size_t intersection_size = utils::DeserializeSize(intersection_size_buffer); + + std::vector<size_t> intersection_idx(intersection_size); + if (intersection_size == 0) { + return intersection_idx; + } + + size_t recv_idx = 0; + while (true) { + PsiDataBatch batch = PsiDataBatch::Deserialize(link_ctx->Recv( + link_ctx->NextRank(), fmt::format("recv batch idx{}", recv_idx))); + + YACL_ENFORCE(batch.flatten_bytes.size() % sizeof(size_t) == 0); + size_t current_num; + current_num = batch.flatten_bytes.size() / sizeof(size_t); + std::memcpy(intersection_idx.data() + recv_idx, batch.flatten_bytes.data(), + batch.flatten_bytes.size()); + + recv_idx += current_num; + + if (batch.is_last_batch) { + SPDLOG_INFO("recv last batch, recv_num: {}", recv_idx); + break; + } + } + + std::vector<size_t> dp_intersection_idx; + dp_intersection_idx.reserve(intersection_idx.size()); + for (const auto& idx : intersection_idx) { + dp_intersection_idx.push_back(sub_sample_result.second[idx]); + } + std::sort(dp_intersection_idx.begin(), dp_intersection_idx.end()); + + SPDLOG_INFO("dp_intersection_idx size:{}", dp_intersection_idx.size()); + + return dp_intersection_idx; +} + +} // namespace psi::psi diff --git a/psi/psi/core/dp_psi/dp_psi.h b/psi/psi/core/dp_psi/dp_psi.h new file mode 100644 index 00000000..c8170b2a --- /dev/null +++ b/psi/psi/core/dp_psi/dp_psi.h @@ -0,0 +1,86 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <cmath> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "spdlog/spdlog.h" +#include "yacl/link/link.h" + +#include "psi/psi/core/ecdh_psi.h" + +namespace psi::psi { + +// bernoulli distribution probability for sub/up samples +struct DpPsiOptions { + explicit DpPsiOptions(double bob_p = 0.9, double epsilon = 3.0) + : p1(bob_p), alice_epsilon(epsilon) { + double e_epsilon = std::exp(alice_epsilon); + p2 = e_epsilon / (1 + e_epsilon); + q = 1 - p2; + SPDLOG_INFO("DpPsiOptions p1:{} epsilon:{} p2:{}, q:{}", p1, epsilon, p2, + q); + } + + // bob SubSampling + double p1; + + // + double alice_epsilon; + + // alice SubSampling + double p2; + // alice UpSampling + double q; +}; + +/** + * @brief + * + * @param dp_psi_options: dp psi options + * @param link_ctx : link for send/recv + * @param items : data + * @param sub_sample_size : alice subsample size + * @param up_sample_size : fake items size in intersection + * @param curve : ecc curve type, default 25519 + * @return size_t : return intersection size + */ +size_t RunDpEcdhPsiAlice(const DpPsiOptions& dp_psi_options, + const std::shared_ptr<yacl::link::Context>& link_ctx, + const std::vector<std::string>& items, + size_t* sub_sample_size, size_t* up_sample_size, + CurveType curve = CurveType::CURVE_25519); + +/** + * @brief + * + * @param dp_psi_options : dp psi options + * @param link_ctx : link for send/recv + * @param items : data + * @param sub_sample_size : bob subsample size + * @param curve : ecc curve type, default 25519 + * @return std::vector<size_t> : return intersection idx + */ +std::vector<size_t> RunDpEcdhPsiBob( + const DpPsiOptions& dp_psi_options, + const std::shared_ptr<yacl::link::Context>& link_ctx, + const std::vector<std::string>& items, size_t* sub_sample_size, + CurveType curve = CurveType::CURVE_25519); + +} // namespace psi::psi diff --git a/psi/psi/core/dp_psi/dp_psi_bench.cc b/psi/psi/core/dp_psi/dp_psi_bench.cc new file mode 100644 index 00000000..ae71659c --- /dev/null +++ b/psi/psi/core/dp_psi/dp_psi_bench.cc @@ -0,0 +1,200 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <algorithm> +#include <future> +#include <random> +#include <string> + +#include "absl/container/flat_hash_set.h" +#include "absl/strings/escaping.h" +#include "absl/strings/str_split.h" +#include "benchmark/benchmark.h" +#include "spdlog/spdlog.h" +#include "yacl/link/test_util.h" + +#include "psi/psi/core/dp_psi/dp_psi.h" + +namespace psi::psi { + +namespace { + +std::vector<std::string> CreateRangeItems(size_t start_pos, size_t size) { + std::vector<std::string> ret(size); + + auto gen_items_proc = [&](size_t begin, size_t end) -> void { + for (size_t i = begin; i < end; ++i) { + ret[i] = std::to_string(start_pos + i); + } + }; + + std::future<void> f_gen = std::async(gen_items_proc, size / 2, size); + + gen_items_proc(0, size / 2); + + f_gen.get(); + + return ret; +} + +std::vector<std::string> GetIntersection( + const std::vector<std::string>& items_a, + const std::vector<std::string>& items_b) { + absl::flat_hash_set<std::string> set(items_a.begin(), items_a.end()); + std::vector<std::string> ret; + for (const auto& s : items_b) { + if (set.count(s) != 0) { + ret.push_back(s); + } + } + return ret; +} + +std::shared_ptr<yacl::link::Context> CreateContext( + int self_rank, yacl::link::ContextDesc& lctx_desc) { + std::shared_ptr<yacl::link::Context> link_ctx; + + yacl::link::FactoryBrpc factory; + link_ctx = factory.CreateContext(lctx_desc, self_rank); + link_ctx->ConnectToMesh(); + + return link_ctx; +} + +std::vector<std::shared_ptr<yacl::link::Context>> CreateLinks( + const std::string& host_str) { + std::vector<std::string> hosts = absl::StrSplit(host_str, ','); + yacl::link::ContextDesc lctx_desc; + for (size_t rank = 0; rank < hosts.size(); rank++) { + const std::string id = fmt::format("party{}", rank); + lctx_desc.parties.push_back({id, hosts[rank]}); + } + + auto proc = [&](int self_rank) -> std::shared_ptr<yacl::link::Context> { + return CreateContext(self_rank, lctx_desc); + }; + + size_t world_size = hosts.size(); + std::vector<std::future<std::shared_ptr<yacl::link::Context>>> f_links( + world_size); + for (size_t i = 0; i < world_size; i++) { + f_links[i] = std::async(proc, i); + } + + std::vector<std::shared_ptr<yacl::link::Context>> links(world_size); + for (size_t i = 0; i < world_size; i++) { + links[i] = f_links[i].get(); + } + + return links; +} + +constexpr char kLinkAddrAB[] = "127.0.0.1:9532,127.0.0.1:9533"; +constexpr uint32_t kLinkRecvTimeout = 30 * 60 * 1000; +constexpr uint32_t kLinkWindowSize = 16; +constexpr double kIntersectionRatio = 0.7; + +std::map<size_t, DpPsiOptions> dp_psi_params_map = { + {1 << 11, DpPsiOptions(0.9)}, {1 << 12, DpPsiOptions(0.9)}, + {1 << 13, DpPsiOptions(0.9)}, {1 << 14, DpPsiOptions(0.9)}, + {1 << 15, DpPsiOptions(0.9)}, {1 << 16, DpPsiOptions(0.9)}, + {1 << 17, DpPsiOptions(0.9)}, {1 << 18, DpPsiOptions(0.9)}, + {1 << 19, DpPsiOptions(0.9)}, {1 << 20, DpPsiOptions(0.995)}, + {1 << 21, DpPsiOptions(0.995)}, {1 << 22, DpPsiOptions(0.995)}, + {1 << 23, DpPsiOptions(0.995)}, {1 << 24, DpPsiOptions(0.995)}, + {1 << 25, DpPsiOptions(0.995)}, {1 << 26, DpPsiOptions(0.995)}, + {1 << 27, DpPsiOptions(0.995)}, {1 << 28, DpPsiOptions(0.995)}, + {1 << 29, DpPsiOptions(0.995)}, {1 << 30, DpPsiOptions(0.995)}}; + +} // namespace + +static void BM_DpPsi(benchmark::State& state) { + for (auto _ : state) { + state.PauseTiming(); + size_t items_size = state.range(0); + + std::vector<std::string> items_a = CreateRangeItems(0, items_size); + std::vector<std::string> items_b = + CreateRangeItems(items_size * (1 - kIntersectionRatio), items_size); + + auto ctxs = CreateLinks(kLinkAddrAB); + + ctxs[0]->SetThrottleWindowSize(kLinkWindowSize); + ctxs[1]->SetThrottleWindowSize(kLinkWindowSize); + + ctxs[0]->SetRecvTimeout(kLinkRecvTimeout); + ctxs[1]->SetRecvTimeout(kLinkRecvTimeout); + + std::vector<std::string> real_intersection = + GetIntersection(items_a, items_b); + + const DpPsiOptions& options = dp_psi_params_map[items_size]; + + state.ResumeTiming(); + + size_t alice_rank = 0; + size_t bob_rank = 1; + + size_t alice_sub_sample_size = 0; + size_t alice_up_sample_size = 0; + size_t bob_sub_sample_size = 0; + + std::future<size_t> f_dp_psi_a = std::async([&] { + return RunDpEcdhPsiAlice(options, ctxs[alice_rank], items_a, + &alice_sub_sample_size, &alice_up_sample_size); + }); + + std::future<std::vector<size_t>> f_dp_psi_b = std::async([&] { + return RunDpEcdhPsiBob(options, ctxs[bob_rank], items_b, + &bob_sub_sample_size); + }); + + size_t alice_intersection_size = f_dp_psi_a.get(); + std::vector<size_t> dp_psi_result = f_dp_psi_b.get(); + + SPDLOG_INFO( + "alice_intersection_size:{} " + "alice_sub_sample_size:{},alice_up_sample_size:{}", + alice_intersection_size, alice_sub_sample_size, alice_up_sample_size); + + SPDLOG_INFO( + "dp psi bob intersection size:{},bob_sub_sample_size:{} " + "real_intersection size: {}", + dp_psi_result.size(), bob_sub_sample_size, real_intersection.size()); + + auto stats0 = ctxs[alice_rank]->GetStats(); + auto stats1 = ctxs[bob_rank]->GetStats(); + + double total_comm_bytes = stats0->sent_bytes + stats0->recv_bytes; + SPDLOG_INFO("bob: sent_bytes:{} recv_bytes:{}, total_comm_bytes:{}", + stats1->sent_bytes.load(), stats1->recv_bytes.load(), + total_comm_bytes / 1024 / 1024); + } +} + +// [256k, 512k, 1m, 2m, 4m, 8m, 16m] +BENCHMARK(BM_DpPsi) + ->Unit(benchmark::kMillisecond) + ->Arg(256 << 10) + ->Arg(512 << 10) + ->Arg(1 << 20) + ->Arg(2 << 20) + ->Arg(4 << 20) + ->Arg(8 << 20) + ->Arg(16 << 20) + ->Arg(32 << 20) + ->Arg(64 << 20) + ->Arg(128 << 20); + +} // namespace psi::psi diff --git a/psi/psi/core/dp_psi/dp_psi_payload_bench.cc b/psi/psi/core/dp_psi/dp_psi_payload_bench.cc new file mode 100644 index 00000000..e848b0c7 --- /dev/null +++ b/psi/psi/core/dp_psi/dp_psi_payload_bench.cc @@ -0,0 +1,397 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <algorithm> +#include <cmath> +#include <future> +#include <random> +#include <string> + +#include "absl/container/flat_hash_set.h" +#include "absl/strings/escaping.h" +#include "absl/strings/str_split.h" +#include "benchmark/benchmark.h" +#include "spdlog/spdlog.h" +#include "yacl/crypto/utils/rand.h" +#include "yacl/link/test_util.h" + +#include "psi/psi/core/dp_psi/dp_psi.h" +#include "psi/psi/core/dp_psi/dp_psi_utils.h" + +namespace psi::psi { + +namespace { + +// ErfInv code from +// https://github.com/abrari/block-cipher-testing/blob/master/stats.c +double ErfInv(double x) { + // beware that the logarithm argument must be + // computed as (1.0 - x) * (1.0 + x), + // it must NOT be simplified as 1.0 - x * x as this + // would induce rounding errors near the boundaries +/-1 + double w = -std::log((1.0 - x) * (1.0 + x)); + double p; + + if (w < 6.25) { + w -= 3.125; + p = -3.6444120640178196996e-21; + p = -1.685059138182016589e-19 + p * w; + p = 1.2858480715256400167e-18 + p * w; + p = 1.115787767802518096e-17 + p * w; + p = -1.333171662854620906e-16 + p * w; + p = 2.0972767875968561637e-17 + p * w; + p = 6.6376381343583238325e-15 + p * w; + p = -4.0545662729752068639e-14 + p * w; + p = -8.1519341976054721522e-14 + p * w; + p = 2.6335093153082322977e-12 + p * w; + p = -1.2975133253453532498e-11 + p * w; + p = -5.4154120542946279317e-11 + p * w; + p = 1.051212273321532285e-09 + p * w; + p = -4.1126339803469836976e-09 + p * w; + p = -2.9070369957882005086e-08 + p * w; + p = 4.2347877827932403518e-07 + p * w; + p = -1.3654692000834678645e-06 + p * w; + p = -1.3882523362786468719e-05 + p * w; + p = 0.0001867342080340571352 + p * w; + p = -0.00074070253416626697512 + p * w; + p = -0.0060336708714301490533 + p * w; + p = 0.24015818242558961693 + p * w; + p = 1.6536545626831027356 + p * w; + } else if (w < 16.0) { + w = std::sqrt(w) - 3.25; + p = 2.2137376921775787049e-09; + p = 9.0756561938885390979e-08 + p * w; + p = -2.7517406297064545428e-07 + p * w; + p = 1.8239629214389227755e-08 + p * w; + p = 1.5027403968909827627e-06 + p * w; + p = -4.013867526981545969e-06 + p * w; + p = 2.9234449089955446044e-06 + p * w; + p = 1.2475304481671778723e-05 + p * w; + p = -4.7318229009055733981e-05 + p * w; + p = 6.8284851459573175448e-05 + p * w; + p = 2.4031110387097893999e-05 + p * w; + p = -0.0003550375203628474796 + p * w; + p = 0.00095328937973738049703 + p * w; + p = -0.0016882755560235047313 + p * w; + p = 0.0024914420961078508066 + p * w; + p = -0.0037512085075692412107 + p * w; + p = 0.005370914553590063617 + p * w; + p = 1.0052589676941592334 + p * w; + p = 3.0838856104922207635 + p * w; + } else if (!std::isinf(w)) { + w = std::sqrt(w) - 5.0; + p = -2.7109920616438573243e-11; + p = -2.5556418169965252055e-10 + p * w; + p = 1.5076572693500548083e-09 + p * w; + p = -3.7894654401267369937e-09 + p * w; + p = 7.6157012080783393804e-09 + p * w; + p = -1.4960026627149240478e-08 + p * w; + p = 2.9147953450901080826e-08 + p * w; + p = -6.7711997758452339498e-08 + p * w; + p = 2.2900482228026654717e-07 + p * w; + p = -9.9298272942317002539e-07 + p * w; + p = 4.5260625972231537039e-06 + p * w; + p = -1.9681778105531670567e-05 + p * w; + p = 7.5995277030017761139e-05 + p * w; + p = -0.00021503011930044477347 + p * w; + p = -0.00013871931833623122026 + p * w; + p = 1.0103004648645343977 + p * w; + p = 4.8499064014085844221 + p * w; + } else { + // this branch does not appears in the original code, it + // was added because the previous branch does not handle + // x = +/-1 correctly. In this case, w is positive infinity + // and as the first coefficient (-2.71e-11) is negative. + // Once the first multiplication is done, p becomes negative + // infinity and remains so throughout the polynomial evaluation. + // So the branch above incorrectly returns negative infinity + // instead of the correct positive infinity. + p = INFINITY; + } + + return p * x; +} + +double TruncatedNormalSample(double mu, double sigma, double a, double b, + uint64_t seed) { + double alpha = (a - mu) / sigma; + double beta = (b - mu) / sigma; + + double sqrt2 = std::sqrt(2); + double lower = std::erf(alpha / sqrt2); + double upper = std::erf(beta / sqrt2); + + std::default_random_engine generator(seed); + std::uniform_real_distribution<double> distribution(lower, upper); + double u = distribution(generator); + double out = sqrt2 * ErfInv(u); + // SPDLOG_INFO("lower:{} upper:{} a:{} b:{} alpha:{} beta:{} sqrt2:{}", lower, + // upper, a, b, alpha, beta, sqrt2); + // SPDLOG_INFO("u:{} out:{}", u, out); + + out = mu + sigma * out; + // SPDLOG_INFO("u:{} out:{}", u, out); + + return out; +} + +std::vector<std::string> CreateRangeItems(size_t start_pos, size_t size) { + std::vector<std::string> ret(size); + + auto gen_items_proc = [&](size_t begin, size_t end) -> void { + for (size_t i = begin; i < end; ++i) { + ret[i] = std::to_string(start_pos + i); + } + }; + + std::future<void> f_gen = std::async(gen_items_proc, size / 2, size); + + gen_items_proc(0, size / 2); + + f_gen.get(); + + return ret; +} + +constexpr double kPayloadMu = 50; +constexpr double kPayloadSigma = 8; +constexpr double kPayloadMinValue = 0; +constexpr double kPayloadMaxValue = 100; + +std::vector<uint32_t> CreateItemsPayload(size_t size) { + std::vector<uint32_t> random_data(size); + + double mu = kPayloadMu; + double sigma = kPayloadSigma; + + double a = kPayloadMinValue; + double b = kPayloadMaxValue; + + std::random_device rd; + + std::mt19937 rand_mt(rd()); + + std::generate(begin(random_data), end(random_data), [&]() { + uint64_t seed = (static_cast<uint64_t>(rand_mt()) << 32) | rand_mt(); + double res = TruncatedNormalSample(mu, sigma, a, b, seed); + return std::round(res); + }); + + return random_data; +} + +std::vector<std::string> GetIntersection( + const std::vector<std::string>& items_a, + const std::vector<std::string>& items_b) { + absl::flat_hash_set<std::string> set(items_a.begin(), items_a.end()); + std::vector<std::string> ret; + for (const auto& s : items_b) { + if (set.count(s) != 0) { + ret.push_back(s); + } + } + return ret; +} + +void PayloadMeanWithDp(const DpPsiOptions& /*dp_psi_options*/, + const std::vector<size_t>& intersection_idx, + const std::vector<uint32_t>& payloads, double* mean, + double* dp_mean, double* sigma, size_t max_value = 100, + double epsilon = 3) { + double sum = 0; + + for (const auto& idx : intersection_idx) { + sum += payloads[idx]; + } + + *mean = sum / intersection_idx.size(); + + std::pair<uint64_t, uint64_t> seed_pair = + yacl::DecomposeUInt128(yacl::crypto::RandSeed()); + std::default_random_engine rng{ + static_cast<std::random_device::result_type>(seed_pair.first)}; + + double GS = static_cast<double>(max_value) / intersection_idx.size(); + double delta = 1.0 / (10 * payloads.size()); + // p1 * p2 * delta_f + // delta = delta * dp_psi_options.p1 * dp_psi_options.p2; + + // SPDLOG_INFO("item size:{},intersection size: {}", payloads.size(), + // intersection_idx.size()); + + SPDLOG_INFO("GS: {}, delta:{}, epsilon:{}", GS, delta, epsilon); + + *sigma = CalibrateAnalyticGaussianMechanism(epsilon, delta, GS); + + double mu{0}; + std::normal_distribution<> norm{mu, *sigma}; + + // mean + dp + *dp_mean = *mean + norm(rng); +} + +std::shared_ptr<yacl::link::Context> CreateContext( + int self_rank, yacl::link::ContextDesc& lctx_desc) { + std::shared_ptr<yacl::link::Context> link_ctx; + + yacl::link::FactoryBrpc factory; + link_ctx = factory.CreateContext(lctx_desc, self_rank); + link_ctx->ConnectToMesh(); + + return link_ctx; +} + +std::vector<std::shared_ptr<yacl::link::Context>> CreateLinks( + const std::string& host_str) { + std::vector<std::string> hosts = absl::StrSplit(host_str, ','); + yacl::link::ContextDesc lctx_desc; + for (size_t rank = 0; rank < hosts.size(); rank++) { + const std::string id = fmt::format("party{}", rank); + lctx_desc.parties.push_back({id, hosts[rank]}); + } + + auto proc = [&](int self_rank) -> std::shared_ptr<yacl::link::Context> { + return CreateContext(self_rank, lctx_desc); + }; + + size_t world_size = hosts.size(); + std::vector<std::future<std::shared_ptr<yacl::link::Context>>> f_links( + world_size); + for (size_t i = 0; i < world_size; i++) { + f_links[i] = std::async(proc, i); + } + + std::vector<std::shared_ptr<yacl::link::Context>> links(world_size); + for (size_t i = 0; i < world_size; i++) { + links[i] = f_links[i].get(); + } + + return links; +} + +constexpr char kLinkAddrAB[] = "127.0.0.1:9532,127.0.0.1:9533"; +constexpr uint32_t kLinkRecvTimeout = 30 * 60 * 1000; +constexpr uint32_t kLinkWindowSize = 16; + +constexpr double kIntersectionRatio = 0.7; + +std::map<size_t, DpPsiOptions> dp_psi_params_map = { + {1 << 11, DpPsiOptions(0.9)}, {1 << 12, DpPsiOptions(0.9)}, + {1 << 13, DpPsiOptions(0.9)}, {1 << 14, DpPsiOptions(0.9)}, + {1 << 15, DpPsiOptions(0.9)}, {1 << 16, DpPsiOptions(0.9)}, + {1 << 17, DpPsiOptions(0.9)}, {1 << 18, DpPsiOptions(0.9)}, + {1 << 19, DpPsiOptions(0.9)}, {1 << 20, DpPsiOptions(0.995)}, + {1 << 21, DpPsiOptions(0.995)}, {1 << 22, DpPsiOptions(0.995)}, + {1 << 23, DpPsiOptions(0.995)}, {1 << 24, DpPsiOptions(0.995)}, + {1 << 25, DpPsiOptions(0.995)}, {1 << 26, DpPsiOptions(0.995)}, + {1 << 27, DpPsiOptions(0.995)}, {1 << 28, DpPsiOptions(0.995)}, + {1 << 29, DpPsiOptions(0.995)}, {1 << 30, DpPsiOptions(0.995)}}; + +} // namespace + +static void BM_DpPsi(benchmark::State& state) { + for (auto _ : state) { + state.PauseTiming(); + size_t items_size = state.range(0); + double payload_epsilon = state.range(1); + + std::vector<std::string> items_a = CreateRangeItems(0, items_size); + std::vector<std::string> items_b = + CreateRangeItems(items_size * (1 - kIntersectionRatio), items_size); + + std::vector<uint32_t> payloads_b = CreateItemsPayload(items_size); + + auto ctxs = CreateLinks(kLinkAddrAB); + + ctxs[0]->SetThrottleWindowSize(kLinkWindowSize); + ctxs[1]->SetThrottleWindowSize(kLinkWindowSize); + + ctxs[0]->SetRecvTimeout(kLinkRecvTimeout); + ctxs[1]->SetRecvTimeout(kLinkRecvTimeout); + + std::vector<std::string> real_intersection = + GetIntersection(items_a, items_b); + + const DpPsiOptions& options = dp_psi_params_map[items_size]; + + state.ResumeTiming(); + + size_t alice_rank = 0; + size_t bob_rank = 1; + + size_t alice_sub_sample_size; + size_t alice_up_sample_size; + size_t bob_sub_sample_size; + + std::future<size_t> f_dp_psi_a = std::async([&] { + return RunDpEcdhPsiAlice(options, ctxs[alice_rank], items_a, + &alice_sub_sample_size, &alice_up_sample_size); + }); + + std::future<std::vector<size_t>> f_dp_psi_b = std::async([&] { + return RunDpEcdhPsiBob(options, ctxs[bob_rank], items_b, + &bob_sub_sample_size); + }); + + size_t alice_intersection_size = f_dp_psi_a.get(); + std::vector<size_t> dp_psi_result = f_dp_psi_b.get(); + + SPDLOG_INFO( + "alice_intersection_size:{} " + "alice_sub_sample_size:{},alice_up_sample_size:{}", + alice_intersection_size, alice_sub_sample_size, alice_up_sample_size); + + SPDLOG_INFO( + "dp psi bob intersection size:{}, bob_sub_sample_size:{} " + "real_intersection size: {}", + dp_psi_result.size(), bob_sub_sample_size, real_intersection.size()); + + double mean; + double dp_mean; + double sigma; + PayloadMeanWithDp(options, dp_psi_result, payloads_b, &mean, &dp_mean, + &sigma, kPayloadMaxValue, payload_epsilon); + + SPDLOG_INFO("mean:{} dp_mean:{}, sigma:{}", mean, dp_mean, sigma); + + auto stats0 = ctxs[alice_rank]->GetStats(); + auto stats1 = ctxs[bob_rank]->GetStats(); + + double total_comm_bytes = stats0->sent_bytes + stats0->recv_bytes; + SPDLOG_INFO("bob: sent_bytes:{} recv_bytes:{} total_comm_bytes:{}", + stats1->sent_bytes.load(), stats1->recv_bytes.load(), + total_comm_bytes / 1024 / 1024); + } +} + +// [256k, 512k, 1m, 2m, 4m, 8m, 16m] +BENCHMARK(BM_DpPsi) + ->Unit(benchmark::kMillisecond) + ->Args({256 << 10, 3}) + ->Args({512 << 10, 3}) + ->Args({1 << 20, 3}) + ->Args({2 << 20, 3}) + ->Args({4 << 20, 3}) + ->Args({8 << 20, 3}) + ->Args({16 << 20, 3}) + ->Args({32 << 20, 3}) + ->Args({64 << 20, 3}) + ->Args({128 << 20, 3}) + ->Args({1 << 20, 1}) + ->Args({1 << 20, 2}) + ->Args({1 << 20, 3}) + ->Args({1 << 20, 4}) + ->Args({1 << 20, 5}); + +} // namespace psi::psi diff --git a/psi/psi/core/dp_psi/dp_psi_test.cc b/psi/psi/core/dp_psi/dp_psi_test.cc new file mode 100644 index 00000000..ad5b656d --- /dev/null +++ b/psi/psi/core/dp_psi/dp_psi_test.cc @@ -0,0 +1,143 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/dp_psi/dp_psi.h" + +#include <random> +#include <set> + +#include "absl/container/flat_hash_set.h" +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/link/test_util.h" + +namespace psi::psi { + +namespace { + +std::vector<std::string> CreateRangeItems(size_t begin, size_t size) { + std::vector<std::string> ret; + for (size_t i = 0; i < size; i++) { + ret.push_back(std::to_string(begin + i)); + } + return ret; +} + +std::vector<size_t> GetIntersectionIdx( + const std::vector<std::string>& items_a, + const std::vector<std::string>& items_b) { + absl::flat_hash_set<std::string> set(items_a.begin(), items_a.end()); + std::vector<size_t> ret; + for (size_t idx = 0; idx < items_b.size(); ++idx) { + if (set.count(items_b[idx]) != 0) { + ret.push_back(idx); + } + } + return ret; +} + +std::vector<std::string> GetIntersection( + const std::vector<std::string>& items_a, + const std::vector<std::string>& items_b) { + absl::flat_hash_set<std::string> set(items_a.begin(), items_a.end()); + std::vector<std::string> ret; + for (const auto& s : items_b) { + if (set.count(s) != 0) { + ret.push_back(s); + } + } + return ret; +} + +struct TestParams { + size_t items_size; + DpPsiOptions options; +}; + +constexpr double kIntersectionRatio = 0.7; + +} // namespace + +class DpPsiTest : public testing::TestWithParam<TestParams> {}; + +TEST_P(DpPsiTest, Works) { + const auto& param = GetParam(); + + SPDLOG_INFO("param.items_size: {}", param.items_size); + + size_t items_size = param.items_size; + + auto link_ctxs = yacl::link::test::SetupWorld(2); + + std::vector<std::string> items_a = CreateRangeItems(0, items_size); + std::vector<std::string> items_b = + CreateRangeItems(items_size * (1 - kIntersectionRatio), items_size); + + size_t alice_rank = 0; + size_t bob_rank = 1; + + size_t alice_sub_sample_size = 0; + size_t alice_up_sample_size = 0; + size_t bob_sub_sample_size = 0; + + std::future<size_t> f_dp_psi_a = std::async([&] { + return RunDpEcdhPsiAlice(param.options, link_ctxs[alice_rank], items_a, + &alice_sub_sample_size, &alice_up_sample_size); + }); + + std::future<std::vector<size_t>> f_dp_psi_b = std::async([&] { + return RunDpEcdhPsiBob(param.options, link_ctxs[bob_rank], items_b, + &bob_sub_sample_size); + }); + + size_t alice_intersection_size = f_dp_psi_a.get(); + std::vector<size_t> dp_psi_result = f_dp_psi_b.get(); + + EXPECT_EQ(alice_intersection_size, dp_psi_result.size()); + + SPDLOG_INFO( + "alice_intersection_size:{} " + "alice_sub_sample_size:{},alice_up_sample_size:{}", + alice_intersection_size, alice_sub_sample_size, alice_up_sample_size); + + SPDLOG_INFO("bob intersection size:{}, bob_sub_sample_size:{}", + dp_psi_result.size(), bob_sub_sample_size); + + std::vector<std::string> real_intersection = + GetIntersection(items_a, items_b); + std::vector<size_t> real_intersection_idx = + GetIntersectionIdx(items_a, items_b); + + SPDLOG_INFO("items_size: {} ,real_intersection.size(): {}", items_size, + real_intersection.size()); + + auto stats0 = link_ctxs[alice_rank]->GetStats(); + auto stats1 = link_ctxs[bob_rank]->GetStats(); + + double total_comm_bytes = stats0->sent_bytes + stats0->recv_bytes; + SPDLOG_INFO("bob: sent_bytes:{} recv_bytes:{}", stats1->sent_bytes.load(), + stats1->recv_bytes.load()); + + total_comm_bytes /= 1024 * 1024; + + SPDLOG_INFO("total_comm_bytes: {} MB", total_comm_bytes); +} + +INSTANTIATE_TEST_SUITE_P(Works_Instances, DpPsiTest, + testing::Values( // + TestParams{20, DpPsiOptions(0.8)} // dummy + ) // +); + +} // namespace psi::psi diff --git a/psi/psi/core/dp_psi/dp_psi_utils.cc b/psi/psi/core/dp_psi/dp_psi_utils.cc new file mode 100644 index 00000000..3a651198 --- /dev/null +++ b/psi/psi/core/dp_psi/dp_psi_utils.cc @@ -0,0 +1,172 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/dp_psi/dp_psi_utils.h" + +#include <cmath> +#include <functional> +#include <iomanip> +#include <iostream> +#include <utility> +#include <vector> + +namespace psi::psi { + +double ComputeEpsilon2(size_t n, double epsilon) { + double epsilon1; + double epsilon2_mid; + double epsilon2_min; + double epsilon2_max; + double err_epsilon; + epsilon2_min = 2.5; + epsilon2_max = kEpsilonPsi; + epsilon2_mid = epsilon2_min + (epsilon2_max - epsilon2_min) / 2; + + do { + epsilon1 = ComputeEpsilon1(n, epsilon2_mid); + + err_epsilon = epsilon - epsilon1 - epsilon2_mid; + + if (std::abs(err_epsilon) > kErrorRate) { + if (err_epsilon > 0) { + epsilon2_min = epsilon2_mid; + } else { + epsilon2_max = epsilon2_mid; + } + epsilon2_mid = epsilon2_min + (epsilon2_max - epsilon2_min) / 2; + } else { + break; + } + + } while (true); + + return epsilon2_mid; +} + +inline double ComputeDelta1(size_t n) { + double delta1; + delta1 = (10 * n); + delta1 = 1.0 / delta1; + + return delta1; +} + +double ComputeEpsilon1(size_t n, double epsilon2) { + double epsilon1; + double delta1 = ComputeDelta1(n); + double p_sub_keep = ComputePSubKeep(epsilon2); + + double m = + n * p_sub_keep - std::sqrt(2 * n * p_sub_keep * std::log(2 / delta1)); + + epsilon1 = + std::sqrt(32 * std::log(4 / delta1) / m) * (1 - (n * p_sub_keep - m) / n); + + return epsilon1; +} + +inline double Phi(double t) { + return 0.5 * (1.0 + std::erf(t / std::sqrt(2.0))); +} + +inline double caseA(double epsilon, double s) { + return Phi(std::sqrt(epsilon * s)) - + std::exp(epsilon) * Phi(-std::sqrt(epsilon * (s + 2.0))); +} + +inline double caseB(double epsilon, double s) { + return Phi(-std::sqrt(epsilon * s)) - + std::exp(epsilon) * Phi(-std::sqrt(epsilon * (s + 2.0))); +} + +std::pair<double, double> DoublingTrick( + const std::function<bool(double)>& predicate_stop, double s_inf, + double s_sup) { + while (!predicate_stop(s_sup)) { + s_inf = s_sup; + s_sup = 2.0 * s_inf; + } + return std::make_pair(s_inf, s_sup); +} + +double BinarySearch(const std::function<bool(double)>& predicate_stop, + const std::function<bool(double)>& predicate_left, + double s_inf, double s_sup) { + double s_mid = s_inf + (s_sup - s_inf) / 2.0; + while (!predicate_stop(s_mid)) { + if (predicate_left(s_mid)) { + s_sup = s_mid; + } else { + s_inf = s_mid; + } + s_mid = s_inf + (s_sup - s_inf) / 2.0; + } + + return s_mid; +} + +double CalibrateAnalyticGaussianMechanism(double epsilon, double delta, + double gs, double tol) { + double alpha; + double delta_thr = caseA(epsilon, 0.0); + const double EPSINON = 0.00001; + + if (std::abs(delta - delta_thr) < EPSINON) { + alpha = 1.0; + } else { + std::function<bool(double)> predicate_stop_DT; + std::function<bool(double)> predicate_stop_BS; + std::function<bool(double)> predicate_left_BS; + std::function<double(double)> function_s_to_alpha; + std::function<double(double)> function_s_to_delta; + + if (delta > delta_thr) { + predicate_stop_DT = [&](double s) { + return (caseA(epsilon, s) >= delta); + }; + function_s_to_delta = [&](double s) { return caseA(epsilon, s); }; + predicate_left_BS = [&](double s) { return (caseA(epsilon, s) > delta); }; + function_s_to_alpha = [&](double s) { + return (std::sqrt(1.0 + s / 2.0) - std::sqrt(s / 2.0)); + }; + } else { + predicate_stop_DT = [&](double s) { + return (caseB(epsilon, s) <= delta); + }; + function_s_to_delta = [&](double s) { return caseB(epsilon, s); }; + predicate_left_BS = [&](double s) { return (caseB(epsilon, s) < delta); }; + function_s_to_alpha = [&](double s) { + return (std::sqrt(1.0 + s / 2.0) + std::sqrt(s / 2.0)); + }; + } + + predicate_stop_BS = [&](double s) { + return (std::abs(function_s_to_delta(s) - delta) <= tol); + }; + + auto s_pair = DoublingTrick(predicate_stop_DT, 0.0, 1.0); + double s_inf = s_pair.first; + double s_sup = s_pair.second; + + double s_final = + BinarySearch(predicate_stop_BS, predicate_left_BS, s_inf, s_sup); + alpha = function_s_to_alpha(s_final); + } + + double sigma = alpha * gs / std::sqrt(2.0 * epsilon); + + return sigma; +} + +} // namespace psi::psi diff --git a/psi/psi/core/dp_psi/dp_psi_utils.h b/psi/psi/core/dp_psi/dp_psi_utils.h new file mode 100644 index 00000000..2b99785b --- /dev/null +++ b/psi/psi/core/dp_psi/dp_psi_utils.h @@ -0,0 +1,37 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <cmath> +#include <memory> +#include <string> + +namespace psi::psi { + +inline constexpr double kEpsilonPsi = 4; +inline constexpr double kErrorRate = 1.e-12; + +double ComputeEpsilon1(size_t n, double epsilon2); + +double ComputeEpsilon2(size_t n, double epsilon = kEpsilonPsi); + +inline double ComputePSubKeep(double epsilon2) { + double a = std::exp(epsilon2); + return a / (1 + a); +} + +double CalibrateAnalyticGaussianMechanism(double epsilon, double delta, + double GS, double tol = kErrorRate); +} // namespace psi::psi diff --git a/psi/psi/core/ecdh_3pc_psi.cc b/psi/psi/core/ecdh_3pc_psi.cc new file mode 100644 index 00000000..017f6faf --- /dev/null +++ b/psi/psi/core/ecdh_3pc_psi.cc @@ -0,0 +1,367 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/ecdh_3pc_psi.h" + +#include <algorithm> +#include <future> +#include <random> +#include <utility> + +#include "fmt/format.h" +#include "openssl/crypto.h" +#include "openssl/rand.h" +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" +#include "yacl/link/link.h" +#include "yacl/utils/serialize.h" + +#include "psi/psi/cryptor/cryptor_selector.h" + +namespace psi::psi { + +EcdhP2PExtendCtx::EcdhP2PExtendCtx(const EcdhPsiOptions& options) + : EcdhPsiContext(options) {} + +void EcdhP2PExtendCtx::MaskSendSelf( + const std::vector<std::string>& self_items) { + auto batch_provider = + std::make_shared<MemoryBatchProvider>(self_items, kEcdhPsiBatchSize); + + MaskSelf(batch_provider); +} + +void EcdhP2PExtendCtx::MaskRecvPeer( + std::vector<std::string>* dup_masked_peer_items) { + auto peer_ec_point_store = std::make_shared<MemoryEcPointStore>(); + + MaskPeer(peer_ec_point_store); + + *dup_masked_peer_items = peer_ec_point_store->content(); +} + +void EcdhP2PExtendCtx::MaskShufflePeer() { + std::vector<std::string> peer_items; + RecvItems(&peer_items); + + std::vector<std::string> dup_masked_items; + if (!peer_items.empty()) { + for (const auto& masked : Mask(options_.ecc_cryptor, peer_items)) { + dup_masked_items.emplace_back(masked.substr( + masked.length() - options_.dual_mask_size, options_.dual_mask_size)); + } + // shuffle x^a^b + std::sort(dup_masked_items.begin(), dup_masked_items.end()); + } + + SendDupMasked(dup_masked_items); +} + +void EcdhP2PExtendCtx::MaskPeerForward( + const std::shared_ptr<EcdhP2PExtendCtx>& forward_ctx, + int32_t truncation_size) { + size_t batch_count = 0; + while (true) { + std::vector<std::string> peer_items; + std::vector<std::string> dup_masked_items; + RecvBatch(&peer_items, batch_count); + if (!peer_items.empty()) { + for (auto& masked : Mask(options_.ecc_cryptor, peer_items)) { + if (truncation_size > 0) { + dup_masked_items.emplace_back(masked.substr( + masked.length() - truncation_size, truncation_size)); + } else { + dup_masked_items.emplace_back(std::move(masked)); + } + } + } + forward_ctx->ForwardBatch(dup_masked_items, batch_count); + if (peer_items.empty()) { + SPDLOG_INFO("MaskPeerForward:{} finished, batch_count={}", + options_.link_ctx->Id(), batch_count); + break; + } + batch_count++; + } +} + +void EcdhP2PExtendCtx::RecvDupMasked( + std::vector<std::string>* dup_masked_items) { + auto self_ec_point_store = std::make_shared<MemoryEcPointStore>(); + + RecvDualMaskedSelf(self_ec_point_store); + + *dup_masked_items = self_ec_point_store->content(); +} + +void EcdhP2PExtendCtx::SendDupMasked( + const std::vector<std::string>& dual_masked_items) { + SendImpl(dual_masked_items, true); +} + +void EcdhP2PExtendCtx::SendItems(const std::vector<std::string>& items) { + SendImpl(items, false); +} + +void EcdhP2PExtendCtx::RecvItems(std::vector<std::string>* items) { + size_t batch_count = 0; + while (true) { + std::vector<std::string> recv_batch_items; + RecvBatch(&recv_batch_items, batch_count); + for (auto& item : recv_batch_items) { + items->emplace_back(std::move(item)); + } + if (recv_batch_items.empty()) { + SPDLOG_INFO("{} recv last batch finished, batch_count={}", + options_.link_ctx->Id(), batch_count); + break; + } + batch_count++; + } +} + +void EcdhP2PExtendCtx::ForwardBatch(const std::vector<std::string>& batch_items, + int32_t batch_idx) { + SendBatch(batch_items, batch_idx); +} + +void EcdhP2PExtendCtx::SendImpl(const std::vector<std::string>& items, + bool dup_masked) { + size_t batch_count = 0; + size_t send_count = 0; + while (true) { + size_t curr_step_item_num = + std::min(kEcdhPsiBatchSize, items.size() - send_count); + std::vector<absl::string_view> batch_items; + for (size_t j = 0; j < curr_step_item_num; ++j) { + batch_items.emplace_back(items[send_count + j]); + } + + if (dup_masked) { + SendDualMaskedBatch(batch_items, batch_count); + } else { + SendBatch(batch_items, batch_count); + } + + if (curr_step_item_num == 0) { + SPDLOG_INFO("SendImpl:{}--finished, batch_count={}", + options_.link_ctx->Id(), batch_count); + break; + } + send_count += curr_step_item_num; + ++batch_count; + } +} + +// shuffled ecdh 3pc psi +ShuffleEcdh3PcPsi::ShuffleEcdh3PcPsi(Options options) + : options_(std::move(std::move(options))) { + YACL_ENFORCE(options_.link_ctx->WorldSize() == 3); + + private_key_.resize(kKeySize); + YACL_ENFORCE(RAND_bytes(private_key_.data(), kKeySize) == 1, + "Cannot create random private key"); + ecc_cryptor_ = CreateEccCryptor(options_.curve_type); + ecc_cryptor_->SetPrivateKey(absl::MakeSpan(private_key_)); +} + +void ShuffleEcdh3PcPsi::MaskMaster(const std::vector<std::string>& self_items, + std::vector<std::string>* masked_items) { + SPDLOG_INFO("MaskMaster:{} begin", options_.link_ctx->Rank()); + if (IsMaster()) { + // c + // - mask and send [c]-->a z^c + // - recv b-->[c] z^c^a^b + auto c_a_ctx = + CreateP2PCtx("MaskMaster", options_.link_ctx->NextRank(), + options_.dual_mask_size, options_.link_ctx->Rank()); + auto c_b_ctx = + CreateP2PCtx("MaskMaster", options_.link_ctx->PrevRank(), + options_.dual_mask_size, options_.link_ctx->Rank()); + + auto mask_send_self = + std::async([&] { return c_a_ctx->MaskSendSelf(self_items); }); + auto recv_triple_masked = + std::async([&] { return c_b_ctx->RecvItems(masked_items); }); + + mask_send_self.get(); + recv_triple_masked.get(); + + SPDLOG_INFO("MaskMaster:{} recv masked master items:{}", + options_.link_ctx->Rank(), masked_items->size()); + } else if (IsCalculator()) { + // a + // - c-->[a] recv z^c mask z^c^a then [a]-->b send z^c^a + auto a_c_ctx = + CreateP2PCtx("MaskMaster", options_.link_ctx->PrevRank(), + options_.dual_mask_size, options_.link_ctx->Rank()); + auto a_b_ctx = + CreateP2PCtx("MaskMaster", options_.link_ctx->NextRank(), + options_.dual_mask_size, options_.link_ctx->NextRank()); + a_c_ctx->MaskPeerForward(a_b_ctx); + } else /*IsPartner()*/ { + // b + // - a-->[b] recv z^c^a mask send [b]-->c z^c^a^b + auto b_a_ctx = + CreateP2PCtx("MaskMaster", options_.link_ctx->PrevRank(), + options_.dual_mask_size, options_.link_ctx->Rank()); + auto b_c_ctx = + CreateP2PCtx("MaskMaster", options_.link_ctx->NextRank(), + options_.dual_mask_size, options_.link_ctx->NextRank()); + b_a_ctx->MaskPeerForward(b_c_ctx, options_.dual_mask_size); + } +} + +void ShuffleEcdh3PcPsi::PartnersPsi(const std::vector<std::string>& self_items, + std::vector<std::string>* results) { + if (IsPartner()) { + std::vector<std::string> shuffle_inputs = self_items; + + std::random_device rd; + std::mt19937 rng(rd()); + std::shuffle(shuffle_inputs.begin(), shuffle_inputs.end(), rng); + + PartnersPsiImpl(shuffle_inputs, results); + } else { + PartnersPsiImpl(self_items, results); + } +} + +void ShuffleEcdh3PcPsi::PartnersPsiImpl( + const std::vector<std::string>& self_items, + std::vector<std::string>* results) { + SPDLOG_INFO("PartnersPsi:{} begin", options_.link_ctx->Rank()); + // a - b psi + if (IsCalculator()) { + std::vector<std::string> self_result; + std::vector<std::string> peer_result; + + auto context = + CreateP2PCtx("PartnersPsi", options_.link_ctx->NextRank(), + ecc_cryptor_->GetMaskLength(), options_.link_ctx->Rank()); + // check config + context->CheckConfig(); + std::future<void> f_mask_self = + std::async([&] { return context->MaskSendSelf(self_items); }); + std::future<void> f_mask_peer = + std::async([&] { return context->MaskRecvPeer(&peer_result); }); + std::future<void> f_recv_peer = + std::async([&] { return context->RecvDupMasked(&self_result); }); + + f_mask_self.get(); + f_mask_peer.get(); + f_recv_peer.get(); + + SPDLOG_INFO("PartnersPsi:{}--self_result:{}, peer_result:{}", + options_.link_ctx->Rank(), self_result.size(), + peer_result.size()); + + // calc intersection_ab + std::sort(self_result.begin(), self_result.end()); + std::sort(peer_result.begin(), peer_result.end()); + + std::vector<std::string> intersection; + std::set_intersection(self_result.begin(), self_result.end(), + peer_result.begin(), peer_result.end(), + std::back_inserter(intersection)); + // send to master + auto a_c_ctx = CreateP2PCtx("PartnersPsi", options_.master_rank, + options_.dual_mask_size, options_.master_rank); + a_c_ctx->SendItems(intersection); + + SPDLOG_INFO("PartnersPsi:{}--send to master_{}, intersection size:{}", + options_.link_ctx->Rank(), options_.master_rank, + intersection.size()); + + } else if (IsPartner()) { + auto context = CreateP2PCtx("PartnersPsi", options_.link_ctx->PrevRank(), + ecc_cryptor_->GetMaskLength(), + options_.link_ctx->PrevRank()); + // check config + context->CheckConfig(); + std::future<void> f_mask_self = + std::async([&] { return context->MaskSendSelf(self_items); }); + // shuffle x^a^b + std::future<void> f_mask_peer = + std::async([&] { return context->MaskShufflePeer(); }); + + f_mask_self.get(); + f_mask_peer.get(); + } else { + // c + // recv result + auto c_a_ctx = + CreateP2PCtx("PartnersPsi", options_.link_ctx->NextRank(), + ecc_cryptor_->GetMaskLength(), options_.link_ctx->Rank()); + c_a_ctx->RecvItems(results); + + SPDLOG_INFO("PartnersPsi:{}--recv partner psi items:{}", + options_.link_ctx->Rank(), results->size()); + } +} + +void ShuffleEcdh3PcPsi::FinalPsi( + const std::vector<std::string>& self_items, + const std::vector<std::string>& master_items, + const std::vector<std::string>& partners_result, + std::vector<std::string>* results) { + if (IsMaster()) { + std::vector<std::string> masked_partners_items; + for (const auto& masked : Mask(ecc_cryptor_, partners_result)) { + masked_partners_items.emplace_back(masked.substr( + masked.length() - options_.dual_mask_size, options_.dual_mask_size)); + } + + std::sort(masked_partners_items.begin(), masked_partners_items.end()); + + for (uint32_t index = 0; index < master_items.size(); index++) { + if (std::binary_search(masked_partners_items.begin(), + masked_partners_items.end(), + master_items[index])) { + YACL_ENFORCE(index < self_items.size()); + results->push_back(self_items[index]); + } + } + } +} + +std::shared_ptr<EcdhP2PExtendCtx> ShuffleEcdh3PcPsi::CreateP2PCtx( + const std::string& link_id_prefix, size_t dst_rank, size_t dual_mask_size, + size_t target_rank) { + EcdhPsiOptions opts; + opts.link_ctx = CreateP2PLinkCtx(link_id_prefix, options_.link_ctx, dst_rank); + opts.ecc_cryptor = ecc_cryptor_; + opts.dual_mask_size = dual_mask_size; + if (target_rank != yacl::link::kAllRank) { + YACL_ENFORCE(target_rank == options_.link_ctx->Rank() || + target_rank == dst_rank); + opts.target_rank = target_rank == dst_rank ? opts.link_ctx->NextRank() + : opts.link_ctx->Rank(); + } else { + opts.target_rank = target_rank; + } + + return std::make_shared<EcdhP2PExtendCtx>(opts); +} + +size_t ShuffleEcdh3PcPsi::GetPartnersPsiPeerRank() { + YACL_ENFORCE(!IsMaster()); + if (IsCalculator()) { + return options_.link_ctx->NextRank(); + } else { + return options_.link_ctx->PrevRank(); + } +} + +} // namespace psi::psi \ No newline at end of file diff --git a/psi/psi/core/ecdh_3pc_psi.h b/psi/psi/core/ecdh_3pc_psi.h new file mode 100644 index 00000000..a36befe9 --- /dev/null +++ b/psi/psi/core/ecdh_3pc_psi.h @@ -0,0 +1,162 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <memory> +#include <string> +#include <vector> + +#include "psi/psi/core/communication.h" +#include "psi/psi/core/ecdh_psi.h" + +namespace psi::psi { + +class EcdhP2PExtendCtx : public EcdhPsiContext { + public: + explicit EcdhP2PExtendCtx(const EcdhPsiOptions& options); + ~EcdhP2PExtendCtx() = default; + + // wrapper of `EcdhPsiContext::MaskSelf` + void MaskSendSelf(const std::vector<std::string>& self_items); + + // wrapper of `EcdhPsiContext::MaskRecvPeer` + void MaskRecvPeer(std::vector<std::string>* dup_masked_peer_items); + + // wrapper of `EcdhPsiContext::RecvDualMaskedSelf` + void RecvDupMasked(std::vector<std::string>* dup_masked_items); + + // recv peer masked items, mask them again then use `forward_ctx` to forward + void MaskPeerForward(const std::shared_ptr<EcdhP2PExtendCtx>& forward_ctx, + int32_t truncation_size = -1); + + // recv peer masked items, mask them again then shuffle and send them back + void MaskShufflePeer(); + + // send the duplicate masked items to peer + void SendDupMasked(const std::vector<std::string>& dual_masked_items); + + void SendItems(const std::vector<std::string>& items); + + void RecvItems(std::vector<std::string>* items); + + // internal + void ForwardBatch(const std::vector<std::string>& batch_items, + int32_t batch_idx); + + private: + void SendImpl(const std::vector<std::string>& items, bool dup_masked); +}; + +// +// 3party ecdh psi algorithm. +// +// master get the final intersection +// calculator role is master-link->NextRank() +// partner role is master-link->PrevRank() +// +// (calculator) (partner) (master) +// alice bob candy +// partners psi ============================================= +// | | shuffle b items | +// x^a | x^a | | +// | --------> | x^a^b | +// | | | +// | y^b | y^b | +// | <-------- | | +// y^b^a | | | +// | | shuffle x^a^b | +// | x^a^b | | +// | <-------- | | +// calc intersection_ab | | +// | intersection_ab | +// | ---------------------------------> | {intersection_ab}^c +// mask master ============================================== +// | z^c | +// | <--------------------------------- | +// z^c^a | | | +// | z^c^a | | +// | --------> | | +// | | calc {z^c^a}^b | +// | | send z^c^a^b | +// | | ------------------> | +// calc result ============================================== +// | | | +// calc intersection_abc +// +class ShuffleEcdh3PcPsi { + public: + struct Options { + // Provides the link for the rank world. + std::shared_ptr<yacl::link::Context> link_ctx; + + // master rank get final result + size_t master_rank; + + // batch_size + // batch compute dh mask + // batch send and read + size_t batch_size = kEcdhPsiBatchSize; + + size_t dual_mask_size = kFinalCompareBytes; + + // curve_type + CurveType curve_type = CurveType::CURVE_25519; + }; + + explicit ShuffleEcdh3PcPsi(Options options); + ~ShuffleEcdh3PcPsi() = default; + + // only master rank can get masked items + void MaskMaster(const std::vector<std::string>& self_items, + std::vector<std::string>* masked_items); + + // master / calculator can get results + void PartnersPsi(const std::vector<std::string>& self_items, + std::vector<std::string>* results); + + // only master rank can get results + void FinalPsi(const std::vector<std::string>& self_items, + const std::vector<std::string>& master_items, + const std::vector<std::string>& partners_result, + std::vector<std::string>* results); + + private: + std::shared_ptr<EcdhP2PExtendCtx> CreateP2PCtx( + const std::string& link_id_prefix, size_t dst_rank, size_t dual_mask_size, + size_t target_rank); + + size_t GetPartnersPsiPeerRank(); + + void PartnersPsiImpl(const std::vector<std::string>& self_items, + std::vector<std::string>* results); + + bool IsCalculator() { + return options_.link_ctx->PrevRank() == options_.master_rank; + } + + bool IsPartner() { + return options_.link_ctx->NextRank() == options_.master_rank; + } + + bool IsMaster() { return options_.link_ctx->Rank() == options_.master_rank; } + + Options options_; + + std::shared_ptr<IEccCryptor> ecc_cryptor_; + // curve 25519 dh private key, 32B + std::vector<uint8_t> private_key_; +}; + +} // namespace psi::psi diff --git a/psi/psi/core/ecdh_3pc_psi_bench.cc b/psi/psi/core/ecdh_3pc_psi_bench.cc new file mode 100644 index 00000000..4a3242e5 --- /dev/null +++ b/psi/psi/core/ecdh_3pc_psi_bench.cc @@ -0,0 +1,94 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <future> +#include <iostream> + +#include "benchmark/benchmark.h" +#include "yacl/base/exception.h" +#include "yacl/link/test_util.h" + +#include "psi/psi/core/ecdh_3pc_psi.h" +#include "psi/psi/utils/test_utils.h" + +static void BM_Ecdh3PcPsi(benchmark::State& state) { + for (auto _ : state) { + state.PauseTiming(); + size_t n = state.range(0); + auto alice_items = psi::psi::test::CreateRangeItems(1, n); + auto bob_items = psi::psi::test::CreateRangeItems(2, n); + auto carol_items = psi::psi::test::CreateRangeItems(3, n); + + auto contexts = yacl::link::test::SetupWorld(3); + + // simple runner + auto psi_func = + [&](const std::shared_ptr<psi::psi::ShuffleEcdh3PcPsi>& handler, + const std::vector<std::string>& items, + std::vector<std::string>* results) { + std::vector<std::string> masked_master_items; + std::vector<std::string> partner_psi_items; + + auto mask_master = std::async( + [&] { return handler->MaskMaster(items, &masked_master_items); }); + auto partner_psi = std::async( + [&] { return handler->PartnersPsi(items, &partner_psi_items); }); + + mask_master.get(); + partner_psi.get(); + + handler->FinalPsi(items, masked_master_items, partner_psi_items, + results); + }; + + state.ResumeTiming(); + + std::vector<std::string> alice_res; + std::vector<std::string> bob_res; + std::vector<std::string> carol_res; + auto alice_func = std::async([&]() { + psi::psi::ShuffleEcdh3PcPsi::Options opts; + opts.link_ctx = contexts[0]; + opts.master_rank = 0; + auto op = std::make_shared<psi::psi::ShuffleEcdh3PcPsi>(opts); + return psi_func(op, alice_items, &alice_res); + }); + auto bob_func = std::async([&]() { + psi::psi::ShuffleEcdh3PcPsi::Options opts; + opts.link_ctx = contexts[1]; + opts.master_rank = 0; + auto op = std::make_shared<psi::psi::ShuffleEcdh3PcPsi>(opts); + return psi_func(op, bob_items, &bob_res); + }); + auto carol_func = std::async([&]() { + psi::psi::ShuffleEcdh3PcPsi::Options opts; + opts.link_ctx = contexts[2]; + opts.master_rank = 0; + auto op = std::make_shared<psi::psi::ShuffleEcdh3PcPsi>(opts); + return psi_func(op, carol_items, &carol_res); + }); + + alice_func.get(); + bob_func.get(); + carol_func.get(); + } +} + +// [256k, 512k, 1m, 2m, 4m, 8m] +BENCHMARK(BM_Ecdh3PcPsi) + ->Unit(benchmark::kMillisecond) + ->Arg(256 << 10) + ->Arg(512 << 10) + ->Arg(1 << 20) + ->Arg(2 << 20); diff --git a/psi/psi/core/ecdh_3pc_psi_test.cc b/psi/psi/core/ecdh_3pc_psi_test.cc new file mode 100644 index 00000000..c22805ce --- /dev/null +++ b/psi/psi/core/ecdh_3pc_psi_test.cc @@ -0,0 +1,247 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/ecdh_3pc_psi.h" + +#include <future> +#include <iostream> +#include <random> +#include <vector> + +#include "absl/strings/str_split.h" +#include "gtest/gtest.h" +#include "yacl/base/exception.h" +#include "yacl/link/test_util.h" + +#include "psi/psi/utils/test_utils.h" + +struct TestParams { + std::vector<std::string> items_a; + std::vector<std::string> items_b; + std::vector<std::string> items_c; +}; + +namespace psi::psi::test { + +class Ecdh3PcPsiTest : public testing::TestWithParam<TestParams> {}; + +TEST_P(Ecdh3PcPsiTest, MaskMaster) { + auto params = GetParam(); + + auto link_abc = yacl::link::test::SetupWorld("abc", 3); + + size_t alice_rank = 1; + size_t bob_rank = 2; + size_t candy_rank = 0; + + size_t master_rank = alice_rank; + + std::shared_ptr<ShuffleEcdh3PcPsi> ecdh_3pc_psi_master; + std::shared_ptr<ShuffleEcdh3PcPsi> ecdh_3pc_psi_master_next; + std::shared_ptr<ShuffleEcdh3PcPsi> ecdh_3pc_psi_master_prev; + + ShuffleEcdh3PcPsi::Options opts; + opts.link_ctx = link_abc[alice_rank]; + opts.master_rank = master_rank; + ecdh_3pc_psi_master = std::make_shared<ShuffleEcdh3PcPsi>(opts); + + opts.link_ctx = link_abc[bob_rank]; + ecdh_3pc_psi_master_next = std::make_shared<ShuffleEcdh3PcPsi>(opts); + + opts.link_ctx = link_abc[candy_rank]; + ecdh_3pc_psi_master_prev = std::make_shared<ShuffleEcdh3PcPsi>(opts); + + std::vector<std::string> master_res; + std::vector<std::string> master_next_res; + std::vector<std::string> master_prev_res; + + auto master_runner = std::async([&] { + return ecdh_3pc_psi_master->MaskMaster(params.items_a, &master_res); + }); + auto master_next_runner = std::async([&] { + return ecdh_3pc_psi_master_next->MaskMaster(params.items_b, + &master_next_res); + }); + auto master_prev_runner = std::async([&] { + return ecdh_3pc_psi_master_prev->MaskMaster(params.items_c, + &master_prev_res); + }); + + master_next_runner.get(); + master_prev_runner.get(); + master_runner.get(); + + // check correctness + EXPECT_EQ(master_res.size(), params.items_a.size()); + EXPECT_EQ(master_next_res.size(), 0); + EXPECT_EQ(master_prev_res.size(), 0); +} + +TEST_P(Ecdh3PcPsiTest, PartnersPsi) { + auto params = GetParam(); + + auto link_abc = yacl::link::test::SetupWorld("abc", 3); + + size_t alice_rank = 1; + size_t bob_rank = 2; + size_t candy_rank = 0; + + size_t master_rank = alice_rank; + + auto intersection_std_bc = GetIntersection(params.items_b, params.items_c); + + std::shared_ptr<ShuffleEcdh3PcPsi> ecdh_3pc_psi_master; + std::shared_ptr<ShuffleEcdh3PcPsi> ecdh_3pc_psi_master_next; + std::shared_ptr<ShuffleEcdh3PcPsi> ecdh_3pc_psi_master_prev; + + ShuffleEcdh3PcPsi::Options opts; + opts.link_ctx = link_abc[alice_rank]; + opts.master_rank = master_rank; + ecdh_3pc_psi_master = std::make_shared<ShuffleEcdh3PcPsi>(opts); + + opts.link_ctx = link_abc[bob_rank]; + ecdh_3pc_psi_master_next = std::make_shared<ShuffleEcdh3PcPsi>(opts); + + opts.link_ctx = link_abc[candy_rank]; + ecdh_3pc_psi_master_prev = std::make_shared<ShuffleEcdh3PcPsi>(opts); + + std::vector<std::string> master_res; + std::vector<std::string> master_next_res; + std::vector<std::string> master_prev_res; + + auto master_runner = std::async([&] { + return ecdh_3pc_psi_master->PartnersPsi(params.items_a, &master_res); + }); + auto master_next_runner = std::async([&] { + return ecdh_3pc_psi_master_next->PartnersPsi(params.items_b, + &master_next_res); + }); + auto master_prev_runner = std::async([&] { + return ecdh_3pc_psi_master_prev->PartnersPsi(params.items_c, + &master_prev_res); + }); + + master_next_runner.get(); + master_prev_runner.get(); + master_runner.get(); + + // check correctness + EXPECT_EQ(master_res.size(), intersection_std_bc.size()); + EXPECT_EQ(master_next_res.size(), 0); + EXPECT_EQ(master_prev_res.size(), 0); +} + +TEST_P(Ecdh3PcPsiTest, Works) { + auto params = GetParam(); + + auto link_abc = yacl::link::test::SetupWorld("abc", 3); + + size_t alice_rank = 1; + size_t bob_rank = 2; + size_t candy_rank = 0; + + size_t master_rank = alice_rank; + + auto intersection_std_ab = GetIntersection(params.items_a, params.items_b); + auto intersection_std_bc = GetIntersection(params.items_b, params.items_c); + auto intersection_std_abc = + GetIntersection(intersection_std_ab, params.items_c); + + std::shared_ptr<ShuffleEcdh3PcPsi> ecdh_3pc_psi_master; + std::shared_ptr<ShuffleEcdh3PcPsi> ecdh_3pc_psi_master_next; + std::shared_ptr<ShuffleEcdh3PcPsi> ecdh_3pc_psi_master_prev; + + ShuffleEcdh3PcPsi::Options opts; + opts.link_ctx = link_abc[alice_rank]; + opts.master_rank = master_rank; + ecdh_3pc_psi_master = std::make_shared<ShuffleEcdh3PcPsi>(opts); + + opts.link_ctx = link_abc[bob_rank]; + ecdh_3pc_psi_master_next = std::make_shared<ShuffleEcdh3PcPsi>(opts); + + opts.link_ctx = link_abc[candy_rank]; + ecdh_3pc_psi_master_prev = std::make_shared<ShuffleEcdh3PcPsi>(opts); + + // simple runner + auto psi_func = [&](const std::shared_ptr<ShuffleEcdh3PcPsi>& handler, + const std::vector<std::string>& items, + std::vector<std::string>* results) { + std::vector<std::string> masked_master_items; + std::vector<std::string> partner_psi_items; + + auto mask_master = std::async( + [&] { return handler->MaskMaster(items, &masked_master_items); }); + auto partner_psi = std::async( + [&] { return handler->PartnersPsi(items, &partner_psi_items); }); + + mask_master.get(); + partner_psi.get(); + + handler->FinalPsi(items, masked_master_items, partner_psi_items, results); + }; + + std::vector<std::string> master_res; + std::vector<std::string> master_next_res; + std::vector<std::string> master_prev_res; + + auto master_runner = std::async( + [&] { psi_func(ecdh_3pc_psi_master, params.items_a, &master_res); }); + auto master_next_runner = std::async([&] { + psi_func(ecdh_3pc_psi_master_next, params.items_b, &master_next_res); + }); + auto master_prev_runner = std::async([&] { + psi_func(ecdh_3pc_psi_master_prev, params.items_c, &master_prev_res); + }); + + master_runner.get(); + master_next_runner.get(); + master_prev_runner.get(); + + // check correctness + EXPECT_EQ(master_res.size(), intersection_std_abc.size()); + EXPECT_EQ(master_next_res.size(), 0); + EXPECT_EQ(master_prev_res.size(), 0); +} + +INSTANTIATE_TEST_SUITE_P( + Works_Instances, Ecdh3PcPsiTest, + testing::Values( + TestParams{{"a", "b"}, {"b", "c"}, {"b", "d"}}, // + TestParams{{"a", "b"}, {"b", "c"}, {"b", "d"}}, // + + TestParams{{"a", "b"}, {"b", "c"}, {"c", "d"}}, // + // + TestParams{{"a", "b"}, {"c", "d"}, {"d", "e"}}, // + TestParams{{"a", "b"}, {"c", "d"}, {"e", "f"}}, // + + // + TestParams{{}, {"a"}, {}}, // + TestParams{{"a"}, {}, {}}, // + TestParams{{}, {}, {"a"}}, // + // + // less than one batch + TestParams{CreateRangeItems(0, 4095), CreateRangeItems(1, 4095), + CreateRangeItems(2, 4095)}, // + + // exactly one batch + TestParams{CreateRangeItems(0, 4096), CreateRangeItems(1, 4096), + CreateRangeItems(2, 4096)}, // + // more than one batch + TestParams{CreateRangeItems(0, 8193), CreateRangeItems(5, 8193), + CreateRangeItems(10, 8193)}, // + // + TestParams{{}, {}, {}} // + )); + +} // namespace psi::psi::test \ No newline at end of file diff --git a/psi/psi/core/ecdh_oprf/BUILD.bazel b/psi/psi/core/ecdh_oprf/BUILD.bazel new file mode 100644 index 00000000..9b7db976 --- /dev/null +++ b/psi/psi/core/ecdh_oprf/BUILD.bazel @@ -0,0 +1,84 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//bazel:psi.bzl", "psi_cc_library", "psi_cc_test") + +package(default_visibility = ["//visibility:public"]) + +psi_cc_library( + name = "ecdh_oprf", + srcs = ["ecdh_oprf.cc"], + hdrs = ["ecdh_oprf.h"], + # Openssl::libcrypto requires `dlopen`... + linkopts = ["-ldl"], + deps = [ + "//psi/psi/cryptor:ecc_cryptor", + "@com_github_openssl_openssl//:openssl", + "@com_google_absl//absl/types:span", + "@yacl//yacl/base:byte_container_view", + "@yacl//yacl/base:exception", + "@yacl//yacl/utils:parallel", + ], +) + +psi_cc_library( + name = "basic_ecdh_oprf", + srcs = ["basic_ecdh_oprf.cc"], + hdrs = ["basic_ecdh_oprf.h"], + defines = [ + "__LINUX__", + ] + select({ + "@bazel_tools//src/conditions:linux_x86_64": [ + "_AMD64_", + "_ASM_", + ], + "@bazel_tools//src/conditions:darwin_arm64": [ + "_ARM64_", + ], + "//conditions:default": [ + "_AMD64_", + ], + }), + deps = [ + ":ecdh_oprf", + "//psi/psi/cryptor:ecc_utils", + "//psi/psi/cryptor:sm2_cryptor", + "@com_github_microsoft_apsi//:apsi", + "@com_google_absl//absl/types:span", + "@yacl//yacl/base:exception", + "@yacl//yacl/crypto/base/hash:blake3", + "@yacl//yacl/crypto/base/hash:hash_utils", + "@yacl//yacl/utils:parallel", + ], +) + +psi_cc_library( + name = "ecdh_oprf_selector", + srcs = ["ecdh_oprf_selector.cc"], + hdrs = ["ecdh_oprf_selector.h"], + deps = [ + ":basic_ecdh_oprf", + "@yacl//yacl/utils:platform_utils", + ], +) + +psi_cc_test( + name = "basic_ecdh_oprf_test", + srcs = ["basic_ecdh_oprf_test.cc"], + deps = [ + ":ecdh_oprf_selector", + "@yacl//yacl/crypto/tools:prg", + "@yacl//yacl/crypto/utils:rand", + ], +) diff --git a/psi/psi/core/ecdh_oprf/basic_ecdh_oprf.cc b/psi/psi/core/ecdh_oprf/basic_ecdh_oprf.cc new file mode 100644 index 00000000..25d22638 --- /dev/null +++ b/psi/psi/core/ecdh_oprf/basic_ecdh_oprf.cc @@ -0,0 +1,422 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/ecdh_oprf/basic_ecdh_oprf.h" + +#include <string> +#include <vector> + +#include "absl/strings/escaping.h" +#include "apsi/fourq/FourQ_api.h" +#include "apsi/fourq/FourQ_internal.h" +#include "yacl/crypto/base/hash/blake3.h" +#include "yacl/crypto/base/hash/hash_utils.h" + +#include "psi/psi/cryptor/ecc_utils.h" + +namespace psi::psi { + +namespace { +// use 96bit as the final compare value +constexpr size_t kEc256CompareLength = 12; + +std::string HashItem(absl::string_view item, absl::string_view masked_item, + size_t hash_len, yacl::crypto::HashAlgorithm hash_type) { + std::unique_ptr<yacl::crypto::HashInterface> hash_algo; + switch (hash_type) { + case yacl::crypto::HashAlgorithm::BLAKE3: + hash_algo = std::make_unique<yacl::crypto::Blake3Hash>(); + break; + default: + hash_algo = std::make_unique<yacl::crypto::SslHash>(hash_type); + break; + } + + if (item.length() > 0) { + hash_algo->Update(item); + } + hash_algo->Update(masked_item); + std::vector<uint8_t> hash = hash_algo->CumulativeHash(); + + YACL_ENFORCE(hash_len <= hash.size()); + + std::string hash_str(hash_len, '\0'); + std::memcpy(hash_str.data(), hash.data(), hash_len); + + return hash_str; +} + +/** + * @brief do ec ponit mul + * + * @param sk_bytes private key data + * @param point_bytes compressed ec point data + * @param ec_group_nid ec group nid + * @return std::string compressed ec point data + */ +std::string EcPointMul(absl::string_view sk_bytes, + absl::string_view point_bytes, int ec_group_nid) { + BnCtxPtr bn_ctx(yacl::CheckNotNull(BN_CTX_new())); + EcGroupSt ec_group(ec_group_nid); + BigNumSt bn_sk; + + YACL_ENFORCE(sk_bytes.size() == kEccKeySize); + bn_sk.FromBytes(absl::string_view(static_cast<const char *>(sk_bytes.data()), + sk_bytes.length()), + ec_group.bn_n); + + EcPointSt ec_point(ec_group); + EC_POINT_oct2point(ec_group.get(), ec_point.get(), + reinterpret_cast<const uint8_t *>(point_bytes.data()), + point_bytes.length(), bn_ctx.get()); + + EcPointSt ec_point2 = ec_point.PointMul(ec_group, bn_sk); + + std::string masked_point_bytes(kEcPointCompressLength, '\0'); + ec_point2.ToBytes( + absl::MakeSpan(reinterpret_cast<uint8_t *>(masked_point_bytes.data()), + masked_point_bytes.size())); + + return masked_point_bytes; +} + +/** + * @brief do ec ponit mul + * + * @param sk_bytes private key data + * @param item_bytes input data, map to ec point internal + * @param ec_group_nid ec group nid + * @return std::string compressed ec point data + */ +std::string ItemMul(absl::string_view sk_bytes, absl::string_view item_bytes, + int ec_group_nid) { + BnCtxPtr bn_ctx(yacl::CheckNotNull(BN_CTX_new())); + EcGroupSt ec_group(ec_group_nid); + BigNumSt bn_sk; + + YACL_ENFORCE(sk_bytes.size() == kEccKeySize); + bn_sk.FromBytes(absl::string_view(static_cast<const char *>(sk_bytes.data()), + sk_bytes.length()), + ec_group.bn_n); + + EcPointSt ec_point = + EcPointSt::CreateEcPointByHashToCurve(item_bytes, ec_group); + + EcPointSt ec_point2 = ec_point.PointMul(ec_group, bn_sk); + std::string point_bytes(kEcPointCompressLength, '\0'); + ec_point2.ToBytes(absl::MakeSpan( + reinterpret_cast<uint8_t *>(point_bytes.data()), point_bytes.size())); + + return point_bytes; +} + +std::vector<uint8_t> EccPrivateKeyInv(int group_id, + yacl::ByteContainerView private_key) { + BnCtxPtr bn_ctx(yacl::CheckNotNull(BN_CTX_new())); + EcGroupSt ec_group(group_id); + BigNumSt bn_sk; + + bn_sk.FromBytes( + absl::string_view(reinterpret_cast<const char *>(private_key.data()), + kEccKeySize), + ec_group.bn_n); + + BigNumSt bn_sk_inv = bn_sk.Inverse(ec_group.bn_n); + + std::vector<uint8_t> sk_inv_bytes(kEccKeySize); + std::string sk_inv = bn_sk_inv.ToBytes(); + YACL_ENFORCE(sk_inv_bytes.size() == sk_inv.length()); + + std::memcpy(sk_inv_bytes.data(), sk_inv.data(), sk_inv.length()); + + return sk_inv_bytes; +} + +} // namespace + +std::string BasicEcdhOprfServer::Evaluate( + absl::string_view blinded_element) const { + return EcPointMul( + absl::string_view(reinterpret_cast<const char *>(&private_key_[0]), + kEccKeySize), + blinded_element, ec_group_nid_); +} + +std::string BasicEcdhOprfServer::FullEvaluate( + yacl::ByteContainerView input) const { + absl::string_view input_sv = absl::string_view( + reinterpret_cast<const char *>(input.data()), input.size()); + std::string point_bytes = ItemMul( + absl::string_view(reinterpret_cast<const char *>(&private_key_[0]), + kEccKeySize), + input_sv, ec_group_nid_); + + return HashItem(input_sv, point_bytes, GetCompareLength(), hash_type_); +} + +std::string BasicEcdhOprfServer::SimpleEvaluate( + yacl::ByteContainerView input) const { + absl::string_view input_sv = absl::string_view( + reinterpret_cast<const char *>(input.data()), input.size()); + + std::string point_bytes = ItemMul( + absl::string_view(reinterpret_cast<const char *>(&private_key_[0]), + kEccKeySize), + input_sv, ec_group_nid_); + + return HashItem(absl::string_view(), point_bytes, GetCompareLength(), + hash_type_); +} + +size_t BasicEcdhOprfServer::GetCompareLength() const { + if (compare_length_ != 0) { + return compare_length_; + } + + return kEc256CompareLength; +} + +size_t BasicEcdhOprfServer::GetEcPointLength() const { + return kEcPointCompressLength; +} + +BasicEcdhOprfClient::BasicEcdhOprfClient(CurveType type) : curve_type_(type) { + ec_group_nid_ = Sm2Cryptor::GetEcGroupId(curve_type_); + + sk_inv_ = EccPrivateKeyInv(ec_group_nid_, + absl::MakeSpan(&private_key_[0], kEccKeySize)); + (void)curve_type_; +} + +BasicEcdhOprfClient::BasicEcdhOprfClient(CurveType type, + yacl::ByteContainerView private_key) + : curve_type_(type) { + YACL_ENFORCE(private_key.size() == kEccKeySize); + std::memcpy(&private_key_[0], private_key.data(), private_key.size()); + + ec_group_nid_ = Sm2Cryptor::GetEcGroupId(curve_type_); + + sk_inv_ = EccPrivateKeyInv(ec_group_nid_, + absl::MakeSpan(&private_key_[0], kEccKeySize)); +} + +std::string BasicEcdhOprfClient::Blind(absl::string_view input) const { + return ItemMul( + absl::string_view(reinterpret_cast<const char *>(&private_key_[0]), + kEccKeySize), + input, ec_group_nid_); +} + +std::string BasicEcdhOprfClient::Finalize( + absl::string_view item, absl::string_view evaluated_element) const { + std::string unblinded_element = Unblind(evaluated_element); + + return HashItem(item, unblinded_element, GetCompareLength(), hash_type_); +} + +std::string BasicEcdhOprfClient::Finalize( + absl::string_view evaluated_element) const { + return Finalize(absl::string_view(), evaluated_element); +} + +std::string BasicEcdhOprfClient::Unblind(absl::string_view input) const { + return EcPointMul( + absl::string_view(reinterpret_cast<const char *>(sk_inv_.data()), + kEccKeySize), + input, ec_group_nid_); +} + +size_t BasicEcdhOprfClient::GetCompareLength() const { + if (compare_length_ != 0) { + return compare_length_; + } + + return kEc256CompareLength; +} + +size_t BasicEcdhOprfClient::GetEcPointLength() const { + return kEcPointCompressLength; +} + +// fourq +namespace { + +std::string FourQPointMul(absl::string_view sk_bytes, point_t point) { + point_t A; + + // clear_cofactor = 1 (TRUE) or 0 (FALSE) + // whether cofactor clearing is required or not, + // + bool status = ecc_mul(point, (digit_t *)sk_bytes.data(), A, false); + YACL_ENFORCE(status, "fourq ecc_mul error, status = {}", status); + + std::string masked_point_bytes(kEccKeySize, '\0'); + encode(A, reinterpret_cast<uint8_t *>(masked_point_bytes.data())); + + return masked_point_bytes; +} + +std::string FourQPointMul(absl::string_view sk_bytes, + absl::Span<const uint8_t> point_bytes) { + point_t A; + ECCRYPTO_STATUS status = ECCRYPTO_ERROR_UNKNOWN; + + if ((point_bytes[15] & 0x80) != 0) { // Is bit128(PublicKey) = 0? + status = ECCRYPTO_ERROR_INVALID_PARAMETER; + YACL_THROW("fourq invalid point status = {}", static_cast<int>(status)); + } + + // Also verifies that A is on the curve. If it is not, it fails + status = decode(point_bytes.data(), A); + YACL_ENFORCE(status == ECCRYPTO_SUCCESS, "fourq decode error, status={}", + static_cast<int>(status)); + + return FourQPointMul(sk_bytes, A); +} + +void FourQHashToCurvePoint(absl::string_view input, point_t pt) { + f2elm_t r; + + // blake3 hash + auto hash = yacl::crypto::Blake3(input); + + std::memcpy(r, hash.data(), hash.size() * sizeof(decltype(hash)::value_type)); + // Reduce r; note that this does not produce a perfectly uniform distribution + // modulo 2^127-1, but it is good enough. + mod1271(r[0]); + mod1271(r[1]); + + HashToCurve(r, pt); +} + +} // namespace + +std::string FourQBasicEcdhOprfServer::Evaluate( + absl::string_view blinded_element) const { + return FourQPointMul( + absl::string_view(reinterpret_cast<const char *>(&private_key_[0]), + kEccKeySize), + absl::MakeSpan(reinterpret_cast<const uint8_t *>(blinded_element.data()), + blinded_element.size())); +} + +std::string FourQBasicEcdhOprfServer::FullEvaluate( + yacl::ByteContainerView input) const { + point_t pt; + absl::string_view input_sv = absl::string_view( + reinterpret_cast<const char *>(input.data()), input.size()); + FourQHashToCurvePoint(input_sv, pt); + + std::string pt_mul_bytes = FourQPointMul( + absl::string_view(reinterpret_cast<const char *>(&private_key_[0]), + kEccKeySize), + pt); + + return HashItem(input_sv, pt_mul_bytes, GetCompareLength(), hash_type_); +} + +std::string FourQBasicEcdhOprfServer::SimpleEvaluate( + yacl::ByteContainerView input) const { + point_t pt; + absl::string_view input_sv = absl::string_view( + reinterpret_cast<const char *>(input.data()), input.size()); + FourQHashToCurvePoint(input_sv, pt); + + std::string pt_mul_bytes = FourQPointMul( + absl::string_view(reinterpret_cast<const char *>(&private_key_[0]), + kEccKeySize), + pt); + + return HashItem(absl::string_view(), pt_mul_bytes, GetCompareLength(), + hash_type_); +} + +size_t FourQBasicEcdhOprfServer::GetCompareLength() const { + if (compare_length_ != 0) { + return compare_length_; + } + return kEc256CompareLength; +} + +size_t FourQBasicEcdhOprfServer::GetEcPointLength() const { + return kEccKeySize; +} + +FourQBasicEcdhOprfClient::FourQBasicEcdhOprfClient() { + to_Montgomery(const_cast<digit_t *>( + reinterpret_cast<const digit_t *>(&private_key_[0])), + reinterpret_cast<digit_t *>(sk_inv_.data())); + Montgomery_inversion_mod_order(reinterpret_cast<digit_t *>(sk_inv_.data()), + reinterpret_cast<digit_t *>(sk_inv_.data())); + from_Montgomery(reinterpret_cast<digit_t *>(sk_inv_.data()), + reinterpret_cast<digit_t *>(sk_inv_.data())); +} + +FourQBasicEcdhOprfClient::FourQBasicEcdhOprfClient( + yacl::ByteContainerView private_key) { + YACL_ENFORCE(private_key.size() == kEccKeySize); + std::memcpy(&private_key_[0], private_key.data(), private_key.size()); + + to_Montgomery(const_cast<digit_t *>( + reinterpret_cast<const digit_t *>(&private_key_[0])), + reinterpret_cast<digit_t *>(sk_inv_.data())); + Montgomery_inversion_mod_order(reinterpret_cast<digit_t *>(sk_inv_.data()), + reinterpret_cast<digit_t *>(sk_inv_.data())); + from_Montgomery(reinterpret_cast<digit_t *>(sk_inv_.data()), + reinterpret_cast<digit_t *>(sk_inv_.data())); +} + +std::string FourQBasicEcdhOprfClient::Blind(absl::string_view input) const { + point_t pt; + FourQHashToCurvePoint(input, pt); + + return FourQPointMul( + absl::string_view(reinterpret_cast<const char *>(&private_key_[0]), + kEccKeySize), + pt); +} + +std::string FourQBasicEcdhOprfClient::Finalize( + absl::string_view item, absl::string_view evaluated_element) const { + std::string unblinded_element = Unblind(evaluated_element); + + return HashItem(item, unblinded_element, GetCompareLength(), hash_type_); +} + +std::string FourQBasicEcdhOprfClient::Finalize( + absl::string_view evaluated_element) const { + return Finalize(absl::string_view(), evaluated_element); +} + +std::string FourQBasicEcdhOprfClient::Unblind(absl::string_view input) const { + return FourQPointMul( + absl::string_view(reinterpret_cast<const char *>(sk_inv_.data()), + kEccKeySize), + absl::MakeSpan(reinterpret_cast<const uint8_t *>(input.data()), + input.size())); +} + +size_t FourQBasicEcdhOprfClient::GetCompareLength() const { + if (compare_length_ != 0) { + return compare_length_; + } + + return kEc256CompareLength; +} + +size_t FourQBasicEcdhOprfClient::GetEcPointLength() const { + return kEccKeySize; +} + +} // namespace psi::psi diff --git a/psi/psi/core/ecdh_oprf/basic_ecdh_oprf.h b/psi/psi/core/ecdh_oprf/basic_ecdh_oprf.h new file mode 100644 index 00000000..d43749a3 --- /dev/null +++ b/psi/psi/core/ecdh_oprf/basic_ecdh_oprf.h @@ -0,0 +1,180 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <array> +#include <memory> +#include <string> +#include <vector> + +#include "absl/types/span.h" +#include "openssl/crypto.h" +#include "openssl/rand.h" +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" +#include "yacl/crypto/base/hash/hash_interface.h" + +#include "psi/psi/core/ecdh_oprf/ecdh_oprf.h" +#include "psi/psi/cryptor/ecc_cryptor.h" +#include "psi/psi/cryptor/sm2_cryptor.h" + +// 2HashDH Oprf +// F_k(x) = H2(x, H1(x)^k) +// reference: JKK14 +// Round-optimal password-protected secret sharing and T-PAKE in the +// password-only model +// https://eprint.iacr.org/2014/650.pdf +// +// server private key: sk +// server H2(x, H1(x)^sk) +// client H2(y, (H1(y)^r)^sk^(1/r))=H2(y, H1(y)^sk) + +namespace psi::psi { + +class BasicEcdhOprfServer : public IEcdhOprfServer { + public: + BasicEcdhOprfServer() = default; + + /** + * @brief Construct a new Basic Ecdh Oprf Server object + * + * @param type support CurveSecp256k1/Sm2/FourQ + */ + explicit BasicEcdhOprfServer(CurveType type) + : curve_type_(type), ec_group_nid_(Sm2Cryptor::GetEcGroupId(type)) { + (void)curve_type_; + } + + BasicEcdhOprfServer(yacl::ByteContainerView private_key, CurveType type) + : IEcdhOprfServer(private_key), + curve_type_(type), + ec_group_nid_(Sm2Cryptor::GetEcGroupId(type)) {} + + ~BasicEcdhOprfServer() override = default; + + OprfType GetOprfType() const override { return OprfType::Basic; } + + std::string Evaluate(absl::string_view blinded_element) const override; + + std::string FullEvaluate(yacl::ByteContainerView input) const override; + std::string SimpleEvaluate(yacl::ByteContainerView input) const override; + + size_t GetCompareLength() const override; + size_t GetEcPointLength() const override; + + void SetHashType(yacl::crypto::HashAlgorithm hash_type) { + hash_type_ = hash_type; + } + + private: + CurveType curve_type_; + int ec_group_nid_{}; + yacl::crypto::HashAlgorithm hash_type_ = yacl::crypto::HashAlgorithm::BLAKE3; +}; + +class BasicEcdhOprfClient : public IEcdhOprfClient { + public: + explicit BasicEcdhOprfClient(CurveType type); + BasicEcdhOprfClient(CurveType type, yacl::ByteContainerView private_key); + BasicEcdhOprfClient(CurveType type, yacl::ByteContainerView private_key, + yacl::ByteContainerView private_key_inv); + + ~BasicEcdhOprfClient() override = default; + + OprfType GetOprfType() const override { return OprfType::Basic; } + + std::string Blind(absl::string_view input) const override; + + std::string Finalize(absl::string_view item, + absl::string_view evaluated_element) const override; + + std::string Finalize(absl::string_view evaluated_element) const override; + + size_t GetCompareLength() const override; + size_t GetEcPointLength() const override; + + std::string Unblind(absl::string_view input) const override; + + void SetHashType(yacl::crypto::HashAlgorithm hash_type) { + hash_type_ = hash_type; + } + + private: + CurveType curve_type_; + int ec_group_nid_; + + std::vector<uint8_t> sk_inv_; + + yacl::crypto::HashAlgorithm hash_type_ = yacl::crypto::HashAlgorithm::BLAKE3; +}; + +class FourQBasicEcdhOprfServer : public IEcdhOprfServer { + public: + FourQBasicEcdhOprfServer() = default; + + explicit FourQBasicEcdhOprfServer(yacl::ByteContainerView private_key) + : IEcdhOprfServer(private_key) {} + + ~FourQBasicEcdhOprfServer() override = default; + + OprfType GetOprfType() const override { return OprfType::Basic; } + + std::string Evaluate(absl::string_view blinded_element) const override; + + std::string FullEvaluate(yacl::ByteContainerView input) const override; + std::string SimpleEvaluate(yacl::ByteContainerView input) const override; + + size_t GetCompareLength() const override; + size_t GetEcPointLength() const override; + + void SetHashType(yacl::crypto::HashAlgorithm hash_type) { + hash_type_ = hash_type; + } + + private: + yacl::crypto::HashAlgorithm hash_type_ = yacl::crypto::HashAlgorithm::BLAKE3; +}; + +class FourQBasicEcdhOprfClient : public IEcdhOprfClient { + public: + FourQBasicEcdhOprfClient(); + explicit FourQBasicEcdhOprfClient(yacl::ByteContainerView private_key); + + ~FourQBasicEcdhOprfClient() override = default; + + OprfType GetOprfType() const override { return OprfType::Basic; } + + std::string Blind(absl::string_view input) const override; + + std::string Finalize(absl::string_view item, + absl::string_view evaluated_element) const override; + + std::string Finalize(absl::string_view evaluated_element) const override; + + size_t GetCompareLength() const override; + size_t GetEcPointLength() const override; + + std::string Unblind(absl::string_view input) const override; + + void SetHashType(yacl::crypto::HashAlgorithm hash_type) { + hash_type_ = hash_type; + } + + private: + std::array<uint8_t, kEccKeySize> sk_inv_; + yacl::crypto::HashAlgorithm hash_type_ = yacl::crypto::HashAlgorithm::BLAKE3; +}; + +} // namespace psi::psi diff --git a/psi/psi/core/ecdh_oprf/basic_ecdh_oprf_test.cc b/psi/psi/core/ecdh_oprf/basic_ecdh_oprf_test.cc new file mode 100644 index 00000000..9d0dc62f --- /dev/null +++ b/psi/psi/core/ecdh_oprf/basic_ecdh_oprf_test.cc @@ -0,0 +1,92 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/ecdh_oprf/basic_ecdh_oprf.h" + +#include <future> +#include <iostream> +#include <random> +#include <string> +#include <vector> + +#include "absl/strings/escaping.h" +#include "absl/strings/str_split.h" +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" +#include "yacl/crypto/tools/prg.h" +#include "yacl/crypto/utils/rand.h" + +#include "psi/psi/core/ecdh_oprf/ecdh_oprf_selector.h" + +namespace psi::psi { +struct TestParams { + size_t items_size; + CurveType type = CurveType::CURVE_SECP256K1; +}; + +class BasicEcdhOprfTest : public ::testing::TestWithParam<TestParams> {}; + +TEST_P(BasicEcdhOprfTest, Works) { + auto params = GetParam(); + + yacl::crypto::Prg<uint64_t> prg(yacl::crypto::SecureRandU64()); + + std::shared_ptr<IEcdhOprfServer> dh_oprf_server = + CreateEcdhOprfServer(OprfType::Basic, params.type); + + std::vector<uint8_t> client_sk(kEccKeySize); + prg.Fill(absl::MakeSpan(client_sk)); + + std::shared_ptr<IEcdhOprfClient> dh_oprf_client = + CreateEcdhOprfClient(client_sk, OprfType::Basic, params.type); + + std::vector<std::string> items_vec(params.items_size); + + for (size_t idx = 0; idx < params.items_size; ++idx) { + items_vec[idx].resize(kEccKeySize); + prg.Fill(absl::MakeSpan(items_vec[idx])); + } + + std::string server_evaluted = dh_oprf_server->FullEvaluate(items_vec[0]); + + std::string blinded_item = dh_oprf_client->Blind(items_vec[0]); + std::string mask_item = dh_oprf_server->Evaluate(blinded_item); + std::string client_evaluted = + dh_oprf_client->Finalize(items_vec[0], mask_item); + + EXPECT_EQ(server_evaluted, client_evaluted); + + std::vector<std::string> server_evaluted_vec = + dh_oprf_server->FullEvaluate(items_vec); + std::vector<std::string> blinded_item_vec = dh_oprf_client->Blind(items_vec); + + std::vector<std::string> mask_item_vec = + dh_oprf_server->Evaluate(blinded_item_vec); + + std::vector<std::string> client_evaluted_vec = + dh_oprf_client->Finalize(items_vec, mask_item_vec); + + EXPECT_EQ(server_evaluted_vec, client_evaluted_vec); +} + +INSTANTIATE_TEST_SUITE_P( + Works_Instances, BasicEcdhOprfTest, + testing::Values(TestParams{1}, TestParams{10}, TestParams{50}, + // fourq + TestParams{1, CurveType::CURVE_FOURQ}, + TestParams{10, CurveType::CURVE_FOURQ}, + TestParams{50, CurveType::CURVE_FOURQ})); + +} // namespace psi::psi diff --git a/psi/psi/core/ecdh_oprf/ecdh_oprf.cc b/psi/psi/core/ecdh_oprf/ecdh_oprf.cc new file mode 100644 index 00000000..ce14b9e5 --- /dev/null +++ b/psi/psi/core/ecdh_oprf/ecdh_oprf.cc @@ -0,0 +1,94 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/ecdh_oprf/ecdh_oprf.h" + +#include <algorithm> +#include <string> +#include <vector> + +#include "yacl/utils/parallel.h" + +namespace psi::psi { + +std::vector<std::string> IEcdhOprfServer::Evaluate( + absl::Span<const std::string> blinded_elements) const { + std::vector<std::string> evaluated_elements(blinded_elements.size()); + + yacl::parallel_for( + 0, blinded_elements.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + evaluated_elements[idx] = Evaluate(blinded_elements[idx]); + } + }); + + return evaluated_elements; +} + +std::vector<std::string> IEcdhOprfServer::FullEvaluate( + absl::Span<const std::string> input) const { + std::vector<std::string> output(input.size()); + + yacl::parallel_for(0, input.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + output[idx] = FullEvaluate(input[idx]); + } + }); + + return output; +} + +std::vector<std::string> IEcdhOprfClient::Blind( + absl::Span<const std::string> input) const { + std::vector<std::string> blinded_elements(input.size()); + + yacl::parallel_for(0, input.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + blinded_elements[idx] = Blind(input[idx]); + } + }); + + return blinded_elements; +} + +std::vector<std::string> IEcdhOprfClient::Finalize( + absl::Span<const std::string> items, + absl::Span<const std::string> evaluated_elements) const { + std::vector<std::string> output(evaluated_elements.size()); + + yacl::parallel_for( + 0, evaluated_elements.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + output[idx] = Finalize(items[idx], evaluated_elements[idx]); + } + }); + + return output; +} + +std::vector<std::string> IEcdhOprfClient::Finalize( + absl::Span<const std::string> evaluated_elements) const { + std::vector<std::string> output(evaluated_elements.size()); + + yacl::parallel_for(0, evaluated_elements.size(), 1, + [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + output[idx] = Finalize(evaluated_elements[idx]); + } + }); + + return output; +} + +} // namespace psi::psi diff --git a/psi/psi/core/ecdh_oprf/ecdh_oprf.h b/psi/psi/core/ecdh_oprf/ecdh_oprf.h new file mode 100644 index 00000000..57318927 --- /dev/null +++ b/psi/psi/core/ecdh_oprf/ecdh_oprf.h @@ -0,0 +1,195 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <array> +#include <memory> +#include <string> +#include <vector> + +#include "absl/strings/escaping.h" +#include "absl/types/span.h" +#include "openssl/crypto.h" +#include "openssl/rand.h" +#include "spdlog/spdlog.h" +#include "yacl/base/byte_container_view.h" +#include "yacl/base/exception.h" + +#include "psi/psi/cryptor/ecc_cryptor.h" + +namespace psi::psi { + +enum class OprfType { + Basic, + // ToDo add this type support + // RfcVOprf, +}; + +// reference: +// voprf rfc draft: +// https://datatracker.ietf.org/doc/draft-irtf-cfrg-voprf/ 3.3 Context API +// +// [TCRSTW21] "A Fast and Simple Partially Oblivious PRF, with Applications", +// <https://eprint.iacr.org/2021/864>. + +// +// Client(input) Server(skS) +// ---------------------------------------------------------------------- +// blindedElement = Blind(input) +// blindedElement +// ----------> +// +// evaluatedElement = Evaluate(skS, blindedElement) +// +// evaluatedElement +// <---------- +// +// output = Finalize(input, evaluatedElement) +// + +class IEcdhOprf { + public: + IEcdhOprf() { + YACL_ENFORCE(RAND_bytes(&private_key_[0], kEccKeySize) == 1, + "Cannot create random private key"); + } + + virtual ~IEcdhOprf() { OPENSSL_cleanse(&private_key_[0], kEccKeySize); } + + virtual OprfType GetOprfType() const = 0; + + virtual size_t GetCompareLength() const { + if (compare_length_) { + return compare_length_; + } + return kEccKeySize; + } + + virtual size_t GetEcPointLength() const { return kEccKeySize; } + + void SetCompareLength(size_t compare_length) { + YACL_ENFORCE(compare_length <= kEccKeySize); + compare_length_ = compare_length; + } + + void SetPrivateKey(yacl::ByteContainerView private_key) { + YACL_ENFORCE(private_key.size() == kEccKeySize); + + std::memcpy(private_key_, private_key.data(), private_key.size()); + } + + protected: + uint8_t private_key_[kEccKeySize]; + size_t compare_length_ = 0; +}; + +class IEcdhOprfServer : public IEcdhOprf { + public: + IEcdhOprfServer() = default; + // set private_key + explicit IEcdhOprfServer(yacl::ByteContainerView private_key) { + SetPrivateKey(private_key); + } + + ~IEcdhOprfServer() override = default; + + /** + * @brief Evaluate takes serialized representations of blinded group elements + * from the client as inputs + * + * @param blinded_element blinded data masked by client's temp private key + * @return std::string mask blinded data with server's private key + */ + virtual std::string Evaluate(absl::string_view blinded_element) const = 0; + + virtual std::vector<std::string> Evaluate( + absl::Span<const std::string> blinded_element) const; + + /** + * @brief FullEvaluate takes input values, and it is useful for applications + * that need to compute the whole OPRF protocol on the server side only. + * + * @param input server's input data + * @return std::string H2(x,H1(x)^sk) + */ + virtual std::string FullEvaluate(yacl::ByteContainerView input) const = 0; + + /** + * @brief SimpleEvaluate takes input values, and it is useful for applications + * that need to compute the whole OPRF protocol on the server side only. + * + * @param input server's input data + * @return std::string H2(H1(x)^sk) + */ + virtual std::string SimpleEvaluate(yacl::ByteContainerView input) const = 0; + + virtual std::vector<std::string> FullEvaluate( + absl::Span<const std::string> input) const; + + virtual std::array<uint8_t, kEccKeySize> GetPrivateKey() const { + std::array<uint8_t, kEccKeySize> key_array{}; + std::memcpy(key_array.data(), &private_key_[0], kEccKeySize); + return key_array; + } +}; + +class IEcdhOprfClient : public IEcdhOprf { + public: + IEcdhOprfClient() = default; + + ~IEcdhOprfClient() override = default; + + // + /** + * @brief Blind the input, use client temp private key + * + * @param input client input data + * @return std::string blinded data + */ + virtual std::string Blind(absl::string_view input) const = 0; + + virtual std::vector<std::string> Blind( + absl::Span<const std::string> input) const; + + virtual std::string Unblind(absl::string_view input) const = 0; + + /** + * @brief unblind evaluated_element, and hash + * + * @param item client input data + * @param evaluated_element + * @return std::string masked data with server's private key + */ + virtual std::string Finalize(absl::string_view item, + absl::string_view evaluated_element) const = 0; + + virtual std::string Finalize(absl::string_view evaluated_element) const = 0; + + /** + * @brief unblind evaluated_element, and hash + * + * @param item client input data + * @param evaluated_element + * @return std::string masked data with server's private key + */ + virtual std::vector<std::string> Finalize( + absl::Span<const std::string> items, + absl::Span<const std::string> evaluated_element) const; + + virtual std::vector<std::string> Finalize( + absl::Span<const std::string> evaluated_element) const; +}; + +} // namespace psi::psi diff --git a/psi/psi/core/ecdh_oprf/ecdh_oprf_selector.cc b/psi/psi/core/ecdh_oprf/ecdh_oprf_selector.cc new file mode 100644 index 00000000..88cc2f2e --- /dev/null +++ b/psi/psi/core/ecdh_oprf/ecdh_oprf_selector.cc @@ -0,0 +1,184 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/ecdh_oprf/ecdh_oprf_selector.h" + +#include "yacl/utils/platform_utils.h" + +#include "psi/psi/core/ecdh_oprf/basic_ecdh_oprf.h" +#include "psi/psi/core/ecdh_oprf/ecdh_oprf.h" + +namespace psi::psi { + +std::unique_ptr<IEcdhOprfServer> CreateEcdhOprfServer( + yacl::ByteContainerView private_key, OprfType oprf_type, + CurveType curve_type) { + std::unique_ptr<IEcdhOprfServer> server; + + switch (oprf_type) { + case OprfType::Basic: { + switch (curve_type) { + case CurveType::CURVE_FOURQ: { + SPDLOG_INFO("use fourq"); +#ifdef __x86_64__ + if (yacl::hasAVX2()) { +#endif + server = std::make_unique<FourQBasicEcdhOprfServer>(private_key); +#ifdef __x86_64__ + } +#endif + break; + } + case CurveType::CURVE_SECP256K1: + [[fallthrough]]; + case CurveType::CURVE_SM2: { + SPDLOG_INFO("use curve sm2/secp256k1"); + server = + std::make_unique<BasicEcdhOprfServer>(private_key, curve_type); + break; + } + default: + YACL_THROW("unknown support Curve type: {}", + static_cast<int>(curve_type)); + break; + } + + break; + } + default: + YACL_THROW("unknown Oprf type: {}", static_cast<int>(oprf_type)); + break; + } + YACL_ENFORCE(server != nullptr, "EcdhOprfServer should not be nullptr"); + + return server; +} + +std::unique_ptr<IEcdhOprfServer> CreateEcdhOprfServer(OprfType oprf_type, + CurveType curve_type) { + std::unique_ptr<IEcdhOprfServer> server; + + switch (oprf_type) { + case OprfType::Basic: { + switch (curve_type) { + case CurveType::CURVE_FOURQ: { + SPDLOG_INFO("use fourq"); +#ifdef __x86_64__ + if (yacl::hasAVX2()) { +#endif + server = std::make_unique<FourQBasicEcdhOprfServer>(); +#ifdef __x86_64__ + } +#endif + break; + } + case CurveType::CURVE_SECP256K1: + [[fallthrough]]; + case CurveType::CURVE_SM2: { + SPDLOG_INFO("use curve sm2/secp256k1"); + server = std::make_unique<BasicEcdhOprfServer>(curve_type); + break; + } + default: + YACL_THROW("unknown support Curve type: {}", + static_cast<int>(curve_type)); + break; + } + break; + } + default: + YACL_THROW("unknown Oprf type: {}", static_cast<int>(oprf_type)); + } + YACL_ENFORCE(server != nullptr, "EcdhOprfServer should not be nullptr"); + + return server; +} + +std::unique_ptr<IEcdhOprfClient> CreateEcdhOprfClient(OprfType oprf_type, + CurveType curve_type) { + std::unique_ptr<IEcdhOprfClient> client; + + switch (oprf_type) { + case OprfType::Basic: { + switch (curve_type) { + case CurveType::CURVE_FOURQ: { +#ifdef __x86_64__ + if (yacl::hasAVX2()) { +#endif + client = std::make_unique<FourQBasicEcdhOprfClient>(); +#ifdef __x86_64__ + } +#endif + break; + } + case CurveType::CURVE_SECP256K1: + [[fallthrough]]; + case CurveType::CURVE_SM2: { + client = std::make_unique<BasicEcdhOprfClient>(curve_type); + break; + } + default: + YACL_THROW("unknown support Curve type: {}", + static_cast<int>(curve_type)); + break; + } + break; + } + } + + YACL_ENFORCE(client != nullptr, "EcdhOprfClient should not be nullptr"); + + return client; +} + +std::unique_ptr<IEcdhOprfClient> CreateEcdhOprfClient( + yacl::ByteContainerView private_key, OprfType oprf_type, + CurveType curve_type) { + std::unique_ptr<IEcdhOprfClient> client; + + switch (oprf_type) { + case OprfType::Basic: { + switch (curve_type) { + case CurveType::CURVE_FOURQ: { +#ifdef __x86_64__ + if (yacl::hasAVX2()) { +#endif + client = std::make_unique<FourQBasicEcdhOprfClient>(private_key); +#ifdef __x86_64__ + } +#endif + break; + } + case CurveType::CURVE_SECP256K1: + [[fallthrough]]; + case CurveType::CURVE_SM2: { + client = + std::make_unique<BasicEcdhOprfClient>(curve_type, private_key); + break; + } + default: + YACL_THROW("unknown support Curve type: {}", + static_cast<int>(curve_type)); + break; + } + break; + } + } + + YACL_ENFORCE(client != nullptr, "EcdhOprfClient should not be nullptr"); + + return client; +} + +} // namespace psi::psi diff --git a/psi/psi/core/ecdh_oprf/ecdh_oprf_selector.h b/psi/psi/core/ecdh_oprf/ecdh_oprf_selector.h new file mode 100644 index 00000000..59050d6f --- /dev/null +++ b/psi/psi/core/ecdh_oprf/ecdh_oprf_selector.h @@ -0,0 +1,37 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <memory> + +#include "psi/psi/core/ecdh_oprf/ecdh_oprf.h" + +namespace psi::psi { + +std::unique_ptr<IEcdhOprfServer> CreateEcdhOprfServer( + yacl::ByteContainerView private_key, OprfType oprf_type, + CurveType curve_type); + +std::unique_ptr<IEcdhOprfServer> CreateEcdhOprfServer(OprfType oprf_type, + CurveType curve_type); + +std::unique_ptr<IEcdhOprfClient> CreateEcdhOprfClient(OprfType oprf_type, + CurveType curve_type); + +std::unique_ptr<IEcdhOprfClient> CreateEcdhOprfClient( + yacl::ByteContainerView private_key, OprfType oprf_type, + CurveType curve_type); + +} // namespace psi::psi diff --git a/psi/psi/core/ecdh_oprf_psi.cc b/psi/psi/core/ecdh_oprf_psi.cc new file mode 100644 index 00000000..3aed7eff --- /dev/null +++ b/psi/psi/core/ecdh_oprf_psi.cc @@ -0,0 +1,638 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/ecdh_oprf_psi.h" + +#include <omp.h> + +#include <algorithm> +#include <chrono> +#include <future> +#include <iterator> +#include <unordered_set> +#include <utility> + +#include "absl/strings/escaping.h" +#include "yacl/base/exception.h" +#include "yacl/utils/parallel.h" +#include "yacl/utils/serialize.h" + +#include "psi/psi/core/communication.h" +#include "psi/psi/cryptor/ecc_utils.h" +#include "psi/psi/utils/ub_psi_cache.h" + +namespace psi::psi { + +size_t EcdhOprfPsiServer::FullEvaluateAndSend( + const std::shared_ptr<IShuffledBatchProvider>& batch_provider, + const std::shared_ptr<IUbPsiCache>& ub_cache) { + return FullEvaluate(batch_provider, ub_cache, true); +} + +size_t EcdhOprfPsiServer::SendFinalEvaluatedItems( + const std::shared_ptr<IBasicBatchProvider>& batch_provider) { + size_t items_count = 0; + size_t batch_count = 0; + + size_t compare_length = oprf_server_->GetCompareLength(); + + while (true) { + PsiDataBatch batch; + auto items = batch_provider->ReadNextBatch(); + batch.is_last_batch = items.empty(); + + if (!batch.is_last_batch) { + batch.flatten_bytes.reserve(items.size() * compare_length); + + for (const auto& item : items) { + batch.flatten_bytes.append(item); + } + } + + // Send x^a. + const auto tag = + fmt::format("EcdhOprfPSI:FinalEvaluatedItems:{}", batch_count); + options_.link0->SendAsyncThrottled(options_.link0->NextRank(), + batch.Serialize(), tag); + + if (batch.is_last_batch) { + SPDLOG_INFO("{} Last batch triggered, batch_count={}", __func__, + batch_count); + break; + } + + items_count += items.size(); + batch_count++; + } + + SPDLOG_INFO("{} finished, batch_count={}", __func__, batch_count); + + return items_count; +} + +size_t EcdhOprfPsiServer::FullEvaluate( + const std::shared_ptr<IShuffledBatchProvider>& batch_provider, + const std::shared_ptr<IUbPsiCache>& ub_cache, bool send_flag) { + size_t items_count = 0; + size_t batch_count = 1; + size_t compare_length = oprf_server_->GetCompareLength(); + + bool stop_flag = false; + omp_lock_t lck_read, lck_send; + + omp_init_lock(&lck_read); + omp_init_lock(&lck_send); + + int tid; + batch_count = 0; + std::vector<std::string> batch_items; + std::vector<size_t> batch_indices; + std::vector<size_t> shuffle_indices; + PsiDataBatch batch; + size_t i; + size_t local_batch_count; + int nthreads = omp_get_num_threads(); + int mcpus = omp_get_num_procs(); + SPDLOG_INFO("omp_get_num_threads:{} cpus:{}", nthreads, mcpus); + omp_set_num_threads(mcpus); + +#pragma omp parallel private(tid, nthreads, i, batch_items, batch_indices, \ + shuffle_indices, batch, local_batch_count) \ + shared(lck_read, lck_send, batch_count, items_count, compare_length, \ + stop_flag) + { + tid = omp_get_thread_num(); + if ((tid == 0) && (batch_count == 0)) { + nthreads = omp_get_num_threads(); + SPDLOG_INFO("tid:{} omp_get_num_threads:{}", tid, nthreads); + } + while (!stop_flag) { + omp_set_lock(&lck_read); + + if (stop_flag) { + omp_unset_lock(&lck_read); + break; + } + std::tie(batch_items, batch_indices, shuffle_indices) = + batch_provider->ReadNextShuffledBatch(); + + batch.is_last_batch = batch_items.empty(); + + if (batch_items.empty()) { + stop_flag = true; + } else { + items_count += batch_items.size(); + batch_count++; + if ((batch_count % 1000) == 0) { + SPDLOG_INFO("batch_count:{}", batch_count); + } + local_batch_count = batch_count; + } + + omp_unset_lock(&lck_read); + + if (batch_items.empty()) { + break; + } + + batch.flatten_bytes.reserve(batch_items.size() * compare_length); + + batch.flatten_bytes = oprf_server_->SimpleEvaluate(batch_items[0]); + for (i = 1; i < batch_items.size(); i++) { + std::string masked_item = oprf_server_->SimpleEvaluate(batch_items[i]); + batch.flatten_bytes.append(masked_item); + } + + omp_set_lock(&lck_send); + + if (send_flag) { + // Send x^a. + options_.link0->SendAsyncThrottled( + options_.link0->NextRank(), batch.Serialize(), + fmt::format("EcdhOprfPSI:FinalEvaluatedItems:{}", + local_batch_count)); + } + + if (ub_cache != nullptr) { + for (size_t i = 0; i < batch_items.size(); i++) { + std::string cache_data = + batch.flatten_bytes.substr(i * compare_length, compare_length); + ub_cache->SaveData(cache_data, batch_indices[i], shuffle_indices[i]); + } + } + + omp_unset_lock(&lck_send); + } + } + + if (send_flag) { + batch.is_last_batch = true; + batch.flatten_bytes.resize(0); + options_.link0->SendAsyncThrottled( + options_.link0->NextRank(), batch.Serialize(), + fmt::format("EcdhOprfPSI last batch,FinalEvaluatedItems:{}", + batch_count)); + } + if (ub_cache != nullptr) { + ub_cache->Flush(); + } + + SPDLOG_INFO("{} finished, batch_count={} items_count={}", __func__, + batch_count, items_count); + + return items_count; +} + +void EcdhOprfPsiServer::RecvBlindAndSendEvaluate() { + size_t batch_count = 0; + + size_t ec_point_length = oprf_server_->GetEcPointLength(); + + while (true) { + const auto tag = fmt::format("EcdhOprfPSI:BlindItems:{}", batch_count); + PsiDataBatch blinded_batch = PsiDataBatch::Deserialize( + options_.link1->Recv(options_.link1->NextRank(), tag)); + + PsiDataBatch evaluated_batch; + evaluated_batch.is_last_batch = blinded_batch.is_last_batch; + + const auto tag_send = + fmt::format("EcdhOprfPSI:EvaluatedItems:{}", batch_count); + + if (blinded_batch.is_last_batch) { + SPDLOG_INFO("{} Last batch triggered, batch_count={}", __func__, + batch_count); + + options_.link1->SendAsyncThrottled(options_.link1->NextRank(), + evaluated_batch.Serialize(), tag_send); + break; + } + + // Fetch blinded y^r. + YACL_ENFORCE(blinded_batch.flatten_bytes.size() % ec_point_length == 0); + size_t num_items = blinded_batch.flatten_bytes.size() / ec_point_length; + + std::vector<std::string> blinded_items(num_items); + for (size_t idx = 0; idx < num_items; ++idx) { + blinded_items[idx] = blinded_batch.flatten_bytes.substr( + idx * ec_point_length, ec_point_length); + } + // (x^r)^s + std::vector<std::string> evaluated_items = + oprf_server_->Evaluate(blinded_items); + + evaluated_batch.flatten_bytes.reserve(evaluated_items.size() * + ec_point_length); + for (const auto& item : evaluated_items) { + evaluated_batch.flatten_bytes.append(item); + } + + options_.link1->SendAsyncThrottled(options_.link1->NextRank(), + evaluated_batch.Serialize(), tag_send); + + batch_count++; + } + SPDLOG_INFO("{} finished, batch_count={}", __func__, batch_count); +} + +void EcdhOprfPsiServer::RecvBlindAndShuffleSendEvaluate() { + size_t batch_count = 0; + + size_t ec_point_length = oprf_server_->GetEcPointLength(); + + std::vector<std::string> evaluated_items; + + while (true) { + const auto tag = fmt::format("EcdhOprfPSI:BlindItems:{}", batch_count); + PsiDataBatch blinded_batch = PsiDataBatch::Deserialize( + options_.link1->Recv(options_.link1->NextRank(), tag)); + + if (blinded_batch.is_last_batch) { + break; + } + + // Fetch blinded y^r. + YACL_ENFORCE(blinded_batch.flatten_bytes.size() % ec_point_length == 0); + size_t num_items = blinded_batch.flatten_bytes.size() / ec_point_length; + + std::vector<std::string> blinded_items(num_items); + for (size_t idx = 0; idx < num_items; ++idx) { + blinded_items[idx] = blinded_batch.flatten_bytes.substr( + idx * ec_point_length, ec_point_length); + } + // (x^r)^s + std::vector<std::string> batch_evaluated_items = + oprf_server_->Evaluate(blinded_items); + + // evaluated_items is scoped and will be destructed soon + evaluated_items.insert( + evaluated_items.end(), + std::make_move_iterator(batch_evaluated_items.begin()), + std::make_move_iterator(batch_evaluated_items.end())); + + batch_count++; + } + SPDLOG_INFO("recv Blind finished, batch_count={}", batch_count); + + std::sort(evaluated_items.begin(), evaluated_items.end()); + + std::unique_ptr<IBasicBatchProvider> provider = + std::make_unique<MemoryBatchProvider>(evaluated_items, + options_.batch_size); + + batch_count = 0; + while (true) { + std::vector<std::string> batch_evaluated_items = provider->ReadNextBatch(); + + PsiDataBatch evaluated_batch; + evaluated_batch.is_last_batch = batch_evaluated_items.empty(); + + const auto tag_send = + fmt::format("EcdhOprfPSI:EvaluatedItems:{}", batch_count); + + if (evaluated_batch.is_last_batch) { + SPDLOG_INFO("{} Last batch triggered, batch_count={}", __func__, + batch_count); + + options_.link1->SendAsyncThrottled(options_.link1->NextRank(), + evaluated_batch.Serialize(), tag_send); + break; + } + + evaluated_batch.flatten_bytes.reserve(batch_evaluated_items.size() * + ec_point_length); + for (const auto& item : batch_evaluated_items) { + evaluated_batch.flatten_bytes.append(item); + } + + options_.link1->SendAsyncThrottled(options_.link1->NextRank(), + evaluated_batch.Serialize(), tag_send); + + batch_count++; + } + SPDLOG_INFO("send evaluated finished, batch_count={}", batch_count); +} + +std::pair<std::vector<uint64_t>, size_t> +EcdhOprfPsiServer::RecvIntersectionMaskedItems( + const std::shared_ptr<IShuffledBatchProvider>& cache_provider) { + std::unordered_set<std::string> client_masked_items; + + size_t compare_length = oprf_server_->GetCompareLength(); + size_t batch_count = 0; + + while (true) { + const auto tag = fmt::format("EcdhOprfPSI:batch_count:{}", batch_count); + PsiDataBatch masked_batch = PsiDataBatch::Deserialize( + options_.link1->Recv(options_.link1->NextRank(), tag)); + + if (masked_batch.is_last_batch) { + break; + } + + // Fetch blinded y^r. + YACL_ENFORCE(masked_batch.flatten_bytes.size() % compare_length == 0); + size_t num_items = masked_batch.flatten_bytes.size() / compare_length; + + std::vector<std::string> batch_masked_items(num_items); + for (size_t idx = 0; idx < num_items; ++idx) { + batch_masked_items[idx] = masked_batch.flatten_bytes.substr( + idx * compare_length, compare_length); + } + + // batch_masked_items is scoped and will be destructed soon + client_masked_items.insert( + std::make_move_iterator(batch_masked_items.begin()), + std::make_move_iterator(batch_masked_items.end())); + + batch_count++; + } + SPDLOG_INFO("Recv intersection masked finished, batch_count={}", batch_count); + + std::vector<uint64_t> indices; + + size_t item_index = 0; + batch_count = 0; + size_t compare_thread_num = omp_get_num_procs(); + + while (true) { + std::vector<std::string> server_masked_items; + std::vector<size_t> batch_indices; + std::vector<size_t> batch_shuffled_indices; + + std::tie(server_masked_items, batch_indices, batch_shuffled_indices) = + cache_provider->ReadNextShuffledBatch(); + if (server_masked_items.empty()) { + break; + } + YACL_ENFORCE(server_masked_items.size() == batch_shuffled_indices.size()); + + size_t compare_size = + (server_masked_items.size() + compare_thread_num - 1) / + compare_thread_num; + + std::vector<std::vector<uint64_t>> batch_result(compare_thread_num); + + auto compare_proc = [&](int idx) -> void { + uint64_t begin = idx * compare_size; + uint64_t end = + std::min<uint64_t>(server_masked_items.size(), begin + compare_size); + + for (uint64_t i = begin; i < end; ++i) { + if (client_masked_items.find(server_masked_items[i]) != + client_masked_items.end()) { + batch_result[idx].push_back(batch_shuffled_indices[i]); + } + } + }; + + std::vector<std::future<void>> f_compare(compare_thread_num); + for (size_t i = 0; i < compare_thread_num; i++) { + f_compare[i] = std::async(compare_proc, i); + } + + for (size_t i = 0; i < compare_thread_num; i++) { + f_compare[i].get(); + } + + // batch_result is scoped and will be destructed soon + for (auto& r : batch_result) { + indices.insert(indices.end(), std::make_move_iterator(r.begin()), + std::make_move_iterator(r.end())); + } + + batch_count++; + + item_index += server_masked_items.size(); + SPDLOG_INFO("GetIndices batch count:{}, item_index:{}", batch_count, + item_index); + } + + return std::make_pair(indices, item_index); +} + +void EcdhOprfPsiClient::RecvFinalEvaluatedItems( + const std::shared_ptr<IEcPointStore>& peer_ec_point_store) { + SPDLOG_INFO("Begin Recv FinalEvaluatedItems items"); + + size_t batch_count = 0; + while (true) { + const auto tag = + fmt::format("EcdhOprfPSI:FinalEvaluatedItems:{}", batch_count); + + // Fetch y^b. + PsiDataBatch masked_batch = PsiDataBatch::Deserialize( + options_.link0->Recv(options_.link0->NextRank(), tag)); + + if (masked_batch.is_last_batch) { + SPDLOG_INFO("{} Last batch triggered, batch_count={}", __func__, + batch_count); + break; + } + + YACL_ENFORCE(masked_batch.flatten_bytes.length() % compare_length_ == 0); + size_t num_items = masked_batch.flatten_bytes.length() / compare_length_; + + std::vector<std::string> evaluated_items(num_items); + for (size_t idx = 0; idx < num_items; ++idx) { + evaluated_items[idx] = + absl::Base64Escape(masked_batch.flatten_bytes.substr( + idx * compare_length_, compare_length_)); + } + peer_ec_point_store->Save(evaluated_items); + + batch_count++; + } + SPDLOG_INFO("End Recv FinalEvaluatedItems items"); +} + +size_t EcdhOprfPsiClient::SendBlindedItems( + const std::shared_ptr<IBasicBatchProvider>& batch_provider) { + size_t batch_count = 0; + size_t items_count = 0; + + SPDLOG_INFO("Begin Send BlindedItems items"); + + while (true) { + auto items = batch_provider->ReadNextBatch(); + + PsiDataBatch blinded_batch; + blinded_batch.is_last_batch = items.empty(); + + const auto tag = fmt::format("EcdhOprfPSI:BlindItems:{}", batch_count); + + if (blinded_batch.is_last_batch) { + SPDLOG_INFO("{} Last batch triggered, batch_count={}", __func__, + batch_count); + + options_.link1->SendAsyncThrottled(options_.link1->NextRank(), + blinded_batch.Serialize(), tag); + break; + } + + std::vector<std::shared_ptr<IEcdhOprfClient>> oprf_clients(items.size()); + std::vector<std::string> blinded_items(items.size()); + + yacl::parallel_for(0, items.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + if (oprf_client_ == nullptr) { + std::shared_ptr<IEcdhOprfClient> oprf_client_ptr = + CreateEcdhOprfClient(options_.oprf_type, options_.curve_type); + + oprf_clients[idx] = oprf_client_ptr; + } else { + oprf_clients[idx] = oprf_client_; + } + + blinded_items[idx] = oprf_clients[idx]->Blind(items[idx]); + } + }); + + blinded_batch.flatten_bytes.reserve(items.size() * ec_point_length_); + + for (uint64_t idx = 0; idx < items.size(); ++idx) { + blinded_batch.flatten_bytes.append(blinded_items[idx]); + } + + // push to oprf_client_queue_ + if (oprf_client_ == nullptr) { + std::unique_lock<std::mutex> lock(mutex_); + queue_push_cv_.wait(lock, [&] { + return (oprf_client_queue_.size() < options_.window_size); + }); + oprf_client_queue_.push(std::move(oprf_clients)); + queue_pop_cv_.notify_one(); + // SPDLOG_INFO("push to queue size:{}", oprf_client_queue_.size()); + } + + options_.link1->SendAsyncThrottled(options_.link1->NextRank(), + blinded_batch.Serialize(), tag); + + items_count += items.size(); + batch_count++; + } + SPDLOG_INFO("{} finished, batch_count={} items_count={}", __func__, + batch_count, items_count); + + return items_count; +} + +void EcdhOprfPsiClient::RecvEvaluatedItems( + const std::shared_ptr<IEcPointStore>& self_ec_point_store) { + SPDLOG_INFO("Begin Recv EvaluatedItems items"); + + size_t batch_count = 0; + + while (true) { + const auto tag = fmt::format("EcdhOprfPSI:EvaluatedItems:{}", batch_count); + PsiDataBatch masked_batch = PsiDataBatch::Deserialize( + options_.link1->Recv(options_.link1->NextRank(), tag)); + // Fetch evaluate y^rs. + + if (masked_batch.is_last_batch) { + SPDLOG_INFO("{} Last batch triggered, batch_count={}", __func__, + batch_count); + break; + } + + YACL_ENFORCE(masked_batch.flatten_bytes.size() % ec_point_length_ == 0); + size_t num_items = masked_batch.flatten_bytes.size() / ec_point_length_; + + std::vector<std::string> evaluate_items(num_items); + for (size_t idx = 0; idx < num_items; ++idx) { + evaluate_items[idx] = masked_batch.flatten_bytes.substr( + idx * ec_point_length_, ec_point_length_); + } + + std::vector<std::string> oprf_items(num_items); + std::vector<std::shared_ptr<IEcdhOprfClient>> oprf_clients; + + // get oprf_clients + if (oprf_client_ == nullptr) { + std::unique_lock<std::mutex> lock(mutex_); + queue_pop_cv_.wait(lock, [&] { return (!oprf_client_queue_.empty()); }); + + oprf_clients = std::move(oprf_client_queue_.front()); + oprf_client_queue_.pop(); + queue_push_cv_.notify_one(); + } else { + oprf_clients.resize(num_items); + for (size_t i = 0; i < oprf_clients.size(); ++i) { + oprf_clients[i] = oprf_client_; + } + } + + YACL_ENFORCE(oprf_clients.size() == num_items, + "EcdhOprfServer should not be nullptr"); + + yacl::parallel_for(0, num_items, 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + oprf_items[idx] = absl::Base64Escape( + oprf_clients[idx]->Finalize(evaluate_items[idx])); + } + }); + + self_ec_point_store->Save(oprf_items); + + batch_count++; + } + SPDLOG_INFO("End Recv EvaluatedItems"); +} + +void EcdhOprfPsiClient::SendIntersectionMaskedItems( + const std::shared_ptr<IBasicBatchProvider>& batch_provider) { + size_t batch_count = 0; + size_t items_count = 0; + + SPDLOG_INFO("Begin Send IntersectionMaskedItems items"); + + while (true) { + auto items = batch_provider->ReadNextBatch(); + + PsiDataBatch blinded_batch; + blinded_batch.is_last_batch = items.empty(); + + const auto tag = fmt::format("EcdhOprfPSI:batch_count:{}", batch_count); + + if (blinded_batch.is_last_batch) { + SPDLOG_INFO("{} Last batch triggered, batch_count={}", __func__, + batch_count); + + options_.link1->SendAsyncThrottled(options_.link1->NextRank(), + blinded_batch.Serialize(), tag); + break; + } + + blinded_batch.flatten_bytes.reserve(items.size() * compare_length_); + + for (uint64_t idx = 0; idx < items.size(); ++idx) { + std::string b64_dest; + absl::Base64Unescape(items[idx], &b64_dest); + + blinded_batch.flatten_bytes.append(b64_dest); + } + + options_.link1->SendAsyncThrottled(options_.link1->NextRank(), + blinded_batch.Serialize(), tag); + + items_count += items.size(); + batch_count++; + } + SPDLOG_INFO("{} finished, batch_count={} items_count={}", __func__, + batch_count, items_count); + + return; +} + +} // namespace psi::psi diff --git a/psi/psi/core/ecdh_oprf_psi.h b/psi/psi/core/ecdh_oprf_psi.h new file mode 100644 index 00000000..dd2048ed --- /dev/null +++ b/psi/psi/core/ecdh_oprf_psi.h @@ -0,0 +1,223 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <functional> +#include <memory> +#include <queue> +#include <string> +#include <utility> +#include <vector> + +#include "yacl/base/byte_container_view.h" +#include "yacl/link/link.h" + +#include "psi/psi/core/ecdh_oprf/ecdh_oprf.h" +#include "psi/psi/core/ecdh_oprf/ecdh_oprf_selector.h" +#include "psi/psi/utils/batch_provider.h" +#include "psi/psi/utils/ec_point_store.h" +#include "psi/psi/utils/ub_psi_cache.h" + +// basic ecdh-oprf based psi +// reference: +// Faster Unbalanced Private Set Intersection +// https://eprint.iacr.org/2017/677 Fig.1 +// CGT12 Fast and Private Computation of Cardinality of Set Intersection and +// Union +// https://eprint.org/2011/141 +// +// Unbalanced psi compuation and communication compare reference +// Labeled PSI from Homomorphic Encryption with Reduced Computation and +// Communication Table 2 (https://eprint.iacr.org/2021/1116.pdf) +// +// server client +// Offline +// data shuffle +// FullEvaluate +// send full evaluated items +// -----------------------> +// ====================================================== +// Online +// Blind +// blinded items(batch_size) +// <---------------------- +// Evaluate +// evaluated items +// -----------------------> +// Finalize +// ======================================================= +// Intersection +// +namespace psi::psi { + +// send queque capacity +inline constexpr size_t kQueueCapacity = 32; +inline constexpr size_t kEcdhOprfPsiBatchSize = 8192; + +struct EcdhOprfPsiOptions { + // Provides the link for server's evaluated data. + std::shared_ptr<yacl::link::Context> link0; + + // Provides the link for client's blind/evaluated data. + std::shared_ptr<yacl::link::Context> link1; + + // Now only support 2HashBased Ecdh-OPRF + OprfType oprf_type = OprfType::Basic; + + // curve_type + // FourQ/SM2/Secp256k1 + CurveType curve_type = CurveType::CURVE_FOURQ; + + // batch_size + // batch read from IBatchProvider + // batch compute oprf blind/evaluate + // batch send and read + size_t batch_size = kEcdhOprfPsiBatchSize; + + // windows_size + // control send speed, avoid send buffer overflow + size_t window_size = kQueueCapacity; +}; + +class EcdhOprfPsiServer { + public: + explicit EcdhOprfPsiServer(const EcdhOprfPsiOptions& options) + : options_(options), + oprf_server_( + CreateEcdhOprfServer(options.oprf_type, options.curve_type)) {} + + EcdhOprfPsiServer(const EcdhOprfPsiOptions& options, + yacl::ByteContainerView private_key) + : options_(options), + oprf_server_(CreateEcdhOprfServer(private_key, options.oprf_type, + options.curve_type)) {} + + /** + * @brief FullEvaluate for server side data + * + * @param batch_provider input data batch provider + * @param ec_point_store masked data store + * @param send_flag default false, just save to cace, + * true, send and save cache + */ + size_t FullEvaluate( + const std::shared_ptr<IShuffledBatchProvider>& batch_provider, + const std::shared_ptr<IUbPsiCache>& ub_cache, bool send_flag = false); + + /** + * @brief send masked data + * + * @param batch_provider masked data batch provider + */ + size_t SendFinalEvaluatedItems( + const std::shared_ptr<IBasicBatchProvider>& batch_provider); + + size_t FullEvaluateAndSend( + const std::shared_ptr<IShuffledBatchProvider>& batch_provider, + const std::shared_ptr<IUbPsiCache>& ub_cache = nullptr); + + /** + * @brief batch recv client blinded items and send evaluate + * + */ + void RecvBlindAndSendEvaluate(); + + /** + * @brief batch recv client blinded items and send shuffled evaluate + * + */ + void RecvBlindAndShuffleSendEvaluate(); + + /** + * @brief Get the Private Key object + * + * @return std::array<uint8_t, kEccKeySize> + */ + std::array<uint8_t, kEccKeySize> GetPrivateKey() { + return oprf_server_->GetPrivateKey(); + } + + size_t GetCompareLength() { return oprf_server_->GetCompareLength(); } + + std::pair<std::vector<uint64_t>, size_t> RecvIntersectionMaskedItems( + const std::shared_ptr<IShuffledBatchProvider>& cache_provider); + + private: + EcdhOprfPsiOptions options_; + + std::shared_ptr<IEcdhOprfServer> oprf_server_; +}; + +class EcdhOprfPsiClient { + public: + explicit EcdhOprfPsiClient(const EcdhOprfPsiOptions& options) + : options_(options) { + std::shared_ptr<IEcdhOprfClient> oprf_client = + CreateEcdhOprfClient(options.oprf_type, options.curve_type); + compare_length_ = oprf_client->GetCompareLength(); + ec_point_length_ = oprf_client->GetEcPointLength(); + } + + explicit EcdhOprfPsiClient(const EcdhOprfPsiOptions& options, + yacl::ByteContainerView private_key) + : options_(options) { + oprf_client_ = CreateEcdhOprfClient(private_key, options.oprf_type, + options.curve_type); + compare_length_ = oprf_client_->GetCompareLength(); + ec_point_length_ = oprf_client_->GetEcPointLength(); + } + + /** + * @brief recv server's masked data + * + * @param ec_point_store store server's masked data to peer results + */ + void RecvFinalEvaluatedItems( + const std::shared_ptr<IEcPointStore>& peer_ec_point_store); + + /** + * @brief blind input data and send to server + * + * @param batch_provider input data batch provider + */ + size_t SendBlindedItems( + const std::shared_ptr<IBasicBatchProvider>& batch_provider); + + /** + * @brief recv evaluated data, do Finalize and store to ec_point_store + * + * @param batch_provider input data batch provider + * @param ec_point_store store finalized data to self results + */ + void RecvEvaluatedItems( + const std::shared_ptr<IEcPointStore>& self_ec_point_store); + + void SendIntersectionMaskedItems( + const std::shared_ptr<IBasicBatchProvider>& batch_provider); + + private: + EcdhOprfPsiOptions options_; + + std::mutex mutex_; + std::condition_variable queue_push_cv_; + std::condition_variable queue_pop_cv_; + std::queue<std::vector<std::shared_ptr<IEcdhOprfClient>>> oprf_client_queue_; + std::shared_ptr<IEcdhOprfClient> oprf_client_ = nullptr; + + size_t compare_length_; + size_t ec_point_length_; +}; + +} // namespace psi::psi diff --git a/psi/psi/core/ecdh_oprf_psi_test.cc b/psi/psi/core/ecdh_oprf_psi_test.cc new file mode 100644 index 00000000..17020690 --- /dev/null +++ b/psi/psi/core/ecdh_oprf_psi_test.cc @@ -0,0 +1,330 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/ecdh_oprf_psi.h" + +#include <algorithm> +#include <future> +#include <iostream> +#include <random> +#include <set> +#include <vector> + +#include "absl/strings/escaping.h" +#include "absl/strings/str_split.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" +#include "yacl/crypto/tools/prg.h" +#include "yacl/crypto/utils/rand.h" +#include "yacl/link/test_util.h" +#include "yacl/utils/scope_guard.h" + +#include "psi/psi/core/ecdh_oprf/ecdh_oprf_selector.h" +#include "psi/psi/io/io.h" +#include "psi/psi/utils/batch_provider.h" +#include "psi/psi/utils/ec_point_store.h" +#include "psi/psi/utils/test_utils.h" + +namespace psi::psi { +namespace { + +void WriteCsvFile(const std::string &file_name, + const std::vector<std::string> &items) { + auto out = io::BuildOutputStream(io::FileIoOptions(file_name)); + out->Write("id\n"); + for (const auto &data : items) { + out->Write(fmt::format("{}\n", data)); + } + out->Close(); +} + +} // namespace + +struct TestParams { + size_t items_size; + CurveType curve_type = CurveType::CURVE_FOURQ; + bool shuffle_online = false; +}; + +class BasicEcdhOprfTest : public ::testing::TestWithParam<TestParams> {}; + +TEST_P(BasicEcdhOprfTest, Works) { + auto params = GetParam(); + auto ctxs = yacl::link::test::SetupWorld(2); + + std::vector<std::string> items_a = + test::CreateRangeItems(0, params.items_size); + std::vector<std::string> items_b = test::CreateRangeItems( + params.items_size / 4, + std::max(static_cast<size_t>(1), params.items_size / 2)); + + auto timestamp_str = std::to_string(absl::ToUnixNanos(absl::Now())); + // server input + auto server_input_path = + std::filesystem::path(fmt::format("server-input-{}", timestamp_str)); + // client input + auto client_input_path = + std::filesystem::path(fmt::format("client-input-{}", timestamp_str)); + + // server output + auto server_output_path = + std::filesystem::path(fmt::format("server-output-{}", timestamp_str)); + // client output + auto client_output_path = + std::filesystem::path(fmt::format("client-output-{}", timestamp_str)); + // server output + auto server_tmp_cache_path = + std::filesystem::path(fmt::format("tmp-cache-{}", timestamp_str)); + + // register remove of temp file. + ON_SCOPE_EXIT([&] { + std::error_code ec; + std::filesystem::remove(server_input_path, ec); + if (ec.value() != 0) { + SPDLOG_WARN("can not remove tmp file: {}, msg: {}", + server_input_path.c_str(), ec.message()); + } + std::filesystem::remove(client_input_path, ec); + if (ec.value() != 0) { + SPDLOG_WARN("can not remove tmp file: {}, msg: {}", + client_input_path.c_str(), ec.message()); + } + std::filesystem::remove(server_tmp_cache_path, ec); + if (ec.value() != 0) { + SPDLOG_WARN("can not remove tmp file: {}, msg: {}", + server_tmp_cache_path.c_str(), ec.message()); + } + }); + + WriteCsvFile(server_input_path.string(), items_a); + WriteCsvFile(client_input_path.string(), items_b); + + EcdhOprfPsiOptions server_options; + EcdhOprfPsiOptions client_options; + + server_options.link0 = ctxs[0]; + server_options.link1 = ctxs[0]->Spawn(); + server_options.curve_type = params.curve_type; + + client_options.link0 = ctxs[1]; + client_options.link1 = ctxs[1]->Spawn(); + client_options.curve_type = params.curve_type; + + // todo psi not support now + // server_options.link0->SetThrottleWindowSize(server_options.window_size); + // client_options.link0->SetThrottleWindowSize(server_options.window_size); + + std::shared_ptr<EcdhOprfPsiServer> dh_oprf_psi_server_offline = + std::make_shared<EcdhOprfPsiServer>(server_options); + std::shared_ptr<EcdhOprfPsiClient> dh_oprf_psi_client_offline = + std::make_shared<EcdhOprfPsiClient>(client_options); + + // + // save server side private key for online use + // + std::array<uint8_t, kEccKeySize> server_private_key = + dh_oprf_psi_server_offline->GetPrivateKey(); + + // server batch provider + std::vector<std::string> cloumn_ids = {"id"}; + std::shared_ptr<CachedCsvBatchProvider> batch_provider_server = + std::make_shared<CachedCsvBatchProvider>( + server_input_path.string(), cloumn_ids, kEcdhOprfPsiBatchSize, 100000, + true); + + // client output + auto client_self_ec_point_store = std::make_shared<MemoryEcPointStore>(); + auto client_peer_ec_point_store = std::make_shared<MemoryEcPointStore>(); + + // + // offline phase: FullEvaluate server's data and store + // + std::shared_ptr<IUbPsiCache> ub_cache = std::make_shared<UbPsiCache>( + server_tmp_cache_path.string(), + dh_oprf_psi_server_offline->GetCompareLength(), cloumn_ids); + + dh_oprf_psi_server_offline->FullEvaluate(batch_provider_server, ub_cache); + + SPDLOG_INFO("Finished FullEvaluate"); + // + // offline phase: server send FullEvaluated data to client + // + std::future<size_t> f_sever_send_fullevaluate = std::async([&] { + std::shared_ptr<IBasicBatchProvider> batch_provider = + std::make_shared<UbPsiCacheProvider>( + server_tmp_cache_path.string(), kEcdhOprfPsiBatchSize, + dh_oprf_psi_server_offline->GetCompareLength()); + + return dh_oprf_psi_server_offline->SendFinalEvaluatedItems(batch_provider); + }); + + std::future<void> f_client_recv_full_evaluate = std::async([&] { + dh_oprf_psi_client_offline->RecvFinalEvaluatedItems( + client_peer_ec_point_store); + }); + + f_sever_send_fullevaluate.get(); + f_client_recv_full_evaluate.get(); + + std::vector<std::string> intersection_std = + test::GetIntersection(items_a, items_b); + + SPDLOG_INFO("a:{} b:{} intersection_std:{}", items_a.size(), items_b.size(), + intersection_std.size()); + + // online phase + // online server, load private key saved by offline phase + std::shared_ptr<EcdhOprfPsiServer> dh_oprf_psi_server_online = + std::make_shared<EcdhOprfPsiServer>(server_options, server_private_key); + + if (params.shuffle_online) { + std::vector<uint8_t> client_private_key = + yacl::crypto::RandBytes(kEccKeySize); + + std::shared_ptr<EcdhOprfPsiClient> dh_oprf_psi_client_online = + std::make_shared<EcdhOprfPsiClient>(client_options, client_private_key); + + std::future<void> f_sever_recv_blind_shuffle = std::async( + [&] { dh_oprf_psi_server_online->RecvBlindAndShuffleSendEvaluate(); }); + + std::future<void> f_client_send_blind = std::async([&] { + std::shared_ptr<IBasicBatchProvider> batch_provider_client = + std::make_shared<CsvBatchProvider>(client_input_path.string(), + cloumn_ids, kEcdhOprfPsiBatchSize); + + dh_oprf_psi_client_online->SendBlindedItems(batch_provider_client); + dh_oprf_psi_client_online->RecvEvaluatedItems(client_self_ec_point_store); + }); + + f_sever_recv_blind_shuffle.get(); + f_client_send_blind.get(); + + std::vector<std::string> &client_peer_evaluate_items = + client_peer_ec_point_store->content(); + + std::vector<std::string> &client_self_evaluate_items = + client_self_ec_point_store->content(); + + std::sort(client_peer_evaluate_items.begin(), + client_peer_evaluate_items.end()); + + for (size_t index = 0; index < client_self_evaluate_items.size(); ++index) { + SPDLOG_INFO("{}: {} {}", index, + absl::BytesToHexString(client_self_evaluate_items[index]), + client_self_evaluate_items[index].length()); + } + + // intersection + std::vector<std::string> intersection; + for (size_t index = 0; index < client_self_evaluate_items.size(); ++index) { + if (std::binary_search(client_peer_evaluate_items.begin(), + client_peer_evaluate_items.end(), + client_self_evaluate_items[index])) { + intersection.push_back(client_self_evaluate_items[index]); + } + } + + SPDLOG_INFO("intersection: {}", intersection.size()); + + std::future<void> f_send_intersection_mask = std::async([&] { + std::shared_ptr<IBasicBatchProvider> batch_provider = + std::make_shared<MemoryBatchProvider>(intersection, + kEcdhOprfPsiBatchSize); + dh_oprf_psi_client_online->SendIntersectionMaskedItems(batch_provider); + }); + + std::future<std::pair<std::vector<uint64_t>, size_t>> + f_recv_intersection_mask = std::async([&] { + std::shared_ptr<IShuffledBatchProvider> batch_provider = + std::make_shared<UbPsiCacheProvider>( + server_tmp_cache_path.string(), kEcdhOprfPsiBatchSize, + dh_oprf_psi_server_offline->GetCompareLength()); + return dh_oprf_psi_server_online->RecvIntersectionMaskedItems( + batch_provider); + }); + + f_send_intersection_mask.get(); + std::vector<uint64_t> indices; + size_t server_items_size; + std::tie(indices, server_items_size) = f_recv_intersection_mask.get(); + SPDLOG_INFO("indices: {} server_items_size:{}", indices.size(), + server_items_size); + } else { + std::shared_ptr<EcdhOprfPsiClient> dh_oprf_psi_client_online = + std::make_shared<EcdhOprfPsiClient>(client_options); + + std::future<void> f_sever_recv_blind = std::async( + [&] { dh_oprf_psi_server_online->RecvBlindAndSendEvaluate(); }); + + std::future<void> f_client_send_blind = std::async([&] { + std::shared_ptr<IBasicBatchProvider> batch_provider_client = + std::make_shared<CsvBatchProvider>(client_input_path.string(), + cloumn_ids, kEcdhOprfPsiBatchSize); + + dh_oprf_psi_client_online->SendBlindedItems(batch_provider_client); + }); + + std::future<void> f_client_recv_evaluate = std::async([&] { + dh_oprf_psi_client_online->RecvEvaluatedItems(client_self_ec_point_store); + }); + + f_sever_recv_blind.get(); + f_client_send_blind.get(); + f_client_recv_evaluate.get(); + + std::vector<std::string> &client_peer_evaluate_items = + client_peer_ec_point_store->content(); + + std::vector<std::string> &client_self_evaluate_items = + client_self_ec_point_store->content(); + + std::sort(client_peer_evaluate_items.begin(), + client_peer_evaluate_items.end()); + + // intersection + std::vector<std::string> intersection; + for (size_t index = 0; index < client_self_evaluate_items.size(); ++index) { + if (std::binary_search(client_peer_evaluate_items.begin(), + client_peer_evaluate_items.end(), + client_self_evaluate_items[index])) { + YACL_ENFORCE(index < items_b.size()); + intersection.push_back(items_b[index]); + } + } + + EXPECT_EQ(intersection_std, intersection); + } +} + +INSTANTIATE_TEST_SUITE_P( + Works_Instances, BasicEcdhOprfTest, + testing::Values( + // CURVE_FOURQ + TestParams{1}, // + TestParams{10}, // + TestParams{50}, // + TestParams{4095}, // less than one batch + TestParams{4096}, // exactly one batch + TestParams{10000}, // more than one batch + // CURVE_SM2 + TestParams{1000, CurveType::CURVE_SM2}, // more than one batch + // Curve256k1 + TestParams{1000, CurveType::CURVE_SECP256K1} // more than one batch + ) // +); + +} // namespace psi::psi diff --git a/psi/psi/core/ecdh_psi.cc b/psi/psi/core/ecdh_psi.cc new file mode 100644 index 00000000..3f09a12a --- /dev/null +++ b/psi/psi/core/ecdh_psi.cc @@ -0,0 +1,429 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/ecdh_psi.h" + +#include <cstdint> +#include <future> +#include <utility> + +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/utils/parallel.h" +#include "yacl/utils/serialize.h" + +#include "psi/psi/core/communication.h" +#include "psi/psi/cryptor/cryptor_selector.h" +#include "psi/psi/utils/batch_provider.h" + +namespace psi::psi { + +constexpr int kLogBatchInterval = 10; + +EcdhPsiContext::EcdhPsiContext(EcdhPsiOptions options) + : options_(std::move(options)), + id_(options_.link_ctx->PartyIdByRank(options_.link_ctx->Rank())) { + YACL_ENFORCE(options_.link_ctx->WorldSize() == 2); + + main_link_ctx_ = options_.link_ctx; + dual_mask_link_ctx_ = options_.link_ctx->Spawn(); +} + +void EcdhPsiContext::CheckConfig() { + if (options_.ic_mode) { + return; + } + + // Sanity check: the `target_rank` and 'curve_type' should match. + std::string my_config = + fmt::format("target_rank={},curve={}", options_.target_rank, + static_cast<int>(options_.ecc_cryptor->GetCurveType())); + yacl::Buffer my_config_buf(my_config.c_str(), my_config.size()); + auto config_list = + yacl::link::AllGather(main_link_ctx_, my_config_buf, "ECDHPSI:SANITY"); + auto peer_config = config_list[main_link_ctx_->NextRank()]; + YACL_ENFORCE(my_config_buf == peer_config, + "EcdhPsiContext Config mismatch, mine={}, peer={}", my_config, + peer_config.data<const char>()); +} + +void EcdhPsiContext::MaskSelf( + const std::shared_ptr<IBasicBatchProvider>& batch_provider, + uint64_t processed_item_cnt) { + size_t batch_count = 0; + size_t item_count = processed_item_cnt; + bool read_next_batch = true; + + std::vector<std::string> batch_items; + while (processed_item_cnt > 0) { + auto read_batch_items = batch_provider->ReadNextBatch(); + + if (read_batch_items.empty()) { + YACL_ENFORCE_EQ(processed_item_cnt, 0U); + } + + if (read_batch_items.size() <= processed_item_cnt) { + processed_item_cnt -= read_batch_items.size(); + } else { + read_next_batch = false; + batch_items = std::vector<std::string>( + read_batch_items.begin() + processed_item_cnt, + read_batch_items.end()); + processed_item_cnt = 0; + } + } + + while (true) { + // NOTE: we still need to send one batch even there is no data. + // This dummy batch is used to notify peer the end of data stream. + if (read_next_batch) { + batch_items = batch_provider->ReadNextBatch(); + } else { + read_next_batch = true; + } + + std::vector<std::string> masked_items; + if (!batch_items.empty()) { + masked_items = Mask(options_.ecc_cryptor, + HashInputs(options_.ecc_cryptor, batch_items)); + } + // Send x^a. + const auto tag = fmt::format("ECDHPSI:X^A:{}", batch_count); + SendBatch(masked_items, batch_count, tag); + if (batch_items.empty()) { + SPDLOG_INFO("MaskSelf:{}--finished, batch_count={}, self_item_count={}", + Id(), batch_count, item_count); + if (options_.statistics) { + options_.statistics->self_item_count = item_count; + } + break; + } + item_count += batch_items.size(); + ++batch_count; + + if (batch_count % kLogBatchInterval == 0) { + SPDLOG_INFO("MaskSelf:{}, batch_count={}, self_item_count={}", Id(), + batch_count, item_count); + } + } +} + +void EcdhPsiContext::MaskPeer( + const std::shared_ptr<IEcPointStore>& peer_ec_point_store) { + size_t batch_count = 0; + size_t item_count = 0; + while (true) { + // Fetch y^b. + std::vector<std::string> peer_items; + std::vector<std::string> dual_masked_peers; + const auto tag = fmt::format("ECDHPSI:Y^B:{}", batch_count); + RecvBatch(&peer_items, batch_count, tag); + + // Compute (y^b)^a. + if (!peer_items.empty()) { + // TODO: avoid mem copy + for (const auto& masked : Mask(options_.ecc_cryptor, peer_items)) { + // In the final comparison, we only send & compare `kFinalCompareBytes` + // number of bytes. + std::string cipher = masked.substr( + masked.length() - options_.dual_mask_size, options_.dual_mask_size); + if (SelfCanTouchResults()) { + // Store cipher of peer items for later intersection compute. + peer_ec_point_store->Save(cipher); + } + dual_masked_peers.emplace_back(std::move(cipher)); + } + + if (SelfCanTouchResults()) { + if (options_.recovery_manager) { + peer_ec_point_store->Flush(); + options_.recovery_manager->UpdateEcdhDualMaskedItemPeerCount( + peer_ec_point_store->ItemCount()); + } + } + } + // Should send out the dual masked items to peer. + if (PeerCanTouchResults()) { + const auto tag = fmt::format("ECDHPSI:Y^B^A:{}", batch_count); + // call non-block to avoid blocking each other with MaskSelf + SendDualMaskedBatchNonBlock(dual_masked_peers, batch_count, tag); + } + if (peer_items.empty()) { + SPDLOG_INFO("MaskPeer:{}--finished, batch_count={}, peer_item_count={}", + Id(), batch_count, item_count); + if (options_.statistics) { + options_.statistics->peer_item_count = item_count; + } + break; + } + item_count += peer_items.size(); + batch_count++; + + if (batch_count % kLogBatchInterval == 0) { + SPDLOG_INFO("MaskPeer:{}, batch_count={}, peer_item_count={}", Id(), + batch_count, item_count); + } + } +} + +void EcdhPsiContext::RecvDualMaskedSelf( + const std::shared_ptr<IEcPointStore>& self_ec_point_store) { + if (!SelfCanTouchResults()) { + return; + } + + // Receive x^a^b. + size_t batch_count = 0; + while (true) { + // TODO: avoid mem copy + std::vector<std::string> masked_items; + const auto tag = fmt::format("ECDHPSI:X^A^B:{}", batch_count); + RecvDualMaskedBatch(&masked_items, batch_count, tag); + for (auto& item : masked_items) { + self_ec_point_store->Save(std::move(item)); + } + if (masked_items.empty()) { + SPDLOG_INFO( + "RecvDualMaskedSelf:{} recv last batch finished, batch_count={}", + Id(), batch_count); + break; + } else { + if (options_.recovery_manager) { + self_ec_point_store->Flush(); + options_.recovery_manager->UpdateEcdhDualMaskedItemSelfCount( + self_ec_point_store->ItemCount()); + } + } + batch_count++; + + // Call the hook. + if (options_.on_batch_finished) { + options_.on_batch_finished(batch_count); + } + } +} + +namespace { + +template <typename T> +PsiDataBatch BatchData(const std::vector<T>& batch_items, std::string_view type, + int32_t batch_idx) { + PsiDataBatch batch; + batch.is_last_batch = batch_items.empty(); + batch.item_num = batch_items.size(); + batch.batch_index = batch_idx; + batch.type = type; + if (!batch_items.empty()) { + batch.flatten_bytes.reserve(batch_items.size() * batch_items[0].size()); + for (const auto& item : batch_items) { + batch.flatten_bytes.append(item); + } + } + return batch; +} + +template <typename T> +void SendBatchImpl(const std::vector<T>& batch_items, + const std::shared_ptr<yacl::link::Context>& link_ctx, + std::string_view type, int32_t batch_idx, + std::string_view tag) { + auto batch = BatchData<T>(batch_items, type, batch_idx); + link_ctx->SendAsyncThrottled( + link_ctx->NextRank(), IcPsiBatchSerializer::Serialize(std::move(batch)), + tag); +} + +template <typename T> +void SendBatchNonBlockImpl(const std::vector<T>& batch_items, + const std::shared_ptr<yacl::link::Context>& link_ctx, + std::string_view type, int32_t batch_idx, + std::string_view tag) { + auto batch = BatchData<T>(batch_items, type, batch_idx); + link_ctx->SendAsync(link_ctx->NextRank(), + IcPsiBatchSerializer::Serialize(std::move(batch)), tag); +} + +void RecvBatchImpl(const std::shared_ptr<yacl::link::Context>& link_ctx, + int32_t batch_idx, std::string_view tag, + std::vector<std::string>* items) { + PsiDataBatch batch = IcPsiBatchSerializer::Deserialize( + link_ctx->Recv(link_ctx->NextRank(), tag)); + + YACL_ENFORCE(batch.batch_index == batch_idx, "Expected batch {}, but got {} ", + batch_idx, batch.batch_index); + + if (batch.item_num > 0) { + auto item_size = batch.flatten_bytes.size() / batch.item_num; + for (size_t i = 0; i < batch.item_num; ++i) { + items->emplace_back(batch.flatten_bytes.substr(i * item_size, item_size)); + } + } +} + +}; // namespace + +void EcdhPsiContext::SendBatch(const std::vector<std::string>& batch_items, + int32_t batch_idx, std::string_view tag) { + SendBatchImpl(batch_items, main_link_ctx_, "enc", batch_idx, tag); +} + +void EcdhPsiContext::SendBatch(const std::vector<std::string_view>& batch_items, + int32_t batch_idx, std::string_view tag) { + SendBatchImpl(batch_items, main_link_ctx_, "enc", batch_idx, tag); +} + +void EcdhPsiContext::RecvBatch(std::vector<std::string>* items, + int32_t batch_idx, std::string_view tag) { + RecvBatchImpl(main_link_ctx_, batch_idx, tag, items); +} + +void EcdhPsiContext::SendDualMaskedBatch( + const std::vector<std::string>& batch_items, int32_t batch_idx, + std::string_view tag) { + SendBatchImpl(batch_items, dual_mask_link_ctx_, "dual.enc", batch_idx, tag); +} + +void EcdhPsiContext::SendDualMaskedBatch( + const std::vector<std::string_view>& batch_items, int32_t batch_idx, + std::string_view tag) { + SendBatchImpl(batch_items, dual_mask_link_ctx_, "dual.enc", batch_idx, tag); +} + +void EcdhPsiContext::SendDualMaskedBatchNonBlock( + const std::vector<std::string>& batch_items, int32_t batch_idx, + std::string_view tag) { + SendBatchNonBlockImpl(batch_items, dual_mask_link_ctx_, "dual.enc", batch_idx, + tag); +} + +void EcdhPsiContext::RecvDualMaskedBatch(std::vector<std::string>* items, + int32_t batch_idx, + std::string_view tag) { + RecvBatchImpl(dual_mask_link_ctx_, batch_idx, tag, items); +} + +void RunEcdhPsi(const EcdhPsiOptions& options, + const std::shared_ptr<IBasicBatchProvider>& batch_provider, + const std::shared_ptr<IEcPointStore>& self_ec_point_store, + const std::shared_ptr<IEcPointStore>& peer_ec_point_store) { + YACL_ENFORCE(options.link_ctx->WorldSize() == 2); + YACL_ENFORCE(batch_provider != nullptr && self_ec_point_store != nullptr && + peer_ec_point_store != nullptr); + + EcdhPsiContext handler(options); + handler.CheckConfig(); + + uint64_t processed_item_cnt = 0; + if (options.recovery_manager) { + if (handler.SelfCanTouchResults() && handler.PeerCanTouchResults()) { + processed_item_cnt = + std::min(options.recovery_manager->ecdh_dual_masked_cnt_from_peer(), + options.recovery_manager->checkpoint() + .ecdh_dual_masked_item_self_count()); + } else if (handler.SelfCanTouchResults() && + !handler.PeerCanTouchResults()) { + processed_item_cnt = options.recovery_manager->checkpoint() + .ecdh_dual_masked_item_self_count(); + } else { + processed_item_cnt = + options.recovery_manager->ecdh_dual_masked_cnt_from_peer(); + } + + SPDLOG_INFO("processed_item_cnt = {}", processed_item_cnt); + } + + std::future<void> f_mask_self = std::async( + [&] { return handler.MaskSelf(batch_provider, processed_item_cnt); }); + std::future<void> f_mask_peer = + std::async([&] { return handler.MaskPeer(peer_ec_point_store); }); + std::future<void> f_recv_peer = std::async( + [&] { return handler.RecvDualMaskedSelf(self_ec_point_store); }); + + // Wait for end of logic flows or exceptions. + // Note: exception_ptr is `shared_ptr` style, hence could be used to prolong + // the lifetime of pointed exceptions. + std::exception_ptr mask_self_exptr = nullptr; + std::exception_ptr mask_peer_exptr = nullptr; + std::exception_ptr recv_peer_exptr = nullptr; + + try { + f_mask_self.get(); + } catch (const std::exception& e) { + mask_self_exptr = std::current_exception(); + SPDLOG_ERROR("Error in mask self: {}", e.what()); + } + + try { + f_mask_peer.get(); + } catch (const std::exception& e) { + mask_peer_exptr = std::current_exception(); + SPDLOG_ERROR("Error in mask peer: {}", e.what()); + } + + try { + f_recv_peer.get(); + } catch (const std::exception& e) { + recv_peer_exptr = std::current_exception(); + SPDLOG_ERROR("Error in recv peer: {}", e.what()); + } + + if (mask_self_exptr) { + std::rethrow_exception(mask_self_exptr); + } + if (mask_peer_exptr) { + std::rethrow_exception(mask_peer_exptr); + } + if (recv_peer_exptr) { + std::rethrow_exception(recv_peer_exptr); + } +} + +std::vector<std::string> RunEcdhPsi( + const std::shared_ptr<yacl::link::Context>& link_ctx, + const std::vector<std::string>& items, size_t target_rank, CurveType curve, + size_t batch_size) { + EcdhPsiOptions options; + options.ecc_cryptor = CreateEccCryptor(curve); + options.link_ctx = link_ctx; + options.target_rank = target_rank; + options.batch_size = batch_size; + + auto self_ec_point_store = std::make_shared<MemoryEcPointStore>(); + auto peer_ec_point_store = std::make_shared<MemoryEcPointStore>(); + auto batch_provider = + std::make_shared<MemoryBatchProvider>(items, batch_size); + + RunEcdhPsi(options, batch_provider, self_ec_point_store, peer_ec_point_store); + + // Originally we should setup a hashset for peer results. + // But tests show that when items_count > 10,000,000, the performance of + // |std::unordered_set| or |absl::flat_hash_set| drops significantly. + // Besides, these hashset containers require more memory. + // Here we choose the compact data structure and stable find costs. + std::vector<std::string> ret; + std::vector<std::string> peer_results(peer_ec_point_store->content()); + std::sort(peer_results.begin(), peer_results.end()); + const auto& self_results = self_ec_point_store->content(); + for (uint32_t index = 0; index < self_results.size(); index++) { + if (std::binary_search(peer_results.begin(), peer_results.end(), + self_results[index])) { + YACL_ENFORCE(index < items.size()); + ret.push_back(items[index]); + } + } + return ret; +} + +} // namespace psi::psi diff --git a/psi/psi/core/ecdh_psi.h b/psi/psi/core/ecdh_psi.h new file mode 100644 index 00000000..75d3edde --- /dev/null +++ b/psi/psi/core/ecdh_psi.h @@ -0,0 +1,160 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <cstdint> +#include <functional> +#include <memory> +#include <string> +#include <string_view> +#include <vector> + +#include "yacl/link/link.h" + +#include "psi/psi/core/communication.h" +#include "psi/psi/cryptor/ecc_cryptor.h" +#include "psi/psi/recovery.h" +#include "psi/psi/utils/batch_provider.h" +#include "psi/psi/utils/ec_point_store.h" + +#include "psi/psi/utils/serializable.pb.h" + +namespace psi::psi { + +using FinishBatchHook = std::function<void(size_t)>; + +struct EcdhPsiStatistics { + size_t self_item_count = 0; + size_t peer_item_count = 0; +}; + +struct EcdhPsiOptions { + // Provides the link for the rank world. + std::shared_ptr<yacl::link::Context> link_ctx; + + // ic_mode = true: 互联互通模式,对方可以是非隐语应用 + // Interconnection mode, the other side can be non-secretflow application + bool ic_mode = false; + + // Provides private inputs for ecdh-psi. + std::shared_ptr<IEccCryptor> ecc_cryptor; + + size_t dual_mask_size = kFinalCompareBytes; + + // batch_size + // batch compute dh mask + // batch send and read + size_t batch_size = kEcdhPsiBatchSize; + + // Points out which rank the psi results should be revealed. + // + // Allowed values: + // - `link::kAllRank` i.e. std::numeric_limits<size_t>::max(), means reveal + // to all rank + // - otherwise the psi results should only revealed to the `target_rank` + size_t target_rank = yacl::link::kAllRank; + + // Finish batch callback. Could be used for logging or update progress. + FinishBatchHook on_batch_finished; + + // Collect information such as the input size of psi. + EcdhPsiStatistics* statistics = nullptr; + + // Optional RecoveryManager to save checkpoints. + std::shared_ptr<RecoveryManager> recovery_manager = nullptr; +}; + +// batch handler for 2-party ecdh psi +class EcdhPsiContext { + public: + explicit EcdhPsiContext(EcdhPsiOptions options); + ~EcdhPsiContext() = default; + + void CheckConfig(); + + // one of main steps for 2 party ecdh psi + // mask self items then send them to peer + void MaskSelf(const std::shared_ptr<IBasicBatchProvider>& batch_provider, + uint64_t processed_item_cnt = 0); + + // one of main steps for 2 party ecdh psi + // recv peer's masked items, then masked them again and send back + // ec_point_store: available only when target_rank equal to self_rank + void MaskPeer(const std::shared_ptr<IEcPointStore>& peer_ec_point_store); + + // one of main steps for 2 party ecdh psi + // recv dual masked self items from peer + // ec_point_store: available only when target_rank equal to self_rank + void RecvDualMaskedSelf( + const std::shared_ptr<IEcPointStore>& self_ec_point_store); + + size_t PeerRank() { return options_.link_ctx->NextRank(); } + + std::string Id() const { return id_; } + + [[nodiscard]] bool SelfCanTouchResults() const { + return options_.target_rank == yacl::link::kAllRank || + options_.target_rank == options_.link_ctx->Rank(); + } + + [[nodiscard]] bool PeerCanTouchResults() const { + return options_.target_rank == yacl::link::kAllRank || + options_.target_rank == options_.link_ctx->NextRank(); + } + + protected: + void SendBatch(const std::vector<std::string>& batch_items, int32_t batch_idx, + std::string_view tag = ""); + + void SendBatch(const std::vector<std::string_view>& batch_items, + int32_t batch_idx, std::string_view tag = ""); + + void RecvBatch(std::vector<std::string>* items, int32_t batch_idx, + std::string_view tag = ""); + + void SendDualMaskedBatch(const std::vector<std::string>& batch_items, + int32_t batch_idx, std::string_view tag = ""); + + void SendDualMaskedBatch(const std::vector<std::string_view>& batch_items, + int32_t batch_idx, std::string_view tag = ""); + + void SendDualMaskedBatchNonBlock(const std::vector<std::string>& batch_items, + int32_t batch_idx, + std::string_view tag = ""); + + void RecvDualMaskedBatch(std::vector<std::string>* items, int32_t batch_idx, + std::string_view tag = ""); + + EcdhPsiOptions options_; + + std::shared_ptr<yacl::link::Context> main_link_ctx_; + std::shared_ptr<yacl::link::Context> dual_mask_link_ctx_; + const std::string id_; +}; + +void RunEcdhPsi(const EcdhPsiOptions& options, + const std::shared_ptr<IBasicBatchProvider>& batch_provider, + const std::shared_ptr<IEcPointStore>& self_ec_point_store, + const std::shared_ptr<IEcPointStore>& peer_ec_point_store); + +// Simple wrapper for a common in memory psi case. It always use cpu based +// ecc cryptor. +std::vector<std::string> RunEcdhPsi( + const std::shared_ptr<yacl::link::Context>& link_ctx, + const std::vector<std::string>& items, size_t target_rank, + CurveType curve = CurveType::CURVE_25519, + size_t batch_size = kEcdhPsiBatchSize); + +} // namespace psi::psi diff --git a/psi/psi/core/ecdh_psi_bench.cc b/psi/psi/core/ecdh_psi_bench.cc new file mode 100644 index 00000000..5796fdf6 --- /dev/null +++ b/psi/psi/core/ecdh_psi_bench.cc @@ -0,0 +1,88 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <future> +#include <iostream> +#include <optional> + +#include "benchmark/benchmark.h" +#include "yacl/base/exception.h" +#include "yacl/link/test_util.h" + +#include "psi/psi/core/ecdh_psi.h" +#include "psi/psi/cryptor/cryptor_selector.h" +#include "psi/psi/utils/batch_provider.h" +#include "psi/psi/utils/ec_point_store.h" + +namespace { + +std::vector<std::string> CreateRangeItems(size_t begin, size_t size) { + std::vector<std::string> ret(size); + for (size_t i = 0; i < size; i++) { + ret[i] = std::to_string(begin + i); + } + return ret; +} + +std::optional<psi::psi::CurveType> GetOverrideCurveType() { + if (const auto* env = std::getenv("OVERRIDE_CURVE")) { + if (std::strcmp(env, "25519") == 0) { + return psi::psi::CurveType::CURVE_25519; + } + if (std::strcmp(env, "FOURQ") == 0) { + return psi::psi::CurveType::CURVE_FOURQ; + } + } + return {}; +} + +} // namespace + +static void BM_EcdhPsi(benchmark::State& state) { + for (auto _ : state) { + state.PauseTiming(); + size_t n = state.range(0); + auto alice_items = CreateRangeItems(1, n); + auto bob_items = CreateRangeItems(2, n); + + auto ctxs = yacl::link::test::SetupWorld(2); + auto proc = [](const std::shared_ptr<yacl::link::Context>& ctx, + const std::vector<std::string>& items, + size_t target_rank) -> std::vector<std::string> { + const auto curve = GetOverrideCurveType(); + return psi::psi::RunEcdhPsi( + ctx, items, target_rank, + curve.has_value() ? *curve : psi::psi::CurveType::CURVE_25519); + }; + + state.ResumeTiming(); + + std::future<std::vector<std::string>> fa = + std::async(proc, ctxs[0], alice_items, 0); + std::future<std::vector<std::string>> fb = + std::async(proc, ctxs[1], bob_items, 0); + + auto results_a = fa.get(); + auto results_b = fb.get(); + } +} + +// [256k, 512k, 1m, 2m, 4m, 8m] +BENCHMARK(BM_EcdhPsi) + ->Arg(256 << 10) + ->Arg(512 << 10) + ->Arg(1 << 20) + ->Arg(2 << 20) + ->Arg(4 << 20) + ->Arg(8 << 20); diff --git a/psi/psi/core/ecdh_psi_test.cc b/psi/psi/core/ecdh_psi_test.cc new file mode 100644 index 00000000..c4a87863 --- /dev/null +++ b/psi/psi/core/ecdh_psi_test.cc @@ -0,0 +1,174 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/ecdh_psi.h" + +#include <future> +#include <iostream> + +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" +#include "yacl/link/test_util.h" + +#include "psi/psi/utils/test_utils.h" + +struct TestParams { + std::vector<std::string> items_a; + std::vector<std::string> items_b; + size_t target_rank; + psi::psi::CurveType curve_type = psi::psi::CurveType::CURVE_25519; +}; + +namespace std { + +std::ostream& operator<<(std::ostream& out, const TestParams& params) { + out << "target_rank=" << params.target_rank; + return out; +} + +} // namespace std + +namespace psi::psi { + +TEST(EcdhPsiTestFailed, TargetRankMismatched) { + for (std::pair<size_t, size_t> ranks : std::vector<std::pair<size_t, size_t>>{ + {0, 1}, {0, yacl::link::kAllRank}, {1, yacl::link::kAllRank}}) { + auto ctxs = yacl::link::test::SetupWorld(2); + auto proc = [&](const std::shared_ptr<yacl::link::Context>& ctx, + const std::vector<std::string>& items, + size_t target_rank) -> std::vector<std::string> { + return RunEcdhPsi(ctx, items, target_rank); + }; + + std::future<std::vector<std::string>> fa = + std::async(proc, ctxs[0], std::vector<std::string>{}, ranks.first); + std::future<std::vector<std::string>> fb = + std::async(proc, ctxs[1], std::vector<std::string>{}, ranks.second); + + ASSERT_THROW(fa.get(), ::yacl::EnforceNotMet); + ASSERT_THROW(fb.get(), ::yacl::EnforceNotMet); + } +} + +TEST(EcdhPsiTestFailed, CurveTypeMismatched) { + std::pair<CurveType, CurveType> curves = {CurveType::CURVE_FOURQ, + CurveType::CURVE_25519}; + + auto ctxs = yacl::link::test::SetupWorld(2); + auto proc = [&](const std::shared_ptr<yacl::link::Context>& ctx, + const std::vector<std::string>& items, + CurveType type) -> std::vector<std::string> { + return RunEcdhPsi(ctx, items, yacl::link::kAllRank, type); + }; + + std::future<std::vector<std::string>> fa = + std::async(proc, ctxs[0], std::vector<std::string>{}, curves.first); + std::future<std::vector<std::string>> fb = + std::async(proc, ctxs[1], std::vector<std::string>{}, curves.second); + + ASSERT_THROW(fa.get(), ::yacl::EnforceNotMet); + ASSERT_THROW(fb.get(), ::yacl::EnforceNotMet); +} + +class EcdhPsiTest : public testing::TestWithParam<TestParams> {}; + +TEST_P(EcdhPsiTest, Works) { + auto params = GetParam(); + auto ctxs = yacl::link::test::SetupWorld(2); + auto proc = + [&](const std::shared_ptr<yacl::link::Context>& ctx, + const std::vector<std::string>& items) -> std::vector<std::string> { + return RunEcdhPsi(ctx, items, params.target_rank, params.curve_type); + }; + + std::future<std::vector<std::string>> fa = + std::async(proc, ctxs[0], params.items_a); + std::future<std::vector<std::string>> fb = + std::async(proc, ctxs[1], params.items_b); + + auto results_a = fa.get(); + auto results_b = fb.get(); + + auto intersection = test::GetIntersection(params.items_a, params.items_b); + if (params.target_rank == yacl::link::kAllRank || params.target_rank == 0) { + EXPECT_EQ(results_a, intersection); + } else { + EXPECT_TRUE(results_a.empty()); + } + if (params.target_rank == yacl::link::kAllRank || params.target_rank == 1) { + EXPECT_EQ(results_b, intersection); + } else { + EXPECT_TRUE(results_b.empty()); + } +} + +INSTANTIATE_TEST_SUITE_P( + Works_Instances, EcdhPsiTest, + testing::Values( + TestParams{{"a", "b"}, {"b", "c"}, yacl::link::kAllRank}, // + TestParams{{"a", "b"}, {"b", "c"}, 0}, // + TestParams{{"a", "b"}, {"b", "c"}, 1}, // + // + TestParams{{"a", "b"}, {"c", "d"}, yacl::link::kAllRank}, // + TestParams{{"a", "b"}, {"c", "d"}, 0}, // + TestParams{{"a", "b"}, {"c", "d"}, 1}, // + // + TestParams{{}, {"a"}, yacl::link::kAllRank}, // + TestParams{{}, {"a"}, 0}, // + TestParams{{}, {"a"}, 1}, // + // + TestParams{{"a"}, {}, yacl::link::kAllRank}, // + TestParams{{"a"}, {}, 0}, // + TestParams{{"a"}, {}, 1}, // + // less than one batch + TestParams{test::CreateRangeItems(0, 4095), + test::CreateRangeItems(1, 4095), yacl::link::kAllRank}, // + TestParams{test::CreateRangeItems(0, 4095), + test::CreateRangeItems(1, 4095), 0}, // + TestParams{test::CreateRangeItems(0, 4095), + test::CreateRangeItems(1, 4095), 1}, // + // exactly one batch + TestParams{test::CreateRangeItems(0, 4096), + test::CreateRangeItems(1, 4096), yacl::link::kAllRank}, // + TestParams{test::CreateRangeItems(0, 4096), + test::CreateRangeItems(1, 4096), 0}, // + TestParams{test::CreateRangeItems(0, 4096), + test::CreateRangeItems(1, 4096), 1}, // + // more than one batch + TestParams{test::CreateRangeItems(0, 40961), + test::CreateRangeItems(5, 40961), yacl::link::kAllRank}, // + TestParams{test::CreateRangeItems(0, 40961), + test::CreateRangeItems(5, 40961), 0}, // + TestParams{test::CreateRangeItems(0, 40961), + test::CreateRangeItems(5, 40961), 1}, // + // + TestParams{{}, {}, yacl::link::kAllRank}, // + TestParams{{}, {}, 0}, // + TestParams{{}, {}, 1}, // + // test sm2 + TestParams{test::CreateRangeItems(0, 4096), + test::CreateRangeItems(1, 4095), yacl::link::kAllRank, + CurveType::CURVE_SM2}, // + // exactly one batch + TestParams{test::CreateRangeItems(0, 4096), + test::CreateRangeItems(1, 4096), yacl::link::kAllRank, + CurveType::CURVE_SECP256K1}, // + // more than one batch + TestParams{test::CreateRangeItems(0, 4096), + test::CreateRangeItems(1, 4096), yacl::link::kAllRank, + CurveType::CURVE_FOURQ} // + )); + +} // namespace psi::psi diff --git a/psi/psi/core/generate_psi.py b/psi/psi/core/generate_psi.py new file mode 100644 index 00000000..06263db7 --- /dev/null +++ b/psi/psi/core/generate_psi.py @@ -0,0 +1,75 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from random import randint +from random import sample +import csv +import sys + + +def random_with_N_digits(n): + range_start = 10 ** (n - 1) + range_end = (10**n) - 1 + return randint(range_start, range_end) + + +row_list = [] +len1 = 10**2 +len2 = 10 +len3 = 10 +len4 = 10 + +if len(sys.argv) > 1: + len1 = int(sys.argv[1]) + len2 = int(len1 / 2) + +if len(sys.argv) > 2: + len3 = int(sys.argv[2]) + +len4 = int(len3 / 2) +print(len1, len2) + + +for i in range(len1): + data_list = [random_with_N_digits(18)] + row_list.append(data_list) + +row_list2 = sample(row_list, len2) +for i in range(len2, len1): + data_list = [random_with_N_digits(18)] + row_list2.append(data_list) + +row_list3 = sample(row_list, len4) +for i in range(len4, len3): + data_list = [random_with_N_digits(18)] + row_list3.append(data_list) + +print(len(row_list2)) +print(len(row_list3)) + +with open('psi_1.csv', 'w', newline='') as file: + writer = csv.writer(file) + writer.writerow(["id"]) + writer.writerows(row_list) + +with open('psi_2.csv', 'w', newline='') as file: + writer = csv.writer(file) + writer.writerow(["id"]) + writer.writerows(row_list2) + +with open('psi_3.csv', 'w', newline='') as file: + writer = csv.writer(file) + writer.writerow(["id"]) + writer.writerows(row_list3) diff --git a/psi/psi/core/kkrt_psi.cc b/psi/psi/core/kkrt_psi.cc new file mode 100644 index 00000000..212a2338 --- /dev/null +++ b/psi/psi/core/kkrt_psi.cc @@ -0,0 +1,417 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/kkrt_psi.h" + +#include <future> +#include <numeric> + +#include "absl/strings/escaping.h" +#include "openssl/crypto.h" +#include "openssl/rand.h" +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/crypto/primitives/ot/base_ot.h" +#include "yacl/crypto/primitives/ot/iknp_ote.h" +#include "yacl/crypto/primitives/ot/kkrt_ote.h" +#include "yacl/crypto/utils/rand.h" + +#include "psi/psi/core/communication.h" +#include "psi/psi/core/cuckoo_index.h" +#include "psi/psi/utils/serialize.h" + +namespace psi::psi { + +namespace { +// constexpr size_t kPsiDataBatchSize = 1 << 10; +constexpr size_t kPsiDataBatchSize = 1024; +constexpr size_t kStashSize = 0; +constexpr size_t kCuckooHashNum = 3; +constexpr size_t kStatSecParam = 40; +constexpr size_t kKkrtOtBatchSize = (65535 / 4 / 16 * 0.8); // NOLINT + +// send set size to peer +// get peer's item size +// +size_t ExchangeSetSize(const std::shared_ptr<yacl::link::Context>& link_ctx, + size_t items_size) { + size_t input_size = items_size; + + link_ctx->SendAsyncThrottled( + link_ctx->NextRank(), utils::SerializeSize(input_size), + fmt::format("KKRT:PSI:SELF_SIZE={}", items_size)); + + size_t peer_size = utils::DeserializeSize( + link_ctx->Recv(link_ctx->NextRank(), fmt::format("KKRT:PSI:PEER_SIZE"))); + + return peer_size; +} + +// +// stat_sec_param = 40, data_size 2^40 encode size is 15 +// data_size > 2^40, use encode size 16, +// hash collision probability will > 2^-40 +inline uint64_t KkrtEncodeSize(uint64_t stat_sec_param, uint128_t self_size, + uint128_t peer_size) { + uint64_t encode_size = + (stat_sec_param + std::log2l(self_size * peer_size) + 7) / 8; + return std::min(encode_size, static_cast<uint64_t>(sizeof(uint128_t))); +} + +} // namespace + +KkrtPsiOptions GetDefaultKkrtPsiOptions() { + KkrtPsiOptions kkrt_psi_options; + + kkrt_psi_options.ot_batch_size = kKkrtOtBatchSize; + kkrt_psi_options.psi_batch_size = kPsiDataBatchSize; + + kkrt_psi_options.stash_size = kStashSize; + kkrt_psi_options.cuckoo_hash_num = kCuckooHashNum; + kkrt_psi_options.stat_sec_param = kStatSecParam; + + return kkrt_psi_options; +} + +// first use baseOT get 128 ots +// then, use iknpOtExtension get 512 ots +yacl::crypto::OtRecvStore GetKkrtOtSenderOptions( + const std::shared_ptr<yacl::link::Context>& link_ctx, const size_t num_ot) { + // use base ot get 128 ots + auto base_ot = yacl::crypto::BaseOtSend(link_ctx, 128); + auto choice = yacl::crypto::RandBits(num_ot); + + return yacl::crypto::IknpOtExtRecv(link_ctx, base_ot, choice, num_ot); +} + +yacl::crypto::OtSendStore GetKkrtOtReceiverOptions( + const std::shared_ptr<yacl::link::Context>& link_ctx, const size_t num_ot) { + // use base ot get 128 ots + size_t base_ot_num = 128; + auto base_ot_choice = yacl::crypto::RandBits(base_ot_num); + auto base_ot = yacl::crypto::BaseOtRecv(link_ctx, base_ot_choice, 128); + + return yacl::crypto::IknpOtExtSend(link_ctx, base_ot, num_ot); +} + +void KkrtPsiSend(const std::shared_ptr<yacl::link::Context>& link_ctx, + const KkrtPsiOptions& kkrt_psi_options, // with kkrt options + const yacl::crypto::OtRecvStore& ot_recv, + const std::vector<uint128_t>& items_hash) { + YACL_ENFORCE((kkrt_psi_options.cuckoo_hash_num == 3) && + (kkrt_psi_options.stash_size == 0), + "now only support cuckoo HashNum = 3 , stash size = 0"); + YACL_ENFORCE(ot_recv.Size() == 512, + "now only support baseRecvOption block size 512"); + + size_t self_size = items_hash.size(); + size_t peer_size = ExchangeSetSize(link_ctx, self_size); + YACL_ENFORCE((peer_size > 0) && (self_size > 0), + "item size need not zero, mine={}, peer={}", self_size, + peer_size); + + uint64_t encode_size = + KkrtEncodeSize(kkrt_psi_options.stat_sec_param, self_size, + peer_size); // by byte + + CuckooIndex::Options option = CuckooIndex::SelectParams( + peer_size, kkrt_psi_options.stash_size, kkrt_psi_options.cuckoo_hash_num); + size_t num_bins = option.NumBins(); + + yacl::crypto::KkrtOtExtSender sender; + sender.Init(link_ctx, ot_recv, option.NumBins()); + sender.SetBatchSize(kKkrtOtBatchSize); + uint64_t kkrtOtBatchSize = sender.GetBatchSize(); + + std::atomic<uint64_t> recv_idx(0); + auto f_recv_corrections = std::async([&]() { + // while there are more corrections for be received + size_t correction_batch_idx = 0; + while (recv_idx < num_bins) { + // compute the size of the current step and the end index + size_t current_step_size = std::min(kkrtOtBatchSize, num_bins - recv_idx); + + // receive the corrections. + auto current_correction_buf = link_ctx->Recv( + link_ctx->NextRank(), + fmt::format("KKRT:PSI:ThrottleControlReceiver recv batch_count:{}", + correction_batch_idx)); + sender.SetCorrection(current_correction_buf, current_step_size); + correction_batch_idx++; + + // notify the other thread that the corrections have arrived + recv_idx.fetch_add(current_step_size, std::memory_order_release); + } + }); + + // permute sender input data + std::vector<size_t> input_permute; + std::vector<size_t> input_permute_inv; + input_permute.resize(self_size); + input_permute_inv.resize(self_size); + + std::iota(input_permute.begin(), input_permute.end(), 0); + + yacl::crypto::Prg<uint128_t> prg(yacl::crypto::RandSeed()); + uint64_t mt_seed; + prg.Fill( + absl::MakeSpan(reinterpret_cast<uint8_t*>(&mt_seed), sizeof(mt_seed))); + std::mt19937 rng(mt_seed); + std::shuffle(input_permute.begin(), input_permute.end(), rng); + for (size_t i = 0; i < self_size; i++) { + input_permute_inv[input_permute[i]] = i; + } + + // hash bucketing + yacl::Buffer encode_buf(self_size * kkrt_psi_options.cuckoo_hash_num * + encode_size); + std::vector<std::array<uint64_t, kCuckooHashNum>> bin_indices; + bin_indices.resize(self_size); + + for (size_t i = 0; i < self_size; ++i) { + CuckooIndex::HashRoom itemHash(items_hash[i]); + uint64_t bin_idx0 = itemHash.GetHash(0) % num_bins; + uint64_t bin_idx1 = itemHash.GetHash(1) % num_bins; + uint64_t bin_idx2 = itemHash.GetHash(2) % num_bins; + + bin_indices[i][0] = bin_idx0; + // check collision + uint8_t c01 = (bin_idx0 == bin_idx1) ? 1 : 0; + bin_indices[i][1] = bin_idx1 | (c01 * static_cast<uint64_t>(-1)); + uint8_t c02 = (bin_idx0 == bin_idx2 || bin_idx1 == bin_idx2) ? 1 : 0; + bin_indices[i][2] = bin_idx2 | (c02 * static_cast<uint64_t>(-1)); + if (c01 == 1) { + uint8_t* encode_pos = + encode_buf.data<uint8_t>() + + (input_permute_inv[i] * kkrt_psi_options.cuckoo_hash_num + 1) * + encode_size; + prg.Fill(absl::MakeSpan(encode_pos, encode_size)); + } + if (c02 == 1) { + uint8_t* encode_pos = + encode_buf.data<uint8_t>() + + (input_permute_inv[i] * kkrt_psi_options.cuckoo_hash_num + 2) * + encode_size; + prg.Fill(absl::MakeSpan(encode_pos, encode_size)); + } + } + + uint64_t t = 0; + uint64_t r = 0; + // while not all the corrections have been received, try to encode any that + // we can + // TODO(shuyan.ycf): this implementation wastes cpus if the networking is + // slow (Due to spin logics). Better use synchronization primitives. + while (r != num_bins) { + // process things in steps + for (uint64_t j = 0; j < kkrtOtBatchSize; ++j) { + // lets check a random item to see if it can be encoded. If so, + // we will write this item's encodings in the myMaskBuff at position i. + auto input_idx = input_permute[t]; + + // for each hash function, try to encode the item. + for (uint64_t h = 0; h < kkrt_psi_options.cuckoo_hash_num; ++h) { + uint64_t b_idx = bin_indices[input_idx][h]; + + // if the bin index is less than r, then we have received + // the correction and can encode it + if (b_idx < r) { + // write the encoding into encode_buf at position t, h + uint8_t* encoding = + encode_buf.data<uint8_t>() + + (t * kkrt_psi_options.cuckoo_hash_num + h) * encode_size; + sender.Encode(b_idx, items_hash[input_idx], encoding, encode_size); + + // make this location as already been encoded + bin_indices[input_idx][h] = -1; + } + } + + // wrap around the input looking for items that we can encode + t = (t + 1) % self_size; + } + + // after stepSize attempts to encode items, lets see if more + // corrections have arrived. + r = recv_idx.load(std::memory_order_acquire); + } + + // Join receiving thread and throw exceptions if any thing is wrong. + f_recv_corrections.get(); + + // encoding buffer + for (size_t i = 0; i < self_size;) { + size_t curr_step_item_num = + std::min(kkrt_psi_options.psi_batch_size, self_size - i); + size_t curr_step_encode_num = + curr_step_item_num * kkrt_psi_options.cuckoo_hash_num; + + PsiDataBatch batch; + batch.flatten_bytes.resize(encode_size * curr_step_encode_num); + uint8_t* encoding = encode_buf.data<uint8_t>() + + (i * kkrt_psi_options.cuckoo_hash_num) * encode_size; + for (size_t j = 0; j < curr_step_item_num; ++j) { + auto input_idx = input_permute[i + j]; + + for (size_t k = 0; k < kkrt_psi_options.cuckoo_hash_num; k++) { + uint64_t b_idx = bin_indices[input_idx][k]; + + if (b_idx != static_cast<uint64_t>(-1)) { + sender.Encode(b_idx, items_hash[input_idx], encoding, encode_size); + } + encoding += encode_size; + } + } + + batch.item_num = curr_step_item_num; + batch.is_last_batch = false; + + encoding = encode_buf.data<uint8_t>() + + (i * kkrt_psi_options.cuckoo_hash_num) * encode_size; + memcpy(batch.flatten_bytes.data(), encoding, + encode_size * curr_step_encode_num); + + i += curr_step_item_num; + if (i == self_size) { + batch.is_last_batch = true; + } + + link_ctx->SendAsyncThrottled( + link_ctx->NextRank(), batch.Serialize(), + fmt::format("KKRT:PSI:SENDER OPRF:{}", curr_step_item_num)); + } + + const char* finish_str = "kkrt finish"; + + link_ctx->SendAsyncThrottled(link_ctx->NextRank(), finish_str, + fmt::format("KKRT:PSI:Finished")); +} + +std::vector<std::size_t> KkrtPsiRecv( + const std::shared_ptr<yacl::link::Context>& link_ctx, + const KkrtPsiOptions& kkrt_psi_options, // with kkrt options + const yacl::crypto::OtSendStore& ot_send, + const std::vector<uint128_t>& items_hash) { + YACL_ENFORCE((kkrt_psi_options.cuckoo_hash_num == 3) && + (kkrt_psi_options.stash_size == 0), + "now only support cuckoo HashNum = 3 , stash size = 0"); + + YACL_ENFORCE(ot_send.Size() == 512, + "now only support yacl::OtSendStore block size 512"); + + std::vector<std::size_t> ret_intersection; + + size_t self_size = items_hash.size(); + size_t peer_size = ExchangeSetSize(link_ctx, self_size); + + YACL_ENFORCE((peer_size > 0) && (!items_hash.empty()), + "item size need not zero, mine={}, peer={}", self_size, + peer_size); + + CuckooIndex::Options option = CuckooIndex::SelectParams( + self_size, kkrt_psi_options.stash_size, kkrt_psi_options.cuckoo_hash_num); + CuckooIndex cuckoo_index(option); + cuckoo_index.Insert(absl::MakeSpan(items_hash)); + YACL_ENFORCE(cuckoo_index.stash().empty(), "stash size not 0"); + size_t kkrt_ot_num = cuckoo_index.bins().size(); + + yacl::crypto::KkrtOtExtReceiver receiver; + receiver.Init(link_ctx, ot_send, kkrt_ot_num); + receiver.SetBatchSize(kkrt_psi_options.ot_batch_size); + uint64_t kkrt_ot_batch_size = receiver.GetBatchSize(); + + std::array<std::unordered_map<std::string, size_t>, kCuckooHashNum> + oprf_encode_map; + for (size_t i = 0; i < kCuckooHashNum; i++) { + oprf_encode_map[i].reserve(kkrt_ot_num); + } + uint64_t encode_size = + KkrtEncodeSize(kkrt_psi_options.stat_sec_param, self_size, + peer_size); // by byte + + // encoding prf & send correction + auto ck_bins = cuckoo_index.bins(); + std::string encode_str(encode_size, '\0'); + const size_t ot_num_batch = + (kkrt_ot_num + kkrt_ot_batch_size - 1) / kkrt_ot_batch_size; + for (size_t batch_idx = 0; batch_idx < ot_num_batch; ++batch_idx) { + const size_t num_this_batch = std::min<size_t>( + kkrt_ot_num - batch_idx * kkrt_ot_batch_size, kkrt_ot_batch_size); + + size_t batch_start = batch_idx * kkrt_ot_batch_size; + for (size_t i = 0; i < num_this_batch; ++i) { + size_t current_idx = batch_start + i; + if (ck_bins[current_idx].IsEmpty()) { + receiver.ZeroEncode(current_idx); + } else { + uint128_t input_item = items_hash[ck_bins[current_idx].InputIdx()]; + receiver.Encode( + current_idx, input_item, + absl::Span<uint8_t>(reinterpret_cast<uint8_t*>(encode_str.data()), + encode_size)); + + uint8_t min_hash_idx = cuckoo_index.MinCollidingHashIdx(current_idx); + oprf_encode_map[min_hash_idx].emplace(encode_str, + ck_bins[current_idx].InputIdx()); + } + } + auto send_buf = receiver.ShiftCorrection(num_this_batch); + link_ctx->SendAsyncThrottled( + link_ctx->NextRank(), send_buf, + fmt::format("KKRT_PSI:sendCorrection:{}", batch_idx)); + } + + size_t batch_count = 0; + while (true) { + // Receive sender prf encode. + PsiDataBatch batch = PsiDataBatch::Deserialize(link_ctx->Recv( + link_ctx->NextRank(), + fmt::format("KKRT:PSI:RECEIVE:Receive sender prf encode:{}", + batch_count))); + batch_count++; + + const bool is_last_batch = batch.is_last_batch; + + size_t curr_step_item_num = batch.item_num; + size_t curr_step_encode_num = + curr_step_item_num * kkrt_psi_options.cuckoo_hash_num; + YACL_ENFORCE_EQ(batch.flatten_bytes.size(), + (curr_step_encode_num * encode_size)); + + for (size_t i = 0; i < curr_step_item_num; ++i) { + for (size_t j = 0; j < kkrt_psi_options.cuckoo_hash_num; ++j) { + std::string encode_sub_str = batch.flatten_bytes.substr( + (i * kkrt_psi_options.cuckoo_hash_num + j) * encode_size, + encode_size); + + auto it = oprf_encode_map[j].find(encode_sub_str); + if (it != oprf_encode_map[j].end()) { + ret_intersection.emplace_back(it->second); + } + } + } + + if (is_last_batch) { + break; + } + } + + link_ctx->Recv(link_ctx->NextRank(), + fmt::format("KKRT:PSI:Wait Sender Finished")); + + return ret_intersection; +} + +} // namespace psi::psi diff --git a/psi/psi/core/kkrt_psi.h b/psi/psi/core/kkrt_psi.h new file mode 100644 index 00000000..ce551308 --- /dev/null +++ b/psi/psi/core/kkrt_psi.h @@ -0,0 +1,93 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <algorithm> +#include <functional> +#include <memory> +#include <string> +#include <unordered_map> +#include <vector> + +#include "yacl/crypto/primitives/ot/ot_store.h" +#include "yacl/link/link.h" + +// +// implementation of KKRT16 PSI protocol +// https://eprint.iacr.org/2016/799.pdf +// +// use Stash less Cuckoo hash optimization +// Reference: +// PSZ18 Scalable private set intersection based on ot extension +// https://eprint.iacr.org/2016/930.pdf +// +namespace psi::psi { + +struct KkrtPsiOptions { + // batch size the receiver send corrections + size_t ot_batch_size = 128; + + // batch size the sender used to send oprf encode + size_t psi_batch_size = 128; + + // cuckoo hash parameter + // now use stashless setting + // stash_size = 0 cuckoo_hash_num =3 + // use stat_sec_param = 40 + size_t cuckoo_hash_num = 3; + size_t stash_size = 0; + size_t stat_sec_param = 40; +}; + +yacl::crypto::OtRecvStore GetKkrtOtSenderOptions( + const std::shared_ptr<yacl::link::Context>& link_ctx, size_t num_ot); + +yacl::crypto::OtSendStore GetKkrtOtReceiverOptions( + const std::shared_ptr<yacl::link::Context>& link_ctx, size_t num_ot); + +KkrtPsiOptions GetDefaultKkrtPsiOptions(); + +// +// sender and receiver psi input data shoud be prepocessed using hash algorithm. +// like sha256 or blake2/blake3 hash algorithm or aes_ecb(key, x)^x +// +void KkrtPsiSend(const std::shared_ptr<yacl::link::Context>& link_ctx, + const KkrtPsiOptions& kkrt_psi_options, // with kkrt options + const yacl::crypto::OtRecvStore& ot_recv, + const std::vector<uint128_t>& items_hash); + +std::vector<std::size_t> KkrtPsiRecv( + const std::shared_ptr<yacl::link::Context>& link_ctx, + const KkrtPsiOptions& kkrt_psi_options, // with kkrt options + const yacl::crypto::OtSendStore& ot_send, + const std::vector<uint128_t>& items_hash); + +// inline functions +inline void KkrtPsiSend(const std::shared_ptr<yacl::link::Context>& link_ctx, + const yacl::crypto::OtRecvStore& ot_recv, + const std::vector<uint128_t>& items_hash) { + KkrtPsiOptions kkrt_psi_options = GetDefaultKkrtPsiOptions(); + return KkrtPsiSend(link_ctx, kkrt_psi_options, ot_recv, items_hash); +} + +inline std::vector<std::size_t> KkrtPsiRecv( + const std::shared_ptr<yacl::link::Context>& link_ctx, + const yacl::crypto::OtSendStore& ot_send, + const std::vector<uint128_t>& items_hash) { + KkrtPsiOptions kkrt_psi_options = GetDefaultKkrtPsiOptions(); + return KkrtPsiRecv(link_ctx, kkrt_psi_options, ot_send, items_hash); +} + +} // namespace psi::psi diff --git a/psi/psi/core/kkrt_psi_bench.cc b/psi/psi/core/kkrt_psi_bench.cc new file mode 100644 index 00000000..14833a5a --- /dev/null +++ b/psi/psi/core/kkrt_psi_bench.cc @@ -0,0 +1,79 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <future> +#include <iostream> + +#include "benchmark/benchmark.h" +#include "yacl/base/exception.h" +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/link/test_util.h" + +#include "psi/psi/core/kkrt_psi.h" + +namespace { +std::vector<uint128_t> CreateRangeItems(size_t begin, size_t size) { + std::vector<uint128_t> ret(size); + for (size_t i = 0; i < size; i++) { + auto hash = yacl::crypto::Blake3(std::to_string(begin + i)); + memcpy(&ret[i], hash.data(), sizeof(uint128_t)); + } + return ret; +} + +void KkrtPsiSend(const std::shared_ptr<yacl::link::Context>& link_ctx, + const std::vector<uint128_t>& items_hash) { + auto ot_recv = psi::psi::GetKkrtOtSenderOptions(link_ctx, 512); + return psi::psi::KkrtPsiSend(link_ctx, ot_recv, items_hash); +} + +std::vector<std::size_t> KkrtPsiRecv( + const std::shared_ptr<yacl::link::Context>& link_ctx, + const std::vector<uint128_t>& items_hash) { + auto ot_send = psi::psi::GetKkrtOtReceiverOptions(link_ctx, 512); + return psi::psi::KkrtPsiRecv(link_ctx, ot_send, items_hash); +} + +} // namespace + +static void BM_KkrtPsi(benchmark::State& state) { + for (auto _ : state) { + state.PauseTiming(); + size_t n = state.range(0); + auto alice_items = CreateRangeItems(1, n); + auto bob_items = CreateRangeItems(2, n); + + auto contexts = yacl::link::test::SetupWorld(2); + + state.ResumeTiming(); + + std::future<void> kkrt_psi_sender = + std::async([&] { return KkrtPsiSend(contexts[0], alice_items); }); + std::future<std::vector<std::size_t>> kkrt_psi_receiver = + std::async([&] { return KkrtPsiRecv(contexts[1], bob_items); }); + + kkrt_psi_sender.get(); + auto results_b = kkrt_psi_receiver.get(); + } +} + +// [256k, 512k, 1m, 2m, 4m, 8m] +BENCHMARK(BM_KkrtPsi) + ->Unit(benchmark::kMillisecond) + ->Arg(256 << 10) + ->Arg(512 << 10) + ->Arg(1 << 20) + ->Arg(2 << 20) + ->Arg(4 << 20) + ->Arg(8 << 20); diff --git a/psi/psi/core/kkrt_psi_test.cc b/psi/psi/core/kkrt_psi_test.cc new file mode 100644 index 00000000..6400bf7e --- /dev/null +++ b/psi/psi/core/kkrt_psi_test.cc @@ -0,0 +1,132 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/kkrt_psi.h" + +#include <future> +#include <iostream> +#include <set> + +#include "gtest/gtest.h" +#include "yacl/base/exception.h" +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/link/test_util.h" + +struct TestParams { + std::vector<uint128_t> items_a; + std::vector<uint128_t> items_b; +}; + +namespace psi::psi { + +void KkrtPsiSend(const std::shared_ptr<yacl::link::Context>& link_ctx, + const std::vector<uint128_t>& items_hash) { + auto ot_recv = GetKkrtOtSenderOptions(link_ctx, 512); + return KkrtPsiSend(link_ctx, ot_recv, items_hash); +} + +std::vector<std::size_t> KkrtPsiRecv( + const std::shared_ptr<yacl::link::Context>& link_ctx, + const std::vector<uint128_t>& items_hash) { + auto ot_send = GetKkrtOtReceiverOptions(link_ctx, 512); + + return KkrtPsiRecv(link_ctx, ot_send, items_hash); +} + +std::vector<size_t> GetIntersection(const TestParams& params) { + std::set<uint128_t> seta(params.items_a.begin(), params.items_a.end()); + std::vector<size_t> ret; + std::set<size_t> ret_set; + size_t idx = 0; + for (const auto& s : params.items_b) { + if (seta.count(s) != 0) { + ret_set.insert(idx); + } + idx++; + } + ret.assign(ret_set.begin(), ret_set.end()); + return ret; +} + +class KkrtPsiTest : public testing::TestWithParam<TestParams> {}; + +TEST_P(KkrtPsiTest, Works) { + auto params = GetParam(); + const int kWorldSize = 2; + auto contexts = yacl::link::test::SetupWorld(kWorldSize); + + std::future<void> kkrtPsi_sender = + std::async([&] { return KkrtPsiSend(contexts[0], params.items_a); }); + std::future<std::vector<std::size_t>> kkrtPsi_receiver = + std::async([&] { return KkrtPsiRecv(contexts[1], params.items_b); }); + + if (params.items_a.empty() || params.items_b.empty()) { + EXPECT_THROW(kkrtPsi_sender.get(), ::yacl::EnforceNotMet); + EXPECT_THROW(kkrtPsi_receiver.get(), ::yacl::EnforceNotMet); + return; + } + kkrtPsi_sender.get(); + auto psi_idx_result = kkrtPsi_receiver.get(); + + std::sort(psi_idx_result.begin(), psi_idx_result.end()); + + auto intersection = GetIntersection(params); + + EXPECT_EQ(psi_idx_result, intersection); +} + +std::vector<uint128_t> CreateRangeItems(size_t begin, size_t size) { + std::vector<uint128_t> ret; + for (size_t i = 0; i < size; i++) { + ret.push_back(yacl::crypto::Blake3_128(std::to_string(begin + i))); + } + return ret; +} + +INSTANTIATE_TEST_SUITE_P( + Works_Instances, KkrtPsiTest, + testing::Values( + TestParams{ + {yacl::crypto::Blake3_128("a"), yacl::crypto::Blake3_128("b")}, + {yacl::crypto::Blake3_128("b"), yacl::crypto::Blake3_128("c")}}, // + // + TestParams{ + {yacl::crypto::Blake3_128("a"), yacl::crypto::Blake3_128("b")}, + {yacl::crypto::Blake3_128("c"), yacl::crypto::Blake3_128("d")}}, + // size not equal + TestParams{ + {yacl::crypto::Blake3_128("a"), yacl::crypto::Blake3_128("b"), + yacl::crypto::Blake3_128("c")}, + {yacl::crypto::Blake3_128("c"), yacl::crypto::Blake3_128("d")}}, // + TestParams{ + {yacl::crypto::Blake3_128("a"), yacl::crypto::Blake3_128("b")}, + {yacl::crypto::Blake3_128("b"), yacl::crypto::Blake3_128("c"), + yacl::crypto::Blake3_128("d")}}, // + // + TestParams{{}, {yacl::crypto::Blake3_128("a")}}, // + // + TestParams{{yacl::crypto::Blake3_128("a")}, {}}, // + // less than one batch + TestParams{CreateRangeItems(0, 1000), CreateRangeItems(1, 1000)}, // + TestParams{CreateRangeItems(0, 1000), CreateRangeItems(1, 800)}, // + TestParams{CreateRangeItems(0, 800), CreateRangeItems(1, 1000)}, // + // exactly one batch + TestParams{CreateRangeItems(0, 1024), CreateRangeItems(1, 1024)}, // + // more than one batch + TestParams{CreateRangeItems(0, 4095), CreateRangeItems(1, 4095)}, // + // + TestParams{{}, {}} // + )); + +} // namespace psi::psi diff --git a/psi/psi/core/labeled_psi/BUILD.bazel b/psi/psi/core/labeled_psi/BUILD.bazel new file mode 100644 index 00000000..3770420d --- /dev/null +++ b/psi/psi/core/labeled_psi/BUILD.bazel @@ -0,0 +1,119 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//bazel:psi.bzl", "psi_cc_binary", "psi_cc_library", "psi_cc_test") +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@rules_cc//cc:defs.bzl", "cc_proto_library") + +package(default_visibility = ["//visibility:public"]) + +psi_cc_library( + name = "labeled_psi", + srcs = [ + "package.cc", + "psi_params.cc", + "receiver.cc", + "sender.cc", + "sender_db.cc", + "sender_kvdb.cc", + "sender_memdb.cc", + ], + hdrs = [ + "package.h", + "psi_params.h", + "receiver.h", + "sender.h", + "sender_db.h", + "sender_kvdb.h", + "sender_memdb.h", + "serialize.h", + ], + deps = [ + ":serializable_cc_proto", + "//psi/psi/core/ecdh_oprf:ecdh_oprf_selector", + "//psi/psi/utils:batch_provider", + "@com_github_microsoft_apsi//:apsi", + "@com_google_absl//absl/strings", + "@yacl//yacl/base:exception", + "@yacl//yacl/crypto/tools:prg", + "@yacl//yacl/io/kv:leveldb_kvstore", + "@yacl//yacl/io/kv:memory_kvstore", + "@yacl//yacl/link", + "@yacl//yacl/utils:parallel", + ], +) + +proto_library( + name = "serializable_proto", + srcs = ["serializable.proto"], +) + +cc_proto_library( + name = "serializable_cc_proto", + deps = [":serializable_proto"], +) + +psi_cc_test( + name = "apsi_test", + srcs = [ + "apsi_test.cc", + ], + deps = [ + ":labeled_psi", + "@com_google_absl//absl/strings", + "@yacl//yacl/crypto/tools:prg", + "@yacl//yacl/utils:scope_guard", + ], +) + +psi_cc_test( + name = "apsi_label_test", + srcs = [ + "apsi_label_test.cc", + ], + deps = [ + ":labeled_psi", + "@com_google_absl//absl/strings", + "@yacl//yacl/crypto/tools:prg", + "@yacl//yacl/utils:scope_guard", + ], +) + +psi_cc_test( + name = "kv_test", + srcs = [ + "kv_test.cc", + ], + deps = [ + ":labeled_psi", + "//psi/psi/utils", + "@com_google_absl//absl/strings", + "@yacl//yacl/crypto/tools:prg", + "@yacl//yacl/utils:scope_guard", + ], +) + +psi_cc_binary( + name = "apsi_bench", + srcs = [ + "apsi_bench.cc", + ], + deps = [ + ":labeled_psi", + "@com_github_google_benchmark//:benchmark_main", + "@com_google_absl//absl/strings", + "@com_google_googletest//:gtest", + "@yacl//yacl/crypto/tools:prg", + ], +) diff --git a/psi/psi/core/labeled_psi/README.md b/psi/psi/core/labeled_psi/README.md new file mode 100644 index 00000000..a70a8362 --- /dev/null +++ b/psi/psi/core/labeled_psi/README.md @@ -0,0 +1,94 @@ +# LabelPSI + +## Introduction + +Fully homomorphic encryption (FHE) can be used to build efficient (labeled) Private Set Intersection protocols in the unbalanced setting, +where one of the sets is much larger than the other. + +Reference: + +- [Fast Private Set Intersection from Homomorphic Encryption](https://eprint.iacr.org/2017/299) +- [Labeled PSI from Fully Homomorphic Encryption with Malicious Security](https://eprint.iacr.org/2018/787) +- [Labeled PSI from Homomorphic Encryption with Reduced Computation and Communication (ACM CCS 2021)](https://eprint.iacr.org/2021/1116) +- [FHE and Private Set Intersection](https://simons.berkeley.edu/talks/fhe-and-private-set-intersection) +- [Private set intersection via somewhat homomorphic encryption by Ilia Iliashenko](https://fhe.org/talks/private-set-intersection) + +Microsoft [APSI (Asymmetric PSI) library](https://github.com/microsoft/APSI) provides a PSI functionality for asymmetric set sizes based +on the latest [Labeled PSI protocol](https://eprint.iacr.org/2021/1116). + +This is a wrap of [APSI library](https://github.com/microsoft/APSI), can be used for + +- Unbalanced PSI +- Malicious PSI +- Labeled PSI +- Keyword PIR + +## LabelPSI Protocol dataflow + +| | Client(Receiver) | | Server(Sender) | +| ------ | ---------------- | ------- | --------------- | +| Step 1 | Request Params | ------> | | +| | | <------ | Response Params | +| Step 2 | | | Setup Server DB | +| Step 3 | Request OPRF | ------> | | +| | | <------ | Response OPRF | +| Step 4 | Request Query | ------> | | +| | | <------ | Response Query | + +## LabelPSI source code + +| | file | class | function | +| --- | --------------- | ------------------ | ------------------- | +| 1 | psi_params.h/cc | | | +| | | | GetPsiParams | +| | | | PsiParamsToBuffer | +| | | | ParsePsiParamsProto | +| 2 | receiver.h/cc | | | +| | | LabelPsiReceiver | RequestPsiParams | +| | | | RequestOPRF | +| | | | RequestQuery | +| 3 | sender.h/cc | | | +| | | LabelPsiSender | RunPsiParams | +| | | | RunOPRF | +| | | | RunQuery | +| 4 | package.h/cc | | | +| | | PlainResultPackage | | +| | | ResultPackage | | +| 5 | sender_db.h/cc | | | +| | | SenderDB | SetData | +| | | | GetItemCount | +| | | | GetBinBundleCount | +| | | | GetPackingRate | + +## LabelPSI Parameters + +| | file | class | function | +| --- | ----------- | -------------------- | ------------------------------------------------------------------- | +| 1 | ItemParams | | | +| | | felts_per_item | how many Microsoft SEAL batching slots should represent each item | +| | | | = item_bit_size / plain_modulus_bits | +| | | | item_bit_size = stats_params + log(ns)+log(nr) | +| 2 | TableParams | | | +| | | hash_func_count | cuckoo hash count. if nr>1,hash_func_count = 3 | +| | | | nr=1-> hash_func_count=1 means essentially disabling cuckoo hashing | +| | | table_size | positive multiple of floor(poly_modulus_degree/felts_per_item) | +| | | max_items_per_bin | how many items fit into each row of the sender's bin bundles | +| 3 | QueryParams | | | +| | | ps_low_degree | any number between 0 and max_items_per_bin | +| | | | If set to zero, the Paterson-Stockmeyer algorithm is not used | +| | | | ps_low_degree > 1, use Paterson-Stockmeyer algorithm | +| | | query_powers | defines which encrypted powers of the query, receiver to sender | +| | | | ref Challis and Robinson (2010) to determine good source powers | +| 4 | SEALParams | | | +| | | poly_modulus_degree | 2048 / 4096 / 8192 | +| | | plain_modulus(_bits) | 65535 / 65535 / 22(bits) | +| | | coeff_modulus_bits | {48} / {48, 30, 30} / {56, 56, 56, 50} | + +[APSI](https://github.com/microsoft/APSI) example parameter sets in the [parameters](https://github.com/microsoft/APSI/tree/main/parameters) subdirectory. + +We select three SEALParams from [APSI parameters](https://github.com/microsoft/APSI/tree/main/parameters) for different receiver and sender items size. + +## Security Tips + +Warning: Labeled PSI are malicious PSI protocols, but malicious sender may do attack with his CuckooHash Table. +We suggest use Labeled PSI protocol as one-way PSI, i.e., just Client(Receiver) gets the final intersection result. diff --git a/psi/psi/core/labeled_psi/apsi_bench.cc b/psi/psi/core/labeled_psi/apsi_bench.cc new file mode 100644 index 00000000..91199925 --- /dev/null +++ b/psi/psi/core/labeled_psi/apsi_bench.cc @@ -0,0 +1,278 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <algorithm> +#include <chrono> +#include <future> +#include <random> +#include <string> + +#include "absl/strings/escaping.h" +#include "absl/strings/str_split.h" +#include "benchmark/benchmark.h" +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/crypto/tools/prg.h" +#include "yacl/link/test_util.h" + +#include "psi/psi/core/ecdh_oprf/ecdh_oprf_selector.h" +#include "psi/psi/core/labeled_psi/psi_params.h" +#include "psi/psi/core/labeled_psi/receiver.h" +#include "psi/psi/core/labeled_psi/sender.h" +#include "psi/psi/core/labeled_psi/sender_kvdb.h" + +namespace psi::psi { + +namespace { + +constexpr char kLinkAddrAB[] = "127.0.0.1:9532,127.0.0.1:9533"; +constexpr uint32_t kLinkRecvTimeout = 30 * 60 * 1000; +constexpr uint32_t kLinkWindowSize = 16; + +using duration_millis = std::chrono::duration<double, std::milli>; + +constexpr size_t kPsiStartPos = 100; + +std::vector<std::string> GenerateData(size_t seed, size_t item_count) { + yacl::crypto::Prg<uint128_t> prg(seed); + + std::vector<std::string> items; + + for (size_t i = 0; i < item_count; ++i) { + std::string item(16, '\0'); + prg.Fill(absl::MakeSpan(item.data(), item.length())); + items.emplace_back(absl::BytesToHexString(item)); + } + + return items; +} + +std::vector<std::string> GenerateSenderData( + size_t seed, size_t item_count, + const absl::Span<std::string>& receiver_items, + std::vector<size_t>* intersection_idx) { + std::vector<std::string> sender_items; + + yacl::crypto::Prg<uint128_t> prg(seed); + + for (size_t i = 0; i < item_count; ++i) { + std::string item(16, '\0'); + prg.Fill(absl::MakeSpan(item.data(), item.size())); + sender_items.emplace_back(absl::BytesToHexString(item)); + } + + for (size_t i = 0; i < receiver_items.size(); i += 3) { + if ((kPsiStartPos + i * 5) >= sender_items.size()) { + break; + } + sender_items[kPsiStartPos + i * 5] = receiver_items[i]; + (*intersection_idx).emplace_back(i); + } + + return sender_items; +} + +std::shared_ptr<yacl::link::Context> CreateContext( + int self_rank, yacl::link::ContextDesc& lctx_desc) { + std::shared_ptr<yacl::link::Context> link_ctx; + + yacl::link::FactoryBrpc factory; + link_ctx = factory.CreateContext(lctx_desc, self_rank); + link_ctx->ConnectToMesh(); + + return link_ctx; +} + +std::vector<std::shared_ptr<yacl::link::Context>> CreateLinks( + std::string host_str) { + std::vector<std::string> hosts = absl::StrSplit(host_str, ','); + yacl::link::ContextDesc lctx_desc; + for (size_t rank = 0; rank < hosts.size(); rank++) { + const std::string id = fmt::format("party{}", rank); + lctx_desc.parties.push_back({id, hosts[rank]}); + } + + auto proc = [&](int self_randk) -> std::shared_ptr<yacl::link::Context> { + return CreateContext(self_randk, lctx_desc); + }; + + size_t world_size = hosts.size(); + std::vector<std::future<std::shared_ptr<yacl::link::Context>>> f_links( + world_size); + for (size_t i = 0; i < world_size; i++) { + f_links[i] = std::async(proc, i); + } + + std::vector<std::shared_ptr<yacl::link::Context>> links(world_size); + for (size_t i = 0; i < world_size; i++) { + links[i] = f_links[i].get(); + } + + return links; +} + +} // namespace + +static void BM_LabeledPsi(benchmark::State& state) { + for (auto _ : state) { + state.PauseTiming(); + size_t nr = state.range(0); + size_t ns = state.range(1); + + auto ctxs = CreateLinks(kLinkAddrAB); + + ctxs[0]->SetThrottleWindowSize(kLinkWindowSize); + ctxs[1]->SetThrottleWindowSize(kLinkWindowSize); + + ctxs[0]->SetRecvTimeout(kLinkRecvTimeout); + ctxs[1]->SetRecvTimeout(kLinkRecvTimeout); + + state.ResumeTiming(); + + apsi::PSIParams psi_params = GetPsiParams(nr, ns); + + // step 1: PsiParams Request and Response + std::future<void> f_sender_params = + std::async([&] { return LabelPsiSender::RunPsiParams(ns, ctxs[0]); }); + + std::future<apsi::PSIParams> f_receiver_params = std::async( + [&] { return LabelPsiReceiver::RequestPsiParams(nr, ctxs[1]); }); + + f_sender_params.get(); + apsi::PSIParams psi_params2 = f_receiver_params.get(); + + EXPECT_EQ(psi_params.table_params().table_size, + psi_params2.table_params().table_size); + + size_t item_count = ns; + size_t nonce_byte_count = 16; + + std::random_device rd; + yacl::crypto::Prg<uint128_t> prg(rd()); + + std::array<uint8_t, 32> oprf_key; + prg.Fill(absl::MakeSpan(oprf_key)); + + bool compressed = false; + std::shared_ptr<ISenderDB> sender_db = std::make_shared<SenderKvDB>( + psi_params, oprf_key, "::memory", 0, nonce_byte_count, compressed); + + std::vector<std::string> receiver_items = GenerateData(rd(), nr); + + std::vector<size_t> intersection_idx; + std::vector<std::string> intersection_label; + + // step 2: set database + + const auto setdb_start = std::chrono::system_clock::now(); + + std::vector<std::string> sender_items = GenerateSenderData( + rd(), item_count, absl::MakeSpan(receiver_items), &intersection_idx); + + std::shared_ptr<IBatchProvider> batch_provider = + std::make_shared<MemoryBatchProvider>(sender_items, 5000); + + sender_db->SetData(batch_provider); + + const auto setdb_end = std::chrono::system_clock::now(); + const duration_millis setdb_duration = setdb_end - setdb_start; + SPDLOG_INFO("*** step2 set db duration:{}", setdb_duration.count()); + + EXPECT_EQ(ns, sender_db->GetItemCount()); + + SPDLOG_INFO("after set db, bin_bundle_count:{}, packing_rate:{}", + sender_db->GetBinBundleCount(), sender_db->GetPackingRate()); + + std::unique_ptr<IEcdhOprfServer> oprf_server = + CreateEcdhOprfServer(oprf_key, OprfType::Basic, CurveType::CURVE_FOURQ); + + LabelPsiSender sender(sender_db); + + LabelPsiReceiver receiver(psi_params, false); + + // step 3: oprf request and response + + const auto oprf_start = std::chrono::system_clock::now(); + + std::future<void> f_sender_oprf = std::async( + [&] { return sender.RunOPRF(std::move(oprf_server), ctxs[0]); }); + + std::future< + std::pair<std::vector<apsi::HashedItem>, std::vector<apsi::LabelKey>>> + f_receiver_oprf = std::async( + [&] { return receiver.RequestOPRF(receiver_items, ctxs[1]); }); + + f_sender_oprf.get(); + std::pair<std::vector<apsi::HashedItem>, std::vector<apsi::LabelKey>> + oprf_pair = f_receiver_oprf.get(); + + const auto oprf_end = std::chrono::system_clock::now(); + const duration_millis oprf_duration = oprf_end - oprf_start; + SPDLOG_INFO("*** step3 oprf duration:{}", oprf_duration.count()); + + SPDLOG_INFO("hashed_item size:{} label keys size:{}", + oprf_pair.first.size(), oprf_pair.second.size()); + + // step 4: Query request and response + + const auto query_start = std::chrono::system_clock::now(); + + std::future<void> f_sender_query = + std::async([&] { return sender.RunQuery(ctxs[0]); }); + + std::future<std::pair<std::vector<size_t>, std::vector<std::string>>> + f_receiver_query = std::async([&] { + return receiver.RequestQuery(oprf_pair.first, oprf_pair.second, + ctxs[1]); + }); + + f_sender_query.get(); + std::pair<std::vector<size_t>, std::vector<std::string>> query_result = + f_receiver_query.get(); + + const auto query_end = std::chrono::system_clock::now(); + const duration_millis query_duration = query_end - query_start; + SPDLOG_INFO("*** step4 query duration:{}", query_duration.count()); + + EXPECT_EQ(query_result.first.size(), intersection_idx.size()); + SPDLOG_INFO("index vec size:{} intersection_idx size:{}", + query_result.first.size(), intersection_idx.size()); + + SPDLOG_INFO("intersection:{}", intersection_idx.size()); + auto stats0 = ctxs[0]->GetStats(); + auto stats1 = ctxs[1]->GetStats(); + SPDLOG_INFO("sender ctx0 sent_bytes:{} recv_bytes:{}", + stats0->sent_bytes.load(), stats0->recv_bytes.load()); + SPDLOG_INFO("receiver ctx1 sent_bytes:{} recv_bytes:{}", + stats1->sent_bytes.load(), stats1->recv_bytes.load()); + } +} + +// [256k, 512k, 1m, 2m, 4m, 8m, 16m] +BENCHMARK(BM_LabeledPsi) + ->Unit(benchmark::kMillisecond) + ->Args({100, 256 << 10}) + ->Args({100, 512 << 10}) + ->Args({100, 1 << 20}) + ->Args({100, 2 << 20}) + ->Args({100, 4 << 20}) + ->Args({100, 8 << 20}) + ->Args({100, 16 << 20}) + ->Args({100, 1 << 25}) + ->Args({100, 1 << 26}) + ->Args({100, 1 << 27}); + +BENCHMARK_MAIN(); + +} // namespace psi::psi diff --git a/psi/psi/core/labeled_psi/apsi_label_test.cc b/psi/psi/core/labeled_psi/apsi_label_test.cc new file mode 100644 index 00000000..4088f860 --- /dev/null +++ b/psi/psi/core/labeled_psi/apsi_label_test.cc @@ -0,0 +1,269 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <chrono> +#include <filesystem> +#include <future> +#include <iostream> +#include <random> +#include <vector> + +#include "absl/strings/escaping.h" +#include "absl/strings/str_split.h" +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" +#include "yacl/crypto/tools/prg.h" +#include "yacl/link/test_util.h" +#include "yacl/utils/scope_guard.h" + +#include "psi/psi/core/ecdh_oprf/ecdh_oprf_selector.h" +#include "psi/psi/core/labeled_psi/psi_params.h" +#include "psi/psi/core/labeled_psi/receiver.h" +#include "psi/psi/core/labeled_psi/sender.h" +#include "psi/psi/core/labeled_psi/sender_kvdb.h" +#include "psi/psi/core/labeled_psi/sender_memdb.h" + +namespace psi::psi { + +namespace { + +using DurationMillis = std::chrono::duration<double, std::milli>; + +constexpr size_t kPsiStartPos = 100; +struct TestParams { + size_t nr; + size_t ns; + size_t label_bytes = 16; + bool use_kvdb = true; +}; + +std::vector<std::string> GenerateData(size_t seed, size_t item_count) { + yacl::crypto::Prg<uint128_t> prg(seed); + + std::vector<std::string> items; + + for (size_t i = 0; i < item_count; ++i) { + std::string item(16, '\0'); + prg.Fill(absl::MakeSpan(item.data(), item.length())); + items.emplace_back(absl::BytesToHexString(item)); + } + + return items; +} + +std::pair<std::vector<std::string>, std::vector<std::string>> +GenerateSenderData(size_t seed, size_t item_count, size_t label_byte_count, + const absl::Span<std::string> &receiver_items, + std::vector<size_t> *intersection_idx, + std::vector<std::string> *intersection_label) { + std::vector<std::string> sender_items; + std::vector<std::string> sender_labels; + + yacl::crypto::Prg<uint128_t> prg(seed); + + for (size_t i = 0; i < item_count; ++i) { + std::string item(16, '\0'); + std::string label((label_byte_count - 1) / 2, '\0'); + + prg.Fill(absl::MakeSpan(item.data(), item.length())); + prg.Fill(absl::MakeSpan(label.data(), label.length())); + sender_items.emplace_back(absl::BytesToHexString(item)); + sender_labels.emplace_back(absl::BytesToHexString(label)); + } + + for (size_t i = 0; i < receiver_items.size(); i += 3) { + if ((kPsiStartPos + i * 5) >= sender_items.size()) { + break; + } + sender_items[kPsiStartPos + i * 5] = receiver_items[i]; + (*intersection_idx).emplace_back(i); + + (*intersection_label).emplace_back(sender_labels[kPsiStartPos + i * 5]); + } + + return std::make_pair(sender_items, sender_labels); +} + +} // namespace + +class LabelPsiTest : public testing::TestWithParam<TestParams> {}; + +TEST_P(LabelPsiTest, Works) { + auto params = GetParam(); + auto ctxs = yacl::link::test::SetupWorld(2); + apsi::PSIParams psi_params = GetPsiParams(params.nr, params.ns); + + // step 1: PsiParams Request and Response + std::future<void> f_sender_params = std::async( + [&] { return LabelPsiSender::RunPsiParams(params.ns, ctxs[0]); }); + + std::future<apsi::PSIParams> f_receiver_params = std::async( + [&] { return LabelPsiReceiver::RequestPsiParams(params.nr, ctxs[1]); }); + + f_sender_params.get(); + apsi::PSIParams psi_params2 = f_receiver_params.get(); + + EXPECT_EQ(psi_params.table_params().table_size, + psi_params2.table_params().table_size); + + size_t item_count = params.ns; + size_t label_byte_count = params.label_bytes; + size_t nonce_byte_count = 16; + + std::random_device rd; + yacl::crypto::Prg<uint128_t> prg(rd()); + + std::array<uint8_t, 32> oprf_key; + prg.Fill(absl::MakeSpan(oprf_key)); + + std::string kv_store_path = fmt::format("data_{}/", params.ns); + std::filesystem::create_directory(kv_store_path); + // register remove of temp dir. + ON_SCOPE_EXIT([&] { + if (!kv_store_path.empty()) { + std::error_code ec; + std::filesystem::remove_all(kv_store_path, ec); + if (ec.value() != 0) { + SPDLOG_WARN("can not remove tmp dir: {}, msg: {}", kv_store_path, + ec.message()); + } + } + }); + + bool compressed = false; + std::shared_ptr<ISenderDB> sender_db; + if (params.use_kvdb) { + sender_db = std::make_shared<SenderKvDB>(psi_params, oprf_key, + kv_store_path, label_byte_count, + nonce_byte_count, compressed); + } else { + sender_db = std::make_shared<SenderMemDB>( + psi_params, oprf_key, label_byte_count, nonce_byte_count, compressed); + } + + std::vector<std::string> receiver_items = GenerateData(rd(), params.nr); + + std::vector<size_t> intersection_idx; + std::vector<std::string> intersection_label; + + // step 2: set database + + const auto setdb_start = std::chrono::system_clock::now(); + + std::vector<std::string> sender_keys; + std::vector<std::string> sender_labels; + + std::tie(sender_keys, sender_labels) = GenerateSenderData( + rd(), item_count, label_byte_count - 4, absl::MakeSpan(receiver_items), + &intersection_idx, &intersection_label); + + std::shared_ptr<IBatchProvider> batch_provider = + std::make_shared<MemoryBatchProvider>(sender_keys, 5000, sender_labels); + + sender_db->SetData(batch_provider); + + const auto setdb_end = std::chrono::system_clock::now(); + const DurationMillis setdb_duration = setdb_end - setdb_start; + SPDLOG_INFO("*** step2 set db duration:{}", setdb_duration.count()); + + EXPECT_EQ(params.ns, sender_db->GetItemCount()); + + SPDLOG_INFO("after set db, bin_bundle_count:{}, packing_rate:{}", + sender_db->GetBinBundleCount(), sender_db->GetPackingRate()); + + const apsi::PSIParams apsi_params = sender_db->GetParams(); + SPDLOG_INFO("params.bundle_idx_count={}", apsi_params.bundle_idx_count()); + + LabelPsiSender sender(sender_db); + + LabelPsiReceiver receiver(psi_params, params.label_bytes > 0); + + // step 3: oprf request and response + + const auto oprf_start = std::chrono::system_clock::now(); + + std::future<void> f_sender_oprf = std::async([&] { + std::unique_ptr<IEcdhOprfServer> oprf_server = + CreateEcdhOprfServer(oprf_key, OprfType::Basic, CurveType::CURVE_FOURQ); + + return sender.RunOPRF(std::move(oprf_server), ctxs[0]); + }); + + std::future< + std::pair<std::vector<apsi::HashedItem>, std::vector<apsi::LabelKey>>> + f_receiver_oprf = std::async( + [&] { return receiver.RequestOPRF(receiver_items, ctxs[1]); }); + + f_sender_oprf.get(); + std::pair<std::vector<apsi::HashedItem>, std::vector<apsi::LabelKey>> + oprf_pair = f_receiver_oprf.get(); + + const auto oprf_end = std::chrono::system_clock::now(); + const DurationMillis oprf_duration = oprf_end - oprf_start; + SPDLOG_INFO("*** step3 oprf duration:{}", oprf_duration.count()); + + SPDLOG_INFO("hashed_item size:{} label keys size:{}", oprf_pair.first.size(), + oprf_pair.second.size()); + + // step 4: Query request and response + + const auto query_start = std::chrono::system_clock::now(); + + std::future<void> f_sender_query = + std::async([&] { return sender.RunQuery(ctxs[0]); }); + + std::future<std::pair<std::vector<size_t>, std::vector<std::string>>> + f_receiver_query = std::async([&] { + return receiver.RequestQuery(oprf_pair.first, oprf_pair.second, + ctxs[1]); + }); + + f_sender_query.get(); + std::pair<std::vector<size_t>, std::vector<std::string>> query_result = + f_receiver_query.get(); + + const auto query_end = std::chrono::system_clock::now(); + const DurationMillis query_duration = query_end - query_start; + SPDLOG_INFO("*** step4 query duration:{}", query_duration.count()); + + SPDLOG_INFO("index vec size:{} intersection_idx size:{}", + query_result.first.size(), intersection_idx.size()); + + EXPECT_EQ(intersection_idx, query_result.first); + EXPECT_EQ(intersection_label, query_result.second); +} + +INSTANTIATE_TEST_SUITE_P(Works_Instances, LabelPsiTest, + testing::Values( // +#if 0 + TestParams{1, 100000, 32}, // 1-100K-32 + TestParams{1, 256000, 32}, // 1-256K-32 + TestParams{1, 1000000, 32}, // 1-1M-32 + TestParams{256, 100000, 32}, // 256-100K-32 + TestParams{512, 100000, 32}, // 512-100K-32 + TestParams{1024, 100000, 32}, // 1024-100K-32 + TestParams{2048, 100000, 32}, // 2048-100K-32 + TestParams{4096, 100000, 32}, // 4096-100K-32 + TestParams{10000, 100000, 32}, // 10000-100K-32 + TestParams{10000, 1000000, 32}) // 10000-1M-32 +#else + TestParams{10, 10000, 32}, // 10-10K-32 + TestParams{2048, 10000, 32}, // 2048-10K-32 + TestParams{100, 100000, 32, false}) // 100-100K-32 + +#endif +); + +} // namespace psi::psi diff --git a/psi/psi/core/labeled_psi/apsi_test.cc b/psi/psi/core/labeled_psi/apsi_test.cc new file mode 100644 index 00000000..1cb5c5bb --- /dev/null +++ b/psi/psi/core/labeled_psi/apsi_test.cc @@ -0,0 +1,256 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <chrono> +#include <filesystem> +#include <future> +#include <iostream> +#include <random> +#include <vector> + +#include "absl/strings/escaping.h" +#include "absl/strings/str_split.h" +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" +#include "yacl/crypto/tools/prg.h" +#include "yacl/link/test_util.h" +#include "yacl/utils/scope_guard.h" + +#include "psi/psi/core/ecdh_oprf/ecdh_oprf_selector.h" +#include "psi/psi/core/labeled_psi/psi_params.h" +#include "psi/psi/core/labeled_psi/receiver.h" +#include "psi/psi/core/labeled_psi/sender.h" +#include "psi/psi/core/labeled_psi/sender_kvdb.h" +#include "psi/psi/core/labeled_psi/sender_memdb.h" + +namespace psi::psi { + +namespace { + +using DurationMillis = std::chrono::duration<double, std::milli>; + +constexpr size_t kPsiStartPos = 100; +struct TestParams { + size_t nr; + size_t ns; + bool use_kvdb = true; +}; + +std::vector<std::string> GenerateData(size_t seed, size_t item_count) { + yacl::crypto::Prg<uint128_t> prg(seed); + + std::vector<std::string> items; + + for (size_t i = 0; i < item_count; ++i) { + std::string item(16, '\0'); + prg.Fill(absl::MakeSpan(item.data(), item.length())); + items.emplace_back(absl::BytesToHexString(item)); + } + + return items; +} + +std::vector<std::string> GenerateSenderData( + size_t seed, size_t item_count, + const absl::Span<std::string> &receiver_items, + std::vector<size_t> *intersection_idx) { + std::vector<std::string> sender_items; + + yacl::crypto::Prg<uint128_t> prg(seed); + + for (size_t i = 0; i < item_count; ++i) { + std::string item(16, '\0'); + prg.Fill(absl::MakeSpan(item.data(), item.size())); + sender_items.emplace_back(absl::BytesToHexString(item)); + } + + for (size_t i = 0; i < receiver_items.size(); i += 3) { + if ((kPsiStartPos + i * 5) >= sender_items.size()) { + break; + } + sender_items[kPsiStartPos + i * 5] = receiver_items[i]; + (*intersection_idx).emplace_back(i); + } + + return sender_items; +} + +} // namespace + +class LabelPsiTest : public testing::TestWithParam<TestParams> {}; + +TEST_P(LabelPsiTest, Works) { + auto params = GetParam(); + auto ctxs = yacl::link::test::SetupWorld(2); + apsi::PSIParams psi_params = GetPsiParams(params.nr, params.ns); + + // step 1: PsiParams Request and Response + std::future<void> f_sender_params = std::async( + [&] { return LabelPsiSender::RunPsiParams(params.ns, ctxs[0]); }); + + std::future<apsi::PSIParams> f_receiver_params = std::async( + [&] { return LabelPsiReceiver::RequestPsiParams(params.nr, ctxs[1]); }); + + f_sender_params.get(); + apsi::PSIParams psi_params2 = f_receiver_params.get(); + + EXPECT_EQ(psi_params.table_params().table_size, + psi_params2.table_params().table_size); + + size_t item_count = params.ns; + size_t nonce_byte_count = 16; + + std::random_device rd; + yacl::crypto::Prg<uint128_t> prg(rd()); + + std::array<uint8_t, 32> oprf_key; + prg.Fill(absl::MakeSpan(oprf_key)); + + std::string kv_store_path = fmt::format("data_{}", params.ns); + std::filesystem::create_directory(kv_store_path); + // register remove of temp dir. + ON_SCOPE_EXIT([&] { + if (!kv_store_path.empty()) { + std::error_code ec; + std::filesystem::remove_all(kv_store_path, ec); + if (ec.value() != 0) { + SPDLOG_WARN("can not remove tmp dir: {}, msg: {}", kv_store_path, + ec.message()); + } + } + }); + + bool compressed = false; + std::shared_ptr<ISenderDB> sender_db; + if (params.use_kvdb) { + sender_db = std::make_shared<SenderKvDB>( + psi_params, oprf_key, kv_store_path, 0, nonce_byte_count, compressed); + } else { + sender_db = std::make_shared<SenderMemDB>(psi_params, oprf_key, 0, + nonce_byte_count, compressed); + } + + std::vector<std::string> receiver_items = GenerateData(rd(), params.nr); + + std::vector<size_t> intersection_idx; + std::vector<std::string> intersection_label; + + // step 2: set database + + const auto setdb_start = std::chrono::system_clock::now(); + + std::vector<std::string> sender_items = GenerateSenderData( + rd(), item_count, absl::MakeSpan(receiver_items), &intersection_idx); + + std::shared_ptr<IBatchProvider> batch_provider = + std::make_shared<MemoryBatchProvider>(sender_items, 5000); + + sender_db->SetData(batch_provider); + + const auto setdb_end = std::chrono::system_clock::now(); + const DurationMillis setdb_duration = setdb_end - setdb_start; + SPDLOG_INFO("*** step2 set db duration:{}", setdb_duration.count()); + + EXPECT_EQ(params.ns, sender_db->GetItemCount()); + + SPDLOG_INFO("after set db, bin_bundle_count:{}, packing_rate:{}", + sender_db->GetBinBundleCount(), sender_db->GetPackingRate()); + + const apsi::PSIParams apsi_params = sender_db->GetParams(); + SPDLOG_INFO("params.bundle_idx_count={}", apsi_params.bundle_idx_count()); + for (size_t i = 0; i < apsi_params.bundle_idx_count(); ++i) { + SPDLOG_INFO("i={},count={}", i, sender_db->GetBinBundleCount(i)); + } + + std::unique_ptr<IEcdhOprfServer> oprf_server = + CreateEcdhOprfServer(oprf_key, OprfType::Basic, CurveType::CURVE_FOURQ); + + LabelPsiSender sender(sender_db); + + LabelPsiReceiver receiver(psi_params, false); + + // step 3: oprf request and response + + const auto oprf_start = std::chrono::system_clock::now(); + + std::future<void> f_sender_oprf = std::async( + [&] { return sender.RunOPRF(std::move(oprf_server), ctxs[0]); }); + + std::future< + std::pair<std::vector<apsi::HashedItem>, std::vector<apsi::LabelKey>>> + f_receiver_oprf = std::async( + [&] { return receiver.RequestOPRF(receiver_items, ctxs[1]); }); + + f_sender_oprf.get(); + std::pair<std::vector<apsi::HashedItem>, std::vector<apsi::LabelKey>> + oprf_pair = f_receiver_oprf.get(); + + const auto oprf_end = std::chrono::system_clock::now(); + const DurationMillis oprf_duration = oprf_end - oprf_start; + SPDLOG_INFO("*** step3 oprf duration:{}", oprf_duration.count()); + + SPDLOG_INFO("hashed_item size:{} label keys size:{}", oprf_pair.first.size(), + oprf_pair.second.size()); + + // step 4: Query request and response + + const auto query_start = std::chrono::system_clock::now(); + + std::future<void> f_sender_query = + std::async([&] { return sender.RunQuery(ctxs[0]); }); + + std::future<std::pair<std::vector<size_t>, std::vector<std::string>>> + f_receiver_query = std::async([&] { + return receiver.RequestQuery(oprf_pair.first, oprf_pair.second, + ctxs[1]); + }); + + f_sender_query.get(); + std::pair<std::vector<size_t>, std::vector<std::string>> query_result = + f_receiver_query.get(); + + const auto query_end = std::chrono::system_clock::now(); + const DurationMillis query_duration = query_end - query_start; + SPDLOG_INFO("*** step4 query duration:{}", query_duration.count()); + + SPDLOG_INFO("index vec size:{} intersection_idx size:{}", + query_result.first.size(), intersection_idx.size()); + + EXPECT_EQ(intersection_idx, query_result.first); +} + +INSTANTIATE_TEST_SUITE_P(Works_Instances, LabelPsiTest, + testing::Values( // +#if 0 + TestParams{1, 10000}, // 1-10K + TestParams{1, 100000}, // 1-100K + TestParams{1, 256000}, // 1-256K + TestParams{1, 512000}, // 1-512K + TestParams{1, 1000000}, // 1-1M + TestParams{256, 100000}, // 256-100K + TestParams{512, 100000}, // 512-100K + TestParams{1024, 100000}, // 1024-100K + TestParams{2048, 100000}, // 2048-100K + TestParams{4096, 100000}, // 4096-100K + TestParams{10000, 100000}, // 10000-100K +#else + TestParams{1, 10000}, // 1-10K + TestParams{1, 10000, false}, // 1-10K memdb + TestParams{10, 10000}, // 1-10K + TestParams{100, 100000, false}) // 100-100K +#endif +); + +} // namespace psi::psi diff --git a/psi/psi/core/labeled_psi/kv_test.cc b/psi/psi/core/labeled_psi/kv_test.cc new file mode 100644 index 00000000..4e7058e9 --- /dev/null +++ b/psi/psi/core/labeled_psi/kv_test.cc @@ -0,0 +1,362 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <chrono> +#include <filesystem> +#include <future> +#include <iostream> +#include <random> +#include <vector> + +#include "absl/strings/escaping.h" +#include "absl/strings/str_split.h" +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" +#include "yacl/crypto/tools/prg.h" +#include "yacl/link/test_util.h" +#include "yacl/utils/scope_guard.h" + +#include "psi/psi/core/ecdh_oprf/ecdh_oprf_selector.h" +#include "psi/psi/core/labeled_psi/psi_params.h" +#include "psi/psi/core/labeled_psi/receiver.h" +#include "psi/psi/core/labeled_psi/sender.h" +#include "psi/psi/core/labeled_psi/sender_kvdb.h" +#include "psi/psi/utils/utils.h" + +namespace psi::psi { + +namespace { + +using duration_millis = std::chrono::duration<double, std::milli>; + +constexpr size_t kPsiStartPos = 100; +struct TestParams { + size_t nr; + size_t ns; + size_t label_bytes; +}; + +std::vector<std::string> GenerateData(size_t seed, size_t item_count) { + yacl::crypto::Prg<uint128_t> prg(seed); + + std::vector<std::string> items; + + for (size_t i = 0; i < item_count; ++i) { + std::string item(16, '\0'); + prg.Fill(absl::MakeSpan(item.data(), item.length())); + items.emplace_back(item); + } + + return items; +} + +std::vector<apsi::Item> GenerateSenderData( + size_t seed, size_t item_count, + const absl::Span<std::string> &receiver_items, + std::vector<size_t> *intersection_idx) { + std::vector<apsi::Item> sender_items; + + yacl::crypto::Prg<uint128_t> prg(seed); + + for (size_t i = 0; i < item_count; ++i) { + apsi::Item::value_type value{}; + prg.Fill(absl::MakeSpan(value)); + sender_items.emplace_back(value); + } + + for (size_t i = 0; i < receiver_items.size(); i += 3) { + apsi::Item::value_type value{}; + std::memcpy(value.data(), receiver_items[i].data(), + receiver_items[i].length()); + apsi::Item item(value); + sender_items[kPsiStartPos + i * 5] = item; + (*intersection_idx).emplace_back(i); + } + + return sender_items; +} + +std::vector<std::pair<apsi::Item, apsi::Label>> GenerateSenderData( + size_t seed, size_t item_count, size_t label_byte_count, + const absl::Span<std::string> &receiver_items, + std::vector<size_t> *intersection_idx, + std::vector<std::string> *intersection_label) { + std::vector<std::pair<apsi::Item, apsi::Label>> sender_items; + + yacl::crypto::Prg<uint128_t> prg(seed); + + for (size_t i = 0; i < item_count; ++i) { + apsi::Item item; + apsi::Label label; + label.resize(label_byte_count); + prg.Fill(absl::MakeSpan(item.value())); + prg.Fill(absl::MakeSpan(label)); + sender_items.emplace_back(item, label); + } + + for (size_t i = 0; i < receiver_items.size(); i += 3) { + apsi::Item item; + std::memcpy(item.value().data(), receiver_items[i].data(), + receiver_items[i].length()); + + sender_items[kPsiStartPos + i * 5].first = item; + (*intersection_idx).emplace_back(i); + std::string label_string(sender_items[kPsiStartPos + i * 5].second.size(), + '\0'); + std::memcpy(&label_string[0], + sender_items[kPsiStartPos + i * 5].second.data(), + sender_items[kPsiStartPos + i * 5].second.size()); + (*intersection_label).emplace_back(label_string); + } + + return sender_items; +} + +} // namespace + +class LabelPsiTest : public testing::TestWithParam<TestParams> {}; + +TEST_P(LabelPsiTest, Works) { + auto params = GetParam(); + auto ctxs = yacl::link::test::SetupWorld(2); + + // padding check + { + std::string data1(16, '\0'); + std::string data2(13, '\0'); + + std::vector<uint8_t> d1 = PaddingData(data1, 25); + std::string d2 = UnPaddingData( + absl::string_view(reinterpret_cast<char *>(d1.data()), d1.size())); + SPDLOG_INFO("d1 len:{} d2 len:{}", d1.size(), d2.length()); + + d1 = PaddingData(data2, 25); + d2 = UnPaddingData( + absl::string_view(reinterpret_cast<char *>(d1.data()), d1.size())); + SPDLOG_INFO("d1 len:{} d2 len:{}", d1.size(), d2.length()); + } + + apsi::PSIParams psi_params = GetPsiParams(params.nr, params.ns); + + // step 1: PsiParams Request and Response + std::future<void> f_sender_params = std::async( + [&] { return LabelPsiSender::RunPsiParams(params.ns, ctxs[0]); }); + + std::future<apsi::PSIParams> f_receiver_params = std::async( + [&] { return LabelPsiReceiver::RequestPsiParams(params.nr, ctxs[1]); }); + + f_sender_params.get(); + apsi::PSIParams psi_params2 = f_receiver_params.get(); + + EXPECT_EQ(psi_params.table_params().table_size, + psi_params2.table_params().table_size); + + size_t item_count = params.ns; + size_t label_byte_count = params.label_bytes; + size_t nonce_byte_count = 16; + + std::random_device rd; + yacl::crypto::Prg<uint128_t> prg(rd()); + + std::array<uint8_t, 32> oprf_key; + prg.Fill(absl::MakeSpan(oprf_key)); + + std::string kv_store_path = fmt::format("data_{}", params.ns); + std::filesystem::create_directory(kv_store_path); + // register remove of temp dir. + ON_SCOPE_EXIT([&] { + if (!kv_store_path.empty()) { + std::error_code ec; + std::filesystem::remove_all(kv_store_path, ec); + if (ec.value() != 0) { + SPDLOG_WARN("can not remove tmp dir: {}, msg: {}", kv_store_path, + ec.message()); + } + } + }); + + bool compressed = false; + std::shared_ptr<ISenderDB> sender_db = std::make_shared<SenderKvDB>( + psi_params, oprf_key, kv_store_path, label_byte_count, nonce_byte_count, + compressed); + + std::vector<std::string> receiver_items = GenerateData(rd(), params.nr); + + std::vector<size_t> intersection_idx; + std::vector<std::string> intersection_label; + + // step 2: set database + + const auto setdb_start = std::chrono::system_clock::now(); + + if (params.label_bytes == 0) { + std::vector<apsi::Item> sender_items = GenerateSenderData( + rd(), item_count, absl::MakeSpan(receiver_items), &intersection_idx); + + // sender_db->SetData(sender_items); + + std::vector<std::string> sender_data(sender_items.size()); + for (size_t i = 0; i < sender_items.size(); ++i) { + sender_data[i].reserve(sender_items[i].value().size()); + + sender_data[i].append(absl::string_view( + reinterpret_cast<char *>(sender_items[i].value().data()), + sender_items[i].value().size())); + } + + std::shared_ptr<IBatchProvider> batch_provider = + std::make_shared<MemoryBatchProvider>(sender_data, 5000); + + sender_db->SetData(batch_provider); + } else { + std::vector<std::pair<apsi::Item, apsi::Label>> sender_items = + GenerateSenderData(rd(), item_count, label_byte_count - 6, + absl::MakeSpan(receiver_items), &intersection_idx, + &intersection_label); + + // sender_db->SetData(sender_items); + std::vector<std::string> sender_data(sender_items.size()); + std::vector<std::string> sender_label(sender_items.size()); + for (size_t i = 0; i < sender_items.size(); ++i) { + sender_data[i].reserve(sender_items[i].first.value().size()); + sender_data[i].append(absl::string_view( + reinterpret_cast<char *>(sender_items[i].first.value().data()), + sender_items[i].first.value().size())); + + sender_label[i].reserve(sender_items[i].second.size()); + sender_label[i].append(absl::string_view( + reinterpret_cast<char *>(sender_items[i].second.data()), + sender_items[i].second.size())); + } + SPDLOG_INFO("sender_data[0].length:{}", sender_data[0].length()); + + std::shared_ptr<IBatchProvider> batch_provider = + std::make_shared<MemoryBatchProvider>(sender_data, 5000, sender_label); + + sender_db->SetData(batch_provider); + } + + const auto setdb_end = std::chrono::system_clock::now(); + const duration_millis setdb_duration = setdb_end - setdb_start; + SPDLOG_INFO("*** step2 set db duration:{}", setdb_duration.count()); + + EXPECT_EQ(params.ns, sender_db->GetItemCount()); + + SPDLOG_INFO("after set db, bin_bundle_count:{}, packing_rate:{}", + sender_db->GetBinBundleCount(), sender_db->GetPackingRate()); + + const apsi::PSIParams &apsi_params = sender_db->GetParams(); + SPDLOG_INFO("bundle_idx_count:{}", apsi_params.bundle_idx_count()); + SPDLOG_INFO("BinBundleCount:{}", sender_db->GetBinBundleCount()); + for (size_t i = 0; i < apsi_params.bundle_idx_count(); ++i) { + SPDLOG_INFO("BinBundleCount[{}]:{}", i, sender_db->GetBinBundleCount(i)); + } + +#if 0 + std::unique_ptr<IEcdhOprfServer> oprf_server = + CreateEcdhOprfServer(oprf_key, OprfType::Basic, CurveType::CURVE_FOURQ); + + LabelPsiSender sender(sender_db); + + LabelPsiReceiver receiver(psi_params, params.label_bytes > 0); + + // step 3: oprf request and response + + const auto oprf_start = std::chrono::system_clock::now(); + + std::future<void> f_sender_oprf = std::async( + [&] { return sender.RunOPRF(std::move(oprf_server), ctxs[0]); }); + + std::future< + std::pair<std::vector<apsi::HashedItem>, std::vector<apsi::LabelKey>>> + f_receiver_oprf = std::async( + [&] { return receiver.RequestOPRF(receiver_items, ctxs[1]); }); + + f_sender_oprf.get(); + std::pair<std::vector<apsi::HashedItem>, std::vector<apsi::LabelKey>> + oprf_pair = f_receiver_oprf.get(); + + const auto oprf_end = std::chrono::system_clock::now(); + const duration_millis oprf_duration = oprf_end - oprf_start; + SPDLOG_INFO("*** step3 oprf duration:{}", oprf_duration.count()); + + SPDLOG_INFO("hashed_item size:{} label keys size:{}", oprf_pair.first.size(), + oprf_pair.second.size()); + + // step 4: Query request and response + + const auto query_start = std::chrono::system_clock::now(); + + std::future<void> f_sender_query = + std::async([&] { return sender.RunQuery(ctxs[0]); }); + + std::future<std::pair<std::vector<size_t>, std::vector<std::string>>> + f_receiver_query = std::async([&] { + return receiver.RequestQuery(oprf_pair.first, oprf_pair.second, + ctxs[1]); + }); + + f_sender_query.get(); + std::pair<std::vector<size_t>, std::vector<std::string>> query_result = + f_receiver_query.get(); + + const auto query_end = std::chrono::system_clock::now(); + const duration_millis query_duration = query_end - query_start; + SPDLOG_INFO("*** step4 query duration:{}", query_duration.count()); + + SPDLOG_INFO("index vec size:{} intersection_idx size:{}", + query_result.first.size(), intersection_idx.size()); + + EXPECT_EQ(intersection_idx, query_result.first); + EXPECT_EQ(intersection_label, query_result.second); + +#endif +} + +INSTANTIATE_TEST_SUITE_P( + Works_Instances, LabelPsiTest, + testing::Values( // +#if 0 + TestParams{1, 10000, 0}, // 1-10K + TestParams{1, 100000, 0}, // 1-100K + TestParams{1, 256000, 0}, // 1-256K + TestParams{1, 512000, 0}, // 1-512K + TestParams{1, 1000000, 0}, // 1-1M + TestParams{256, 100000, 0}, // 256-100K + TestParams{512, 100000, 0}, // 512-100K + TestParams{1024, 100000, 0}, // 1024-100K + TestParams{2048, 100000, 0}, // 2048-100K + TestParams{4096, 100000, 0}, // 4096-100K + TestParams{10000, 100000, 0}, // 10000-100K + TestParams{1, 100000, 32}, // 1-100K-32 + TestParams{1, 256000, 32}, // 1-256K-32 + TestParams{1, 1000000, 32}, // 1-1M-32 + TestParams{256, 100000, 32}, // 256-100K-32 + TestParams{512, 100000, 32}, // 512-100K-32 + TestParams{1024, 100000, 32}, // 1024-100K-32 + TestParams{2048, 100000, 32}, // 2048-100K-32 + TestParams{4096, 100000, 32}, // 4096-100K-32 + TestParams{10000, 100000, 32}, // 10000-100K-32 + TestParams{10000, 1000000, 32}) // 10000-1M-32 +#else + // TestParams{1, 10000, 0}, // 1-10K + // TestParams{1, 100000, 0}, // 1-100K + // TestParams{256, 100000, 0}, // 256-100K + // TestParams{2048, 100000, 32}, // 2048-100K-32 + TestParams{1, 100000, 32}) // 1-10K +#endif +); + +} // namespace psi::psi diff --git a/psi/psi/core/labeled_psi/package.cc b/psi/psi/core/labeled_psi/package.cc new file mode 100644 index 00000000..55d262ae --- /dev/null +++ b/psi/psi/core/labeled_psi/package.cc @@ -0,0 +1,63 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/labeled_psi/package.h" + +#include <utility> + +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" + +namespace psi::psi { + +PlainResultPackage ResultPackage::extract( + const apsi::CryptoContext &crypto_context) { + YACL_ENFORCE(crypto_context.decryptor(), + "decryptor is not configured in CryptoContext"); + + // SPDLOG_INFO("extract ciphertext"); + + seal::Ciphertext psi_result_ct = + psi_result.extract(crypto_context.seal_context()); + seal::Plaintext psi_result_pt; + crypto_context.decryptor()->decrypt(psi_result_ct, psi_result_pt); + + SPDLOG_DEBUG( + "Matching result noise budget: {}", + crypto_context.decryptor()->invariant_noise_budget(psi_result_ct)); + + PlainResultPackage plain_rp; + plain_rp.bundle_idx = bundle_idx; + crypto_context.encoder()->decode(psi_result_pt, plain_rp.psi_result); + + plain_rp.label_byte_count = label_byte_count; + plain_rp.nonce_byte_count = nonce_byte_count; + for (auto &ct : label_result) { + seal::Ciphertext label_result_ct = + ct.extract(crypto_context.seal_context()); + seal::Plaintext label_result_pt; + crypto_context.decryptor()->decrypt(label_result_ct, label_result_pt); + + std::vector<uint64_t> label_result_data; + crypto_context.encoder()->decode(label_result_pt, label_result_data); + plain_rp.label_result.push_back(std::move(label_result_data)); + } + + // Clear the label data + label_result.clear(); + + return plain_rp; +} + +} // namespace psi::psi diff --git a/psi/psi/core/labeled_psi/package.h b/psi/psi/core/labeled_psi/package.h new file mode 100644 index 00000000..6139e8fd --- /dev/null +++ b/psi/psi/core/labeled_psi/package.h @@ -0,0 +1,55 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <vector> + +#include "apsi/crypto_context.h" +#include "apsi/seal_object.h" +#include "gsl/span" +#include "seal/seal.h" + +namespace psi::psi { + +struct PlainResultPackage { + std::uint32_t bundle_idx; + + std::vector<std::uint64_t> psi_result; + + std::uint32_t label_byte_count; + + std::uint32_t nonce_byte_count; + + std::vector<std::vector<std::uint64_t>> label_result; +}; + +class ResultPackage { + public: + PlainResultPackage extract(const apsi::CryptoContext& crypto_context); + + std::uint32_t bundle_idx; + + seal::compr_mode_type compr_mode = seal::Serialization::compr_mode_default; + + apsi::SEALObject<seal::Ciphertext> psi_result; + + std::uint32_t label_byte_count; + + std::uint32_t nonce_byte_count; + + std::vector<apsi::SEALObject<seal::Ciphertext>> label_result; +}; // struct ResultPackage + +} // namespace psi::psi diff --git a/psi/psi/core/labeled_psi/psi_params.cc b/psi/psi/core/labeled_psi/psi_params.cc new file mode 100644 index 00000000..eb80e6b5 --- /dev/null +++ b/psi/psi/core/labeled_psi/psi_params.cc @@ -0,0 +1,313 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/labeled_psi/psi_params.h" + +#include <algorithm> +#include <cmath> +#include <map> +#include <string> + +#include "spdlog/spdlog.h" + +namespace psi::psi { + +namespace { + +// SEAL parameters +// from microsoft APSI example parameter sets +// https://github.com/microsoft/APSI/tree/main/parameters +std::vector<SEALParams> kSealParams = { + {2048, 65537, 0, {48}}, // 0 * + {4096, 40961, 0, {40, 32, 32}}, // 1 + {4096, 65537, 0, {40, 34, 30}}, // 2 + {4096, 65537, 0, {48, 30, 30}}, // 3 + {4096, 65537, 0, {48, 32, 24}}, // 4 + {4096, 65537, 0, {48, 34, 27}}, // 5 + {4096, 0, 18, {48, 32, 24}}, // 6 * + {8192, 0, 21, {56, 56, 24, 24}}, // 7 + {8192, 0, 21, {56, 56, 56, 50}}, // 8 + {8192, 0, 21, {48, 56, 56, 48}}, // 9 + {8192, 0, 22, {56, 56, 56, 32}}, // 10 + {8192, 0, 22, {56, 56, 56, 50}}, // 11 + {8192, 0, 26, {50, 50, 50, 38, 30}}, // 12 * + {8192, 65537, 0, {56, 48, 48}}, // 13 + {8192, 65537, 0, {56, 56, 30}}, // 14 +}; + +std::map<uint32_t, apsi::PSIParams::QueryParams> kPolynomialParams = { + {20, {0, {1, 2, 5, 8, 9, 10}}}, + {35, {5, {1, 2, 3, 4, 5, 6, 18, 30, 42, 54, 60}}}, + {36, {0, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36}}}, + {42, {0, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42}}}, + {51, {3, {1, 2, 3, 4, 12, 20, 24}}}, + {55, {0, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}}}, + {64, {0, {1, 3, 4, 9, 11, 16, 21, 23, 28, 29, 31, 32}}}, + {72, {0, {1, 3, 4, 9, 11, 16, 20, 25, 27, 32, 33, 35, 36}}}, + {98, {8, {1, 3, 4, 9, 27}}}, + {125, {5, {1, 2, 3, 4, 5, 6, 18, 30, 42, 54, 60}}}, + {128, + {0, {1, 3, 4, 5, 8, 14, 20, 26, 32, 38, 44, 50, 56, 59, 60, 61, 63, 64}}}, + {180, {0, {1, 3, 4, 6, 10, 13, 15, 21, 29, 37, 45, + 53, 61, 69, 77, 81, 83, 86, 87, 90, 92, 96}}}, + {228, {0, {1, 3, 8, 19, 33, 39, 92, 102}}}, + {512, + {0, {1, 3, 4, 6, 10, 13, 15, 21, 29, 37, 45, 53, 61, 69, + 75, 77, 80, 84, 86, 87, 89, 90, 181, 183, 188, 190, 195, 197, + 206, 213, 214, 222, 230, 238, 246, 254, 261, 337, 338, 345, 353, 361, + 370, 372, 377, 379, 384, 386, 477, 479, 486, 487, 495, 503, 511}}}, + {726, {0, {1, 4, 13, 18, 51, 92, 163, 208, 223}}}, + {770, {26, {1, 5, 8, 27, 135}}}, + {782, {26, {1, 5, 8, 27, 135}}}, + {1016, {0, {1, 6, 8, 21, 60, 93, 104, 154, 378, 414}}}, + {1024, + {0, + {1, 3, 4, 6, 10, 13, 15, 21, 29, 37, 45, 53, 61, 69, + 75, 77, 80, 84, 86, 87, 89, 90, 181, 183, 188, 190, 195, 197, + 206, 213, 214, 222, 230, 238, 246, 254, 261, 337, 338, 345, 353, 361, + 370, 372, 377, 379, 384, 386, 477, 479, 486, 487, 495, 503, 511, 519, + 521, 528, 533, 540, 548, 553, 590, 591, 593, 597, 601, 613, 616, 624, + 639, 642, 651, 662, 673, 684, 695, 701, 711, 722, 733, 744, 755, 766, + 777, 788, 799, 811, 822, 833, 844, 855, 866, 877, 888, 899, 901, 911, + 922, 933, 944, 955, 966, 977, 988, 993, 999, 1011, 1021, 1022, 1023}}}, + {1304, {44, {1, 3, 11, 18, 45, 225}}}, + {4000, {310, {1, 4, 10, 11, 28, 33, 78, 118, 143, 311, 1555}}}, + {8100, {310, {1, 4, 10, 11, 28, 33, 78, 118, 143, 311, 1555}}} // +}; +} // namespace + +yacl::Buffer PsiParamsToBuffer(const apsi::PSIParams &psi_params) { + proto::LabelPsiParamsProto psi_params_proto; + + psi_params_proto.set_hash_func_count( + psi_params.table_params().hash_func_count); + + psi_params_proto.set_table_size(psi_params.table_params().table_size); + psi_params_proto.set_max_items_per_bin( + psi_params.table_params().max_items_per_bin); + + psi_params_proto.set_felts_per_item(psi_params.item_params().felts_per_item); + + psi_params_proto.set_ps_low_degree(psi_params.query_params().ps_low_degree); + + if (psi_params.query_params().query_powers.size() > 0) { + for (const auto &power : psi_params.query_params().query_powers) { + psi_params_proto.add_query_powers(power); + } + } + + auto *seal_params_proto = new proto::SealParamsProto(); + seal_params_proto->set_plain_modulus( + psi_params.seal_params().plain_modulus().value()); + seal_params_proto->set_poly_modulus_degree( + psi_params.seal_params().poly_modulus_degree()); + + auto coeff_modulus = psi_params.seal_params().coeff_modulus(); + for (auto &coeff_moduli : coeff_modulus) { + seal_params_proto->add_coeff_modulus(coeff_moduli.value()); + } + + psi_params_proto.set_allocated_seal_params(seal_params_proto); + + yacl::Buffer buffer(psi_params_proto.ByteSizeLong()); + psi_params_proto.SerializePartialToArray(buffer.data(), buffer.size()); + return buffer; +} + +apsi::PSIParams ParsePsiParamsProto(const yacl::Buffer &buffer) { + proto::LabelPsiParamsProto psi_params_proto; + + YACL_ENFORCE(psi_params_proto.ParseFromArray(buffer.data(), buffer.size())); + + return ParsePsiParamsProto(psi_params_proto); +} + +apsi::PSIParams ParsePsiParamsProto( + const proto::LabelPsiParamsProto &psi_params_proto) { + apsi::PSIParams::ItemParams item_params; + apsi::PSIParams::TableParams table_params; + apsi::PSIParams::QueryParams query_params; + apsi::PSIParams::SEALParams seal_params; + + item_params.felts_per_item = psi_params_proto.felts_per_item(); + + table_params.hash_func_count = psi_params_proto.hash_func_count(); + table_params.table_size = psi_params_proto.table_size(); + table_params.max_items_per_bin = psi_params_proto.max_items_per_bin(); + + query_params.ps_low_degree = psi_params_proto.ps_low_degree(); + + if (psi_params_proto.query_powers_size() > 0) { + for (int idx = 0; idx < psi_params_proto.query_powers_size(); ++idx) { + query_params.query_powers.insert(psi_params_proto.query_powers(idx)); + } + } else { + for (size_t idx = 0; idx < table_params.max_items_per_bin; ++idx) { + query_params.query_powers.insert(idx + 1); + } + } + + std::vector<seal::Modulus> coeff_modulus( + psi_params_proto.seal_params().coeff_modulus_size()); + for (int idx = 0; idx < psi_params_proto.seal_params().coeff_modulus_size(); + ++idx) { + coeff_modulus[idx] = psi_params_proto.seal_params().coeff_modulus(idx); + } + + size_t poly_modulus_degree = + psi_params_proto.seal_params().poly_modulus_degree(); + size_t plain_modulus = psi_params_proto.seal_params().plain_modulus(); + + seal_params.set_poly_modulus_degree(poly_modulus_degree); + + seal_params.set_plain_modulus(plain_modulus); + + seal_params.set_coeff_modulus(coeff_modulus); + + apsi::PSIParams psi_params(item_params, table_params, query_params, + seal_params); + + return psi_params; +} + +inline size_t GetHashTruncateSize(size_t nr, size_t ns, + size_t stats_params = 40) { + // reference: + // Fast Private Set Intersection from Homomorphic Encryption + // https://eprint.iacr.org/2017/299 + // section 4.2 + // log2(Nx)+log2(Ny)+stats_params + size_t l1 = + std::ceil(std::log2(ns)) + std::ceil(std::log2(nr)) + stats_params; + l1 = std::max(static_cast<size_t>(80), l1); + + return l1; +} + +SEALParams GetSealParams(size_t nr, size_t ns) { + if (nr == 1) { + if (ns <= 3000000) { // 3M + return kSealParams[0]; + } else { + return kSealParams[12]; + } + } + if ((nr <= 4096) && (ns <= 1000000)) { // 1M + return kSealParams[6]; + } + + return kSealParams[12]; +} + +apsi::PSIParams GetPsiParams(size_t nr, size_t ns, size_t max_items_per_bin) { + SEALParams seal_params = GetSealParams(nr, ns); + + apsi::PSIParams::ItemParams item_params; + apsi::PSIParams::TableParams table_params; + apsi::PSIParams::QueryParams query_params; + apsi::PSIParams::SEALParams apsi_seal_params; + + size_t hash_size = GetHashTruncateSize(nr, ns); + item_params.felts_per_item = std::ceil( + static_cast<double>(hash_size) / (seal_params.GetPlainModulusBits() - 1)); + + table_params.hash_func_count = 3; + if (nr == 1) { + table_params.hash_func_count = 1; + } + size_t poly_item_count; + poly_item_count = + seal_params.poly_modulus_degree / item_params.felts_per_item; + table_params.table_size = poly_item_count; + + size_t cuckoo_table_size = std::ceil(1.6 * nr); + + while (table_params.table_size < cuckoo_table_size) { + table_params.table_size += poly_item_count; + } + + table_params.max_items_per_bin = + ns * table_params.hash_func_count / table_params.table_size; + + // receiver has just one item + // sender's items size < 3 million + if ((nr == 1) && (seal_params.poly_modulus_degree == 2048)) { + if (ns <= 100000) { + table_params.max_items_per_bin = 20; + } else if (ns <= 256000) { + table_params.max_items_per_bin = 35; + } else { + table_params.max_items_per_bin = 55; + } + + query_params.ps_low_degree = 0; + for (size_t idx = 0; idx < table_params.max_items_per_bin; ++idx) { + query_params.query_powers.insert(idx + 1); + } + } + + // query_powers reference Challis and Robinson (2010) + // http://emis.library.cornell.edu/journals/JIS/VOL13/Challis/challis6.pdf + if (seal_params.poly_modulus_degree == 4096) { + // 256K-512.json + table_params.max_items_per_bin = 64; + + query_params.ps_low_degree = 0; + query_params.query_powers = {1, 3, 4, 9, 11, 16, 21, 23, 28, 29, 31, 32}; + } else if (seal_params.poly_modulus_degree == 8192) { + // 1M-1-32.json + table_params.max_items_per_bin = 228; + + query_params.ps_low_degree = 0; + query_params.query_powers = {1, 3, 8, 19, 33, 39, 92, 102}; + } + + // seal param + apsi_seal_params.set_poly_modulus_degree(seal_params.poly_modulus_degree); + + if (seal_params.plain_modulus_bits > 0) { + apsi_seal_params.set_plain_modulus(seal::PlainModulus::Batching( + seal_params.poly_modulus_degree, seal_params.plain_modulus_bits)); + + } else if (seal_params.plain_modulus > 0) { + apsi_seal_params.set_plain_modulus(seal_params.plain_modulus); + } else { + YACL_THROW( + "SEALParams error, must set plain_modulus or plain_modulus_bits"); + } + + apsi_seal_params.set_coeff_modulus(seal::CoeffModulus::Create( + seal_params.poly_modulus_degree, seal_params.coeff_modulus_bits)); + + if ((table_params.hash_func_count > 1) && (max_items_per_bin != 0) && + kPolynomialParams.count(max_items_per_bin)) { + table_params.max_items_per_bin = max_items_per_bin; + + query_params = kPolynomialParams[max_items_per_bin]; + } + + apsi::PSIParams psi_params(item_params, table_params, query_params, + apsi_seal_params); + + return psi_params; +} + +} // namespace psi::psi diff --git a/psi/psi/core/labeled_psi/psi_params.h b/psi/psi/core/labeled_psi/psi_params.h new file mode 100644 index 00000000..1742edcb --- /dev/null +++ b/psi/psi/core/labeled_psi/psi_params.h @@ -0,0 +1,79 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <algorithm> +#include <cmath> +#include <string> +#include <vector> + +#include "apsi/psi_params.h" +#include "yacl/base/exception.h" +#include "yacl/link/link.h" + +#include "psi/psi/core/labeled_psi/serializable.pb.h" + +namespace psi::psi { + +struct SEALParams { + size_t poly_modulus_degree; + // plain_modulus or plain_modulus_bits + size_t plain_modulus = 0; + size_t plain_modulus_bits = 0; + std::vector<int> coeff_modulus_bits; + + size_t GetPlainModulusBits() { + // plain_modulus_bits + if (plain_modulus_bits > 0) { + return plain_modulus_bits; + } else if (plain_modulus > 0) { + // get plain_modulus_bits by plain_modulus + return std::floor(std::log2(plain_modulus)); + } else { + YACL_THROW( + "SEALParams error, must set plain_modulus or plain_modulus_bits"); + } + } +}; + +/** + * @brief Get the Psi Params object + * + * @param nr receiver's items size + * @param ns sender's items size + * @return apsi::PSIParams + */ +apsi::PSIParams GetPsiParams(size_t nr, size_t ns, + size_t max_items_per_bin = 0); + +/** + * @brief Serialize apsi::PSIParams to yacl::Buffer + * + * @param psi_params apsi::PSIParams + * @return yacl::Buffer + */ +yacl::Buffer PsiParamsToBuffer(const apsi::PSIParams &psi_params); + +/** + * @brief DeSerialize yacl::Buffer to apsi::PSIParams + * + * @param buffer PSIParams bytes buffer + * @return apsi::PSIParams + */ +apsi::PSIParams ParsePsiParamsProto(const yacl::Buffer &buffer); +apsi::PSIParams ParsePsiParamsProto( + const proto::LabelPsiParamsProto &psi_params_proto); + +} // namespace psi::psi diff --git a/psi/psi/core/labeled_psi/receiver.cc b/psi/psi/core/labeled_psi/receiver.cc new file mode 100644 index 00000000..60d7ebc2 --- /dev/null +++ b/psi/psi/core/labeled_psi/receiver.cc @@ -0,0 +1,572 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/labeled_psi/receiver.h" + +#include <algorithm> +#include <cstddef> +#include <future> +#include <map> +#include <memory> +#include <set> +#include <string> +#include <unordered_map> +#include <utility> +#include <vector> + +#include "absl/types/span.h" +#include "apsi/plaintext_powers.h" +#include "apsi/util/db_encoding.h" +#include "apsi/util/label_encryptor.h" +#include "spdlog/spdlog.h" +#include "yacl/utils/parallel.h" + +#include "psi/psi/core/ecdh_oprf/ecdh_oprf_selector.h" +#include "psi/psi/core/labeled_psi/package.h" +#include "psi/psi/core/labeled_psi/psi_params.h" +#include "psi/psi/utils/utils.h" + +namespace psi::psi { + +namespace { + +constexpr std::uint64_t kCuckooTableInsertAttempts = 500; + +template <typename T> +bool HasNZeros(T *ptr, size_t count) { + return std::all_of(ptr, ptr + count, + [](auto a) { return a == static_cast<T>(0); }); +} +} // namespace + +LabelPsiReceiver::LabelPsiReceiver(const apsi::PSIParams &params, + bool has_label) + : psi_params_(params), has_label_(has_label) { + Initialize(); +} + +void LabelPsiReceiver::Initialize() { + SPDLOG_DEBUG("PSI parameters set to: {}", psi_params_.to_string()); + SPDLOG_DEBUG( + "item_bit_count_per_felt: {} ; item_bit_count: {};" + " bins_per_bundle: {}; bundle_idx_count: {}", + psi_params_.item_bit_count_per_felt(), psi_params_.item_bit_count(), + psi_params_.bins_per_bundle(), psi_params_.bundle_idx_count()); + + // Initialize the CryptoContext with a new SEALContext + crypto_context_ = apsi::CryptoContext(psi_params_); + + // Set up the PowersDag + ResetPowersDag(psi_params_.query_params().query_powers); + + // Create new keys + ResetKeys(); +} + +void LabelPsiReceiver::ResetKeys() { + // Generate new keys + seal::KeyGenerator generator(*GetSealContext()); + + // Set the symmetric key, encryptor, and decryptor + crypto_context_.set_secret(generator.secret_key()); + + // Create Serializable<RelinKeys> and move to relin_keys_ for storage + relin_keys_.clear(); + if (GetSealContext()->using_keyswitching()) { + seal::Serializable<seal::RelinKeys> relin_keys( + generator.create_relin_keys()); + relin_keys_.set(std::move(relin_keys)); + } +} + +std::uint32_t LabelPsiReceiver::ResetPowersDag( + const std::set<std::uint32_t> &source_powers) { + // First compute the target powers + std::set<uint32_t> target_powers = apsi::util::create_powers_set( + psi_params_.query_params().ps_low_degree, + psi_params_.table_params().max_items_per_bin); + + // Configure the PowersDag + pd_.configure(source_powers, target_powers); + + // Check that the PowersDag is valid + if (!pd_.is_configured()) { + SPDLOG_ERROR( + "Failed to configure PowersDag (source_powers:{} target_powers:{})", + apsi::util::to_string(source_powers), + apsi::util::to_string(target_powers)); + + YACL_THROW("failed to configure PowersDag"); + } + SPDLOG_DEBUG("Configured PowersDag with depth {}", pd_.depth()); + + return pd_.depth(); +} + +apsi::PSIParams LabelPsiReceiver::RequestPsiParams( + size_t items_size, const std::shared_ptr<yacl::link::Context> &link_ctx) { + yacl::Buffer buffer(&items_size, sizeof(items_size)); + + link_ctx->SendAsyncThrottled( + link_ctx->NextRank(), buffer, + fmt::format("send client items size:{}", items_size)); + + yacl::Buffer psi_params_buffer = link_ctx->Recv( + link_ctx->NextRank(), fmt::format("recv psi params message")); + + return ParsePsiParamsProto(psi_params_buffer); +} + +std::pair<std::vector<apsi::HashedItem>, std::vector<apsi::LabelKey>> +LabelPsiReceiver::RequestOPRF( + const std::vector<std::string> &items, + const std::shared_ptr<yacl::link::Context> &link_ctx) { + std::vector<std::string> blind_items(items.size()); + std::vector<std::shared_ptr<IEcdhOprfClient>> oprf_clients(items.size()); + + yacl::parallel_for(0, items.size(), 1, [&](int64_t begin, int64_t end) { + for (int idx = begin; idx < end; ++idx) { + oprf_clients[idx] = + CreateEcdhOprfClient(OprfType::Basic, CurveType::CURVE_FOURQ); + oprf_clients[idx]->SetCompareLength(kEccKeySize); + + blind_items[idx] = oprf_clients[idx]->Blind(items[idx]); + } + }); + + proto::OprfProto oprf_proto; + for (auto &blind_item : blind_items) { + oprf_proto.add_data(blind_item.data(), blind_item.length()); + } + + yacl::Buffer blind_buffer(oprf_proto.ByteSizeLong()); + oprf_proto.SerializePartialToArray(blind_buffer.data(), blind_buffer.size()); + + link_ctx->SendAsyncThrottled( + link_ctx->NextRank(), blind_buffer, + fmt::format("send oprf blind items buffer size:{}", blind_buffer.size())); + + yacl::Buffer evaluated_buffer = link_ctx->Recv( + link_ctx->NextRank(), fmt::format("recv oprf evaluated message")); + + proto::OprfProto evaluated_proto; + YACL_ENFORCE(evaluated_proto.ParseFromArray(evaluated_buffer.data(), + evaluated_buffer.size())); + + std::vector<std::string> items_oprf(evaluated_proto.data_size()); + yacl::parallel_for(0, evaluated_proto.data_size(), 1, + [&](int64_t begin, int64_t end) { + for (int idx = begin; idx < end; ++idx) { + items_oprf[idx] = oprf_clients[idx]->Finalize( + items[idx], evaluated_proto.data(idx)); + } + }); + + std::vector<apsi::HashedItem> hashed_items(items_oprf.size()); + std::vector<apsi::LabelKey> label_keys(items_oprf.size()); + + for (size_t idx = 0; idx < items_oprf.size(); ++idx) { + std::memcpy(hashed_items[idx].value().data(), items_oprf[idx].data(), + hashed_items[idx].value().size()); + + std::memcpy(label_keys[idx].data(), + &items_oprf[idx][hashed_items[idx].value().size()], + label_keys[idx].size()); + } + + return std::make_pair(std::move(hashed_items), std::move(label_keys)); +} + +std::pair<std::vector<size_t>, std::vector<std::string>> +LabelPsiReceiver::RequestQuery( + const std::vector<apsi::HashedItem> &hashed_items, + const std::vector<apsi::LabelKey> &label_keys, + const std::shared_ptr<yacl::link::Context> &link_ctx) { + kuku::KukuTable cuckoo( + psi_params_.table_params().table_size, // Size of the hash table + 0, // Not using a stash + psi_params_.table_params().hash_func_count, // Number of hash functions + {0, 0}, // Hardcoded { 0, 0 } as the seed + kCuckooTableInsertAttempts, // The number of insertion attempts + {0, 0}); + + SPDLOG_INFO("cuckoo table_size:{}", cuckoo.table_size()); + + // Hash the data into a cuckoo hash table + // cuckoo_hashing + for (size_t item_idx = 0; item_idx < hashed_items.size(); item_idx++) { + const auto &item = hashed_items[item_idx]; + if (!cuckoo.insert(item.get_as<kuku::item_type>().front())) { + // Insertion can fail for two reasons: + // + // (1) The item was already in the table, in which case the + // "leftover item" is empty; (2) Cuckoo hashing failed due to too + // small table or too few hash functions. + // + // In case (1) simply move on to the next item and log this issue. Case + // (2) is a critical issue so we throw and exception. + if (cuckoo.is_empty_item(cuckoo.leftover_item())) { + SPDLOG_INFO("Skipping repeated insertion of items{}:{}", item_idx, + item.to_string()); + } else { + SPDLOG_INFO("Failed to insert items[{}:{}; cuckoo table fill-rate: {}", + item_idx, item.to_string(), cuckoo.fill_rate()); + YACL_THROW("failed to insert item into cuckoo table"); + } + } + } + SPDLOG_INFO( + "Finished inserting items with {} hash functions; cuckoo table " + "fill-rate: {}", + cuckoo.loc_func_count(), cuckoo.fill_rate()); + + apsi::receiver::IndexTranslationTable itt; + itt.item_count_ = hashed_items.size(); + + for (size_t item_idx = 0; item_idx < hashed_items.size(); item_idx++) { + auto item_loc = + cuckoo.query(hashed_items[item_idx].get_as<kuku::item_type>().front()); + itt.table_idx_to_item_idx_[item_loc.location()] = item_idx; + } + + // Set up unencrypted query data + std::vector<apsi::receiver::PlaintextPowers> plain_powers; + + // prepare_data + { + STOPWATCH(apsi::util::recv_stopwatch, + "Receiver::create_query::prepare_data"); + for (uint32_t bundle_idx = 0; bundle_idx < psi_params_.bundle_idx_count(); + bundle_idx++) { + SPDLOG_DEBUG("Preparing data for bundle index {}", bundle_idx); + + // First, find the items for this bundle index + absl::Span<const kuku::item_type> bundle_items = absl::MakeSpan( + cuckoo.table().data() + + static_cast<size_t>(bundle_idx * psi_params_.items_per_bundle()), + psi_params_.items_per_bundle()); + + std::vector<uint64_t> alg_items; + for (const auto &item : bundle_items) { + // Now set up a BitstringView to this item + gsl::span<const unsigned char> item_bytes( + reinterpret_cast<const unsigned char *>(item.data()), sizeof(item)); + apsi::BitstringView<const unsigned char> item_bits( + item_bytes, psi_params_.item_bit_count()); + + // Create an algebraic item by breaking up the item into modulo + // plain_modulus parts + std::vector<uint64_t> alg_item = apsi::util::bits_to_field_elts( + item_bits, psi_params_.seal_params().plain_modulus()); + std::copy(alg_item.cbegin(), alg_item.cend(), back_inserter(alg_items)); + } + + // Now that we have the algebraized items for this bundle index, we + // create a PlaintextPowers object that computes all necessary powers of + // the algebraized items. + plain_powers.emplace_back(std::move(alg_items), psi_params_, pd_); + } + } + SPDLOG_INFO("plain_powers size:{}, using_keyswitching:{}", + plain_powers.size(), GetSealContext()->using_keyswitching()); + + // The very last thing to do is encrypt the plain_powers and consolidate the + // matching powers for different bundle indices + std::unordered_map<uint32_t, std::vector<apsi::SEALObject<seal::Ciphertext>>> + encrypted_powers; + + // encrypt_data + { + SPDLOG_DEBUG("Receiver::create_query::encrypt_data"); + for (uint32_t bundle_idx = 0; bundle_idx < psi_params_.bundle_idx_count(); + bundle_idx++) { + SPDLOG_DEBUG("Encoding and encrypting data for bundle index {}", + bundle_idx); + + // Encrypt the data for this power + auto encrypted_power(plain_powers[bundle_idx].encrypt(crypto_context_)); + + // Move the encrypted data to encrypted_powers + for (auto &e : encrypted_power) { + encrypted_powers[e.first].emplace_back(std::move(e.second)); + } + } + } + SPDLOG_INFO("encrypted_powers size: {}", encrypted_powers.size()); + + std::vector<uint8_t> temp; + if (GetSealContext()->using_keyswitching()) { + temp.resize(relin_keys_.save_size(compr_mode_)); + relin_keys_.save(temp, compr_mode_); + } + + proto::QueryRequestProto query_proto; + query_proto.set_relin_keys(temp.data(), temp.size()); + + for (const auto &q : encrypted_powers) { + proto::EncryptedPowersProto *powers_proto = + query_proto.add_encrypted_powers(); + powers_proto->set_power(q.first); + for (const auto &ct : q.second) { + // Save each SEALObject<seal::Ciphertext> + temp.resize(ct.save_size(compr_mode_)); + auto size = ct.save(temp, compr_mode_); + powers_proto->add_ciphertexts(temp.data(), size); + } + SPDLOG_DEBUG("ciphertexts_size:{}", powers_proto->ciphertexts_size()); + } + + yacl::Buffer query_buffer(query_proto.ByteSizeLong()); + query_proto.SerializePartialToArray(query_buffer.data(), query_buffer.size()); + + link_ctx->SendAsyncThrottled( + link_ctx->NextRank(), query_buffer, + fmt::format("send query buffer size:{}", query_buffer.size())); + + yacl::Buffer response_buffer = link_ctx->Recv( + link_ctx->NextRank(), fmt::format("recv server query response message")); + + proto::QueryResponseProto response_proto; + YACL_ENFORCE(response_proto.ParseFromArray(response_buffer.data(), + response_buffer.size())); + + std::vector<std::pair<size_t, std::string>> query_result_vec; + + std::vector<std::vector<std::pair<size_t, std::string>>> results( + response_proto.results_size()); + + yacl::parallel_for(0, response_proto.results_size(), 1, + [&](int64_t begin, int64_t end) { + for (int idx = begin; idx < end; ++idx) { + const proto::QueryResultProto &query_result_proto = + response_proto.results(idx); + results[idx] = LabelPsiReceiver::ProcessQueryResult( + query_result_proto, itt, label_keys); + } + }); + + for (int idx = 0; idx < response_proto.results_size(); ++idx) { + query_result_vec.insert(query_result_vec.end(), results[idx].begin(), + results[idx].end()); + } + + std::sort(query_result_vec.begin(), query_result_vec.end(), + [](const std::pair<size_t, std::string> &a, + const std::pair<size_t, std::string> &b) { + return a.first < b.first; + }); + + std::vector<size_t> query_result; + std::vector<std::string> query_labels; + + for (const auto &[index, value] : query_result_vec) { + query_result.emplace_back(index); + if (has_label_) { + query_labels.emplace_back(value); + } + } + + return std::make_pair(std::move(query_result), std::move(query_labels)); +} + +std::vector<std::pair<size_t, std::string>> +LabelPsiReceiver::ProcessQueryResult( + const proto::QueryResultProto &query_result_proto, + const apsi::receiver::IndexTranslationTable &itt, + const std::vector<apsi::LabelKey> &label_keys) { + auto seal_context = GetSealContext(); + ResultPackage result_package; + + result_package.bundle_idx = query_result_proto.bundle_idx(); + + auto query_cipher_data = query_result_proto.ciphertext(); + gsl::span<const unsigned char> query_cipher_data_span( + reinterpret_cast<const unsigned char *>(query_cipher_data.data()), + query_cipher_data.length()); + result_package.psi_result.load(seal_context, query_cipher_data_span); + + result_package.label_byte_count = query_result_proto.label_byte_count(); + result_package.nonce_byte_count = query_result_proto.nonce_byte_count(); + + for (int idx = 0; idx < query_result_proto.label_results_size(); ++idx) { + auto label_data = query_result_proto.label_results(idx); + gsl::span<const unsigned char> label_data_span( + reinterpret_cast<const unsigned char *>(label_data.data()), + label_data.length()); + apsi::SEALObject<seal::Ciphertext> temp; + temp.load(seal_context, label_data_span); + result_package.label_result.emplace_back(std::move(temp)); + } + + PlainResultPackage plain_rp = result_package.extract(crypto_context_); + + size_t item_count = itt.item_count(); + + auto felts_per_item = + seal::util::safe_cast<size_t>(psi_params_.item_params().felts_per_item); + auto items_per_bundle = + seal::util::safe_cast<size_t>(psi_params_.items_per_bundle()); + size_t bundle_start = seal::util::mul_safe( + seal::util::safe_cast<size_t>(plain_rp.bundle_idx), items_per_bundle); + + SPDLOG_DEBUG("bundle_start:{},felts_per_item:{}", bundle_start, + felts_per_item); + + // Check if we are supposed to have label data present but don't have for + // some reason + auto label_byte_count = + seal::util::safe_cast<size_t>(plain_rp.label_byte_count); + if ((label_byte_count != 0) && plain_rp.label_result.empty()) { + SPDLOG_WARN( + "Expected {}-byte labels in this result part, but label data is " + "missing entirely", + label_byte_count); + + // Just ignore the label data + label_byte_count = 0; + } + + // Read the nonce byte count and compute the effective label byte count; set + // the nonce byte count to zero if no label is expected anyway. + size_t nonce_byte_count = + label_byte_count != 0 + ? seal::util::safe_cast<size_t>(plain_rp.nonce_byte_count) + : 0; + size_t effective_label_byte_count = + seal::util::add_safe(nonce_byte_count, label_byte_count); + + // How much label data did we actually receive? + size_t received_label_bit_count = seal::util::mul_safe( + seal::util::safe_cast<size_t>(psi_params_.item_bit_count()), + plain_rp.label_result.size()); + + // Compute the received label byte count and check that it is not less than + // what was expected + size_t received_label_byte_count = received_label_bit_count / 8; + if (received_label_byte_count < nonce_byte_count) { + SPDLOG_WARN( + "Expected {} bytes of nonce data in this result part but only {} bytes " + "were received; ignoring the label data", + nonce_byte_count, received_label_byte_count); + + // Just ignore the label data + label_byte_count = 0; + effective_label_byte_count = 0; + } else if (received_label_byte_count < effective_label_byte_count) { + SPDLOG_WARN( + "Expected {} bytes of label data in this result part but only {} bytes " + "were received", + label_byte_count, (received_label_byte_count - nonce_byte_count)); + + // Reset our expectations to what was actually received + label_byte_count = received_label_byte_count - nonce_byte_count; + effective_label_byte_count = received_label_byte_count; + } + + // If there is a label, then we better have the appropriate label encryption + // keys available + if ((label_byte_count != 0) && label_keys.size() != item_count) { + SPDLOG_WARN( + "Expected {} label encryption keys but only {} were given; ignoring " + "the label data", + item_count, label_keys.size()); + + SPDLOG_INFO( + "Expected {} label encryption keys but only {} were given; ignoring " + "the label data", + item_count, label_keys.size()); + + // Just ignore the label data + label_byte_count = 0; + effective_label_byte_count = 0; + } + + seal::util::StrideIter<const uint64_t *> plain_rp_iter( + plain_rp.psi_result.data(), felts_per_item); + + std::vector<std::pair<size_t, std::string>> match_ids; + seal_for_each_n( + seal::util::iter(plain_rp_iter, static_cast<size_t>(0)), items_per_bundle, + [&](auto &&i) { + // Find felts_per_item consecutive zeros + bool match = HasNZeros(std::get<0>(i).ptr(), felts_per_item); + + if (!match) { + return; + } + + // Compute the cuckoo table index for this item. Then find + // the corresponding index + // in the input items vector so we know where to place the + // result. + size_t table_idx = seal::util::add_safe(std::get<1>(i), bundle_start); + auto item_idx = itt.find_item_idx(table_idx); + + // If this table_idx doesn't match any item_idx, ignore the + // result no matter what it is + if (item_idx == itt.item_count()) { + return; + } + + SPDLOG_DEBUG("Match found for items[{}] at cuckoo table index {}", + item_idx, table_idx); + apsi::Label label; + if (label_byte_count) { + SPDLOG_DEBUG( + "Found {} label parts for items[{}]; expecting {}-byte label ", + plain_rp.label_result.size(), item_idx, label_byte_count); + + // Collect the entire label into this vector + apsi::util::AlgLabel alg_label; + + size_t label_offset = + seal::util::mul_safe(std::get<1>(i), felts_per_item); + for (auto &label_parts : plain_rp.label_result) { + gsl::span<apsi::util::felt_t> label_part( + label_parts.data() + label_offset, felts_per_item); + std::copy(label_part.begin(), label_part.end(), + back_inserter(alg_label)); + } + + // Create the label + apsi::EncryptedLabel encrypted_label = apsi::util::dealgebraize_label( + alg_label, received_label_bit_count, + psi_params_.seal_params().plain_modulus()); + + // Resize down to the effective byte count + encrypted_label.resize(effective_label_byte_count); + + // Decrypt the label + label = apsi::util::decrypt_label( + encrypted_label, label_keys[item_idx], nonce_byte_count); + } + + std::string label_str; + if (label.size() > 0) { + label_str = UnPaddingData(label); + } + + match_ids.push_back(std::make_pair(item_idx, label_str)); + }); + + std::sort(match_ids.begin(), match_ids.end(), + [](const std::pair<size_t, std::string> &a, + const std::pair<size_t, std::string> &b) { + return a.first < b.first; + }); + + return match_ids; +} + +} // namespace psi::psi diff --git a/psi/psi/core/labeled_psi/receiver.h b/psi/psi/core/labeled_psi/receiver.h new file mode 100644 index 00000000..42023485 --- /dev/null +++ b/psi/psi/core/labeled_psi/receiver.h @@ -0,0 +1,136 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <memory> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "apsi/crypto_context.h" +#include "apsi/itt.h" +#include "apsi/powers.h" +#include "apsi/psi_params.h" +#include "apsi/seal_object.h" +#include "gsl/span" +#include "seal/seal.h" +#include "yacl/link/link.h" + +#include "psi/psi/core/ecdh_oprf/ecdh_oprf.h" + +#include "psi/psi/core/labeled_psi/serializable.pb.h" + +namespace psi::psi { + +class LabelPsiReceiver { + public: + explicit LabelPsiReceiver(const apsi::PSIParams &params, + bool has_label = false); + + /** + * @brief Request PSI Parameters + * + * @param items_size receiver's items size + * @param link_ctx link context + * @return apsi::PSIParams + */ + static apsi::PSIParams RequestPsiParams( + size_t items_size, const std::shared_ptr<yacl::link::Context> &link_ctx); + + /** + * @brief Request items oprf + * + * @param items receiver's items + * @param link_ctx link context + * @return std::pair<std::vector<apsi::HashedItem>, + * std::vector<apsi::LabelKey>> + * + * split items's oprf(32B) to HashedItem(16B) and LabelKey(16B) + */ + static std::pair<std::vector<apsi::HashedItem>, std::vector<apsi::LabelKey>> + RequestOPRF(const std::vector<std::string> &items, + const std::shared_ptr<yacl::link::Context> &link_ctx); + + /** + * @brief Request PSI Query + * send items's query_powers + * + * @param hashed_items + * @param label_keys + * @param link_ctx + * @return std::pair<std::vector<size_t>, std::vector<std::string>> + * Get query polynomial ciphertext + * + */ + std::pair<std::vector<size_t>, std::vector<std::string>> RequestQuery( + const std::vector<apsi::HashedItem> &hashed_items, + const std::vector<apsi::LabelKey> &label_keys, + const std::shared_ptr<yacl::link::Context> &link_ctx); + + /** + Generates a new set of keys to use for queries. + */ + void ResetKeys(); + + /** + Returns a reference to the PowersDag configured for this Receiver. + */ + const apsi::PowersDag &GetPowersDag() const { return pd_; } + + /** + Returns a reference to the CryptoContext for this Receiver. + */ + const apsi::CryptoContext &GetCryptoContext() const { + return crypto_context_; + } + + /** + Returns a reference to the SEALContext for this Receiver. + */ + std::shared_ptr<seal::SEALContext> GetSealContext() const { + return crypto_context_.seal_context(); + } + + private: + /** + Recomputes the PowersDag. The function returns the depth of the + PowersDag. In some cases the receiver may want to ensure that the depth of + the powers computation will be as expected (PowersDag::depth), and + otherwise attempt to reconfigure the PowersDag. + */ + std::uint32_t ResetPowersDag(const std::set<std::uint32_t> &source_powers); + + void Initialize(); + + std::vector<std::pair<size_t, std::string>> ProcessQueryResult( + const proto::QueryResultProto &query_result_proto, + const apsi::receiver::IndexTranslationTable &itt, + const std::vector<apsi::LabelKey> &label_keys); + + apsi::PSIParams psi_params_; + + apsi::CryptoContext crypto_context_; + + apsi::PowersDag pd_; + + apsi::SEALObject<seal::RelinKeys> relin_keys_; + + // NOTE(juhou): we now support zstd compression by default + seal::compr_mode_type compr_mode_ = seal::Serialization::compr_mode_default; + + bool has_label_; +}; +} // namespace psi::psi diff --git a/psi/psi/core/labeled_psi/sender.cc b/psi/psi/core/labeled_psi/sender.cc new file mode 100644 index 00000000..08b03f03 --- /dev/null +++ b/psi/psi/core/labeled_psi/sender.cc @@ -0,0 +1,544 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/labeled_psi/sender.h" + +#include <algorithm> +#include <cmath> +#include <future> +#include <memory> +#include <set> +#include <string> +#include <unordered_map> +#include <utility> +#include <vector> + +#include "apsi/powers.h" +#include "apsi/util/label_encryptor.h" +#include "gsl/span" +#include "yacl/utils/parallel.h" + +#include "psi/psi/core/labeled_psi/package.h" +#include "psi/psi/core/labeled_psi/sender_db.h" +#include "psi/psi/core/labeled_psi/sender_kvdb.h" + +#include "psi/psi/core/labeled_psi/serializable.pb.h" + +namespace psi::psi { + +namespace { + +class QueryRequest { + public: + QueryRequest(apsi::SEALObject<seal::RelinKeys> *relin_keys, + std::unordered_map< + uint32_t, std::vector<apsi::SEALObject<seal::Ciphertext>>> + &encrypted_powers, + const std::shared_ptr<ISenderDB> &sender_db) { + auto seal_context = sender_db->GetSealContext(); + + for (auto &q : encrypted_powers) { + SPDLOG_DEBUG("Extracting {} ciphertexts for exponent {}", q.second.size(), + q.first); + std::vector<seal::Ciphertext> cts; + for (auto &ct : q.second) { + cts.push_back(ct.extract(seal_context)); + if (!is_valid_for(cts.back(), *seal_context)) { + SPDLOG_ERROR("Extracted ciphertext is invalid for SEALContext"); + YACL_THROW("Extracted ciphertext is invalid for SEALContext"); + return; + } + } + data_[q.first] = std::move(cts); + } + + if (seal_context->using_keyswitching()) { + relin_keys_ = relin_keys->extract(seal_context); + if (!is_valid_for(relin_keys_, *seal_context)) { + YACL_THROW( + "Extracted relinearization keys are invalid for SEALContext"); + } + } + } + + const seal::RelinKeys &relin_keys() const noexcept { return relin_keys_; } + + auto &data() const noexcept { return data_; } + + seal::compr_mode_type compr_mode() const noexcept { return compr_mode_; } + + private: + seal::RelinKeys relin_keys_; + + /** + Holds the encrypted query data. In the map the key labels the exponent of the + query ciphertext and the vector holds the ciphertext data for different bundle + indices. + */ + std::unordered_map<std::uint32_t, std::vector<seal::Ciphertext>> data_; + + seal::compr_mode_type compr_mode_ = seal::Serialization::compr_mode_default; +}; + +using CiphertextPowers = std::vector<seal::Ciphertext>; + +uint32_t reset_powers_dag(apsi::PowersDag *pd, const apsi::PSIParams &params, + const std::set<uint32_t> &source_powers) { + // First compute the target powers + std::set<uint32_t> target_powers = + apsi::util::create_powers_set(params.query_params().ps_low_degree, + params.table_params().max_items_per_bin); + SPDLOG_DEBUG("target_powers size:{}", target_powers.size()); + + // Configure the PowersDag + pd->configure(source_powers, target_powers); + + // Check that the PowersDag is valid + if (!pd->is_configured()) { + SPDLOG_INFO( + "Failed to configure PowersDag (" + "source_powers: {}, target_powers: {}", + apsi::util::to_string(source_powers), + apsi::util::to_string(target_powers)); + YACL_THROW("failed to configure PowersDag"); + } + SPDLOG_INFO("Configured PowersDag with depth {}", pd->depth()); + + return pd->depth(); +} + +} // namespace + +LabelPsiSender::LabelPsiSender(std::shared_ptr<ISenderDB> sender_db) + : sender_db_(std::move(sender_db)) { + apsi::PSIParams params(sender_db_->GetParams()); + + crypto_context_ = apsi::CryptoContext(sender_db_->GetParams()); + + SPDLOG_INFO("begin set PowersDag"); + reset_powers_dag(&pd_, params, params.query_params().query_powers); + + SPDLOG_INFO("pd_ is_configured:{}", pd_.is_configured()); +} + +void LabelPsiSender::RunPsiParams( + size_t items_size, const std::shared_ptr<yacl::link::Context> &link_ctx) { + yacl::Buffer nr_buffer = + link_ctx->Recv(link_ctx->NextRank(), fmt::format("recv psi item size")); + + size_t nr; + YACL_ENFORCE(sizeof(nr) == nr_buffer.size()); + std::memcpy(&nr, nr_buffer.data(), nr_buffer.size()); + + apsi::PSIParams psi_params = GetPsiParams(nr, items_size); + + yacl::Buffer params_buffer = PsiParamsToBuffer(psi_params); + + link_ctx->SendAsyncThrottled( + link_ctx->NextRank(), params_buffer, + fmt::format("send psi params buffer size:{}", params_buffer.size())); +} + +void LabelPsiSender::RunOPRF( + const std::shared_ptr<IEcdhOprfServer> &oprf_server, + const std::shared_ptr<yacl::link::Context> &link_ctx) { + oprf_server->SetCompareLength(kEccKeySize); + + yacl::Buffer blind_buffer = link_ctx->Recv( + link_ctx->NextRank(), fmt::format("recv oprf blind message")); + + proto::OprfProto blind_proto; + YACL_ENFORCE( + blind_proto.ParseFromArray(blind_buffer.data(), blind_buffer.size())); + + proto::OprfProto evaluated_proto; + std::vector<std::string> evaluated_vec(blind_proto.data_size()); + yacl::parallel_for( + 0, blind_proto.data_size(), 1, [&](int64_t begin, int64_t end) { + for (int idx = begin; idx < end; ++idx) { + evaluated_vec[idx] = oprf_server->Evaluate(blind_proto.data(idx)); + } + }); + + for (int idx = 0; idx < blind_proto.data_size(); ++idx) { + evaluated_proto.add_data(evaluated_vec[idx].data(), + evaluated_vec[idx].length()); + } + + yacl::Buffer evaluated_buffer(evaluated_proto.ByteSizeLong()); + evaluated_proto.SerializePartialToArray(evaluated_buffer.data(), + evaluated_buffer.size()); + + link_ctx->SendAsyncThrottled( + link_ctx->NextRank(), evaluated_buffer, + fmt::format("send evaluated items buffer size:{}", + evaluated_buffer.size())); +} + +std::vector<std::shared_ptr<ResultPackage>> SenderRunQuery( + const QueryRequest &query, const std::shared_ptr<ISenderDB> &sender_db, + const apsi::PowersDag &pd); + +void LabelPsiSender::RunQuery( + const std::shared_ptr<yacl::link::Context> &link_ctx) { + yacl::Buffer query_buffer = link_ctx->Recv( + link_ctx->NextRank(), fmt::format("recv client query message")); + + proto::QueryRequestProto query_proto; + YACL_ENFORCE( + query_proto.ParseFromArray(query_buffer.data(), query_buffer.size())); + + auto seal_context = sender_db_->GetSealContext(); + + apsi::SEALObject<seal::RelinKeys> relin_keys; + if (seal_context->using_keyswitching()) { + auto relin_keys_data = query_proto.relin_keys(); + gsl::span<const unsigned char> relin_keys_data_span( + reinterpret_cast<const unsigned char *>(relin_keys_data.data()), + relin_keys_data.length()); + + relin_keys.load(seal_context, relin_keys_data_span); + } + + std::unordered_map<uint32_t, std::vector<apsi::SEALObject<seal::Ciphertext>>> + encrypted_powers; + for (int idx = 0; idx < query_proto.encrypted_powers_size(); ++idx) { + const proto::EncryptedPowersProto &encrypted_powers_proto = + query_proto.encrypted_powers(idx); + + std::vector<apsi::SEALObject<seal::Ciphertext>> ciphertexts; + ciphertexts.reserve(encrypted_powers_proto.ciphertexts_size()); + + for (int cipher_idx = 0; + cipher_idx < encrypted_powers_proto.ciphertexts_size(); ++cipher_idx) { + auto ct = encrypted_powers_proto.ciphertexts(cipher_idx); + gsl::span<const unsigned char> ct_span( + reinterpret_cast<const unsigned char *>(ct.data()), ct.length()); + apsi::SEALObject<seal::Ciphertext> temp; + temp.load(seal_context, ct_span); + ciphertexts.emplace_back(std::move(temp)); + } + encrypted_powers.emplace(encrypted_powers_proto.power(), + std::move(ciphertexts)); + } + + QueryRequest request(&relin_keys, encrypted_powers, sender_db_); + + std::vector<std::shared_ptr<ResultPackage>> query_result = + SenderRunQuery(request, sender_db_, pd_); + + proto::QueryResponseProto response_proto; + + std::vector<std::future<void>> futures; + apsi::ThreadPoolMgr tpm; + + for (auto &result : query_result) { + proto::QueryResultProto *result_proto = response_proto.add_results(); + futures.push_back(tpm.thread_pool().enqueue([&, result, result_proto]() { + result_proto->set_bundle_idx(result->bundle_idx); + std::vector<uint8_t> temp(result->psi_result.save_size(compr_mode_)); + auto size = result->psi_result.save(temp, compr_mode_); + result_proto->set_ciphertext(temp.data(), temp.size()); + result_proto->set_label_byte_count(result->label_byte_count); + result_proto->set_nonce_byte_count(result->nonce_byte_count); + + for (auto &r : result->label_result) { + temp.resize(r.save_size(compr_mode_)); + size = r.save(temp, compr_mode_); + result_proto->add_label_results(temp.data(), size); + } + })); + } + for (auto &f : futures) { + f.get(); + } + + yacl::Buffer response_buffer(response_proto.ByteSizeLong()); + response_proto.SerializePartialToArray(response_buffer.data(), + response_buffer.size()); + + SPDLOG_DEBUG("response_buffer size:{}, query_result size:{}", + response_buffer.size(), query_result.size()); + + link_ctx->SendAsyncThrottled( + link_ctx->NextRank(), response_buffer, + fmt::format("send query response size:{}", response_buffer.size())); +} + +void ComputePowers(const std::shared_ptr<ISenderDB> &sender_db, + const apsi::CryptoContext &crypto_context, + std::vector<CiphertextPowers> *all_powers, + const apsi::PowersDag &pd, uint32_t bundle_idx, + seal::MemoryPoolHandle *pool); + +void ProcessBinBundleCache( + const std::shared_ptr<ISenderDB> &sender_db, + const apsi::CryptoContext &crypto_context, + const std::shared_ptr<apsi::sender::BinBundle> &bundle, + std::vector<CiphertextPowers> *all_powers, uint32_t bundle_idx, + seal::compr_mode_type compr_mode, seal::MemoryPoolHandle *pool, + const std::shared_ptr<ResultPackage> &result); + +std::vector<std::shared_ptr<ResultPackage>> SenderRunQuery( + const QueryRequest &query, const std::shared_ptr<ISenderDB> &sender_db, + const apsi::PowersDag &pd) { + // We use a custom SEAL memory that is freed after the query is done + auto pool = seal::MemoryManager::GetPool(seal::mm_force_new); + + apsi::ThreadPoolMgr tpm; + + // Acquire read lock on SenderDB + // auto sender_db = sender_db; + auto sender_db_lock = sender_db->GetReaderLock(); + + SPDLOG_INFO("Start processing query request on database with {} items", + sender_db->GetItemCount()); + + // Copy over the CryptoContext from SenderDB; set the Evaluator for this local + // instance. Relinearization keys may not have been included in the query. In + // that case query.relin_keys() simply holds an empty seal::RelinKeys + // instance. There is no problem with the below call to + // CryptoContext::set_evaluator. + apsi::CryptoContext crypto_context(sender_db->GetCryptoContext()); + crypto_context.set_evaluator(query.relin_keys()); + + // Get the PSIParams + apsi::PSIParams params(sender_db->GetParams()); + + uint32_t bundle_idx_count = params.bundle_idx_count(); + + uint32_t max_items_per_bin = params.table_params().max_items_per_bin; + + // For each bundle index i, we need a vector of powers of the query Qᵢ. We + // need powers all the way up to Qᵢ^max_items_per_bin. We don't store the + // zeroth power. If Paterson-Stockmeyer is used, then only a subset of the + // powers will be populated. + std::vector<CiphertextPowers> all_powers(bundle_idx_count); + + // Initialize powers + for (CiphertextPowers &powers : all_powers) { + // The + 1 is because we index by power. The 0th power is a dummy value. I + // promise this makes things easier to read. + size_t powers_size = static_cast<size_t>(max_items_per_bin) + 1; + powers.reserve(powers_size); + for (size_t i = 0; i < powers_size; i++) { + powers.emplace_back(pool); + } + } + + // Load inputs provided in the query + for (const auto &q : query.data()) { + // The exponent of all the query powers we're about to iterate through + auto exponent = static_cast<size_t>(q.first); + + // Load Qᵢᵉ for all bundle indices i, where e is the exponent specified + // above + for (size_t bundle_idx = 0; bundle_idx < all_powers.size(); bundle_idx++) { + // Load input^power to all_powers[bundle_idx][exponent] + + SPDLOG_DEBUG("Extracting query ciphertext power {} for bundle index {}", + exponent, bundle_idx); + all_powers[bundle_idx][exponent] = q.second[bundle_idx]; + } + } + + // Compute query powers for the bundle indexes + for (size_t bundle_idx = 0; bundle_idx < bundle_idx_count; bundle_idx++) { + ComputePowers(sender_db, crypto_context, &all_powers, pd, + static_cast<uint32_t>(bundle_idx), &pool); + } + + SPDLOG_INFO("Finished computing powers for all bundle indices"); + SPDLOG_INFO("Start processing bin bundle caches"); + + std::vector<std::shared_ptr<ResultPackage>> query_results; + + for (size_t bundle_idx = 0; bundle_idx < bundle_idx_count; bundle_idx++) { + size_t cache_count = + sender_db->GetBinBundleCount(static_cast<uint32_t>(bundle_idx)); + + std::vector<std::future<void>> futures; + + for (size_t cache_idx = 0; cache_idx < cache_count; ++cache_idx) { + std::shared_ptr<apsi::sender::BinBundle> bundle = + sender_db->GetBinBundleAt(static_cast<uint32_t>(bundle_idx), + cache_idx); + + query_results.push_back(std::make_shared<ResultPackage>()); + std::shared_ptr<ResultPackage> result = *(query_results.rbegin()); + + futures.push_back(tpm.thread_pool().enqueue([&, bundle_idx, bundle, + result]() { + ProcessBinBundleCache(sender_db, crypto_context, bundle, &all_powers, + static_cast<uint32_t>(bundle_idx), + query.compr_mode(), &pool, result); + })); + } + + // Wait until all bin bundle caches have been processed + for (auto &f : futures) { + f.get(); + } + } + + SPDLOG_INFO("Finished processing query request"); + + return query_results; +} + +void ComputePowers(const std::shared_ptr<ISenderDB> &sender_db, + const apsi::CryptoContext &crypto_context, + std::vector<CiphertextPowers> *all_powers, + const apsi::PowersDag &pd, uint32_t bundle_idx, + seal::MemoryPoolHandle *pool) { + SPDLOG_DEBUG("Sender::ComputePowers"); + + // Compute all powers of the query + SPDLOG_DEBUG("Computing all query ciphertext powers for bundle index {}", + bundle_idx); + + auto evaluator = crypto_context.evaluator(); + auto relin_keys = crypto_context.relin_keys(); + + CiphertextPowers &powers_at_this_bundle_idx = (*all_powers)[bundle_idx]; + bool relinearize = crypto_context.seal_context()->using_keyswitching(); + pd.parallel_apply([&](const apsi::PowersDag::PowersNode &node) { + if (!node.is_source()) { + auto parents = node.parents; + seal::Ciphertext prod(*pool); + if (parents.first == parents.second) { + evaluator->square(powers_at_this_bundle_idx[parents.first], prod, + *pool); + } else { + evaluator->multiply(powers_at_this_bundle_idx[parents.first], + powers_at_this_bundle_idx[parents.second], prod, + *pool); + } + if (relinearize) { + evaluator->relinearize_inplace(prod, *relin_keys, *pool); + } + powers_at_this_bundle_idx[node.power] = std::move(prod); + } + }); + + // Now that all powers of the ciphertext have been computed, we need to + // transform them to NTT form. This will substantially improve the polynomial + // evaluation, because the plaintext polynomials are already in NTT + // transformed form, and the ciphertexts are used repeatedly for each bin + // bundle at this index. This computation is separate from the graph + // processing above, because the multiplications must all be done before + // transforming to NTT form. We omit the first ciphertext in the vector, + // because it corresponds to the zeroth power of the query and is included + // only for convenience of the indexing; the ciphertext is actually not set or + // valid for use. + + apsi::ThreadPoolMgr tpm; + + // After computing all powers we will modulus switch down to parameters that + // one more level for low powers than for high powers; same choice must be + // used when encoding/NTT transforming the SenderDB data. + auto high_powers_parms_id = + get_parms_id_for_chain_idx(*crypto_context.seal_context(), 1); + auto low_powers_parms_id = + get_parms_id_for_chain_idx(*crypto_context.seal_context(), 2); + + uint32_t ps_low_degree = sender_db->GetParams().query_params().ps_low_degree; + + std::vector<std::future<void>> futures; + for (uint32_t power : pd.target_powers()) { + futures.push_back(tpm.thread_pool().enqueue([&, power]() { + if (ps_low_degree == 0) { + // Only one ciphertext-plaintext multiplication is needed after this + evaluator->mod_switch_to_inplace(powers_at_this_bundle_idx[power], + high_powers_parms_id, *pool); + + // All powers must be in NTT form + evaluator->transform_to_ntt_inplace(powers_at_this_bundle_idx[power]); + } else { + if (power <= ps_low_degree) { + // Low powers must be at a higher level than high powers + evaluator->mod_switch_to_inplace(powers_at_this_bundle_idx[power], + low_powers_parms_id, *pool); + + // Low powers must be in NTT form + evaluator->transform_to_ntt_inplace(powers_at_this_bundle_idx[power]); + } else { + // High powers are only modulus switched + evaluator->mod_switch_to_inplace(powers_at_this_bundle_idx[power], + high_powers_parms_id, *pool); + } + } + })); + } + + for (auto &f : futures) { + f.get(); + } +} + +void ProcessBinBundleCache( + const std::shared_ptr<ISenderDB> &sender_db, + const apsi::CryptoContext &crypto_context, + const std::shared_ptr<apsi::sender::BinBundle> &bundle, + std::vector<CiphertextPowers> *all_powers, uint32_t bundle_idx, + seal::compr_mode_type compr_mode, seal::MemoryPoolHandle *pool, + const std::shared_ptr<ResultPackage> &result) { + SPDLOG_DEBUG("Sender::ProcessBinBundleCache"); + + std::reference_wrapper<const apsi::sender::BinBundleCache> cache = + std::cref(bundle->get_cache()); + + // Package for the result data + result->compr_mode = compr_mode; + + result->bundle_idx = bundle_idx; + result->nonce_byte_count = + seal::util::safe_cast<uint32_t>(sender_db->GetNonceByteCount()); + result->label_byte_count = + seal::util::safe_cast<uint32_t>(sender_db->GetLabelByteCount()); + + // Compute the matching result and move to rp + const apsi::sender::BatchedPlaintextPolyn &matching_polyn = + cache.get().batched_matching_polyn; + + // Determine if we use Paterson-Stockmeyer or not + uint32_t ps_low_degree = sender_db->GetParams().query_params().ps_low_degree; + uint32_t degree = + seal::util::safe_cast<uint32_t>(matching_polyn.batched_coeffs.size()) - 1; + bool using_ps = (ps_low_degree > 1) && (ps_low_degree < degree); + + if (using_ps) { + result->psi_result = matching_polyn.eval_patstock( + crypto_context, (*all_powers)[bundle_idx], + seal::util::safe_cast<size_t>(ps_low_degree), *pool); + } else { + result->psi_result = matching_polyn.eval((*all_powers)[bundle_idx], *pool); + } + + for (const auto &interp_polyn : cache.get().batched_interp_polyns) { + // Compute the label result and move to rp + degree = + seal::util::safe_cast<uint32_t>(interp_polyn.batched_coeffs.size()) - 1; + using_ps = (ps_low_degree > 1) && (ps_low_degree < degree); + if (using_ps) { + result->label_result.emplace_back(interp_polyn.eval_patstock( + crypto_context, (*all_powers)[bundle_idx], ps_low_degree, *pool)); + } else { + result->label_result.emplace_back( + interp_polyn.eval((*all_powers)[bundle_idx], *pool)); + } + } +} + +} // namespace psi::psi diff --git a/psi/psi/core/labeled_psi/sender.h b/psi/psi/core/labeled_psi/sender.h new file mode 100644 index 00000000..792eb657 --- /dev/null +++ b/psi/psi/core/labeled_psi/sender.h @@ -0,0 +1,71 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <memory> + +#include "apsi/powers.h" +#include "apsi/psi_params.h" +#include "apsi/seal_object.h" +#include "seal/seal.h" +#include "yacl/base/byte_container_view.h" +#include "yacl/base/exception.h" +#include "yacl/link/link.h" + +#include "psi/psi/core/ecdh_oprf/ecdh_oprf.h" +#include "psi/psi/core/labeled_psi/psi_params.h" +#include "psi/psi/core/labeled_psi/sender_db.h" + +namespace psi::psi { + +class LabelPsiSender { + public: + explicit LabelPsiSender(std::shared_ptr<ISenderDB> sender_db); + + /** + * @brief Receive PsiParams Request and Send PsiParams Response + * + * @param items_size + * @param link_ctx + */ + static void RunPsiParams( + size_t items_size, const std::shared_ptr<yacl::link::Context>& link_ctx); + + /** + * @brief Receive OPRF Request and Send OPRF Response + * + * @param oprf_server + * @param link_ctx + */ + static void RunOPRF(const std::shared_ptr<IEcdhOprfServer>& oprf_server, + const std::shared_ptr<yacl::link::Context>& link_ctx); + + /** + * @brief Receive query_powers Request and Send polynomial ciphertext Response + * + * @param link_ctx + */ + void RunQuery(const std::shared_ptr<yacl::link::Context>& link_ctx); + + private: + std::shared_ptr<ISenderDB> sender_db_; + + apsi::CryptoContext crypto_context_; + seal::compr_mode_type compr_mode_ = seal::Serialization::compr_mode_default; + + apsi::PowersDag pd_; +}; + +} // namespace psi::psi diff --git a/psi/psi/core/labeled_psi/sender_db.cc b/psi/psi/core/labeled_psi/sender_db.cc new file mode 100644 index 00000000..da7c325f --- /dev/null +++ b/psi/psi/core/labeled_psi/sender_db.cc @@ -0,0 +1,198 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// code reference https://github.com/microsoft/APSI/sender/sender_db.cpp +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +// we are using our own OPRF, the reason is we wanna make the oprf +// switchable between secp256k1, sm2 or other types + +// STD +#include <algorithm> +#include <chrono> +#include <future> +#include <iterator> +#include <memory> +#include <mutex> +#include <set> +#include <sstream> +#include <string> + +// APSI +#include "apsi/psi_params.h" +#include "apsi/thread_pool_mgr.h" +#include "apsi/util/db_encoding.h" +#include "apsi/util/label_encryptor.h" +#include "apsi/util/utils.h" +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" + +#include "psi/psi/core/ecdh_oprf/ecdh_oprf_selector.h" +#include "psi/psi/core/labeled_psi/sender_db.h" +#include "psi/psi/core/labeled_psi/serialize.h" +#include "psi/psi/utils/utils.h" + +// Kuku +#include "kuku/locfunc.h" + +// SEAL +#include "absl/strings/escaping.h" +#include "seal/util/common.h" +#include "seal/util/streambuf.h" +#include "yacl/crypto/utils/rand.h" +#include "yacl/utils/parallel.h" + +namespace psi::psi { + +namespace { + +using DurationMillis = std::chrono::duration<double, std::milli>; + +} + +namespace labeled_psi { + +/** +Creates and returns the vector of hash functions similarly to how Kuku 2.x sets +them internally. +*/ +std::vector<kuku::LocFunc> HashFunctions(const apsi::PSIParams &params) { + std::vector<kuku::LocFunc> result; + for (uint32_t i = 0; i < params.table_params().hash_func_count; i++) { + result.emplace_back(params.table_params().table_size, + kuku::make_item(i, 0)); + } + + return result; +} + +/** +Computes all cuckoo hash table locations for a given item. +*/ +std::unordered_set<kuku::location_type> AllLocations( + const std::vector<kuku::LocFunc> &hash_funcs, + const apsi::HashedItem &item) { + std::unordered_set<kuku::location_type> result; + for (const auto &hf : hash_funcs) { + result.emplace(hf(item.get_as<kuku::item_type>().front())); + } + + return result; +} + +/** +Compute the label size in multiples of item-size chunks. +*/ +size_t ComputeLabelSize(size_t label_byte_count, + const apsi::PSIParams &params) { + return (label_byte_count * 8 + params.item_bit_count() - 1) / + params.item_bit_count(); +} + +/** +Unpacks a cuckoo idx into its bin and bundle indices +*/ +std::pair<size_t, size_t> UnpackCuckooIdx(size_t cuckoo_idx, + size_t bins_per_bundle) { + // Recall that bin indices are relative to the bundle index. That is, the + // first bin index of a bundle at bundle index 5 is 0. A cuckoo index is + // similar, except it is not relative to the bundle index. It just keeps + // counting past bundle boundaries. So in order to get the bin index from the + // cuckoo index, just compute cuckoo_idx (mod bins_per_bundle). + size_t bin_idx = cuckoo_idx % bins_per_bundle; + + // Compute which bundle index this cuckoo index belongs to + size_t bundle_idx = (cuckoo_idx - bin_idx) / bins_per_bundle; + + return {bin_idx, bundle_idx}; +} + +} // namespace labeled_psi + +ISenderDB::ISenderDB(const apsi::PSIParams &params, + yacl::ByteContainerView oprf_key, + std::size_t label_byte_count, std::size_t nonce_byte_count, + bool compressed) + : params_(params), + crypto_context_(params_), + label_byte_count_(label_byte_count), + nonce_byte_count_(label_byte_count_ != 0 ? nonce_byte_count : 0), + item_count_(0), + compressed_(compressed), + stripped_(false) { + // The labels cannot be more than 1 KB. + if (label_byte_count_ > 1024) { + SPDLOG_ERROR("Requested label byte count {} exceeds the maximum (1024)", + label_byte_count_); + + YACL_THROW("label_byte_count is too large"); + } + + if (nonce_byte_count_ > apsi::max_nonce_byte_count) { + SPDLOG_ERROR("Request nonce byte count {} exceeds the maximum ({}) ", + nonce_byte_count_, apsi::max_nonce_byte_count); + YACL_THROW("nonce_byte_count is too large"); + } + + // If the nonce byte count is less than max_nonce_byte_count, print a warning; + // this is a labeled SenderDB but may not be safe to use for arbitrary label + // changes. + if ((label_byte_count_ != 0) && + nonce_byte_count_ < apsi::max_nonce_byte_count) { + SPDLOG_WARN( + "You have instantiated a labeled SenderDB instance with a nonce byte " + "count {} , which is less than the safe default value {} . Updating " + "labels for existing items in the SenderDB or removing and reinserting " + "items with different labels may leak information about the labels.", + nonce_byte_count_, apsi::max_nonce_byte_count); + } + + // Set the evaluator. This will be used for BatchedPlaintextPolyn::eval. + crypto_context_.set_evaluator(); + + oprf_key_.resize(oprf_key.size()); + std::memcpy(oprf_key_.data(), oprf_key.data(), oprf_key.size()); + + oprf_server_ = + CreateEcdhOprfServer(oprf_key, OprfType::Basic, CurveType::CURVE_FOURQ); + oprf_server_->SetCompareLength(kEccKeySize); +} + +double ISenderDB::GetPackingRate() const { + // Lock the database for reading + auto lock = GetReaderLock(); + + uint64_t item_count = seal::util::mul_safe( + static_cast<uint64_t>(GetItemCount()), + static_cast<uint64_t>(params_.table_params().hash_func_count)); + uint64_t max_item_count = seal::util::mul_safe( + static_cast<uint64_t>(GetBinBundleCount()), + static_cast<uint64_t>(params_.items_per_bundle()), + static_cast<uint64_t>(params_.table_params().max_items_per_bin)); + + return max_item_count != 0 ? static_cast<double>(item_count) / + static_cast<double>(max_item_count) + : 0.0; +} + +std::vector<uint8_t> ISenderDB::GetOprfKey() const { + if (stripped_) { + SPDLOG_ERROR("Cannot return the OPRF key from a stripped SenderDB"); + YACL_THROW("failed to return OPRF key"); + } + return oprf_key_; +} + +} // namespace psi::psi diff --git a/psi/psi/core/labeled_psi/sender_db.h b/psi/psi/core/labeled_psi/sender_db.h new file mode 100644 index 00000000..cb6e8336 --- /dev/null +++ b/psi/psi/core/labeled_psi/sender_db.h @@ -0,0 +1,241 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// code reference https://github.com/microsoft/APSI/sender/sender_db.h +// Licensed under the MIT license. + +#pragma once + +// STD +#include <atomic> +#include <cstddef> +#include <cstdint> +#include <iostream> +#include <memory> +#include <string> +#include <unordered_set> +#include <utility> +#include <vector> + +// GSL +#include "gsl/span" + +// APSI +#include "apsi/bin_bundle.h" +#include "apsi/crypto_context.h" +#include "apsi/item.h" +#include "apsi/psi_params.h" +#include "yacl/base/byte_container_view.h" +#include "yacl/io/kv/leveldb_kvstore.h" +#include "yacl/io/kv/memory_kvstore.h" + +#include "psi/psi/core/ecdh_oprf/basic_ecdh_oprf.h" +#include "psi/psi/utils/batch_provider.h" + +// SEAL +#include "seal/plaintext.h" +#include "seal/util/locks.h" +#include "spdlog/spdlog.h" + +namespace psi::psi { + +/** +A SenderDB maintains an in-memory representation of the sender's set of items +and labels (in labeled mode). This data is not simply copied into the SenderDB +data structures, but also preprocessed heavily to allow for faster online +computation time. Since inserting a large number of new items into a SenderDB +can take time, it is not recommended to recreate the SenderDB when the +database changes a little bit. Instead, the class supports fast update and +deletion operations that should be preferred: SenderDB::InsertOrAssign and +SenderDB::remove. + +The SenderDB constructor allows the label byte count to be specified; +unlabeled mode is activated by setting the label byte count to zero. It is +possible to optionally specify the size of the nonce used in encrypting the +labels, but this is best left to its default value unless the user is +absolutely sure of what they are doing. + +The SenderDB requires substantially more memory than the raw data would. Part +of that memory can automatically be compressed when it is not in use; this +feature is enabled by default, and can be disabled when constructing the +SenderDB. The downside of in-memory compression is a performance reduction +from decompressing parts of the data when they are used, and recompressing +them if they are updated. +*/ +class ISenderDB { + public: + /** + Creates a new SenderDB. + */ + ISenderDB(const apsi::PSIParams &params, yacl::ByteContainerView oprf_key, + std::size_t label_byte_count = 0, std::size_t nonce_byte_count = 16, + bool compressed = false); + + virtual ~ISenderDB() { std::memset(oprf_key_.data(), 0, oprf_key_.size()); } + + /** + Returns whether this is a labeled SenderDB. + */ + virtual bool IsLabeled() const { return 0 != label_byte_count_; } + + /** + Returns the label byte count. A zero value indicates an unlabeled SenderDB. + */ + virtual std::size_t GetLabelByteCount() const { return label_byte_count_; } + + /** + Returns the nonce byte count used for encrypting labels. + */ + virtual std::size_t GetNonceByteCount() const { return nonce_byte_count_; } + + /** + Indicates whether SEAL plaintexts are compressed in memory. + */ + virtual bool IsCompressed() const { return compressed_; } + + /** + Indicates whether the SenderDB has been stripped of all information not + needed for serving a query. + */ + virtual bool IsStripped() const { return stripped_; } + + virtual void SetData( + const std::shared_ptr<IBatchProvider> &batch_provider) = 0; + + /** + Returns the bundle at the given bundle index. + */ + virtual std::shared_ptr<apsi::sender::BinBundle> GetBinBundleAt( + std::uint32_t bundle_idx, size_t cache_idx) = 0; + + /** + Returns a reference to the PSI parameters for this SenderDB. + */ + virtual const apsi::PSIParams &GetParams() const { return params_; } + + /** + Returns a reference to the CryptoContext for this SenderDB. + */ + virtual const apsi::CryptoContext &GetCryptoContext() const { + return crypto_context_; + } + + /** + Returns a reference to the SEALContext for this SenderDB. + */ + virtual std::shared_ptr<seal::SEALContext> GetSealContext() const { + return crypto_context_.seal_context(); + } + + /** + Returns the number of items in this SenderDB. + */ + virtual size_t GetItemCount() const { return item_count_; } + + /** + Returns the total number of bin bundles at a specific bundle index. + */ + virtual std::size_t GetBinBundleCount(std::uint32_t bundle_idx) const = 0; + + /** + Returns the total number of bin bundles. + */ + virtual std::size_t GetBinBundleCount() const = 0; + + /** + Returns how efficiently the SenderDB is packaged. A higher rate indicates + better performance and a lower communication cost in a query execution. + */ + virtual double GetPackingRate() const; + + /** + Obtains a scoped lock preventing the SenderDB from being changed. + */ + virtual seal::util::ReaderLock GetReaderLock() const { + return db_lock_.acquire_read(); + } + + virtual std::vector<uint8_t> GetOprfKey() const; + + protected: + seal::util::WriterLock GetWriterLock() { return db_lock_.acquire_write(); } + + /** + The PSI parameters define the SEAL parameters, base field, item size, table + size, etc. + */ + apsi::PSIParams params_; + + /** + Necessary for evaluating polynomials of Plaintexts. + */ + apsi::CryptoContext crypto_context_; + + /** + A read-write lock to protect the database from modification while in use. + */ + mutable seal::util::ReaderWriterLocker db_lock_; + + /** + Indicates the size of the label in bytes. A zero value indicates an + unlabeled SenderDB. + */ + std::size_t label_byte_count_; + + /** + Indicates the number of bytes of the effective label reserved for a randomly + sampled nonce. The effective label byte count is the sum of label_byte_count + and nonce_byte_count. The value can range between 0 and 16. If + label_byte_count is zero, nonce_byte_count has no effect. + */ + std::size_t nonce_byte_count_; + + /** + The number of items currently in the SenderDB. + */ + std::size_t item_count_; + + /** + Indicates whether SEAL plaintexts are compressed in memory. + */ + bool compressed_; + + /** + Indicates whether the SenderDB has been stripped of all information not + needed for serving a query. + */ + bool stripped_; + + /** + Holds the OPRF key for this SenderDB. + */ + std::vector<uint8_t> oprf_key_; + std::unique_ptr<IEcdhOprfServer> oprf_server_; +}; // class SenderDB + +namespace labeled_psi { + +std::vector<kuku::LocFunc> HashFunctions(const apsi::PSIParams &params); + +std::unordered_set<kuku::location_type> AllLocations( + const std::vector<kuku::LocFunc> &hash_funcs, const apsi::HashedItem &item); + +size_t ComputeLabelSize(size_t label_byte_count, const apsi::PSIParams &params); + +std::pair<size_t, size_t> UnpackCuckooIdx(size_t cuckoo_idx, + size_t bins_per_bundle); + +} // namespace labeled_psi + +} // namespace psi::psi diff --git a/psi/psi/core/labeled_psi/sender_kvdb.cc b/psi/psi/core/labeled_psi/sender_kvdb.cc new file mode 100644 index 00000000..c2c93560 --- /dev/null +++ b/psi/psi/core/labeled_psi/sender_kvdb.cc @@ -0,0 +1,850 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// code reference https://github.com/microsoft/APSI/sender/sender_db.cpp +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +// we are using our own OPRF, the reason is we wanna make the oprf +// switchable between secp256k1, sm2 or other types + +// STD +#include <algorithm> +#include <chrono> +#include <future> +#include <iterator> +#include <memory> +#include <mutex> +#include <set> +#include <sstream> +#include <string> + +// APSI +#include "apsi/psi_params.h" +#include "apsi/thread_pool_mgr.h" +#include "apsi/util/db_encoding.h" +#include "apsi/util/label_encryptor.h" +#include "apsi/util/utils.h" +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" + +#include "psi/psi/core/ecdh_oprf/ecdh_oprf_selector.h" +#include "psi/psi/core/labeled_psi/sender_kvdb.h" +#include "psi/psi/core/labeled_psi/serialize.h" +#include "psi/psi/utils/utils.h" + +// Kuku +#include "kuku/locfunc.h" + +// SEAL +#include "absl/strings/escaping.h" +#include "seal/util/common.h" +#include "seal/util/streambuf.h" +#include "yacl/crypto/utils/rand.h" +#include "yacl/utils/parallel.h" + +namespace psi::psi { + +namespace { + +using DurationMillis = std::chrono::duration<double, std::milli>; + +std::vector<std::pair<apsi::util::AlgItem, size_t>> PreprocessUnlabeledData( + const apsi::HashedItem &hashed_item, const apsi::PSIParams &params) { + // Some variables we'll need + size_t bins_per_item = params.item_params().felts_per_item; + size_t item_bit_count = params.item_bit_count(); + + // Set up Kuku hash functions + auto hash_funcs = labeled_psi::HashFunctions(params); + + std::vector<std::pair<apsi::util::AlgItem, size_t>> data_with_indices; + + // Serialize the data into field elements + apsi::util::AlgItem alg_item = algebraize_item( + hashed_item, item_bit_count, params.seal_params().plain_modulus()); + + // Get the cuckoo table locations for this item and add to data_with_indices + for (auto location : labeled_psi::AllLocations(hash_funcs, hashed_item)) { + // The current hash value is an index into a table of Items. In reality + // our BinBundles are tables of bins, which contain chunks of items. How + // many chunks? bins_per_item many chunks + size_t bin_idx = location * bins_per_item; + + // Store the data along with its index + data_with_indices.emplace_back(std::make_pair(alg_item, bin_idx)); + } + + return data_with_indices; +} + +std::vector<std::pair<apsi::util::AlgItemLabel, size_t>> PreprocessLabeledData( + const std::pair<apsi::HashedItem, apsi::EncryptedLabel> &item_label_pair, + const apsi::PSIParams &params, + const std::vector<kuku::LocFunc> &hash_funcs) { + SPDLOG_DEBUG("Start preprocessing {} labeled items", distance(begin, end)); + + // Some variables we'll need + size_t bins_per_item = params.item_params().felts_per_item; + size_t item_bit_count = params.item_bit_count(); + + std::vector<std::pair<apsi::util::AlgItemLabel, size_t>> data_with_indices; + + // Serialize the data into field elements + const apsi::HashedItem &item = item_label_pair.first; + const apsi::EncryptedLabel &label = item_label_pair.second; + apsi::util::AlgItemLabel alg_item_label = algebraize_item_label( + item, label, item_bit_count, params.seal_params().plain_modulus()); + + std::set<size_t> loc_set; + + // Get the cuckoo table locations for this item and add to data_with_indices + for (auto location : labeled_psi::AllLocations(hash_funcs, item)) { + // The current hash value is an index into a table of Items. In reality + // our BinBundles are tables of bins, which contain chunks of items. How + // many chunks? bins_per_item many chunks + if (loc_set.find(location) == loc_set.end()) { + size_t bin_idx = location * bins_per_item; + + // Store the data along with its index + data_with_indices.emplace_back(alg_item_label, bin_idx); + loc_set.insert(location); + } + } + + return data_with_indices; +} + +/** +Inserts the given items and corresponding labels into bin_bundles at their +respective cuckoo indices. It will only insert the data with bundle index in the +half-open range range indicated by work_range. If inserting into a BinBundle +would make the number of items in a bin larger than max_bin_size, this function +will create and insert a new BinBundle. If overwrite is set, this will overwrite +the labels if it finds an AlgItemLabel that matches the input perfectly. +*/ +template <typename T> +void InsertOrAssignWorker( + const std::vector<std::pair<T, size_t>> &data_with_indices, + std::vector<std::shared_ptr<yacl::io::IndexStore>> *bundles_store, + std::vector<size_t> *bundles_store_idx, + const apsi::CryptoContext &crypto_context, uint32_t bundle_index, + uint32_t bins_per_bundle, size_t label_size, size_t max_bin_size, + size_t ps_low_degree, bool overwrite, bool compressed) { + STOPWATCH(sender_stopwatch, "insert_or_assign_worker"); + SPDLOG_DEBUG( + "Insert-or-Assign worker for bundle index {}; mode of operation: {}", + bundle_index, overwrite ? "overwriting existing" : "inserting new"); + + // Create the bundle set at the given bundle index + std::vector<apsi::sender::BinBundle> bundle_set; + + // Iteratively insert each item-label pair at the given cuckoo index + for (auto &data_with_idx : data_with_indices) { + const T &data = data_with_idx.first; + + // Get the bundle index + size_t cuckoo_idx = data_with_idx.second; + size_t bin_idx; + size_t bundle_idx; + std::tie(bin_idx, bundle_idx) = + labeled_psi::UnpackCuckooIdx(cuckoo_idx, bins_per_bundle); + + // If the bundle_idx isn't in the prescribed range, don't try to insert this + // data + if (bundle_idx != bundle_index) { + // Dealing with this bundle index is not our job + continue; + } + + // Try to insert or overwrite these field elements in an existing BinBundle + // at this bundle index. Keep track of whether or not we succeed. + bool written = false; + + for (auto bundle_it = bundle_set.rbegin(); bundle_it != bundle_set.rend(); + bundle_it++) { + // If we're supposed to overwrite, try to overwrite. One of these + // BinBundles has to have the data we're trying to overwrite. + if (overwrite) { + // If we successfully overwrote, we're done with this bundle + written = bundle_it->try_multi_overwrite(data, bin_idx); + if (written) { + break; + } + } + + // Do a dry-run insertion and see if the new largest bin size in the range + // exceeds the limit + int32_t new_largest_bin_size = + bundle_it->multi_insert_dry_run(data, bin_idx); + + // Check if inserting would violate the max bin size constraint + if (new_largest_bin_size > 0 && + seal::util::safe_cast<size_t>(new_largest_bin_size) < max_bin_size) { + // All good + bundle_it->multi_insert_for_real(data, bin_idx); + written = true; + break; + } + } + + // We tried to overwrite an item that doesn't exist. This should never + // happen + if (overwrite && !written) { + SPDLOG_ERROR( + "Insert-or-Assign worker: " + "failed to overwrite item at bundle index {} because the item was " + "not found", + bundle_idx); + YACL_THROW("tried to overwrite non-existent item"); + } + + // If we had conflicts everywhere when trying to insert, then we need to + // make a new BinBundle and insert the data there + if (!written) { + // Make a fresh BinBundle and insert + apsi::sender::BinBundle new_bin_bundle( + crypto_context, label_size, max_bin_size, ps_low_degree, + bins_per_bundle, compressed, false); + int res = new_bin_bundle.multi_insert_for_real(data, bin_idx); + + // If even that failed, I don't know what could've happened + if (res < 0) { + SPDLOG_ERROR( + "Insert-or-Assign worker: " + "failed to insert item into a new BinBundle at bundle index {}", + bundle_idx); + YACL_THROW("failed to insert item into a new BinBundle"); + } + + // Push a new BinBundle to the set of BinBundles at this bundle index + bundle_set.push_back(std::move(new_bin_bundle)); + } + } + + const auto db_save_start = std::chrono::system_clock::now(); + + for (auto &bundle : bundle_set) { + size_t store_idx = (*bundles_store_idx)[bundle_index]++; + + // Generate the BinBundle caches + // bundle.regen_cache(); + bundle.strip(); + + std::stringstream stream; + bundle.save(stream, store_idx); + + (*bundles_store)[bundle_index]->Put(store_idx, stream.str()); + } + + const auto db_save_end = std::chrono::system_clock::now(); + const DurationMillis db_save_duration = db_save_end - db_save_start; + SPDLOG_INFO("*** step leveldb put duration:{}", db_save_duration.count()); + + SPDLOG_DEBUG("Insert-or-Assign worker: finished processing bundle index {}", + bundle_index); +} + +void InsertOrAssignWorker( + const std::shared_ptr<yacl::io::IndexStore> &indices_store, + size_t indices_count, + std::vector<std::shared_ptr<yacl::io::IndexStore>> *bundles_store, + std::vector<size_t> *bundles_store_idx, bool is_labeled, + const apsi::CryptoContext &crypto_context, uint32_t bundle_index, + uint32_t bins_per_bundle, size_t label_size, size_t max_bin_size, + + size_t ps_low_degree, bool overwrite, bool compressed) { + STOPWATCH(sender_stopwatch, "insert_or_assign_worker"); + + SPDLOG_DEBUG( + "Insert-or-Assign worker for bundle index {}; mode of operation: {}", + bundle_index, overwrite ? "overwriting existing" : "inserting new"); + + // Create the bundle set at the given bundle index + std::vector<apsi::sender::BinBundle> bundle_set; + + // Iteratively insert each item-label pair at the given cuckoo index + for (size_t i = 0; i < indices_count; ++i) { + yacl::Buffer value; + bool get_status = indices_store->Get(i, &value); + YACL_ENFORCE(get_status, "get_status:{}", get_status); + + size_t cuckoo_idx; + + std::pair<apsi::util::AlgItemLabel, size_t> datalabel_with_idx; + std::pair<apsi::util::AlgItem, size_t> data_with_idx; + + if (is_labeled) { + datalabel_with_idx = DeserializeDataLabelWithIndices(std::string_view( + reinterpret_cast<char *>(value.data()), value.size())); + + cuckoo_idx = datalabel_with_idx.second; + } else { + data_with_idx = DeserializeDataWithIndices(std::string_view( + reinterpret_cast<char *>(value.data()), value.size())); + cuckoo_idx = data_with_idx.second; + } + + // const apsi::util::AlgItem &data = data_with_idx.first; + + // Get the bundle index + size_t bin_idx, bundle_idx; + std::tie(bin_idx, bundle_idx) = + labeled_psi::UnpackCuckooIdx(cuckoo_idx, bins_per_bundle); + + // If the bundle_idx isn't in the prescribed range, don't try to insert + // this data + if (bundle_idx != bundle_index) { + // Dealing with this bundle index is not our job + continue; + } + + // Try to insert or overwrite these field elements in an existing + // BinBundle at this bundle index. Keep track of whether or not we + // succeed. + bool written = false; + + for (auto bundle_it = bundle_set.rbegin(); bundle_it != bundle_set.rend(); + bundle_it++) { + // If we're supposed to overwrite, try to overwrite. One of these + // BinBundles has to have the data we're trying to overwrite. + if (overwrite) { + // If we successfully overwrote, we're done with this bundle + // written = bundle_it->try_multi_overwrite(data, bin_idx); + if (is_labeled) { + written = + bundle_it->try_multi_overwrite(datalabel_with_idx.first, bin_idx); + + } else { + written = + bundle_it->try_multi_overwrite(data_with_idx.first, bin_idx); + } + + if (written) { + break; + } + } + + // Do a dry-run insertion and see if the new largest bin size in the + // range exceeds the limit + // int32_t new_largest_bin_size = bundle_it->multi_insert_dry_run(data, + // bin_idx); + int32_t new_largest_bin_size; + if (is_labeled) { + new_largest_bin_size = + bundle_it->multi_insert_dry_run(datalabel_with_idx.first, bin_idx); + + } else { + new_largest_bin_size = + bundle_it->multi_insert_dry_run(data_with_idx.first, bin_idx); + } + + // Check if inserting would violate the max bin size constraint + if (new_largest_bin_size > 0 && + seal::util::safe_cast<size_t>(new_largest_bin_size) < max_bin_size) { + // All good + // bundle_it->multi_insert_for_real(data, bin_idx); + if (is_labeled) { + bundle_it->multi_insert_for_real(datalabel_with_idx.first, bin_idx); + } else { + bundle_it->multi_insert_for_real(data_with_idx.first, bin_idx); + } + written = true; + break; + } + } + + // We tried to overwrite an item that doesn't exist. This should never + // happen + if (overwrite && !written) { + SPDLOG_ERROR( + "Insert-or-Assign worker: " + "failed to overwrite item at bundle index {} because the item was " + "not found", + bundle_idx); + YACL_THROW("tried to overwrite non-existent item"); + } + + // If we had conflicts everywhere when trying to insert, then we need to + // make a new BinBundle and insert the data there + if (!written) { + // Make a fresh BinBundle and insert + apsi::sender::BinBundle new_bin_bundle( + crypto_context, label_size, max_bin_size, ps_low_degree, + bins_per_bundle, compressed, false); + + // int res = new_bin_bundle.multi_insert_for_real(data, bin_idx); + int res; + if (is_labeled) { + res = new_bin_bundle.multi_insert_for_real(datalabel_with_idx.first, + bin_idx); + + } else { + res = + new_bin_bundle.multi_insert_for_real(data_with_idx.first, bin_idx); + } + + // If even that failed, I don't know what could've happened + if (res < 0) { + SPDLOG_ERROR( + "Insert-or-Assign worker: " + "failed to insert item into a new BinBundle at bundle index {}", + bundle_idx); + YACL_THROW("failed to insert item into a new BinBundle"); + } + + // Push a new BinBundle to the set of BinBundles at this bundle index + bundle_set.push_back(std::move(new_bin_bundle)); + } + } + + const auto db_save_start = std::chrono::system_clock::now(); + + for (auto &bundle : bundle_set) { + size_t store_idx = (*bundles_store_idx)[bundle_index]++; + + SPDLOG_INFO( + "Polynomial Interpolate and HE Plaintext Encode, bundle_indx:{}, " + "store_idx:{}", + bundle_index, store_idx); + + // Generate the BinBundle caches + // bundle.regen_cache(); + bundle.strip(); + + std::stringstream stream; + bundle.save(stream, store_idx); + + (*bundles_store)[bundle_index]->Put(store_idx, stream.str()); + } + const auto db_save_end = std::chrono::system_clock::now(); + const DurationMillis db_save_duration = db_save_end - db_save_start; + SPDLOG_INFO("*** step leveldb put duration:{}", db_save_duration.count()); + + SPDLOG_DEBUG("Insert-or-Assign worker: finished processing bundle index {}", + bundle_index); +} + +/** +Takes algebraized data to be inserted, splits it up, and distributes it so +that thread_count many threads can all insert in parallel. If overwrite is +set, this will overwrite the labels if it finds an AlgItemLabel that matches +the input perfectly. +*/ +template <typename T> +void DispatchInsertOrAssign( + const std::vector<std::pair<T, size_t>> &data_with_indices, + std::vector<std::shared_ptr<yacl::io::IndexStore>> *bundles_store, + std::vector<size_t> *bundles_store_idx, + const apsi::CryptoContext &crypto_context, uint32_t bins_per_bundle, + size_t label_size, uint32_t max_bin_size, uint32_t ps_low_degree, + bool overwrite, bool compressed) { + apsi::ThreadPoolMgr tpm; + + // Collect the bundle indices and partition them into thread_count many + // partitions. By some uniformity assumption, the number of things to insert + // per partition should be roughly the same. Note that the contents of + // bundle_indices is always sorted (increasing order). + std::set<size_t> bundle_indices_set; + for (auto &data_with_idx : data_with_indices) { + size_t cuckoo_idx = data_with_idx.second; + size_t bin_idx; + size_t bundle_idx; + std::tie(bin_idx, bundle_idx) = + labeled_psi::UnpackCuckooIdx(cuckoo_idx, bins_per_bundle); + bundle_indices_set.insert(bundle_idx); + } + + // Copy the set of indices into a vector and sort so each thread processes a + // range of indices + std::vector<size_t> bundle_indices; + bundle_indices.reserve(bundle_indices_set.size()); + std::copy(bundle_indices_set.begin(), bundle_indices_set.end(), + std::back_inserter(bundle_indices)); + std::sort(bundle_indices.begin(), bundle_indices.end()); + + // Run the threads on the partitions + std::vector<std::future<void>> futures(bundle_indices.size()); + SPDLOG_INFO("Launching {} insert-or-assign worker tasks", + bundle_indices.size()); + size_t future_idx = 0; + for (auto &bundle_idx : bundle_indices) { + futures[future_idx++] = tpm.thread_pool().enqueue([&, bundle_idx]() { + InsertOrAssignWorker(data_with_indices, bundles_store, bundles_store_idx, + crypto_context, static_cast<uint32_t>(bundle_idx), + bins_per_bundle, label_size, max_bin_size, + ps_low_degree, overwrite, compressed); + }); + } + + // Wait for the tasks to finish + for (auto &f : futures) { + f.get(); + } + + SPDLOG_INFO("Finished insert-or-assign worker tasks"); +} + +void DispatchInsertOrAssign( + const std::shared_ptr<yacl::io::IndexStore> &indices_store, + size_t indices_count, const std::set<size_t> &bundle_indices_set, + std::vector<std::shared_ptr<yacl::io::IndexStore>> *bundles_store, + std::vector<size_t> *bundles_store_idx, bool is_labeled, + const apsi::CryptoContext &crypto_context, uint32_t bins_per_bundle, + size_t label_size, uint32_t max_bin_size, uint32_t ps_low_degree, + bool overwrite, bool compressed) { + apsi::ThreadPoolMgr tpm; + + std::vector<size_t> bundle_indices; + bundle_indices.reserve(bundle_indices_set.size()); + std::copy(bundle_indices_set.begin(), bundle_indices_set.end(), + std::back_inserter(bundle_indices)); + std::sort(bundle_indices.begin(), bundle_indices.end()); + + // Run the threads on the partitions + std::vector<std::future<void>> futures(bundle_indices.size()); + SPDLOG_INFO("Launching {} insert-or-assign worker tasks", + bundle_indices.size()); + size_t future_idx = 0; + for (auto &bundle_idx : bundle_indices) { + futures[future_idx++] = tpm.thread_pool().enqueue([&, bundle_idx]() { + InsertOrAssignWorker(indices_store, indices_count, bundles_store, + bundles_store_idx, is_labeled, crypto_context, + static_cast<uint32_t>(bundle_idx), bins_per_bundle, + label_size, max_bin_size, ps_low_degree, overwrite, + compressed); + }); + } + + // Wait for the tasks to finish + for (auto &f : futures) { + f.get(); + } + + SPDLOG_INFO("Finished insert-or-assign worker tasks"); +} + +constexpr char kMetaInfoStoreName[] = "db_meta_info"; +constexpr char kServerDataCount[] = "server_data_count"; + +constexpr char kMemoryStoreFlag[] = "::memory"; + +} // namespace + +SenderKvDB::SenderKvDB(const apsi::PSIParams &params, + yacl::ByteContainerView oprf_key, + std::string_view kv_store_path, size_t label_byte_count, + size_t nonce_byte_count, bool compressed) + : ISenderDB(params, oprf_key, label_byte_count, nonce_byte_count, + compressed), + kv_store_path_(kv_store_path) { + // Reset the SenderDB data structures + clear(); + + bundles_store_.resize(params_.bundle_idx_count()); + bundles_store_idx_.resize(params_.bundle_idx_count()); + + if (kv_store_path == kMemoryStoreFlag) { + meta_info_store_ = std::make_shared<yacl::io::MemoryKVStore>(); + + for (size_t i = 0; i < bundles_store_.size(); ++i) { + std::shared_ptr<yacl::io::KVStore> kv_store = + std::make_shared<yacl::io::MemoryKVStore>(); + + bundles_store_[i] = std::make_shared<yacl::io::IndexStore>(kv_store); + } + } else { + std::string meta_store_name = + fmt::format("{}/{}", kv_store_path, kMetaInfoStoreName); + + meta_info_store_ = + std::make_shared<yacl::io::LeveldbKVStore>(false, meta_store_name); + + for (size_t i = 0; i < bundles_store_.size(); ++i) { + std::string bundle_store_name = + fmt::format("{}/bundle_{}", kv_store_path_, i); + + std::shared_ptr<yacl::io::KVStore> kv_store = + std::make_shared<yacl::io::LeveldbKVStore>(false, bundle_store_name); + + bundles_store_[i] = std::make_shared<yacl::io::IndexStore>(kv_store); + } + } + + try { + yacl::Buffer temp_value; + + meta_info_store_->Get(kServerDataCount, &temp_value); + item_count_ = std::stoul(std::string(std::string_view( + reinterpret_cast<char *>(temp_value.data()), temp_value.size()))); + + for (size_t i = 0; i < bundles_store_idx_.size(); ++i) { + bundles_store_idx_[i] = bundles_store_[i]->Count(); + } + + } catch (const std::exception &e) { + SPDLOG_INFO("key item_count no value"); + } +} + +size_t SenderKvDB::GetBinBundleCount(uint32_t bundle_idx) const { + // Lock the database for reading + auto lock = GetReaderLock(); + + // return bin_bundles_.at(seal::util::safe_cast<size_t>(bundle_idx)).size(); + return bundles_store_idx_[seal::util::safe_cast<size_t>(bundle_idx)]; +} + +size_t SenderKvDB::GetBinBundleCount() const { + // Lock the database for reading + auto lock = GetReaderLock(); + + // Compute the total number of BinBundles + // return std::accumulate(bin_bundles_.cbegin(), bin_bundles_.cend(), + // static_cast<size_t>(0), + // [&](auto &a, auto &b) { return a + b.size(); }); + return std::accumulate(bundles_store_idx_.cbegin(), bundles_store_idx_.cend(), + static_cast<size_t>(0), + [&](auto &a, auto &b) { return a + b; }); +} + +void SenderKvDB::ClearInternal() { + item_count_ = 0; + + // Reset the stripped_ flag + stripped_ = false; + // TODO(changjun): delete kv store +} + +void SenderKvDB::clear() { + // Lock the database for writing + auto lock = GetWriterLock(); + + ClearInternal(); +} + +void SenderKvDB::GenerateCaches() { + STOPWATCH(sender_stopwatch, "SenderDB::GenerateCaches"); + SPDLOG_INFO("Start generating bin bundle caches"); + + SPDLOG_INFO("Finished generating bin bundle caches"); +} + +std::shared_ptr<apsi::sender::BinBundle> SenderKvDB::GetBinBundleAt( + uint32_t bundle_idx, size_t cache_idx) { + yacl::Buffer value; + + bool get_status = bundles_store_[bundle_idx]->Get(cache_idx, &value); + + YACL_ENFORCE(get_status); + + size_t label_size = labeled_psi::ComputeLabelSize( + nonce_byte_count_ + label_byte_count_, params_); + + uint32_t bins_per_bundle = params_.bins_per_bundle(); + uint32_t max_bin_size = params_.table_params().max_items_per_bin; + uint32_t ps_low_degree = params_.query_params().ps_low_degree; + + bool compressed = false; + + std::shared_ptr<apsi::sender::BinBundle> load_bin_bundle = + std::make_shared<apsi::sender::BinBundle>( + crypto_context_, label_size, max_bin_size, ps_low_degree, + bins_per_bundle, compressed, false); + + gsl::span<unsigned char> value_span = { + reinterpret_cast<unsigned char *>(value.data()), + gsl::narrow_cast<gsl::span<unsigned char>::size_type>(value.size())}; + std::pair<std::uint32_t, std::size_t> load_ret = + load_bin_bundle->load(value_span); + + YACL_ENFORCE(load_ret.first == cache_idx); + + // check cache is valid + if (load_bin_bundle->cache_invalid()) { + load_bin_bundle->regen_cache(); + } + + return load_bin_bundle; +} + +void SenderKvDB::strip() { + // Lock the database for writing + auto lock = GetWriterLock(); + + stripped_ = true; + + memset(oprf_key_.data(), 0, oprf_key_.size()); + + SPDLOG_INFO("SenderDB has been stripped"); +} + +void SenderKvDB::InsertOrAssign( + const std::shared_ptr<IBatchProvider> &batch_provider) { + size_t batch_count = 0; + size_t indices_count = 0; + + std::shared_ptr<yacl::io::KVStore> kv_store; + + if (kv_store_path_ == kMemoryStoreFlag) { + kv_store = std::make_shared<yacl::io::MemoryKVStore>(); + } else { + kv_store = std::make_shared<yacl::io::LeveldbKVStore>(true); + } + + std::shared_ptr<yacl::io::IndexStore> items_oprf_store = + std::make_shared<yacl::io::IndexStore>(kv_store); + + std::set<size_t> bundle_indices_set; + + // Dispatch the insertion + uint32_t bins_per_bundle = params_.bins_per_bundle(); + uint32_t max_bin_size = params_.table_params().max_items_per_bin; + uint32_t ps_low_degree = params_.query_params().ps_low_degree; + + while (true) { + std::vector<std::string> batch_items; + std::vector<std::string> batch_labels; + + if (IsLabeled()) { + std::shared_ptr<ILabeledBatchProvider> labeled_batch_provider = + std::dynamic_pointer_cast<ILabeledBatchProvider>(batch_provider); + + std::tie(batch_items, batch_labels) = + labeled_batch_provider->ReadNextLabeledBatch(); + } else { + std::shared_ptr<IBasicBatchProvider> basic_batch_provider = + std::dynamic_pointer_cast<IBasicBatchProvider>(batch_provider); + + batch_items = basic_batch_provider->ReadNextBatch(); + } + + if (batch_items.empty()) { + SPDLOG_INFO( + "OPRF FullEvaluate and EncryptLabel Last batch count: " + "{}, item_count:{}", + batch_count, item_count_); + break; + } + SPDLOG_INFO("OPRF FullEvaluate and EncryptLabel batch_count: {}", + batch_count); + + std::vector<std::string> oprf_out = oprf_server_->FullEvaluate(batch_items); + + std::vector<std::vector<std::pair<apsi::util::AlgItemLabel, size_t>>> + data_with_indices_vec; + + if (IsLabeled()) { + data_with_indices_vec.resize(oprf_out.size()); + size_t key_offset_pos = sizeof(apsi::Item::value_type); + + // Set up Kuku hash functions + auto hash_funcs = labeled_psi::HashFunctions(params_); + + yacl::parallel_for( + 0, oprf_out.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + apsi::Item::value_type value{}; + std::memcpy(value.data(), &oprf_out[idx][0], value.size()); + + apsi::HashedItem hashed_item(value); + + apsi::LabelKey key; + std::memcpy(key.data(), &oprf_out[idx][key_offset_pos], + apsi::label_key_byte_count); + + apsi::Label label_with_padding = + PaddingData(batch_labels[idx], label_byte_count_); + + apsi::EncryptedLabel encrypted_label = apsi::util::encrypt_label( + label_with_padding, key, label_byte_count_, + nonce_byte_count_); + + std::pair<apsi::HashedItem, apsi::EncryptedLabel> + item_label_pair = + std::make_pair(hashed_item, encrypted_label); + + data_with_indices_vec[idx] = + PreprocessLabeledData(item_label_pair, params_, hash_funcs); + } + }); + + for (size_t i = 0; i < oprf_out.size(); ++i) { + for (size_t j = 0; j < data_with_indices_vec[i].size(); ++j) { + std::string indices_buffer = + SerializeDataLabelWithIndices(data_with_indices_vec[i][j]); + + items_oprf_store->Put(indices_count + j, indices_buffer); + + size_t cuckoo_idx = data_with_indices_vec[i][j].second; + size_t bin_idx, bundle_idx; + std::tie(bin_idx, bundle_idx) = + labeled_psi::UnpackCuckooIdx(cuckoo_idx, bins_per_bundle); + bundle_indices_set.insert(bundle_idx); + } + + indices_count += data_with_indices_vec[i].size(); + } + + } else { + for (size_t i = 0; i < oprf_out.size(); ++i) { + // + apsi::Item::value_type value{}; + std::memcpy(value.data(), &oprf_out[i][0], value.size()); + + apsi::HashedItem hashed_item(value); + + std::vector<std::pair<apsi::util::AlgItem, size_t>> data_with_indices = + PreprocessUnlabeledData(hashed_item, params_); + + for (size_t j = 0; j < data_with_indices.size(); ++j) { + std::string indices_buffer = + SerializeDataWithIndices(data_with_indices[j]); + + items_oprf_store->Put(indices_count + j, indices_buffer); + + size_t cuckoo_idx = data_with_indices[j].second; + + size_t bin_idx, bundle_idx; + std::tie(bin_idx, bundle_idx) = + labeled_psi::UnpackCuckooIdx(cuckoo_idx, bins_per_bundle); + + bundle_indices_set.insert(bundle_idx); + } + indices_count += data_with_indices.size(); + } + } + item_count_ += batch_items.size(); + + batch_count++; + } + meta_info_store_->Put(kServerDataCount, std::to_string(item_count_)); + + size_t label_size = 0; + if (IsLabeled()) { + label_size = labeled_psi::ComputeLabelSize( + nonce_byte_count_ + label_byte_count_, params_); + } + + DispatchInsertOrAssign( + items_oprf_store, indices_count, bundle_indices_set, &bundles_store_, + &bundles_store_idx_, IsLabeled(), crypto_context_, bins_per_bundle, + label_size, /* label size */ + max_bin_size, ps_low_degree, false, /* don't overwrite items */ + compressed_); + + SPDLOG_INFO("Finished inserting {} items in SenderDB", item_count_); +} + +} // namespace psi::psi diff --git a/psi/psi/core/labeled_psi/sender_kvdb.h b/psi/psi/core/labeled_psi/sender_kvdb.h new file mode 100644 index 00000000..95f4a2dc --- /dev/null +++ b/psi/psi/core/labeled_psi/sender_kvdb.h @@ -0,0 +1,164 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// code reference https://github.com/microsoft/APSI/sender/sender_db.h +// Licensed under the MIT license. + +#pragma once + +// STD +#include <atomic> +#include <cstddef> +#include <cstdint> +#include <iostream> +#include <memory> +#include <string> +#include <unordered_set> +#include <utility> +#include <vector> + +// GSL +#include "gsl/span" + +// APSI +#include "apsi/bin_bundle.h" +#include "apsi/crypto_context.h" +#include "apsi/item.h" +#include "apsi/psi_params.h" +#include "yacl/base/byte_container_view.h" +#include "yacl/io/kv/leveldb_kvstore.h" +#include "yacl/io/kv/memory_kvstore.h" + +#include "psi/psi/core/ecdh_oprf/basic_ecdh_oprf.h" +#include "psi/psi/core/labeled_psi/sender_db.h" +#include "psi/psi/utils/batch_provider.h" + +// SEAL +#include "seal/plaintext.h" +#include "seal/util/locks.h" +#include "spdlog/spdlog.h" + +namespace psi::psi { + +/** +A SenderDB maintains an in-memory representation of the sender's set of items +and labels (in labeled mode). This data is not simply copied into the SenderDB +data structures, but also preprocessed heavily to allow for faster online +computation time. Since inserting a large number of new items into a SenderDB +can take time, it is not recommended to recreate the SenderDB when the +database changes a little bit. Instead, the class supports fast update and +deletion operations that should be preferred: SenderDB::InsertOrAssign and +SenderDB::remove. + +The SenderDB constructor allows the label byte count to be specified; +unlabeled mode is activated by setting the label byte count to zero. It is +possible to optionally specify the size of the nonce used in encrypting the +labels, but this is best left to its default value unless the user is +absolutely sure of what they are doing. + +The SenderDB requires substantially more memory than the raw data would. Part +of that memory can automatically be compressed when it is not in use; this +feature is enabled by default, and can be disabled when constructing the +SenderDB. The downside of in-memory compression is a performance reduction +from decompressing parts of the data when they are used, and recompressing +them if they are updated. +*/ +class SenderKvDB : public ISenderDB { + public: + /** + Creates a new SenderDB. + */ + SenderKvDB(const apsi::PSIParams &params, yacl::ByteContainerView oprf_key, + std::string_view kv_store_path = "", + std::size_t label_byte_count = 0, + std::size_t nonce_byte_count = 16, bool compressed = true); + + /** + Clears the database. Every item and label will be removed. The OPRF key is + unchanged. + */ + void clear(); + + /** + Strips the SenderDB of all information not needed for serving a query. + Returns a copy of the OPRF key and clears it from the SenderDB. + */ + void strip(); + + /** + * @brief Insert data from BatchProvider + * + * @param batch_provider + */ + void InsertOrAssign(const std::shared_ptr<IBatchProvider> &batch_provider); + + /** + Clears the database and inserts the given data. This function can be used + only on a labeled SenderDB instance. + */ + void SetData(const std::vector<std::string> &keys, + const std::vector<std::string> &labels, + size_t batch_size = 500000) { + std::shared_ptr<IBatchProvider> batch_provider = + std::make_shared<MemoryBatchProvider>(keys, batch_size, labels); + + SetData(batch_provider); + } + + /** + Clears the database and inserts the given data. This function can be used + only on an unlabeled SenderDB instance. + */ + void SetData(const std::vector<std::string> &data, + size_t batch_size = 500000) { + std::shared_ptr<IBatchProvider> batch_provider = + std::make_shared<MemoryBatchProvider>(data, batch_size); + + SetData(batch_provider); + } + + void SetData(const std::shared_ptr<IBatchProvider> &batch_provider) override { + clear(); + InsertOrAssign(batch_provider); + } + + /** + Returns the bundle at the given bundle index. + */ + std::shared_ptr<apsi::sender::BinBundle> GetBinBundleAt( + std::uint32_t bundle_idx, size_t cache_idx) override; + + /** + Returns the total number of bin bundles at a specific bundle index. + */ + std::size_t GetBinBundleCount(std::uint32_t bundle_idx) const override; + + /** + Returns the total number of bin bundles. + */ + std::size_t GetBinBundleCount() const override; + + private: + void ClearInternal(); + + void GenerateCaches(); + + std::string kv_store_path_; + std::shared_ptr<yacl::io::KVStore> meta_info_store_; + + std::vector<std::shared_ptr<yacl::io::IndexStore>> bundles_store_; + std::vector<size_t> bundles_store_idx_; +}; // class SenderDB + +} // namespace psi::psi diff --git a/psi/psi/core/labeled_psi/sender_memdb.cc b/psi/psi/core/labeled_psi/sender_memdb.cc new file mode 100644 index 00000000..69f53dff --- /dev/null +++ b/psi/psi/core/labeled_psi/sender_memdb.cc @@ -0,0 +1,964 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// code reference https://github.com/microsoft/APSI/sender/sender_db.cpp +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +// we are using our own OPRF, the reason is we wanna make the oprf +// switchable between secp256k1, sm2 or other types + +// STD +#include <algorithm> +#include <future> +#include <iterator> +#include <memory> +#include <mutex> +#include <set> +#include <sstream> +#include <string> + +// APSI +#include "apsi/psi_params.h" +#include "apsi/thread_pool_mgr.h" +#include "apsi/util/db_encoding.h" +#include "apsi/util/label_encryptor.h" +#include "apsi/util/utils.h" +#include "spdlog/spdlog.h" + +#include "psi/psi/core/ecdh_oprf/ecdh_oprf_selector.h" +#include "psi/psi/core/labeled_psi/sender_memdb.h" +#include "psi/psi/utils/utils.h" + +// Kuku +#include "kuku/locfunc.h" + +// SEAL +#include "absl/strings/escaping.h" +#include "seal/util/common.h" +#include "seal/util/streambuf.h" +#include "yacl/utils/parallel.h" + +namespace psi::psi { + +namespace { + +/** +Converts each given Item-Label pair in between the given iterators into its +algebraic form, i.e., a sequence of felt-felt pairs. Also computes each Item's +cuckoo index. +*/ +std::vector<std::pair<apsi::util::AlgItemLabel, size_t>> PreprocessLabeledData( + const std::vector<std::pair<apsi::HashedItem, + apsi::EncryptedLabel>>::const_iterator begin, + const std::vector< + std::pair<apsi::HashedItem, apsi::EncryptedLabel>>::const_iterator end, + const apsi::PSIParams &params) { + STOPWATCH(sender_stopwatch, "preprocess_labeled_data"); + SPDLOG_DEBUG("Start preprocessing {} labeled items", distance(begin, end)); + + // Some variables we'll need + size_t bins_per_item = params.item_params().felts_per_item; + size_t item_bit_count = params.item_bit_count(); + + // Set up Kuku hash functions + auto hash_funcs = labeled_psi::HashFunctions(params); + + // Calculate the cuckoo indices for each item. Store every pair of + // (item-label, cuckoo_idx) in a vector. Later, we're gonna sort this vector + // by cuckoo_idx and use the result to parallelize the work of inserting the + // items into BinBundles. + std::vector<std::pair<apsi::util::AlgItemLabel, size_t>> data_with_indices; + + for (auto it = begin; it != end; it++) { + const std::pair<apsi::HashedItem, apsi::EncryptedLabel> &item_label_pair = + *it; + + // Serialize the data into field elements + const apsi::HashedItem &item = item_label_pair.first; + const apsi::EncryptedLabel &label = item_label_pair.second; + apsi::util::AlgItemLabel alg_item_label = algebraize_item_label( + item, label, item_bit_count, params.seal_params().plain_modulus()); + + std::vector<std::pair<apsi::util::AlgItemLabel, size_t>> temp_data; + std::set<size_t> loc_set; + + // Get the cuckoo table locations for this item and add to + // data_with_indices + for (auto location : labeled_psi::AllLocations(hash_funcs, item)) { + // The current hash value is an index into a table of Items. In + // reality our BinBundles are tables of bins, which contain chunks + // of items. How many chunks? bins_per_item many chunks + if (loc_set.find(location) == loc_set.end()) { + size_t bin_idx = location * bins_per_item; + + // Store the data along with its index + temp_data.emplace_back(alg_item_label, bin_idx); + loc_set.insert(location); + } + } + + data_with_indices.insert(data_with_indices.end(), temp_data.begin(), + temp_data.end()); + } + + SPDLOG_DEBUG("Finished preprocessing {} labeled items", distance(begin, end)); + + return data_with_indices; +} + +/** +Converts each given Item into its algebraic form, i.e., a sequence of +felt-monostate pairs. Also computes each Item's cuckoo index. +*/ +std::vector<std::pair<apsi::util::AlgItem, size_t>> PreprocessUnlabeledData( + const std::vector<apsi::HashedItem>::const_iterator begin, + const std::vector<apsi::HashedItem>::const_iterator end, + const apsi::PSIParams &params) { + STOPWATCH(sender_stopwatch, "preprocess_unlabeled_data"); + SPDLOG_DEBUG("Start preprocessing {} unlabeled items", distance(begin, end)); + + // Some variables we'll need + size_t bins_per_item = params.item_params().felts_per_item; + size_t item_bit_count = params.item_bit_count(); + + // Set up Kuku hash functions + auto hash_funcs = labeled_psi::HashFunctions(params); + + // Calculate the cuckoo indices for each item. Store every pair of + // (item-label, cuckoo_idx) in a vector. Later, we're gonna sort this vector + // by cuckoo_idx and use the result to parallelize the work of inserting the + // items into BinBundles. + std::vector<std::pair<apsi::util::AlgItem, size_t>> data_with_indices; + for (auto it = begin; it != end; it++) { + const apsi::HashedItem &item = *it; + + // Serialize the data into field elements + apsi::util::AlgItem alg_item = algebraize_item( + item, item_bit_count, params.seal_params().plain_modulus()); + + // Get the cuckoo table locations for this item and add to data_with_indices + for (auto location : labeled_psi::AllLocations(hash_funcs, item)) { + // The current hash value is an index into a table of Items. In reality + // our BinBundles are tables of bins, which contain chunks of items. How + // many chunks? bins_per_item many chunks + size_t bin_idx = location * bins_per_item; + + // Store the data along with its index + data_with_indices.emplace_back(alg_item, bin_idx); + } + } + + SPDLOG_DEBUG("Finished preprocessing {} unlabeled items", + distance(begin, end)); + + return data_with_indices; +} + +/** +Converts given Item into its algebraic form, i.e., a sequence of felt-monostate +pairs. Also computes the Item's cuckoo index. +*/ +std::vector<std::pair<apsi::util::AlgItem, size_t>> PreprocessUnlabeledData( + const apsi::HashedItem &item, const apsi::PSIParams &params) { + std::vector<apsi::HashedItem> item_singleton{item}; + return PreprocessUnlabeledData(item_singleton.begin(), item_singleton.end(), + params); +} + +/** +Inserts the given items and corresponding labels into bin_bundles at their +respective cuckoo indices. It will only insert the data with bundle index in the +half-open range range indicated by work_range. If inserting into a BinBundle +would make the number of items in a bin larger than max_bin_size, this function +will create and insert a new BinBundle. If overwrite is set, this will overwrite +the labels if it finds an AlgItemLabel that matches the input perfectly. +*/ +template <typename T> +void InsertOrAssignWorker( + const std::vector<std::pair<T, size_t>> &data_with_indices, + std::vector<std::vector<std::shared_ptr<apsi::sender::BinBundle>>> + *bin_bundles, + const apsi::CryptoContext &crypto_context, uint32_t bundle_index, + uint32_t bins_per_bundle, size_t label_size, size_t max_bin_size, + size_t ps_low_degree, bool overwrite, bool compressed) { + STOPWATCH(sender_stopwatch, "insert_or_assign_worker"); + SPDLOG_DEBUG( + "Insert-or-Assign worker for bundle index {}; mode of operation: {}", + bundle_index, overwrite ? "overwriting existing" : "inserting new"); + + // Iteratively insert each item-label pair at the given cuckoo index + for (auto &data_with_idx : data_with_indices) { + const T &data = data_with_idx.first; + + // Get the bundle index + size_t cuckoo_idx = data_with_idx.second; + size_t bin_idx; + size_t bundle_idx; + std::tie(bin_idx, bundle_idx) = + labeled_psi::UnpackCuckooIdx(cuckoo_idx, bins_per_bundle); + + // If the bundle_idx isn't in the prescribed range, don't try to insert this + // data + if (bundle_idx != bundle_index) { + // Dealing with this bundle index is not our job + continue; + } + + // Get the bundle set at the given bundle index + std::vector<std::shared_ptr<apsi::sender::BinBundle>> &bundle_set = + (*bin_bundles)[bundle_idx]; + + // Try to insert or overwrite these field elements in an existing BinBundle + // at this bundle index. Keep track of whether or not we succeed. + bool written = false; + for (auto bundle_it = bundle_set.rbegin(); bundle_it != bundle_set.rend(); + bundle_it++) { + // If we're supposed to overwrite, try to overwrite. One of these + // BinBundles has to have the data we're trying to overwrite. + if (overwrite) { + // If we successfully overwrote, we're done with this bundle + written = (*bundle_it)->try_multi_overwrite(data, bin_idx); + if (written) { + break; + } + } + + // Do a dry-run insertion and see if the new largest bin size in the range + // exceeds the limit + int32_t new_largest_bin_size = + (*bundle_it)->multi_insert_dry_run(data, bin_idx); + + // Check if inserting would violate the max bin size constraint + if (new_largest_bin_size > 0 && + seal::util::safe_cast<size_t>(new_largest_bin_size) < max_bin_size) { + // All good + (*bundle_it)->multi_insert_for_real(data, bin_idx); + written = true; + break; + } + } + + // We tried to overwrite an item that doesn't exist. This should never + // happen + if (overwrite && !written) { + SPDLOG_ERROR( + "Insert-or-Assign worker: " + "failed to overwrite item at bundle index {} because the item was " + "not found", + bundle_idx); + YACL_THROW("tried to overwrite non-existent item"); + } + + // If we had conflicts everywhere when trying to insert, then we need to + // make a new BinBundle and insert the data there + if (!written) { + // Make a fresh BinBundle and insert + std::shared_ptr<apsi::sender::BinBundle> new_bin_bundle = + std::make_shared<apsi::sender::BinBundle>( + crypto_context, label_size, max_bin_size, ps_low_degree, + bins_per_bundle, compressed, false); + int res = new_bin_bundle->multi_insert_for_real(data, bin_idx); + + // If even that failed, I don't know what could've happened + if (res < 0) { + SPDLOG_ERROR( + "Insert-or-Assign worker: " + "failed to insert item into a new BinBundle at bundle index {}", + bundle_idx); + YACL_THROW("failed to insert item into a new BinBundle"); + } + + // Push a new BinBundle to the set of BinBundles at this bundle index + bundle_set.push_back(new_bin_bundle); + } + } + + SPDLOG_DEBUG("Insert-or-Assign worker: finished processing bundle index {}", + bundle_index); +} + +/** +Takes algebraized data to be inserted, splits it up, and distributes it so that +thread_count many threads can all insert in parallel. If overwrite is set, this +will overwrite the labels if it finds an AlgItemLabel that matches the input +perfectly. +*/ +template <typename T> +void DispatchInsertOrAssign( + const std::vector<std::pair<T, size_t>> &data_with_indices, + std::vector<std::vector<std::shared_ptr<apsi::sender::BinBundle>>> + *bin_bundles, + const apsi::CryptoContext &crypto_context, uint32_t bins_per_bundle, + size_t label_size, uint32_t max_bin_size, uint32_t ps_low_degree, + bool overwrite, bool compressed) { + apsi::ThreadPoolMgr tpm; + + // Collect the bundle indices and partition them into thread_count many + // partitions. By some uniformity assumption, the number of things to insert + // per partition should be roughly the same. Note that the contents of + // bundle_indices is always sorted (increasing order). + std::set<size_t> bundle_indices_set; + for (auto &data_with_idx : data_with_indices) { + size_t cuckoo_idx = data_with_idx.second; + size_t bin_idx; + size_t bundle_idx; + std::tie(bin_idx, bundle_idx) = + labeled_psi::UnpackCuckooIdx(cuckoo_idx, bins_per_bundle); + bundle_indices_set.insert(bundle_idx); + } + + // Copy the set of indices into a vector and sort so each thread processes a + // range of indices + std::vector<size_t> bundle_indices; + bundle_indices.reserve(bundle_indices_set.size()); + copy(bundle_indices_set.begin(), bundle_indices_set.end(), + back_inserter(bundle_indices)); + std::sort(bundle_indices.begin(), bundle_indices.end()); + + // Run the threads on the partitions + std::vector<std::future<void>> futures(bundle_indices.size()); + SPDLOG_INFO("Launching {} insert-or-assign worker tasks", + bundle_indices.size()); + size_t future_idx = 0; + for (auto &bundle_idx : bundle_indices) { + futures[future_idx++] = tpm.thread_pool().enqueue([&, bundle_idx]() { + InsertOrAssignWorker(data_with_indices, bin_bundles, crypto_context, + static_cast<uint32_t>(bundle_idx), bins_per_bundle, + label_size, max_bin_size, ps_low_degree, overwrite, + compressed); + }); + } + + // Wait for the tasks to finish + for (auto &f : futures) { + f.get(); + } + + SPDLOG_INFO("Finished insert-or-assign worker tasks"); +} + +/** +Removes the given items and corresponding labels from bin_bundles at their +respective cuckoo indices. +*/ +void RemoveWorker( + const std::vector<std::pair<apsi::util::AlgItem, size_t>> + &data_with_indices, + std::vector<std::vector<std::shared_ptr<apsi::sender::BinBundle>>> + *bin_bundles, + uint32_t bundle_index, uint32_t bins_per_bundle) { + STOPWATCH(sender_stopwatch, "remove_worker"); + SPDLOG_INFO("Remove worker [{}]", bundle_index); + + // Iteratively remove each item-label pair at the given cuckoo index + for (const auto &data_with_idx : data_with_indices) { + // Get the bundle index + size_t cuckoo_idx = data_with_idx.second; + size_t bin_idx; + size_t bundle_idx; + std::tie(bin_idx, bundle_idx) = + labeled_psi::UnpackCuckooIdx(cuckoo_idx, bins_per_bundle); + + // If the bundle_idx isn't in the prescribed range, don't try to remove this + // data + if (bundle_idx != bundle_index) { + // Dealing with this bundle index is not our job + continue; + } + + // Get the bundle set at the given bundle index + std::vector<std::shared_ptr<apsi::sender::BinBundle>> &bundle_set = + (*bin_bundles)[bundle_idx]; + + // Try to remove these field elements from an existing BinBundle at this + // bundle index. Keep track of whether or not we succeed. + bool removed = false; + for (auto &bundle : bundle_set) { + // If we successfully removed, we're done with this bundle + removed = bundle->try_multi_remove(data_with_idx.first, bin_idx); + if (removed) { + break; + } + } + + // We may have produced some empty BinBundles so just remove them all + auto rem_it = std::remove_if(bundle_set.begin(), bundle_set.end(), + [](auto &bundle) { return bundle->empty(); }); + bundle_set.erase(rem_it, bundle_set.end()); + + // We tried to remove an item that doesn't exist. This should never happen + if (!removed) { + SPDLOG_ERROR( + "Remove worker: " + "failed to remove item at bundle index {} because the item was not " + "found", + bundle_idx); + YACL_THROW("failed to remove item"); + } + } + + SPDLOG_INFO("Remove worker: finished processing bundle index {}", + bundle_index); +} + +/** +Takes algebraized data to be removed, splits it up, and distributes it so that +thread_count many threads can all remove in parallel. +*/ +void DispatchRemove( + const std::vector<std::pair<apsi::util::AlgItem, size_t>> + &data_with_indices, + std::vector<std::vector<std::shared_ptr<apsi::sender::BinBundle>>> + *bin_bundles, + uint32_t bins_per_bundle) { + apsi::ThreadPoolMgr tpm; + + // Collect the bundle indices and partition them into thread_count many + // partitions. By some uniformity assumption, the number of things to remove + // per partition should be roughly the same. Note that the contents of + // bundle_indices is always sorted (increasing order). + std::set<size_t> bundle_indices_set; + for (const auto &data_with_idx : data_with_indices) { + size_t cuckoo_idx = data_with_idx.second; + size_t bin_idx; + size_t bundle_idx; + std::tie(bin_idx, bundle_idx) = + labeled_psi::UnpackCuckooIdx(cuckoo_idx, bins_per_bundle); + bundle_indices_set.insert(bundle_idx); + } + + // Copy the set of indices into a vector and sort so each thread processes a + // range of indices + std::vector<size_t> bundle_indices; + bundle_indices.reserve(bundle_indices_set.size()); + copy(bundle_indices_set.begin(), bundle_indices_set.end(), + back_inserter(bundle_indices)); + sort(bundle_indices.begin(), bundle_indices.end()); + + // Run the threads on the partitions + std::vector<std::future<void>> futures(bundle_indices.size()); + SPDLOG_INFO("Launching {} remove worker tasks", bundle_indices.size()); + size_t future_idx = 0; + for (auto &bundle_idx : bundle_indices) { + futures[future_idx++] = tpm.thread_pool().enqueue([&]() { + RemoveWorker(data_with_indices, bin_bundles, + static_cast<uint32_t>(bundle_idx), bins_per_bundle); + }); + } + + // Wait for the tasks to finish + for (auto &f : futures) { + f.get(); + } +} + +} // namespace + +SenderMemDB::SenderMemDB(const apsi::PSIParams &params, + yacl::ByteContainerView oprf_key, + size_t label_byte_count, size_t nonce_byte_count, + bool compressed) + : ISenderDB(params, oprf_key, label_byte_count, nonce_byte_count, + compressed) { + // Reset the SenderDB data structures + clear(); +} + +size_t SenderMemDB::GetBinBundleCount(uint32_t bundle_idx) const { + // Lock the database for reading + auto lock = GetReaderLock(); + + return bin_bundles_.at(seal::util::safe_cast<size_t>(bundle_idx)).size(); +} + +size_t SenderMemDB::GetBinBundleCount() const { + // Lock the database for reading + auto lock = GetReaderLock(); + + // Compute the total number of BinBundles + return std::accumulate(bin_bundles_.cbegin(), bin_bundles_.cend(), + static_cast<size_t>(0), + [&](auto &a, auto &b) { return a + b.size(); }); +} + +void SenderMemDB::ClearInternal() { + // Assume the SenderDB is already locked for writing + + // Clear the set of inserted items + hashed_items_.clear(); + item_count_ = 0; + + // Clear the BinBundles + bin_bundles_.clear(); + bin_bundles_.resize(params_.bundle_idx_count()); + + // Reset the stripped_ flag + stripped_ = false; +} + +void SenderMemDB::clear() { + if (!hashed_items_.empty()) { + SPDLOG_INFO("Removing {} items pairs from SenderDB", hashed_items_.size()); + } + + // Lock the database for writing + auto lock = GetWriterLock(); + + ClearInternal(); +} + +void SenderMemDB::GenerateCaches() { + STOPWATCH(sender_stopwatch, "SenderDB::GenerateCaches"); + SPDLOG_INFO("Start generating bin bundle caches"); + + apsi::ThreadPoolMgr tpm; + + std::vector<std::future<void>> futures; + + for (auto &bundle_idx : bin_bundles_) { + futures.push_back(tpm.thread_pool().enqueue([&bundle_idx]() { + for (auto &bb : bundle_idx) { + bb->regen_cache(); + } + })); + } + + // Wait for the tasks to finish + for (auto &f : futures) { + f.get(); + } + + SPDLOG_INFO("Finished generating bin bundle caches"); +} + +void SenderMemDB::strip() { + // Lock the database for writing + auto lock = GetWriterLock(); + + stripped_ = true; + + memset(oprf_key_.data(), 0, oprf_key_.size()); + hashed_items_.clear(); + + apsi::ThreadPoolMgr tpm; + + std::vector<std::future<void>> futures; + for (auto &bundle_idx : bin_bundles_) { + for (auto &bb : bundle_idx) { + futures.push_back(tpm.thread_pool().enqueue([&bb]() { bb->strip(); })); + } + } + + // Wait for the tasks to finish + for (auto &f : futures) { + f.get(); + } + + SPDLOG_INFO("SenderDB has been stripped"); +} + +void SenderMemDB::InsertOrAssign(const std::vector<std::string> &keys, + const std::vector<std::string> &labels) { + if (stripped_) { + SPDLOG_ERROR("Cannot insert data to a stripped SenderDB"); + YACL_THROW("failed to insert data"); + } + if (!IsLabeled()) { + SPDLOG_ERROR( + "Attempted to insert labeled data but this is an unlabeled SenderDB"); + YACL_THROW("failed to insert data"); + } + + SPDLOG_INFO("Start inserting {} items in SenderDB", keys.size()); + + // First compute the hashes for the input data + std::vector<std::string> oprf_out = oprf_server_->FullEvaluate(keys); + std::vector<std::pair<apsi::HashedItem, apsi::EncryptedLabel>> hashed_data( + oprf_out.size()); + + yacl::parallel_for(0, oprf_out.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t i = begin; i < end; ++i) { + apsi::HashedItem hashed_item; + std::memcpy(hashed_item.value().data(), oprf_out[i].data(), + hashed_item.value().size()); + + apsi::LabelKey key; + std::memcpy(key.data(), &oprf_out[i][hashed_item.value().size()], + key.size()); + + apsi::Label label_with_padding = + PaddingData(labels[i], label_byte_count_); + + apsi::EncryptedLabel encrypted_label = encrypt_label( + label_with_padding, key, label_byte_count_, nonce_byte_count_); + + hashed_data[i] = std::make_pair(hashed_item, encrypted_label); + } + }); + + // Lock the database for writing + auto lock = GetWriterLock(); + + // We need to know which items are new and which are old, since we have to + // tell dispatch_insert_or_assign when to have an overwrite-on-collision + // versus add-binbundle-on-collision policy. + auto new_data_end = std::remove_if( + hashed_data.begin(), hashed_data.end(), [&](const auto &item_label_pair) { + bool found = + hashed_items_.find(item_label_pair.first) != hashed_items_.end(); + if (!found) { + // Add to hashed_items_ already at this point! + hashed_items_.insert(item_label_pair.first); + item_count_++; + } + + // Remove those that were found + return found; + }); + + // Dispatch the insertion, first for the new data, then for the data we're + // gonna overwrite + uint32_t bins_per_bundle = params_.bins_per_bundle(); + uint32_t max_bin_size = params_.table_params().max_items_per_bin; + uint32_t ps_low_degree = params_.query_params().ps_low_degree; + + // Compute the label size; this ceil(effective_label_bit_count / + // item_bit_count) + size_t label_size = labeled_psi::ComputeLabelSize( + nonce_byte_count_ + label_byte_count_, params_); + + auto new_item_count = distance(hashed_data.begin(), new_data_end); + auto existing_item_count = distance(new_data_end, hashed_data.end()); + + if (existing_item_count != 0) { + SPDLOG_INFO("Found {} existing items to replace in SenderDB", + existing_item_count); + + // Break the data into field element representation. Also compute the items' + // cuckoo indices. + std::vector<std::pair<apsi::util::AlgItemLabel, size_t>> data_with_indices = + PreprocessLabeledData(new_data_end, hashed_data.end(), params_); + + DispatchInsertOrAssign(data_with_indices, &bin_bundles_, crypto_context_, + bins_per_bundle, label_size, max_bin_size, + ps_low_degree, true, /* overwrite items */ + compressed_); + + // Release memory that is no longer needed + hashed_data.erase(new_data_end, hashed_data.end()); + } + + if (new_item_count != 0) { + SPDLOG_INFO("Found {} new items to insert in SenderDB", new_item_count); + + // Process and add the new data. Break the data into field element + // representation. Also compute the items' cuckoo indices. + std::vector<std::pair<apsi::util::AlgItemLabel, size_t>> data_with_indices = + PreprocessLabeledData(hashed_data.begin(), hashed_data.end(), params_); + + DispatchInsertOrAssign(data_with_indices, &bin_bundles_, crypto_context_, + bins_per_bundle, label_size, max_bin_size, + ps_low_degree, false, /* don't overwrite items */ + compressed_); + } + + SPDLOG_INFO("Finished inserting {} items in SenderDB", keys.size()); +} + +void SenderMemDB::InsertOrAssign(const std::vector<std::string> &data) { + if (stripped_) { + SPDLOG_ERROR("Cannot insert data to a stripped SenderDB"); + YACL_THROW("failed to insert data"); + } + if (IsLabeled()) { + SPDLOG_ERROR( + "Attempted to insert unlabeled data but this is a labeled SenderDB"); + YACL_THROW("failed to insert data"); + } + + STOPWATCH(sender_stopwatch, "SenderMemDB::insert_or_assign (unlabeled)"); + SPDLOG_INFO("Start inserting {} items in SenderMemDB", data.size()); + + // First compute the hashes for the input data + std::vector<std::string> oprf_out = oprf_server_->FullEvaluate(data); + + std::vector<apsi::HashedItem> hashed_data; + for (const auto &out : oprf_out) { + apsi::Item::value_type value{}; + std::memcpy(value.data(), out.data(), value.size()); + + hashed_data.emplace_back(value); + } + + // Lock the database for writing + auto lock = GetWriterLock(); + + // We are not going to insert items that already appear in the database. + auto new_data_end = std::remove_if( + hashed_data.begin(), hashed_data.end(), [&](const auto &item) { + bool found = hashed_items_.find(item) != hashed_items_.end(); + if (!found) { + // Add to hashed_items_ already at this point! + hashed_items_.insert(item); + item_count_++; + } + + // Remove those that were found + return found; + }); + + // Erase the previously existing items from hashed_data; in unlabeled case + // there is nothing to do + hashed_data.erase(new_data_end, hashed_data.end()); + + SPDLOG_INFO("Found {} new items to insert in SenderDB", hashed_data.size()); + + // Break the new data down into its field element representation. Also compute + // the items' cuckoo indices. + std::vector<std::pair<apsi::util::AlgItem, size_t>> data_with_indices = + PreprocessUnlabeledData(hashed_data.begin(), hashed_data.end(), params_); + + // Dispatch the insertion + uint32_t bins_per_bundle = params_.bins_per_bundle(); + uint32_t max_bin_size = params_.table_params().max_items_per_bin; + uint32_t ps_low_degree = params_.query_params().ps_low_degree; + + DispatchInsertOrAssign(data_with_indices, &bin_bundles_, crypto_context_, + bins_per_bundle, 0, /* label size */ + max_bin_size, ps_low_degree, + false, /* don't overwrite items */ + compressed_); + + SPDLOG_INFO("Finished inserting {} items in SenderDB", data.size()); +} + +void SenderMemDB::remove(const std::vector<apsi::Item> &data) { + if (stripped_) { + SPDLOG_ERROR("Cannot remove data from a stripped SenderDB"); + YACL_THROW("failed to remove data"); + } + + STOPWATCH(sender_stopwatch, "SenderDB::remove"); + SPDLOG_INFO("Start removing {} items from SenderDB", data.size()); + + // First compute the hashes for the input data + // auto hashed_data = OPRFSender::ComputeHashes(data, oprf_key_); + std::vector<std::string> data_str(data.size()); + for (size_t i = 0; i < data.size(); ++i) { + data_str[i].reserve(data[i].value().size()); + std::memcpy(data_str[i].data(), data[i].value().data(), + data[i].value().size()); + } + std::vector<std::string> oprf_out = oprf_server_->FullEvaluate(data_str); + std::vector<apsi::HashedItem> hashed_data; + for (const auto &out : oprf_out) { + apsi::Item::value_type value{}; + std::memcpy(value.data(), out.data(), value.size()); + + hashed_data.emplace_back(value); + } + + // Lock the database for writing + auto lock = GetWriterLock(); + + // Remove items that do not exist in the database. + auto existing_data_end = std::remove_if( + hashed_data.begin(), hashed_data.end(), [&](const auto &item) { + bool found = hashed_items_.find(item) != hashed_items_.end(); + if (found) { + // Remove from hashed_items_ already at this point! + hashed_items_.erase(item); + item_count_--; + } + + // Remove those that were not found + return !found; + }); + + // This distance is always non-negative + auto existing_item_count = + static_cast<size_t>(distance(existing_data_end, hashed_data.end())); + if (existing_item_count != 0) { + SPDLOG_WARN("Ignoring {} items that are not present in the SenderDB", + existing_item_count); + } + + // Break the data down into its field element representation. Also compute the + // items' cuckoo indices. + std::vector<std::pair<apsi::util::AlgItem, size_t>> data_with_indices = + PreprocessUnlabeledData(hashed_data.begin(), hashed_data.end(), params_); + + // Dispatch the removal + uint32_t bins_per_bundle = params_.bins_per_bundle(); + DispatchRemove(data_with_indices, &bin_bundles_, bins_per_bundle); + + // Generate the BinBundle caches + GenerateCaches(); + + SPDLOG_INFO("Finished removing {} items from SenderDB", data.size()); +} + +bool SenderMemDB::HasItem(const apsi::Item &item) const { + if (stripped_) { + SPDLOG_ERROR( + "Cannot retrieve the presence of an item from a stripped SenderDB"); + YACL_THROW("failed to retrieve the presence of item"); + } + + // First compute the hash for the input item + // auto hashed_item = OPRFSender::ComputeHashes({&item, 1}, oprf_key_)[0]; + std::string item_str; + item_str.reserve(item.value().size()); + std::memcpy(item_str.data(), item.value().data(), item.value().size()); + std::string oprf_out = oprf_server_->FullEvaluate(item_str); + apsi::HashedItem hashed_item; + std::memcpy(hashed_item.value().data(), oprf_out.data(), + hashed_item.value().size()); + + // Lock the database for reading + auto lock = GetReaderLock(); + + return hashed_items_.find(hashed_item) != hashed_items_.end(); +} + +apsi::Label SenderMemDB::GetLabel(const apsi::Item &item) const { + if (stripped_) { + SPDLOG_ERROR("Cannot retrieve a label from a stripped SenderDB"); + YACL_THROW("failed to retrieve label"); + } + if (!IsLabeled()) { + SPDLOG_ERROR( + "Attempted to retrieve a label but this is an unlabeled SenderDB"); + YACL_THROW("failed to retrieve label"); + } + + // First compute the hash for the input item + apsi::HashedItem hashed_item; + apsi::LabelKey key; + // tie(hashed_item, key) = OPRFSender::GetItemHash(item, oprf_key_); + + std::string item_str; + item_str.reserve(item.value().size()); + std::memcpy(item_str.data(), item.value().data(), item.value().size()); + std::string oprf_out = oprf_server_->FullEvaluate(item_str); + std::memcpy(hashed_item.value().data(), oprf_out.data(), + hashed_item.value().size()); + + // Lock the database for reading + auto lock = GetReaderLock(); + + // Check if this item is in the DB. If not, throw an exception + if (hashed_items_.find(hashed_item) == hashed_items_.end()) { + SPDLOG_ERROR( + "Cannot retrieve label for an item that is not in the SenderDB"); + YACL_THROW("failed to retrieve label"); + } + + uint32_t bins_per_bundle = params_.bins_per_bundle(); + + // Preprocess a single element. This algebraizes the item and gives back its + // field element representation as well as its cuckoo hash. We only read one + // of the locations because the labels are the same in each location. + apsi::util::AlgItem alg_item; + size_t cuckoo_idx; + std::tie(alg_item, cuckoo_idx) = + PreprocessUnlabeledData(hashed_item, params_)[0]; + + // Now figure out where to look to get the label + size_t bin_idx; + size_t bundle_idx; + std::tie(bin_idx, bundle_idx) = + labeled_psi::UnpackCuckooIdx(cuckoo_idx, bins_per_bundle); + + // Retrieve the algebraic labels from one of the BinBundles at this index + const std::vector<std::shared_ptr<apsi::sender::BinBundle>> &bundle_set = + bin_bundles_[bundle_idx]; + std::vector<felt_t> alg_label; + bool got_labels = false; + for (const auto &bundle : bundle_set) { + // Try to retrieve the contiguous labels from this BinBundle + if (bundle->try_get_multi_label(alg_item, bin_idx, alg_label)) { + got_labels = true; + break; + } + } + + // It shouldn't be possible to have items in your set but be unable to + // retrieve the associated label. Throw an exception because something is + // terribly wrong. + if (!got_labels) { + SPDLOG_ERROR( + "Failed to retrieve label for an item that was supposed to be in the " + "SenderDB"); + YACL_THROW("failed to retrieve label"); + } + + // All good. Now just reconstruct the big label from its split-up parts + apsi::EncryptedLabel encrypted_label = dealgebraize_label( + alg_label, + alg_label.size() * static_cast<size_t>(params_.item_bit_count_per_felt()), + params_.seal_params().plain_modulus()); + + // Resize down to the effective byte count + encrypted_label.resize(nonce_byte_count_ + label_byte_count_); + + // Decrypt the label + return decrypt_label(encrypted_label, key, nonce_byte_count_); +} + +void SenderMemDB::SetData( + const std::shared_ptr<IBatchProvider> &batch_provider) { + size_t batch_count = 0; + + while (true) { + std::vector<std::string> batch_items; + std::vector<std::string> batch_labels; + + if (IsLabeled()) { + std::shared_ptr<ILabeledBatchProvider> labeled_batch_provider = + std::dynamic_pointer_cast<ILabeledBatchProvider>(batch_provider); + + std::tie(batch_items, batch_labels) = + labeled_batch_provider->ReadNextLabeledBatch(); + } else { + std::shared_ptr<IBasicBatchProvider> basic_batch_provider = + std::dynamic_pointer_cast<IBasicBatchProvider>(batch_provider); + + batch_items = basic_batch_provider->ReadNextBatch(); + } + if (batch_items.empty()) { + SPDLOG_INFO("count: {}, item_count:{}", batch_count, item_count_); + break; + } + + if (IsLabeled()) { + InsertOrAssign(batch_items, batch_labels); + } else { + InsertOrAssign(batch_items); + } + + batch_count++; + } + + // Generate the BinBundle caches + GenerateCaches(); +} + +std::shared_ptr<apsi::sender::BinBundle> SenderMemDB::GetBinBundleAt( + std::uint32_t bundle_idx, size_t cache_idx) { + return bin_bundles_[bundle_idx][cache_idx]; +} + +} // namespace psi::psi diff --git a/psi/psi/core/labeled_psi/sender_memdb.h b/psi/psi/core/labeled_psi/sender_memdb.h new file mode 100644 index 00000000..2d8dd057 --- /dev/null +++ b/psi/psi/core/labeled_psi/sender_memdb.h @@ -0,0 +1,227 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// code reference https://github.com/microsoft/APSI/sender/sender_db.h +// Licensed under the MIT license. + +#pragma once + +// STD +#include <atomic> +#include <cstddef> +#include <cstdint> +#include <iostream> +#include <memory> +#include <string> +#include <unordered_set> +#include <utility> +#include <vector> + +// GSL +#include "gsl/span" + +// APSI +#include "apsi/bin_bundle.h" +#include "apsi/crypto_context.h" +#include "apsi/item.h" +#include "apsi/psi_params.h" +#include "yacl/base/byte_container_view.h" + +#include "psi/psi/core/ecdh_oprf/basic_ecdh_oprf.h" +#include "psi/psi/core/labeled_psi/sender_db.h" + +// SEAL +#include "seal/plaintext.h" +#include "seal/util/locks.h" +#include "spdlog/spdlog.h" + +namespace psi::psi { + +/** +A SenderDB maintains an in-memory representation of the sender's set of items +and labels (in labeled mode). This data is not simply copied into the SenderDB +data structures, but also preprocessed heavily to allow for faster online +computation time. Since inserting a large number of new items into a SenderDB +can take time, it is not recommended to recreate the SenderDB when the database +changes a little bit. Instead, the class supports fast update and deletion +operations that should be preferred: SenderDB::InsertOrAssign and +SenderDB::remove. + +The SenderDB constructor allows the label byte count to be specified; unlabeled +mode is activated by setting the label byte count to zero. It is possible to +optionally specify the size of the nonce used in encrypting the labels, but this +is best left to its default value unless the user is absolutely sure of what +they are doing. + +The SenderDB requires substantially more memory than the raw data would. Part of +that memory can automatically be compressed when it is not in use; this feature +is enabled by default, and can be disabled when constructing the SenderDB. The +downside of in-memory compression is a performance reduction from decompressing +parts of the data when they are used, and recompressing them if they are +updated. +*/ +class SenderMemDB : public ISenderDB { + public: + /** + Creates a new SenderDB. + */ + SenderMemDB(const apsi::PSIParams &params, yacl::ByteContainerView oprf_key, + std::size_t label_byte_count = 0, + std::size_t nonce_byte_count = 16, bool compressed = true); + + /** + Clears the database. Every item and label will be removed. The OPRF key is + unchanged. + */ + void clear(); + + /** + Strips the SenderDB of all information not needed for serving a query. + */ + void strip(); + + /** + Inserts the given data into the database. This function can be used only on a + labeled SenderDB instance. If an item already exists in the database, its + label is overwritten with the new label. + */ + void InsertOrAssign(const std::vector<std::string> &keys, + const std::vector<std::string> &labels); + + /** + Inserts the given (hashed) item-label pair into the database. This function + can be used only on a labeled SenderDB instance. If the item already exists in + the database, its label is overwritten with the new label. + */ + void InsertOrAssign(const std::string &key, const std::string &label) { + std::vector<std::string> key_singleton{key}; + std::vector<std::string> label_singleton{label}; + InsertOrAssign(key_singleton, label_singleton); + } + + /** + Inserts the given data into the database. This function can be used only on an + unlabeled SenderDB instance. + */ + void InsertOrAssign(const std::vector<std::string> &data); + + /** + Inserts the given (hashed) item into the database. This function can be used + only on an unlabeled SenderDB instance. + */ + void InsertOrAssign(const std::string &data) { + std::vector<std::string> data_singleton{data}; + InsertOrAssign(data_singleton); + } + + /** + Clears the database and inserts the given data. This function can be used only + on a labeled SenderDB instance. + */ + void SetData(const std::vector<std::string> &keys, + const std::vector<std::string> &labels) { + clear(); + InsertOrAssign(keys, labels); + // Generate the BinBundle caches + GenerateCaches(); + } + + void SetData(const std::shared_ptr<IBatchProvider> &batch_provider) override; + + /** + Clears the database and inserts the given data. This function can be used only + on an unlabeled SenderDB instance. + */ + void SetData(const std::vector<std::string> &data) { + clear(); + InsertOrAssign(data); + // Generate the BinBundle caches + GenerateCaches(); + } + + /** + Removes the given data from the database, using at most thread_count threads. + */ + void remove(const std::vector<apsi::Item> &data); + + /** + Removes the given (hashed) item from the database. + */ + void remove(const apsi::Item &data) { + std::vector<apsi::Item> data_singleton{data}; + remove(data_singleton); + } + + /** + Returns whether the given item has been inserted in the SenderDB. + */ + bool HasItem(const apsi::Item &item) const; + + /** + Returns the label associated to the given item in the database. Throws + std::invalid_argument if the item does not appear in the database. + */ + apsi::Label GetLabel(const apsi::Item &item) const; + + /** + Returns a set of cache references corresponding to the bundles at the given + bundle index. Even though this function returns a vector, the order has no + significance. This function is meant for internal use. + */ + std::shared_ptr<apsi::sender::BinBundle> GetBinBundleAt( + std::uint32_t bundle_idx, size_t cache_idx) override; + + /** + Returns a reference to a set of item hashes already existing in the SenderDB. + */ + const std::unordered_set<apsi::HashedItem> &GetHashedItems() const { + return hashed_items_; + } + + /** + Returns the number of items in this SenderDB. + */ + size_t GetItemCount() const override { return item_count_; } + + /** + Returns the total number of bin bundles at a specific bundle index. + */ + std::size_t GetBinBundleCount(std::uint32_t bundle_idx) const override; + + /** + Returns the total number of bin bundles. + */ + std::size_t GetBinBundleCount() const override; + + private: + void ClearInternal(); + + void GenerateCaches(); + + /** + The set of all items that have been inserted into the database + */ + std::unordered_set<apsi::HashedItem> hashed_items_; + + /** + All the BinBundles in the database, indexed by bundle index. The set + (represented by a vector internally) at bundle index i contains all the + BinBundles with bundle index i. + */ + std::vector<std::vector<std::shared_ptr<apsi::sender::BinBundle>>> + bin_bundles_; + +}; // class SenderDB + +} // namespace psi::psi diff --git a/psi/psi/core/labeled_psi/serializable.proto b/psi/psi/core/labeled_psi/serializable.proto new file mode 100644 index 00000000..b7519000 --- /dev/null +++ b/psi/psi/core/labeled_psi/serializable.proto @@ -0,0 +1,87 @@ +// +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +package psi.psi.proto; + +message SealParamsProto { + uint32 poly_modulus_degree = 1; + uint32 plain_modulus = 2; + repeated uint64 coeff_modulus = 3; +} + +message LabelPsiParamsProto { + uint32 hash_func_count = 1; + uint32 table_size = 2; + uint32 max_items_per_bin = 3; + + uint32 felts_per_item = 4; + + uint32 ps_low_degree = 5; + repeated uint32 query_powers = 6; + + SealParamsProto seal_params = 7; +} + +message OprfProto { + repeated bytes data = 1; +} + +message EncryptedPowersProto { + uint64 power = 1; + repeated bytes ciphertexts = 2; +} + +message QueryRequestProto { + bytes relin_keys = 1; + repeated EncryptedPowersProto encrypted_powers = 2; +} + +message QueryResultProto { + uint32 bundle_idx = 1; + bytes ciphertext = 2; + uint32 label_byte_count = 3; + uint32 nonce_byte_count = 4; + repeated bytes label_results = 5; +} + +message QueryResponseProto { + repeated QueryResultProto results = 1; +} + +message AlgItemProto { + repeated uint64 item = 1; +} + +message AlgItemLabelPairProto { + uint64 item = 1; + bytes label_data = 2; +} + +message AlgItemLabelProto { + repeated AlgItemLabelPairProto item_label = 1; +} + +message DataWithIndicesProto { + AlgItemProto data = 1; + uint64 index = 2; +} + +message DataLabelWithIndicesProto { + AlgItemLabelProto data = 1; + uint64 index = 2; +} diff --git a/psi/psi/core/labeled_psi/serialize.h b/psi/psi/core/labeled_psi/serialize.h new file mode 100644 index 00000000..7af467b8 --- /dev/null +++ b/psi/psi/core/labeled_psi/serialize.h @@ -0,0 +1,185 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <string> +#include <utility> +#include <vector> + +#include "absl/strings/string_view.h" +#include "apsi/util/db_encoding.h" +#include "spdlog/spdlog.h" + +#include "psi/psi/core/labeled_psi/serializable.pb.h" + +namespace psi::psi { + +inline void SerializeAlgItem(const apsi::util::AlgItem& alg_items, + proto::AlgItemProto* proto) { + for (auto& alg_item : alg_items) { + proto->add_item(alg_item); + } +} + +inline proto::AlgItemProto SerializeAlgItem( + const apsi::util::AlgItem& alg_items) { + proto::AlgItemProto proto; + + SerializeAlgItem(alg_items, &proto); + + return proto; +} + +inline std::string SerializeAlgItemToString( + const apsi::util::AlgItem& alg_items) { + proto::AlgItemProto proto = SerializeAlgItem(alg_items); + + std::string item_string(proto.ByteSizeLong(), '\0'); + proto.SerializePartialToArray(item_string.data(), item_string.length()); + + return item_string; +} + +inline apsi::util::AlgItem DeserializeAlgItem( + const proto::AlgItemProto& proto) { + apsi::util::AlgItem alg_items; + + alg_items.resize(proto.item_size()); + for (int i = 0; i < proto.item_size(); ++i) { + alg_items[i] = proto.item(i); + } + + return alg_items; +} + +inline apsi::util::AlgItem DeserializeAlgItem(const absl::string_view& buf) { + apsi::util::AlgItem alg_items; + proto::AlgItemProto proto; + proto.ParseFromArray(buf.data(), buf.length()); + + return DeserializeAlgItem(proto); +} + +inline void SerializeAlgItemLabel( + const apsi::util::AlgItemLabel& item_label_pair, + proto::AlgItemLabelProto* proto) { + for (size_t i = 0; i < item_label_pair.size(); ++i) { + proto::AlgItemLabelPairProto* pair_proto = proto->add_item_label(); + + pair_proto->set_item(item_label_pair[i].first); + pair_proto->set_label_data( + item_label_pair[i].second.data(), + item_label_pair[i].second.size() * sizeof(uint64_t)); + } +} + +inline proto::AlgItemLabelProto SerializeAlgItemLabel( + const apsi::util::AlgItemLabel& item_label_pair) { + proto::AlgItemLabelProto proto; + SerializeAlgItemLabel(item_label_pair, &proto); + + return proto; +} + +inline std::string SerializeAlgItemLabelToString( + const apsi::util::AlgItemLabel& item_label_pair) { + proto::AlgItemLabelProto proto = SerializeAlgItemLabel(item_label_pair); + + std::string item_string(proto.ByteSizeLong(), '\0'); + proto.SerializePartialToArray(item_string.data(), item_string.length()); + + return item_string; +} + +inline apsi::util::AlgItemLabel DeserializeAlgItemLabel( + const proto::AlgItemLabelProto& proto) { + apsi::util::AlgItemLabel item_label_pair; + + for (int i = 0; i < proto.item_label_size(); ++i) { + auto pair_proto = proto.item_label(i); + + auto label_data = pair_proto.label_data(); + + std::vector<apsi::util::felt_t> labels(label_data.size() / + sizeof(apsi::util::felt_t)); + + std::memcpy(labels.data(), label_data.data(), label_data.size()); + item_label_pair.emplace_back(pair_proto.item(), labels); + } + + return item_label_pair; +} + +inline apsi::util::AlgItemLabel DeserializeAlgItemLabel( + const absl::string_view& buf) { + proto::AlgItemLabelProto proto; + proto.ParseFromArray(buf.data(), buf.size()); + + return DeserializeAlgItemLabel(proto); +} + +inline std::string SerializeDataWithIndices( + const std::pair<apsi::util::AlgItem, size_t>& data_with_indices) { + proto::DataWithIndicesProto proto; + + proto::AlgItemProto* item_proto = new proto::AlgItemProto(); + SerializeAlgItem(data_with_indices.first, item_proto); + + proto.set_allocated_data(item_proto); + proto.set_index(data_with_indices.second); + + std::string item_string(proto.ByteSizeLong(), '\0'); + proto.SerializePartialToArray(item_string.data(), proto.ByteSizeLong()); + + return item_string; +} + +inline std::pair<apsi::util::AlgItem, size_t> DeserializeDataWithIndices( + const absl::string_view& buf) { + proto::DataWithIndicesProto proto; + proto.ParseFromArray(buf.data(), buf.size()); + + apsi::util::AlgItem alg_item = DeserializeAlgItem(proto.data()); + + return std::make_pair(alg_item, proto.index()); +} + +inline std::string SerializeDataLabelWithIndices( + const std::pair<apsi::util::AlgItemLabel, size_t>& data_with_indices) { + proto::DataLabelWithIndicesProto proto; + + proto::AlgItemLabelProto* item_proto = new proto::AlgItemLabelProto(); + SerializeAlgItemLabel(data_with_indices.first, item_proto); + + proto.set_allocated_data(item_proto); + proto.set_index(data_with_indices.second); + + std::string item_string(proto.ByteSizeLong(), '\0'); + proto.SerializePartialToArray(item_string.data(), item_string.length()); + + return item_string; +} + +inline std::pair<apsi::util::AlgItemLabel, size_t> +DeserializeDataLabelWithIndices(const absl::string_view& buf) { + proto::DataLabelWithIndicesProto proto; + proto.ParseFromArray(buf.data(), buf.size()); + + apsi::util::AlgItemLabel alg_item = DeserializeAlgItemLabel(proto.data()); + + return std::make_pair(alg_item, proto.index()); +} + +} // namespace psi::psi diff --git a/psi/psi/core/mini_psi.cc b/psi/psi/core/mini_psi.cc new file mode 100644 index 00000000..dc3854a1 --- /dev/null +++ b/psi/psi/core/mini_psi.cc @@ -0,0 +1,624 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/mini_psi.h" + +#include <future> +#include <map> +#include <random> +#include <set> +#include <unordered_set> + +#include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" +#include "openssl/crypto.h" +#include "openssl/rand.h" +#include "spdlog/spdlog.h" + +extern "C" { +#include "curve25519.h" +} + +#include "yacl/base/exception.h" +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/crypto/base/symmetric_crypto.h" +#include "yacl/crypto/tools/prg.h" +#include "yacl/utils/parallel.h" + +#include "psi/psi/core/communication.h" +#include "psi/psi/core/cuckoo_index.h" +#include "psi/psi/core/polynomial/polynomial.h" +#include "psi/psi/utils/batch_provider.h" +#include "psi/psi/utils/serialize.h" + +namespace psi::psi { + +namespace { + +constexpr uint32_t kLinkRecvTimeout = 30 * 60 * 1000; +// first prime over 2^256, used as module for polynomial interpolate +std::string kPrimeOver256bHexStr = + "010000000000000000000000000000000000000000000000000000000000000129"; + +// batch size of Cuckoo Hash +constexpr size_t kCuckooHashBatchSize = 2000; + +std::vector<std::string> HashInputs(const std::vector<std::string>& items) { + std::vector<std::string> ret(items.size()); + yacl::parallel_for(0, items.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + auto hash = yacl::crypto::Sha256(items[idx]); + ret[idx].resize(hash.size()); + std::memcpy(ret[idx].data(), hash.data(), hash.size()); + } + }); + return ret; +} + +struct MiniPsiSendCtx { + MiniPsiSendCtx() { + yacl::crypto::Prg<uint64_t> prg(0, yacl::crypto::PRG_MODE::kNistAesCtrDrbg); + prg.Fill(absl::MakeSpan(private_key.data(), kKeySize)); + + curve25519_donna_basepoint(static_cast<unsigned char*>(public_key.data()), + private_key.data()); + + uint128_t aes_key = yacl::crypto::Blake3_128(public_key); + aes_ecb = std::make_shared<yacl::crypto::SymmetricCrypto>( + yacl::crypto::SymmetricCrypto::CryptoType::AES128_ECB, aes_key, 0); + + prime256_str = absl::HexStringToBytes(kPrimeOver256bHexStr); + } + + void RecvPolynomialCoeff( + const std::shared_ptr<yacl::link::Context>& link_ctx) { + size_t batch_count = 0; + + yacl::link::RecvTimeoutGuard guard(link_ctx, kLinkRecvTimeout); + while (true) { + const auto tag = fmt::format("MINI-PSI:X^A:{}", batch_count); + PsiDataBatch coeff_batch = + PsiDataBatch::Deserialize(link_ctx->Recv(link_ctx->NextRank(), tag)); + // Fetch y^b. + YACL_ENFORCE(coeff_batch.flatten_bytes.size() % kHashSize == 0); + size_t num_items = coeff_batch.flatten_bytes.size() / kHashSize; + + if (num_items > 0) { + absl::string_view flatten_bytes = coeff_batch.flatten_bytes; + + for (size_t i = 0; i < num_items; ++i) { + polynomial_coeff.emplace_back( + flatten_bytes.substr(i * kHashSize, kHashSize)); + } + } + + if (coeff_batch.is_last_batch) { + break; + } + batch_count++; + } + } + + void EvalPolynomial(const std::vector<std::string>& items) { + polynomial_eval_values.resize(items.size()); + masked_values.resize(items.size()); + + items_hash = HashInputs(items); + + yacl::parallel_for(0, items.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + polynomial_eval_values[idx] = ::psi::psi::EvalPolynomial( + polynomial_coeff, absl::string_view(items_hash[idx]), prime256_str); + + std::array<uint8_t, kKeySize> ideal_permutation; + // Ideal Permutation + aes_ecb->Decrypt(absl::MakeSpan(reinterpret_cast<uint8_t*>( + polynomial_eval_values[idx].data()), + polynomial_eval_values[idx].length()), + absl::MakeSpan(ideal_permutation)); + + std::string masked(kKeySize, '\0'); + + curve25519_donna( + reinterpret_cast<unsigned char*>(masked.data()), private_key.data(), + static_cast<const unsigned char*>(ideal_permutation.data())); + + yacl::crypto::Sha256Hash sha256; + sha256.Update(items[idx].data()); + sha256.Update(masked.data()); + std::vector<uint8_t> mask_hash = sha256.CumulativeHash(); + masked_values[idx].resize(kFinalCompareBytes); + std::memcpy(masked_values[idx].data(), mask_hash.data(), + kFinalCompareBytes); + } + }); + + // use sort as shuffle + std::sort(masked_values.begin(), masked_values.end()); + } + + void SendMaskedEvalValues( + const std::shared_ptr<yacl::link::Context>& link_ctx) { + size_t batch_count = 0; + + std::shared_ptr<IBasicBatchProvider> batch_provider = + std::make_shared<MemoryBatchProvider>(masked_values, kEcdhPsiBatchSize); + + while (true) { + PsiDataBatch batch; + // NOTE: we still need to send one batch even there is no data. + // This dummy batch is used to notify peer the end of data stream. + auto items = batch_provider->ReadNextBatch(); + batch.is_last_batch = items.empty(); + // Mask and Send this batch. + if (!items.empty()) { + batch.flatten_bytes.reserve(items.size() * kFinalCompareBytes); + + for (const auto& item : items) { + batch.flatten_bytes.append(item); + } + } + // Send x^a. + const auto tag = fmt::format("MINI-PSI:X^A:{}", batch_count); + link_ctx->SendAsyncThrottled(link_ctx->NextRank(), batch.Serialize(), + tag); + if (batch.is_last_batch) { + SPDLOG_INFO("Last batch triggered, batch_count={}", batch_count); + break; + } + batch_count++; + } + } + + // key + std::array<uint8_t, kKeySize> private_key; + std::array<uint8_t, kKeySize> public_key; + + // next prime over 2^256 + std::string prime256_str; + + // hash of items + std::vector<std::string> items_hash; + + // polynomial_coeff + std::vector<std::string> polynomial_coeff; + + std::vector<std::string> polynomial_eval_values; + std::vector<std::string> masked_values; + + // use aes-128-ecb as Ideal Permutation + std::shared_ptr<yacl::crypto::SymmetricCrypto> aes_ecb; +}; + +struct MiniPsiRecvCtx { + MiniPsiRecvCtx() { + prime256_str = absl::HexStringToBytes(kPrimeOver256bHexStr); + } + + void GenerateSeeds(size_t data_size) { + seeds.resize(data_size); + seeds_point.resize(data_size); + + yacl::parallel_for(0, data_size, 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + yacl::crypto::Prg<uint64_t> prg( + 0, yacl::crypto::PRG_MODE::kNistAesCtrDrbg); + prg.Fill(absl::MakeSpan(seeds[idx].data(), kKeySize)); + + curve25519_donna_basepoint( + static_cast<unsigned char*>(seeds_point[idx].data()), + seeds[idx].data()); + } + }); + } + + void InterpolatePolynomial(const std::vector<std::string>& items) { + items_hash = HashInputs(items); + + std::vector<absl::string_view> poly_x(items_hash.size()); + std::vector<absl::string_view> poly_y(items_hash.size()); + std::vector<std::array<uint8_t, kKeySize>> poly_y_permutation( + items_hash.size()); + + for (size_t idx = 0; idx < items_hash.size(); idx++) { + poly_x[idx] = absl::string_view(items_hash[idx]); + + // Ideal Permutation + aes_ecb->Encrypt(absl::MakeSpan(seeds_point[idx]), + absl::MakeSpan(poly_y_permutation[idx])); + + poly_y[idx] = absl::string_view( + reinterpret_cast<const char*>(poly_y_permutation[idx].data()), + kKeySize); + } + + // ToDo: now use newton Polynomial Interpolation, need optimize to fft + // + polynomial_coeff = + ::psi::psi::InterpolatePolynomial(poly_x, poly_y, prime256_str); + } + + void SendPolynomialCoeff( + const std::shared_ptr<yacl::link::Context>& link_ctx) { + size_t batch_count = 0; + + std::shared_ptr<IBasicBatchProvider> batch_provider = + std::make_shared<MemoryBatchProvider>(polynomial_coeff, + kEcdhPsiBatchSize); + + while (true) { + PsiDataBatch batch; + // NOTE: we still need to send one batch even there is no data. + // This dummy batch is used to notify peer the end of data stream. + auto items = batch_provider->ReadNextBatch(); + batch.is_last_batch = items.empty(); + // Mask and Send this batch. + if (!items.empty()) { + batch.flatten_bytes.reserve(items.size() * kHashSize); + + for (const auto& item : items) { + batch.flatten_bytes.append(item); + } + } + // Send x^a. + const auto tag = fmt::format("MINI-PSI:X^A:{}", batch_count); + link_ctx->SendAsyncThrottled(link_ctx->NextRank(), batch.Serialize(), + tag); + if (batch.is_last_batch) { + SPDLOG_INFO("Last batch triggered, batch_count={}", batch_count); + break; + } + batch_count++; + } + } + + void RecvMaskedEvalValues( + const std::shared_ptr<yacl::link::Context>& link_ctx) { + size_t batch_count = 0; + + yacl::link::RecvTimeoutGuard guard(link_ctx, kLinkRecvTimeout); + while (true) { + const auto tag = fmt::format("MINI-PSI:X^A^B:{}", batch_count); + PsiDataBatch masked_eval_batch = + PsiDataBatch::Deserialize(link_ctx->Recv(link_ctx->NextRank(), tag)); + // Fetch y^b. + YACL_ENFORCE( + masked_eval_batch.flatten_bytes.size() % kFinalCompareBytes == 0); + size_t num_items = + masked_eval_batch.flatten_bytes.size() / kFinalCompareBytes; + + if (num_items > 0) { + absl::string_view flatten_bytes = masked_eval_batch.flatten_bytes; + + for (size_t i = 0; i < num_items; ++i) { + peer_masked_values.emplace( + flatten_bytes.substr(i * kFinalCompareBytes, kFinalCompareBytes)); + } + } + if (masked_eval_batch.is_last_batch) { + break; + } + batch_count++; + } + } + + void MaskPeerPublicKey(const std::vector<std::string>& items) { + masked_values.resize(seeds.size()); + + yacl::parallel_for(0, seeds.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + std::string masked(kKeySize, '\0'); + curve25519_donna(reinterpret_cast<unsigned char*>(masked.data()), + seeds[idx].data(), peer_public_key.data()); + + yacl::crypto::Sha256Hash sha256; + sha256.Update(items[idx].data()); + sha256.Update(masked.data()); + std::vector<uint8_t> mask_hash = sha256.CumulativeHash(); + masked_values[idx].resize(kFinalCompareBytes); + std::memcpy(masked_values[idx].data(), mask_hash.data(), + kFinalCompareBytes); + } + }); + } + + std::vector<std::string> GetIntersection( + const std::vector<std::string>& items) { + std::vector<std::string> ret; + + for (uint32_t index = 0; index < masked_values.size(); index++) { + if (peer_masked_values.find(masked_values[index]) != + peer_masked_values.end()) { + ret.push_back(items[index]); + } + } + + return ret; + } + + std::vector<std::array<uint8_t, kKeySize>> seeds; + std::vector<std::array<uint8_t, kKeySize>> seeds_point; + + // peer's public key + std::array<uint8_t, kKeySize> peer_public_key; + + // next prime over 2^256 + std::string prime256_str; + + // hash of items + std::vector<std::string> items_hash; + + // polynomial_coeff + std::vector<std::string> polynomial_coeff; + + // dual mask value + std::vector<std::string> masked_values; + // peer's dual mask value + std::unordered_set<std::string> peer_masked_values; + + // use aes-128-ecb as Ideal Permutation + std::shared_ptr<yacl::crypto::SymmetricCrypto> aes_ecb; +}; + +} // namespace + +// #define DEBUG_OUT + +void MiniPsiSend(const std::shared_ptr<yacl::link::Context>& link_ctx, + const std::vector<std::string>& items) { + MiniPsiSendCtx send_ctx; + + // + // TODO: whether use zk to prove sender's public_key + // https://github.com/osu-crypto/MiniPSI/blob/master/libPSI/MiniPSI/MiniSender.cpp#L601 + // MiniPSI code use zk prove public_key (discrete logarithm) + // in the origin paper no use zk + // + link_ctx->SendAsyncThrottled( + link_ctx->NextRank(), + yacl::Buffer(send_ctx.public_key.data(), send_ctx.public_key.size()), + "MINI-PSI:X^A"); + + // receive Polynomial Coefficient + send_ctx.RecvPolynomialCoeff(link_ctx); + + std::future<void> f_eval = + std::async([&] { send_ctx.EvalPolynomial(items); }); + + f_eval.get(); + + // send Polynomial evaluation and mask value to receiver + send_ctx.SendMaskedEvalValues(link_ctx); +} + +std::vector<std::string> MiniPsiRecv( + const std::shared_ptr<yacl::link::Context>& link_ctx, + const std::vector<std::string>& items) { + MiniPsiRecvCtx recv_ctx; + + std::future<void> f_get_pubkey = std::async([&] { + // receive sender's public key + yacl::Buffer buf = + link_ctx->Recv(link_ctx->NextRank(), fmt::format("MINI-PSI:X^A")); + std::memcpy(recv_ctx.peer_public_key.data(), buf.data(), buf.size()); + + uint128_t aes_key = yacl::crypto::Blake3_128(recv_ctx.peer_public_key); + recv_ctx.aes_ecb = std::make_shared<yacl::crypto::SymmetricCrypto>( + yacl::crypto::SymmetricCrypto::CryptoType::AES128_ECB, aes_key, 0); + }); + + std::future<void> f_gen_seeds = std::async([&] { + // generate seed + recv_ctx.GenerateSeeds(items.size()); + }); + f_get_pubkey.get(); + f_gen_seeds.get(); + + std::future<void> f_interpolate = + std::async([&] { recv_ctx.InterpolatePolynomial(items); }); + + f_interpolate.get(); + + // send polynomial coefficient to sender + recv_ctx.SendPolynomialCoeff(link_ctx); + + std::future<void> f_mask_peer = + std::async([&] { return recv_ctx.MaskPeerPublicKey(items); }); + + f_mask_peer.get(); + + // get sender's masked value + recv_ctx.RecvMaskedEvalValues(link_ctx); + + // get intersection + return recv_ctx.GetIntersection(items); +} + +// big data +void MiniPsiSendBatch(const std::shared_ptr<yacl::link::Context>& link_ctx, + const std::vector<std::string>& items) { + size_t peer_size = utils::DeserializeSize( + link_ctx->Recv(link_ctx->NextRank(), fmt::format("RECV PEER SIZE"))); + + CuckooIndex::Options option = CuckooIndex::SelectParams(peer_size, 0, 3); + + size_t num_bins = option.NumBins(); + std::vector<std::string> items_hash = HashInputs(items); + // + std::vector<std::vector<std::string>> simple_hash(num_bins); + + // + for (size_t idx = 0; idx < items.size(); idx++) { + uint128_t items_u128; + std::memcpy(&items_u128, items_hash[idx].data(), sizeof(uint128_t)); + CuckooIndex::HashRoom itemHash(items_u128); + uint64_t bin_idx0 = itemHash.GetHash(0) % num_bins; + uint64_t bin_idx1 = itemHash.GetHash(1) % num_bins; + uint64_t bin_idx2 = itemHash.GetHash(2) % num_bins; + std::set<uint64_t> bin_idx_set; + bin_idx_set.insert(bin_idx0); + bin_idx_set.insert(bin_idx1); + bin_idx_set.insert(bin_idx2); + + for (const auto& idx_inter : bin_idx_set) { + simple_hash[idx_inter].push_back(items[idx]); + } + } + size_t nthread = utils::DeserializeSize(link_ctx->Recv( + link_ctx->NextRank(), fmt::format("Mini-PSI RECV THREAD Num"))); + + auto thread = [&](const std::shared_ptr<yacl::link::Context>& thread_link_ctx, + size_t thread_idx) { + size_t start_idx = num_bins * thread_idx / nthread; + size_t end_idx = num_bins * (thread_idx + 1) / nthread; + + for (size_t idx = start_idx; idx < end_idx; idx += kCuckooHashBatchSize) { + size_t current_batch_size = std::min(kCuckooHashBatchSize, end_idx - idx); + std::set<std::string> batch_items_set; + std::vector<std::string> batch_items_vec; + for (size_t batch_idx = 0; batch_idx < current_batch_size; batch_idx++) { + for (auto& item : simple_hash[idx + batch_idx]) { + batch_items_set.insert(item); + } + } + batch_items_vec.assign(batch_items_set.begin(), batch_items_set.end()); + SPDLOG_INFO("thread:{}, batch_idx:{}/{}, batch_items size:{} ", + thread_idx, idx, end_idx, batch_items_vec.size()); + MiniPsiSend(thread_link_ctx, batch_items_vec); + } + }; + + std::vector<std::future<void>> futures; + std::vector<std::shared_ptr<yacl::link::Context>> thread_link_ctxs(nthread); + + for (size_t thread_idx = 0; thread_idx < nthread; ++thread_idx) { + thread_link_ctxs[thread_idx] = link_ctx->Spawn(); + futures.push_back( + std::async(thread, thread_link_ctxs[thread_idx], thread_idx)); + } + // wait thread + for (auto& f : futures) { + f.get(); + } +} + +std::vector<std::string> MiniPsiRecvBatch( + const std::shared_ptr<yacl::link::Context>& link_ctx, + const std::vector<std::string>& items) { + // send size to peer + link_ctx->SendAsyncThrottled(link_ctx->NextRank(), + utils::SerializeSize(items.size()), "RECV SIZE"); + + std::vector<std::string> items_hash = HashInputs(items); + std::vector<uint128_t> items_hash_u128(items.size()); + + yacl::parallel_for(0, items.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + std::memcpy(&items_hash_u128[idx], items_hash[idx].data(), + sizeof(uint128_t)); + } + }); + // cuckoo hash + CuckooIndex::Options option = CuckooIndex::SelectParams(items.size(), 0, 3); + CuckooIndex cuckoo_index(option); + + cuckoo_index.Insert(absl::MakeSpan(items_hash_u128)); + + YACL_ENFORCE(cuckoo_index.stash().empty(), "stash size not 0"); + + size_t nthreads = yacl::intraop_default_num_threads(); + // send thread num + if (items.size() < 100000) { + nthreads = 1; + } else { + nthreads /= 8; + } + link_ctx->Send(link_ctx->NextRank(), utils::SerializeSize(nthreads), + "Mini-PSI SEND THREAD NUM"); + + size_t num_bins = option.NumBins(); + auto ck_bins = cuckoo_index.bins(); + + std::vector<std::string> ret; + std::vector<size_t> ret_idx; + std::vector<std::vector<size_t>> ret_idx_vec(nthreads); + + // thread func + auto thread = [&](const std::shared_ptr<yacl::link::Context>& thread_link_ctx, + size_t thread_idx) { + size_t start_idx = num_bins * thread_idx / nthreads; + size_t end_idx = num_bins * (thread_idx + 1) / nthreads; + + std::random_device rd; + yacl::crypto::Prg<uint64_t> prg(rd()); + + for (size_t idx = start_idx; idx < end_idx; idx += kCuckooHashBatchSize) { + size_t current_batch_size = std::min(kCuckooHashBatchSize, end_idx - idx); + std::vector<std::string> batch_items; + std::unordered_map<std::string, size_t> batch_map; + for (size_t batch_idx = 0; batch_idx < current_batch_size; batch_idx++) { + // real data + if (!ck_bins[idx + batch_idx].IsEmpty()) { + batch_items.push_back(items[ck_bins[idx + batch_idx].InputIdx()]); + batch_map.emplace(items[ck_bins[idx + batch_idx].InputIdx()], + ck_bins[idx + batch_idx].InputIdx()); + } else { + // insert padding data + std::string padding_data(kKeySize, '\0'); + prg.Fill(absl::MakeSpan(padding_data.data(), padding_data.length())); + batch_items.push_back(padding_data); + } + } + SPDLOG_INFO("thread:{}, batch_idx:{}/{}, batch_items size:{} ", + thread_idx, idx, end_idx, batch_items.size()); + + std::vector<std::string> intersection = + MiniPsiRecv(thread_link_ctx, batch_items); + + for (auto& batch_idx : intersection) { + auto it = batch_map.find(batch_idx); + ret_idx_vec[thread_idx].push_back(it->second); + } + } + std::sort(ret_idx_vec[thread_idx].begin(), ret_idx_vec[thread_idx].end()); + }; + + std::vector<std::future<void>> futures; + std::vector<std::shared_ptr<yacl::link::Context>> thread_link_ctxs(nthreads); + + for (size_t thread_idx = 0; thread_idx < nthreads; ++thread_idx) { + thread_link_ctxs[thread_idx] = link_ctx->Spawn(); + futures.push_back( + std::async(thread, thread_link_ctxs[thread_idx], thread_idx)); + } + // wait thread + for (auto& f : futures) { + f.get(); + } + + for (size_t thread_idx = 0; thread_idx < nthreads; ++thread_idx) { + ret_idx.insert(ret_idx.begin(), ret_idx_vec[thread_idx].begin(), + ret_idx_vec[thread_idx].end()); + } + std::sort(ret_idx.begin(), ret_idx.end()); + + ret.reserve(ret_idx.size()); + for (auto idx : ret_idx) { + ret.push_back(items[idx]); + } + + return ret; +} + +} // namespace psi::psi \ No newline at end of file diff --git a/psi/psi/core/mini_psi.h b/psi/psi/core/mini_psi.h new file mode 100644 index 00000000..195aa391 --- /dev/null +++ b/psi/psi/core/mini_psi.h @@ -0,0 +1,47 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <functional> +#include <memory> +#include <string> +#include <vector> + +#include "absl/types/span.h" +#include "yacl/link/link.h" + +namespace psi::psi { + +// +// Compact and Malicious Private Set Intersection for Small Sets +// https://eprint.iacr.org/2021/1159.pdf +// opensource code +// https://github.com/osu-crypto/Mini-PSI +// +void MiniPsiSend(const std::shared_ptr<yacl::link::Context>& link_ctx, + const std::vector<std::string>& items); + +std::vector<std::string> MiniPsiRecv( + const std::shared_ptr<yacl::link::Context>& link_ctx, + const std::vector<std::string>& items); + +// use cuckoo hash to batch process +void MiniPsiSendBatch(const std::shared_ptr<yacl::link::Context>& link_ctx, + const std::vector<std::string>& items); + +std::vector<std::string> MiniPsiRecvBatch( + const std::shared_ptr<yacl::link::Context>& link_ctx, + const std::vector<std::string>& items); +} // namespace psi::psi \ No newline at end of file diff --git a/psi/psi/core/mini_psi_demo.cc b/psi/psi/core/mini_psi_demo.cc new file mode 100644 index 00000000..0bdb05f8 --- /dev/null +++ b/psi/psi/core/mini_psi_demo.cc @@ -0,0 +1,190 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <exception> +#include <fstream> +#include <iostream> +#include <iterator> +#include <sstream> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" +#include "gflags/gflags.h" +#include "spdlog/spdlog.h" + +#include "psi/psi/core/ecdh_psi.h" +#include "psi/psi/core/mini_psi.h" + +DEFINE_int32(role, 1, "sender:0, receiver: 1"); +DEFINE_int32(rank, 0, "self rank 0/1"); +DEFINE_string(in, "in.csv", "input file"); +DEFINE_string(out, "out.csv", "psi out file"); +DEFINE_string(id, "id", "id of the csv"); +DEFINE_string(local, "127.0.0.1:1234", "local address and port"); +DEFINE_string(remote, "127.0.0.1:1235", "remote address and port"); +DEFINE_string(protocol, "semi-honest", "semi-honest/malicious"); + +namespace { +constexpr uint32_t kLinkRecvTimeout = 30 * 60 * 1000; + +class CSVRow { + public: + std::string_view operator[](std::size_t index) const { + return std::string_view(&m_line_[m_data_[index] + 1], + m_data_[index + 1] - (m_data_[index] + 1)); + } + std::size_t size() const { return m_data_.size() - 1; } + void readNextRow(std::istream& str) { + std::getline(str, m_line_); + + m_data_.clear(); + m_data_.emplace_back(-1); + std::string::size_type pos = 0; + while ((pos = m_line_.find(',', pos)) != std::string::npos) { + m_data_.emplace_back(pos); + ++pos; + } + // This checks for a trailing comma with no data after it. + pos = m_line_.size(); + m_data_.emplace_back(pos); + } + + private: + std::string m_line_; + std::vector<int> m_data_; +}; + +std::istream& operator>>(std::istream& str, CSVRow& data) { + data.readNextRow(str); + return str; +} + +std::vector<std::string> ReadCsvData(const std::string& file_name) { + std::vector<std::string> items; + std::ifstream file(file_name); + + CSVRow row; + // read header + file >> row; + while (file >> row) { + items.emplace_back(row[0]); + } + return items; +} + +void WriteCsvData(const std::string& file_name, + const std::vector<std::string>& items) { + std::ofstream out_file; + out_file.open(file_name, std::ios::out); + out_file << "id" << '\r' << std::endl; + for (const auto& item : items) { + out_file << item << '\r' << std::endl; + } + out_file.close(); +} + +std::shared_ptr<yacl::link::Context> CreateContext( + int self_rank, yacl::link::ContextDesc& lctx_desc) { + std::shared_ptr<yacl::link::Context> link_ctx; + + yacl::link::FactoryBrpc factory; + link_ctx = factory.CreateContext(lctx_desc, self_rank); + link_ctx->ConnectToMesh(); + + return link_ctx; +} + +std::shared_ptr<yacl::link::Context> CreateLinks(const std::string& local_addr, + const std::string& remote_addr, + int self_rank) { + yacl::link::ContextDesc lctx_desc; + + // int self_rank = 0; + + if (self_rank == 0) { + std::string id = fmt::format("party{}", 0); + lctx_desc.parties.push_back({id, local_addr}); + id = fmt::format("party{}", 1); + lctx_desc.parties.push_back({id, remote_addr}); + } else { + std::string id = fmt::format("party{}", 0); + lctx_desc.parties.push_back({id, remote_addr}); + id = fmt::format("party{}", 1); + lctx_desc.parties.push_back({id, local_addr}); + } + + return CreateContext(self_rank, lctx_desc); +} + +} // namespace + +// +// script generate_psi.py +// used to generate test data 18 digits id 50% intersection rate +// +// psi demo +// -- sender +// ./bazel-bin/psi/psi/core/mini_psi_demo --in ./100m/psi_1.csv --local +// "127.0.0.1:1234" --remote "127.0.0.1:2222" --rank 0 --role 0 --protocol +// semi-honest +// +// -- receiver +// ./bazel-bin/psi/psi/core/mini_psi_demo --in ./100m/psi_1.csv --local +// "127.0.0.1:1234" --remote "127.0.0.1:2222" --rank 1 --role 1 --protocol +// semi-honest +// +int main(int argc, char** argv) { + gflags::AllowCommandLineReparsing(); + gflags::ParseCommandLineFlags(&argc, &argv, true); + + std::cout << FLAGS_role << "," << FLAGS_rank << std::endl; + std::cout << FLAGS_in << "," << FLAGS_out << std::endl; + std::cout << FLAGS_local << "," << FLAGS_remote << std::endl; + std::cout << FLAGS_protocol << "," << FLAGS_id << std::endl; + + std::vector<std::string> items = ReadCsvData(FLAGS_in); + std::cout << items.size() << std::endl; + + try { + std::shared_ptr<yacl::link::Context> link_ctx = + CreateLinks(FLAGS_in, FLAGS_remote, FLAGS_rank); + link_ctx->SetRecvTimeout(kLinkRecvTimeout); + + std::string file_name = FLAGS_protocol; + file_name.append("_").append(FLAGS_out); + + std::vector<std::string> intersection; + if (FLAGS_protocol == "semi-honest") { + intersection = psi::psi::RunEcdhPsi(link_ctx, items, 1); + if (FLAGS_rank == 1) { + SPDLOG_INFO("intersection size:{}", intersection.size()); + + WriteCsvData(file_name, intersection); + } + } else if (FLAGS_protocol == "malicious") { + if (FLAGS_role == 0) { + psi::psi::MiniPsiSendBatch(link_ctx, items); + } else if (FLAGS_role == 1) { + intersection = psi::psi::MiniPsiRecvBatch(link_ctx, items); + SPDLOG_INFO("intersection size:{}", intersection.size()); + WriteCsvData(file_name, intersection); + } + } + } catch (std::exception& e) { + SPDLOG_INFO("exception {}", e.what()); + } + + return 0; +} diff --git a/psi/psi/core/mini_psi_test.cc b/psi/psi/core/mini_psi_test.cc new file mode 100644 index 00000000..acf2cbac --- /dev/null +++ b/psi/psi/core/mini_psi_test.cc @@ -0,0 +1,88 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/mini_psi.h" + +#include <future> +#include <iostream> +#include <random> +#include <vector> + +#include "absl/strings/str_split.h" +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" +#include "yacl/link/test_util.h" + +#include "psi/psi/utils/test_utils.h" + +struct TestParams { + std::vector<std::string> items_a; + std::vector<std::string> items_b; + bool batch = false; +}; + +namespace psi::psi { +class MiniPsiTest : public testing::TestWithParam<TestParams> {}; + +TEST_P(MiniPsiTest, Works) { + auto params = GetParam(); + + auto link_ab = yacl::link::test::SetupWorld("mini", 2); + + auto intersection_std_ab = + test::GetIntersection(params.items_a, params.items_b); + + std::future<void> f_send; + std::future<std::vector<std::string>> f_recv; + if (!params.batch) { + f_send = + std::async([&] { return MiniPsiSend(link_ab[0], params.items_a); }); + + f_recv = + std::async([&] { return MiniPsiRecv(link_ab[1], params.items_b); }); + } else { + f_send = std::async( + [&] { return MiniPsiSendBatch(link_ab[0], params.items_a); }); + + f_recv = std::async( + [&] { return MiniPsiRecvBatch(link_ab[1], params.items_b); }); + } + f_send.get(); + auto intersection = f_recv.get(); + + SPDLOG_INFO("{}:{}, intersection.size():{}", __func__, __LINE__, + intersection.size()); + + EXPECT_EQ(intersection, intersection_std_ab); +} + +INSTANTIATE_TEST_SUITE_P( + Works_Instances, MiniPsiTest, + testing::Values(TestParams{{"a", "b"}, {"b", "c"}}, // + TestParams{{"a", "b"}, {"c", "d"}}, // + // + TestParams{{}, {"a"}}, // + // + TestParams{{"a"}, {}}, // + // less than one batch + TestParams{test::CreateRangeItems(0, 1800), + test::CreateRangeItems(1, 1800), false}, + // exactly one batch + TestParams{test::CreateRangeItems(0, 2000), + test::CreateRangeItems(1, 2000), true}, // + // + TestParams{{}, {}} // + )); +} // namespace psi::psi diff --git a/psi/psi/core/polynomial/BUILD.bazel b/psi/psi/core/polynomial/BUILD.bazel new file mode 100644 index 00000000..17c72c5b --- /dev/null +++ b/psi/psi/core/polynomial/BUILD.bazel @@ -0,0 +1,41 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//bazel:psi.bzl", "psi_cc_library", "psi_cc_test") + +package(default_visibility = ["//visibility:public"]) + +psi_cc_library( + name = "polynomial", + srcs = [ + "polynomial.cc", + ], + hdrs = [ + "polynomial.h", + ], + deps = [ + "@com_github_openssl_openssl//:openssl", + "@com_google_absl//absl/strings", + "@yacl//yacl/base:exception", + ], +) + +psi_cc_test( + name = "polynomial_test", + srcs = ["polynomial_test.cc"], + deps = [ + ":polynomial", + "@yacl//yacl/crypto/tools:prg", + ], +) diff --git a/psi/psi/core/polynomial/polynomial.cc b/psi/psi/core/polynomial/polynomial.cc new file mode 100644 index 00000000..a9d407bb --- /dev/null +++ b/psi/psi/core/polynomial/polynomial.cc @@ -0,0 +1,240 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/polynomial/polynomial.h" + +#include <algorithm> +#include <memory> + +#include "openssl/bn.h" +#include "yacl/base/exception.h" + +namespace psi::psi { + +namespace { +class BNDeleter { + public: + void operator()(BIGNUM *bn) { BN_free(bn); } +}; +using BigNumPtr = std::unique_ptr<BIGNUM, BNDeleter>; + +BigNumPtr GetBigNumPtr(int v) { + BIGNUM *bn = BN_new(); + BN_set_word(bn, v); + return BigNumPtr(bn); +} + +BigNumPtr GetBigNumPtr(std::string_view v) { + BIGNUM *bn = BN_bin2bn(reinterpret_cast<const uint8_t *>(v.data()), + v.length(), nullptr); + YACL_ENFORCE(bn != nullptr); + return BigNumPtr(bn); +} + +std::vector<BigNumPtr> GetBigNumPtrVector(size_t data_size, int value = 0) { + std::vector<BigNumPtr> res(data_size); + + for (size_t idx = 0; idx < data_size; idx++) { + BIGNUM *bn = BN_new(); + BN_set_word(bn, value); + res[idx] = BigNumPtr(bn); + } + + return res; +} + +std::vector<BigNumPtr> GetBigNumPtrVector( + const std::vector<absl::string_view> &data_vec) { + std::vector<BigNumPtr> res(data_vec.size()); + + for (size_t idx = 0; idx < data_vec.size(); idx++) { + BIGNUM *bn = + BN_bin2bn(reinterpret_cast<const uint8_t *>(data_vec[idx].data()), + data_vec[idx].length(), nullptr); + YACL_ENFORCE(bn != nullptr); + res[idx] = BigNumPtr(bn); + } + + return res; +} +} // namespace + +std::string EvalPolynomial(const std::vector<absl::string_view> &coeff, + absl::string_view poly_x, std::string_view p_str) { + BigNumPtr acc = GetBigNumPtr(0); + BigNumPtr bn_x = GetBigNumPtr(poly_x); + BigNumPtr bn_p = GetBigNumPtr(p_str); + + BN_CTX *bn_ctx = BN_CTX_new(); + for (int64_t i = coeff.size() - 1; i >= 0; i--) { + // acc = acc * X; // mul(acc, acc, a); + // acc = acc + coeff[i]; // add(acc, acc, f.rep[i]); + BigNumPtr bn_coeff = GetBigNumPtr(coeff[i]); + BN_mod_mul(acc.get(), acc.get(), bn_x.get(), bn_p.get(), bn_ctx); + BN_mod_add(acc.get(), acc.get(), bn_coeff.get(), bn_p.get(), bn_ctx); + } + + BN_CTX_free(bn_ctx); + + std::string res; + int len = + std::max(BN_num_bytes(acc.get()), static_cast<int>(poly_x.length())); + res.resize(len); + + BN_bn2binpad(acc.get(), reinterpret_cast<unsigned char *>(res.data()), len); + + return res; +} + +std::string EvalPolynomial(const std::vector<std::string> &coeff, + absl::string_view poly_x, std::string_view p_str) { + std::vector<absl::string_view> coeff2(coeff.size()); + + for (size_t idx = 0; idx < coeff.size(); idx++) { + coeff2[idx] = absl::string_view(coeff[idx]); + } + + return EvalPolynomial(coeff2, poly_x, p_str); +} + +std::vector<std::string> EvalPolynomial( + const std::vector<absl::string_view> &coeff, + const std::vector<absl::string_view> &poly_x, std::string_view p_str) { + std::vector<std::string> res(poly_x.size()); + + for (size_t idx = 0; idx < poly_x.size(); idx++) { + res[idx] = EvalPolynomial(coeff, poly_x[idx], p_str); + } + return res; +} + +std::vector<std::string> InterpolatePolynomial( + const std::vector<absl::string_view> &poly_x, + const std::vector<absl::string_view> &poly_y, std::string_view p_str) { + int64_t m = poly_x.size(); + + YACL_ENFORCE(poly_y.size() == poly_x.size()); + + BigNumPtr bn_p = GetBigNumPtr(p_str); + + // std::vector<MersennePrime> prod(X); + std::vector<BigNumPtr> prod = GetBigNumPtrVector(poly_x); + + BigNumPtr t1 = GetBigNumPtr(0); + BigNumPtr t2 = GetBigNumPtr(0); + + int64_t k; + int64_t i; + + std::vector<BigNumPtr> bn_x = GetBigNumPtrVector(poly_x); + std::vector<BigNumPtr> bn_y = GetBigNumPtrVector(poly_y); + std::vector<BigNumPtr> res_bn = GetBigNumPtrVector(m); + + BN_CTX *bn_ctx = BN_CTX_new(); + + for (k = 0; k < m; k++) { + // const MersennePrime &aa = X[k]; + const BigNumPtr aa = GetBigNumPtr(poly_x[k]); + + // t1 = (uint64_t)1; + BN_one(t1.get()); + for (i = k - 1; i >= 0; i--) { + // t1 = t1 * aa; // mul(t1, t1, aa); + // t1 = t1 + prod[i]; // add(t1, t1, prod[i]); + BN_mod_mul(t1.get(), t1.get(), aa.get(), bn_p.get(), bn_ctx); + + BN_mod_add(t1.get(), t1.get(), prod[i].get(), bn_p.get(), bn_ctx); + } + + // t2 = (uint64_t)0; // clear(t2); + BN_zero(t2.get()); + for (i = k - 1; i >= 0; i--) { + // t2 = t2 * aa; // mul(t2, t2, aa); + // t2 = t2 + res_bn[i]; // add(t2, t2, res[i]); + BN_mod_mul(t2.get(), t2.get(), aa.get(), bn_p.get(), bn_ctx); + + BN_mod_add(t2.get(), t2.get(), res_bn[i].get(), bn_p.get(), bn_ctx); + } + + // t1 = one / t1; // inv(t1, t1); + // t2 = Y[k] - t2; // sub(t2, b[k], t2); + // t1 = t1 * t2; // mul(t1, t1, t2); + + BN_mod_inverse(t1.get(), t1.get(), bn_p.get(), bn_ctx); + + BN_mod_sub(t2.get(), bn_y[k].get(), t2.get(), bn_p.get(), bn_ctx); + + BN_mod_mul(t1.get(), t1.get(), t2.get(), bn_p.get(), bn_ctx); + + for (i = 0; i < k; i++) { + // t2 = prod[i] * t1; // mul(t2, prod[i], t1); + // res_bn[i] = res_bn[i] + t2; // add(res[i], res[i], t2); + BN_mod_mul(t2.get(), prod[i].get(), t1.get(), bn_p.get(), bn_ctx); + + BN_mod_add(res_bn[i].get(), res_bn[i].get(), t2.get(), bn_p.get(), + bn_ctx); + } + + // res_bn[k] = t1; + BN_copy(res_bn[k].get(), t1.get()); + + if (k < m - 1) { + if (k == 0) { + // prod[0] = p - prod[0]; + BN_mod_sub(prod[0].get(), bn_p.get(), prod[0].get(), bn_p.get(), + bn_ctx); + + } else { + // t1 = p - X[k]; + BN_mod_sub(t1.get(), bn_p.get(), bn_x[k].get(), bn_p.get(), bn_ctx); + + // prod[k] = t1 + prod[k - 1]; // add(prod[k], t1, prod[k-1]); + BN_mod_add(prod[k].get(), t1.get(), prod[k - 1].get(), bn_p.get(), + bn_ctx); + + for (i = k - 1; i >= 1; i--) { + // t2 = prod[i] * t1; // mul(t2, prod[i], t1); + // prod[i] = t2 + prod[i - 1]; // add(prod[i], t2, prod[i-1]); + BN_mod_mul(t2.get(), prod[i].get(), t1.get(), bn_p.get(), bn_ctx); + + BN_mod_add(prod[i].get(), t2.get(), prod[i - 1].get(), bn_p.get(), + bn_ctx); + } + // prod[0] = prod[0] * t1; // mul(prod[0], prod[0], t1); + BN_mod_mul(prod[0].get(), prod[0].get(), t1.get(), bn_p.get(), bn_ctx); + } + } + } + + BN_CTX_free(bn_ctx); + // while (m > 0 && !(res_bn[m - 1] != zero)) m--; + while ((m > 0) && (BN_is_zero(res_bn[m - 1].get()) != 0)) { + m--; + } + + res_bn.resize(m); + + std::vector<std::string> res(m); + for (int64_t idx = 0; idx < m; idx++) { + int len = std::max(BN_num_bytes(res_bn[idx].get()), + static_cast<int>(poly_y[0].length())); + res[idx].resize(len); + BN_bn2binpad(res_bn[idx].get(), + reinterpret_cast<unsigned char *>(res[idx].data()), len); + } + + return res; +} + +} // namespace psi::psi diff --git a/psi/psi/core/polynomial/polynomial.h b/psi/psi/core/polynomial/polynomial.h new file mode 100644 index 00000000..9428944d --- /dev/null +++ b/psi/psi/core/polynomial/polynomial.h @@ -0,0 +1,44 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <cstdint> +#include <string> +#include <vector> + +#include "absl/strings/string_view.h" + +namespace psi::psi { + +// for big num +std::string EvalPolynomial(const std::vector<absl::string_view> &coeff, + absl::string_view X, std::string_view p); + +std::string EvalPolynomial(const std::vector<std::string> &coeff, + absl::string_view X, std::string_view p); + +std::vector<std::string> EvalPolynomial( + const std::vector<absl::string_view> &coeff, + const std::vector<absl::string_view> &poly_x, std::string_view p_str); + +std::vector<std::string> EvalPolynomial( + const std::vector<std::string> &coeff, + const std::vector<absl::string_view> &poly_x, std::string_view p_str); + +std::vector<std::string> InterpolatePolynomial( + const std::vector<absl::string_view> &poly_x, + const std::vector<absl::string_view> &poly_y, std::string_view p_str); + +} // namespace psi::psi diff --git a/psi/psi/core/polynomial/polynomial_test.cc b/psi/psi/core/polynomial/polynomial_test.cc new file mode 100644 index 00000000..cfecea4f --- /dev/null +++ b/psi/psi/core/polynomial/polynomial_test.cc @@ -0,0 +1,86 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/polynomial/polynomial.h" + +#include <future> +#include <iostream> +#include <random> + +#include "absl/strings/escaping.h" +#include "gtest/gtest.h" +#include "yacl/crypto/tools/prg.h" + +namespace { +struct TestParams { + uint64_t polynomial_order; +}; + +// first prime over 2^256, used as module for polynoimal interpolate +std::string kPrimeOver256bHexStr = + "010000000000000000000000000000000000000000000000000000000000000129"; +constexpr uint32_t kBnByteSize = 32; + +} // namespace + +namespace psi::psi { +// test 256b big num polynomial interpolate and eval +class PolynomialBnTest : public testing::TestWithParam<TestParams> {}; + +TEST_P(PolynomialBnTest, Works) { + auto params = GetParam(); + uint64_t polynomial_order = params.polynomial_order; + + std::vector<std::string> poly_x(polynomial_order); + std::vector<std::string> poly_y(polynomial_order); + std::vector<absl::string_view> poly_x_sv(polynomial_order); + std::vector<absl::string_view> poly_y_sv(polynomial_order); + std::vector<std::string> coeff; + + std::random_device rd; + yacl::crypto::Prg<uint64_t> prg1(rd()); + yacl::crypto::Prg<uint64_t> prg2(rd()); + + std::string prime_data = absl::HexStringToBytes(kPrimeOver256bHexStr); + + for (uint64_t i = 0; i < polynomial_order; ++i) { + poly_x[i].resize(kBnByteSize); + poly_y[i].resize(kBnByteSize); + prg1.Fill(absl::MakeSpan(poly_x[i].data(), kBnByteSize)); + prg2.Fill(absl::MakeSpan(poly_y[i].data(), kBnByteSize)); + + poly_x_sv[i] = poly_x[i]; + poly_y_sv[i] = poly_y[i]; + } + + coeff = InterpolatePolynomial(poly_x_sv, poly_y_sv, prime_data); + + for (uint64_t i = 0; i < polynomial_order; ++i) { + std::string eval_y = EvalPolynomial(coeff, poly_x[i], prime_data); + EXPECT_EQ(eval_y, poly_y[i]); + } +} + +INSTANTIATE_TEST_SUITE_P(Works_Instances, PolynomialBnTest, + testing::Values(TestParams{0}, // + TestParams{1}, // + TestParams{10}, // + TestParams{32}, // + TestParams{128}, // + TestParams{256}, // + TestParams{1024}, // + TestParams{1025} // + )); + +} // namespace psi::psi diff --git a/psi/psi/core/vole_psi/BUILD.bazel b/psi/psi/core/vole_psi/BUILD.bazel new file mode 100644 index 00000000..176ef3e2 --- /dev/null +++ b/psi/psi/core/vole_psi/BUILD.bazel @@ -0,0 +1,96 @@ +# Copyright 2023 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//bazel:psi.bzl", "psi_cc_binary", "psi_cc_library", "psi_cc_test") + +package(default_visibility = ["//visibility:public"]) + +psi_cc_library( + name = "rr22_oprf", + srcs = ["rr22_oprf.cc"], + hdrs = ["rr22_oprf.h"], + deps = [ + "//psi/psi/core/vole_psi/okvs:baxos", + "//psi/psi/core/vole_psi/okvs:aes_crhash", + "@yacl//yacl/base:buffer", + "@yacl//yacl/crypto/tools:prg", + "@yacl//yacl/crypto/utils:rand", + "@yacl//yacl/crypto/primitives/vole/f2k:silent_vole", + "@yacl//yacl/link", + "@yacl//yacl/math/f2k:f2k", + "@yacl//yacl/utils:parallel", + ], +) + +psi_cc_test( + name = "rr22_oprf_test", + srcs = ["rr22_oprf_test.cc"], + deps = [ + ":rr22_oprf", + "//psi/psi/utils:test_utils", + ], +) + +psi_cc_library( + name = "rr22_psi", + srcs = ["rr22_psi.cc"], + hdrs = ["rr22_psi.h"], + deps = [ + ":rr22_oprf", + ":rr22_utils", + "//psi/psi/utils:utils", + ], +) + +psi_cc_test( + name = "rr22_psi_test", + srcs = ["rr22_psi_test.cc"], + deps = [ + ":rr22_psi", + "@yacl//yacl/crypto/utils:rand", + ], +) + +psi_cc_binary( + name = "rr22_psi_bench", + srcs = ["rr22_psi_bench.cc"], + deps = [ + ":rr22_psi", + "//psi/psi/utils:test_utils", + "@yacl//yacl/crypto/utils:rand", + "@com_github_google_benchmark//:benchmark_main", + ], +) + +psi_cc_library( + name = "rr22_utils", + srcs = ["rr22_utils.cc"], + hdrs = ["rr22_utils.h"], + deps = [ + "//psi/psi/core/vole_psi/okvs:galois128", + "//psi/psi/core/vole_psi/okvs:simple_index", + "@yacl//yacl/base:int128", + "@yacl//yacl/base:buffer", + "@yacl//yacl/link", + "@com_github_ridiculousfish_libdivide//:libdivide", + "@com_github_sparsehash_sparsehash//:sparsehash", + ], +) + +psi_cc_library( + name = "sparsehash_config", + hdrs = ["sparseconfig.h"], + include_prefix = "sparsehash/internal", + visibility = ["//visibility:public"], +) diff --git a/psi/psi/core/vole_psi/okvs/BUILD.bazel b/psi/psi/core/vole_psi/okvs/BUILD.bazel new file mode 100644 index 00000000..c637946f --- /dev/null +++ b/psi/psi/core/vole_psi/okvs/BUILD.bazel @@ -0,0 +1,177 @@ +# Copyright 2023 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//bazel:psi.bzl", "psi_cc_binary", "psi_cc_library", "psi_cc_test") + +package(default_visibility = ["//visibility:public"]) + +psi_cc_library( + name = "baxos", + srcs = ["baxos.cc"], + hdrs = ["baxos.h"], + deps = [ + ":paxos", + ":dense_mtx", + ":galois128", + ":paxos_hash", + ":paxos_utils", + ":simple_index", + ], +) + +psi_cc_test( + name = "baxos_test", + srcs = ["baxos_test.cc"], + deps = [ + ":baxos", + "@com_google_absl//absl/strings", + "@yacl//yacl/crypto/tools:prg", + "@yacl//yacl/crypto/utils:rand", + ], +) + +psi_cc_library( + name = "paxos", + srcs = ["paxos.cc"], + hdrs = [ + "paxos.h", + ], + deps = [ + ":dense_mtx", + ":galois128", + ":paxos_hash", + ":paxos_utils", + ], +) + +psi_cc_test( + name = "paxos_test", + srcs = ["paxos_test.cc"], + deps = [ + ":paxos", + "@com_google_absl//absl/strings", + "@yacl//yacl/crypto/tools:prg", + "@yacl//yacl/crypto/utils:rand", + ], +) + +psi_cc_library( + name = "paxos_hash", + srcs = ["paxos_hash.cc"], + hdrs = ["paxos_hash.h"], + copts = [ + "-march=native", + ], + deps = [ + ":aes_crhash", + ":galois128", + "@yacl//yacl/math:gadget", + "@yacl//yacl/utils:platform_utils", + "@com_github_ridiculousfish_libdivide//:libdivide", + "@com_google_absl//absl/types:span", + ], +) + +psi_cc_test( + name = "paxos_hash_test", + srcs = ["paxos_hash_test.cc"], + deps = [ + ":paxos_hash", + "@yacl//yacl/crypto/utils:rand", + ], +) + +psi_cc_library( + name = "paxos_utils", + srcs = ["paxos_utils.cc"], + hdrs = ["paxos_utils.h"], + deps = [ + ":galois128", + "@yacl//yacl/crypto/tools:prg", + "@com_google_absl//absl/types:span", + ], +) + +psi_cc_library( + name = "aes_crhash", + srcs = ["aes_crhash.cc"], + hdrs = ["aes_crhash.h"], + deps = [ + "@yacl//yacl/crypto/base:symmetric_crypto", + "@yacl//yacl/utils:parallel", + ], +) + +psi_cc_test( + name = "aes_crhash_test", + srcs = ["aes_crhash_test.cc"], + deps = [ + ":aes_crhash", + ":galois128", + "@yacl//yacl/crypto/tools:prg", + "@com_google_absl//absl/strings", + ], +) + + +psi_cc_library( + name = "galois128", + srcs = ["galois128.cc"], + hdrs = [ + "galois128.h", + ], + copts = [ + "-march=native", + ], + deps = [ + "@yacl//yacl/base:block", + "@yacl//yacl/base:int128", + "@yacl//yacl/link", + "@yacl//yacl/utils:platform_utils", + "@com_google_absl//absl/strings", + ], +) + +psi_cc_test( + name = "galois128_test", + srcs = ["galois128_test.cc"], + deps = [ + ":galois128", + "@com_google_absl//absl/strings", + "@yacl//yacl/crypto/tools:prg", + "@yacl//yacl/crypto/utils:rand", + ], +) + +psi_cc_library( + name = "dense_mtx", + srcs = ["dense_mtx.cc"], + hdrs = ["dense_mtx.h"], + deps = [ + ":galois128", + "@yacl//yacl/crypto/tools:prg", + "@com_google_absl//absl/types:span", + ], +) + +psi_cc_library( + name = "simple_index", + srcs = ["simple_index.cc"], + hdrs = ["simple_index.h"], + deps = [ + "@yacl//yacl/base:exception", + "@boost//:math", + "@boost//:multiprecision", + ], +) diff --git a/psi/psi/core/vole_psi/okvs/aes_crhash.cc b/psi/psi/core/vole_psi/okvs/aes_crhash.cc new file mode 100644 index 00000000..0258762c --- /dev/null +++ b/psi/psi/core/vole_psi/okvs/aes_crhash.cc @@ -0,0 +1,96 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/vole_psi/okvs/aes_crhash.h" + +#include <vector> + +#include "spdlog/spdlog.h" +#include "yacl/utils/parallel.h" + +namespace psi::psi::okvs { + +namespace { + +constexpr size_t kBatchSize = 8; + +} // namespace + +void AesCrHash::Hash(absl::Span<const uint8_t> plaintext, + absl::Span<uint8_t> ciphertext) const { + constexpr size_t block_size = yacl::crypto::SymmetricCrypto::BlockSize(); + constexpr size_t batch_byte_size = kBatchSize * block_size; + + std::vector<uint8_t> enc_outputs(batch_byte_size); + + size_t batch_max_index = + (plaintext.size() / batch_byte_size) * batch_byte_size; + + size_t i = 0; + for (i = 0; i < batch_max_index; i += batch_byte_size) { + Encrypt(plaintext.subspan(i, batch_byte_size), absl::MakeSpan(enc_outputs)); + + for (size_t j = 0; j < batch_byte_size; j++) { + ciphertext[i + j] = enc_outputs[j] ^ plaintext[i + j]; + } + } + + if (i < plaintext.size()) { + Encrypt(plaintext.subspan(i, plaintext.size() - i), + absl::MakeSpan(&enc_outputs[0], plaintext.size() - i)); + for (size_t j = 0; i < plaintext.size(); ++i, ++j) { + ciphertext[i] = enc_outputs[j] ^ plaintext[i]; + } + } +} + +void AesCrHash::Hash(absl::Span<const uint128_t> plaintext, + absl::Span<uint128_t> ciphertext) const { + size_t batch_max_index = plaintext.size() / kBatchSize * kBatchSize; + + std::vector<uint128_t> enc_outputs(kBatchSize); + + for (size_t i = 0; i < batch_max_index; i += kBatchSize) { + Encrypt(plaintext.subspan(i, kBatchSize), absl::MakeSpan(enc_outputs)); + + ciphertext[i + 0] = enc_outputs[0] ^ plaintext[i + 0]; + ciphertext[i + 1] = enc_outputs[1] ^ plaintext[i + 1]; + ciphertext[i + 2] = enc_outputs[2] ^ plaintext[i + 2]; + ciphertext[i + 3] = enc_outputs[3] ^ plaintext[i + 3]; + ciphertext[i + 4] = enc_outputs[4] ^ plaintext[i + 4]; + ciphertext[i + 5] = enc_outputs[5] ^ plaintext[i + 5]; + ciphertext[i + 6] = enc_outputs[6] ^ plaintext[i + 6]; + ciphertext[i + 7] = enc_outputs[7] ^ plaintext[i + 7]; + } + + if (batch_max_index < plaintext.size()) { + Encrypt( + plaintext.subspan(batch_max_index, plaintext.size() - batch_max_index), + absl::MakeSpan(&enc_outputs[0], plaintext.size() - batch_max_index)); + + size_t i = batch_max_index; + + for (size_t j = 0; i < plaintext.size(); ++i, ++j) { + ciphertext[i] = enc_outputs[j] ^ plaintext[i]; + } + } +} + +uint128_t AesCrHash::Hash(uint128_t input) const { + uint128_t output = Encrypt(input) ^ input; + + return output; +} + +} // namespace psi::psi::okvs diff --git a/psi/psi/core/vole_psi/okvs/aes_crhash.h b/psi/psi/core/vole_psi/okvs/aes_crhash.h new file mode 100644 index 00000000..615c671f --- /dev/null +++ b/psi/psi/core/vole_psi/okvs/aes_crhash.h @@ -0,0 +1,44 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "yacl/base/byte_container_view.h" +#include "yacl/base/int128.h" +#include "yacl/crypto/base/symmetric_crypto.h" + +// Correlation robust hash function. +// H(x) = AES(x) + x. +namespace psi::psi::okvs { + +class AesCrHash : public yacl::crypto::SymmetricCrypto { + public: + AesCrHash(uint128_t key, uint128_t iv = 0) + : yacl::crypto::SymmetricCrypto( + yacl::crypto::SymmetricCrypto::CryptoType::AES128_ECB, key, iv) {} + + AesCrHash(yacl::ByteContainerView key, yacl::ByteContainerView iv) + : yacl::crypto::SymmetricCrypto( + yacl::crypto::SymmetricCrypto::CryptoType::AES128_ECB, key, iv) {} + + void Hash(absl::Span<const uint8_t> plaintext, + absl::Span<uint8_t> ciphertext) const; + + void Hash(absl::Span<const uint128_t> plaintext, + absl::Span<uint128_t> ciphertext) const; + + uint128_t Hash(uint128_t input) const; +}; + +} // namespace psi::psi::okvs diff --git a/psi/psi/core/vole_psi/okvs/aes_crhash_test.cc b/psi/psi/core/vole_psi/okvs/aes_crhash_test.cc new file mode 100644 index 00000000..b7c03ac6 --- /dev/null +++ b/psi/psi/core/vole_psi/okvs/aes_crhash_test.cc @@ -0,0 +1,61 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/vole_psi/okvs/aes_crhash.h" + +#include <ostream> +#include <vector> + +#include "absl/strings/escaping.h" +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/crypto/tools/prg.h" + +#include "psi/psi/core/vole_psi/okvs/galois128.h" + +namespace psi::psi::okvs { + +class AesCrHashTest : public testing::TestWithParam<std::size_t> {}; + +TEST_P(AesCrHashTest, Works) { + AesCrHash aes_crhash(yacl::MakeUint128(0xabc, 0x89ef)); + + const size_t items_size = GetParam(); + + std::vector<uint128_t> inputs(items_size); + std::vector<uint128_t> outputs(items_size); + std::vector<uint128_t> outputs2(items_size); + std::vector<uint8_t> outputs3(items_size * + yacl::crypto::SymmetricCrypto::BlockSize()); + + yacl::crypto::Prg<uint128_t> prng(yacl::MakeUint128(0x1234, 0x5678)); + prng.Fill(absl::MakeSpan(inputs)); + + aes_crhash.Hash(absl::MakeSpan(inputs), absl::MakeSpan(outputs)); + + for (size_t i = 0; i < items_size; ++i) { + outputs2[i] = aes_crhash.Hash(inputs[i]); + } + + aes_crhash.Hash(absl::MakeSpan((uint8_t *)(&inputs[0]), outputs3.size()), + absl::MakeSpan(outputs3)); + + EXPECT_EQ(outputs, outputs2); + EXPECT_EQ(std::memcmp(&outputs[0], &outputs3[0], outputs3.size()), 0); +} + +INSTANTIATE_TEST_SUITE_P(Works_Instances, AesCrHashTest, + testing::Values(1, 2, 5, 10, 20)); + +} // namespace psi::psi::okvs diff --git a/psi/psi/core/vole_psi/okvs/baxos.cc b/psi/psi/core/vole_psi/okvs/baxos.cc new file mode 100644 index 00000000..ad6039c7 --- /dev/null +++ b/psi/psi/core/vole_psi/okvs/baxos.cc @@ -0,0 +1,681 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/vole_psi/okvs/baxos.h" + +#include <algorithm> +#include <array> +#include <future> +#include <unordered_set> +#include <vector> + +#include "spdlog/spdlog.h" + +#include "psi/psi/core/vole_psi/okvs/simple_index.h" + +namespace psi::psi::okvs { + +uint64_t Baxos::GetBinSize(uint64_t num_bins, uint64_t num_balls, + uint64_t stat_sec_param) { + return SimpleIndex::GetBinSize(num_bins, num_balls, stat_sec_param); +} + +template void Baxos::ImplParSolve<uint8_t>( + absl::Span<const uint128_t> inputs_, const PxVector& vals_, PxVector& p_, + const std::shared_ptr<yacl::crypto::Prg<uint8_t>>& prng, + uint64_t num_threads, PxVector::Helper& h); + +template void Baxos::ImplParSolve<uint16_t>( + absl::Span<const uint128_t> inputs_, const PxVector& vals_, PxVector& p_, + const std::shared_ptr<yacl::crypto::Prg<uint8_t>>& prng, + uint64_t num_threads, PxVector::Helper& h); + +template void Baxos::ImplParSolve<uint32_t>( + absl::Span<const uint128_t> inputs_, const PxVector& vals_, PxVector& p_, + const std::shared_ptr<yacl::crypto::Prg<uint8_t>>& prng, + uint64_t num_threads, PxVector::Helper& h); + +template void Baxos::ImplParSolve<uint64_t>( + absl::Span<const uint128_t> inputs_, const PxVector& vals_, PxVector& p_, + const std::shared_ptr<yacl::crypto::Prg<uint8_t>>& prng, + uint64_t num_threads, PxVector::Helper& h); + +template void Baxos::ImplParDecode<uint8_t>(absl::Span<const uint128_t> inputs, + PxVector& values, + const PxVector& pp, + PxVector::Helper& h, + uint64_t num_threads); + +template void Baxos::ImplParDecode<uint16_t>(absl::Span<const uint128_t> inputs, + PxVector& values, + const PxVector& pp, + PxVector::Helper& h, + uint64_t num_threads); + +template void Baxos::ImplParDecode<uint32_t>(absl::Span<const uint128_t> inputs, + PxVector& values, + const PxVector& pp, + PxVector::Helper& h, + uint64_t num_threads); + +template void Baxos::ImplParDecode<uint64_t>(absl::Span<const uint128_t> inputs, + PxVector& values, + const PxVector& pp, + PxVector::Helper& h, + uint64_t num_threads); + +template void Baxos::ImplDecodeBatch<uint8_t>( + absl::Span<const uint128_t> inputs, PxVector& values, const PxVector& pp, + PxVector::Helper& h); +template void Baxos::ImplDecodeBatch<uint16_t>( + absl::Span<const uint128_t> inputs, PxVector& values, const PxVector& pp, + PxVector::Helper& h); +template void Baxos::ImplDecodeBatch<uint32_t>( + absl::Span<const uint128_t> inputs, PxVector& values, const PxVector& pp, + PxVector::Helper& h); +template void Baxos::ImplDecodeBatch<uint64_t>( + absl::Span<const uint128_t> inputs, PxVector& values, const PxVector& pp, + PxVector::Helper& h); + +template void Baxos::ImplDecodeBin(uint64_t bin_idx, + absl::Span<uint128_t> hashes, + PxVector& values, PxVector& values_buff, + absl::Span<uint64_t> in_idxs, + const PxVector& PP, PxVector::Helper& h, + Paxos<uint8_t>& paxos); + +template void Baxos::ImplDecodeBin(uint64_t bin_idx, + absl::Span<uint128_t> hashes, + PxVector& values, PxVector& values_buff, + absl::Span<uint64_t> in_idxs, + const PxVector& PP, PxVector::Helper& h, + Paxos<uint16_t>& paxos); + +template void Baxos::ImplDecodeBin(uint64_t bin_idx, + absl::Span<uint128_t> hashes, + PxVector& values, PxVector& values_buff, + absl::Span<uint64_t> in_idxs, + const PxVector& PP, PxVector::Helper& h, + Paxos<uint32_t>& paxos); + +template void Baxos::ImplDecodeBin(uint64_t bin_idx, + absl::Span<uint128_t> hashes, + PxVector& values, PxVector& values_buff, + absl::Span<uint64_t> in_idxs, + const PxVector& PP, PxVector::Helper& h, + Paxos<uint64_t>& paxos); + +void Baxos::Solve(absl::Span<const uint128_t> inputs, + const absl::Span<uint128_t> values, + absl::Span<uint128_t> output, + const std::shared_ptr<yacl::crypto::Prg<uint8_t>>& prng, + uint64_t num_threads) { + PxVector V(values); + PxVector P(output); + auto h = P.DefaultHelper(); + Solve(inputs, V, P, prng, num_threads, h); +} + +void Baxos::Solve(absl::Span<const uint128_t> inputs, const PxVector& V, + PxVector& P, + const std::shared_ptr<yacl::crypto::Prg<uint8_t>>& prng, + uint64_t num_threads, PxVector::Helper& h) { + // select the smallest index type which will work. + auto bit_length = + RoundUpTo(yacl::math::Log2Ceil((paxos_param_.sparse_size + 1)), 8); + + SPDLOG_DEBUG("bit_length:{}", bit_length); + + if (bit_length <= 8) { + ImplParSolve<uint8_t>(inputs, V, P, prng, num_threads, h); + } else if (bit_length <= 16) { + ImplParSolve<uint16_t>(inputs, V, P, prng, num_threads, h); + } else if (bit_length <= 32) { + ImplParSolve<uint32_t>(inputs, V, P, prng, num_threads, h); + } else { + ImplParSolve<uint64_t>(inputs, V, P, prng, num_threads, h); + } +} + +template <typename IdxType> +void Baxos::ImplParSolve( + absl::Span<const uint128_t> inputs_param, const PxVector& vals_, + PxVector& p_, const std::shared_ptr<yacl::crypto::Prg<uint8_t>>& prng, + uint64_t num_threads, PxVector::Helper& h) { + YACL_ENFORCE(p_.size() == size(), "p size:{} baox size:{}", p_.size(), + size()); + YACL_ENFORCE(inputs_param.size() <= num_items_, "input size:{}, num_item:{}", + inputs_param.size()); + + SPDLOG_DEBUG( + "item size:{} paxos size:{} num_bins_:{}, items_per_bin_:{} idxType:{}", + inputs_param.size(), size(), num_bins_, items_per_bin_, sizeof(IdxType)); + + if (num_bins_ == 1) { + Paxos<IdxType> paxos; + paxos.Init(num_items_, paxos_param_, seed_); + + paxos.SetInput(inputs_param); + + paxos.Encode(vals_, p_, h, prng); + + return; + } + + num_threads = std::max<uint64_t>(1, num_threads); + + SPDLOG_DEBUG("num_threads: {}", num_threads); + + static constexpr const uint64_t batch_size = 32; + + // total number of bins between all threads. + auto total_num_bins = num_bins_ * num_threads; + + auto items_per_thrd = (num_items_ + num_threads - 1) / num_threads; + + SPDLOG_DEBUG("num_bins_:{} total_num_bins: {}, items_per_thrd:{}", num_bins_, + total_num_bins, items_per_thrd); + + // maximum number of items any single thread will map to a bin. + auto per_thrd_max_bin_size = GetBinSize(num_bins_, items_per_thrd, ssp_); + SPDLOG_DEBUG("per_thrd_max_bin_size:{}", per_thrd_max_bin_size); + + // the combined max size of the i'th bin held by each thread. + uint64_t combined_max_bin_size = per_thrd_max_bin_size * num_threads; + SPDLOG_DEBUG("combined_max_bin_size: {}", combined_max_bin_size); + + // keeps track of the size of each bin for each thread. Additional spaces to + // prevent false sharing + std::vector<uint64_t> thrd_bin_sizes_data(num_threads * num_bins_); + MatrixView<uint64_t> thrd_bin_sizes_mtx(thrd_bin_sizes_data.data(), + num_threads, num_bins_); + + SPDLOG_DEBUG("thrd_bin_sizes_mtx row: {}, col: {}", thrd_bin_sizes_mtx.rows(), + thrd_bin_sizes_mtx.cols()); + + // keeps track of input index of each item in each bin,thread. + std::vector<uint64_t> input_mapping(total_num_bins * per_thrd_max_bin_size); + + // for the given thread, bin, return the list which map the bin + // value back to the input value. + auto GetInputMapping = [&](uint64_t thrd_idx, uint64_t bin_idx) { + auto bin_begin = combined_max_bin_size * bin_idx; + auto thrd_begin = per_thrd_max_bin_size * thrd_idx; + absl::Span<uint64_t> mapping(input_mapping.data() + bin_begin + thrd_begin, + per_thrd_max_bin_size); + YACL_ENFORCE( + (input_mapping.data() + total_num_bins * per_thrd_max_bin_size) >= + (mapping.data() + mapping.size())); + return mapping; + }; + + auto val_backing = h.NewVec(total_num_bins * per_thrd_max_bin_size); + + // get the values mapped to the given bin by the given thread. + auto GetValues = [&](uint64_t thrd_idx, uint64_t bin_idx) { + auto bin_begin = combined_max_bin_size * bin_idx; + auto thrd_begin = per_thrd_max_bin_size * thrd_idx; + + return val_backing.subspan(bin_begin + thrd_begin, per_thrd_max_bin_size); + }; + + std::vector<uint128_t> hash_backing(total_num_bins * per_thrd_max_bin_size); + + // get the hashes mapped to the given bin by the given thread. + auto GetHashes = [&](uint64_t thrd_idx, uint64_t bin_idx) { + auto bin_begin = combined_max_bin_size * bin_idx; + auto thrd_begin = per_thrd_max_bin_size * thrd_idx; + + return absl::Span<uint128_t>(hash_backing.data() + bin_begin + thrd_begin, + per_thrd_max_bin_size); + }; + + libdivide::libdivide_u64_t divider = libdivide::libdivide_u64_gen(num_bins_); + + AesCrHash aes_crhasher(seed_); + + std::atomic<uint64_t> num_done(0); + std::promise<void> hashing_done_prom; + auto hashing_done_fu = hashing_done_prom.get_future().share(); + + auto routine = [&](uint64_t thrd_idx) { + auto begin = (inputs_param.size() * thrd_idx) / num_threads; + auto end = (inputs_param.size() * (thrd_idx + 1)) / num_threads; + auto inputs_span = inputs_param.subspan(begin, end - begin); + + SPDLOG_DEBUG("inputs size:{}, thrd_idx:{}", inputs_span.size(), thrd_idx); + + // hash all the inputs in my range [begin, end) into their bin. + // Each thread will have its own set of bins. ie a total of numThreads * + // numBins bins in total. These bin will need to be merged. Each thread will + // also get its own reverse mapping. + { + SPDLOG_DEBUG("thrd_idx:{}", thrd_idx); + auto bin_sizes = thrd_bin_sizes_mtx[thrd_idx]; + + SPDLOG_DEBUG("bin_sizes:{}", bin_sizes.size()); + + auto in_iter = inputs_span.data(); + std::array<uint128_t, batch_size> hashes; + + auto main = inputs_span.size() / batch_size * batch_size; + std::array<uint64_t, batch_size> bin_idxs; + + uint64_t i = 0; + auto in_idx = begin; + for (; i < main; i += batch_size, in_iter += batch_size) { + aes_crhasher.Hash( + absl::MakeSpan((uint8_t*)(in_iter + 0), sizeof(uint128_t) * 8), + absl::MakeSpan((uint8_t*)(hashes.data() + 0), + sizeof(uint128_t) * 8)); + aes_crhasher.Hash( + absl::MakeSpan((uint8_t*)(in_iter + 8), sizeof(uint128_t) * 8), + absl::MakeSpan((uint8_t*)(hashes.data() + 8), + sizeof(uint128_t) * 8)); + aes_crhasher.Hash( + absl::MakeSpan((uint8_t*)(in_iter + 16), sizeof(uint128_t) * 8), + absl::MakeSpan((uint8_t*)(hashes.data() + 16), + sizeof(uint128_t) * 8)); + aes_crhasher.Hash( + absl::MakeSpan((uint8_t*)(in_iter + 24), sizeof(uint128_t) * 8), + absl::MakeSpan((uint8_t*)(hashes.data() + 24), + sizeof(uint128_t) * 8)); + + for (uint64_t k = 0; k < batch_size; ++k) { + bin_idxs[k] = BinIdxCompress(hashes[k]); + SPDLOG_DEBUG("k:{}, bin_idxs:{} ", k, bin_idxs[k]); + } + + DoMod32(bin_idxs.data(), &divider, num_bins_); + + for (uint64_t k = 0; k < batch_size; ++k, ++in_idx) { + auto bin_idx = bin_idxs[k]; + SPDLOG_DEBUG("k:{} bin_idx:{}", k, bin_idx); + auto bs = bin_sizes[bin_idx]++; + + SPDLOG_DEBUG("i:{}, main:{} batch_size:{}", i, main, batch_size); + SPDLOG_DEBUG("k:{} thrd_idx: {}, bin_idx: {},bs: {}, in_idx: {}", k, + thrd_idx, bin_idx, bs, in_idx); + + SPDLOG_DEBUG("total_num_bins:{} per_thrd_max_bin_size:{}", + total_num_bins, per_thrd_max_bin_size); + + GetInputMapping(thrd_idx, bin_idx)[bs] = in_idx; + h.Assign(GetValues(thrd_idx, bin_idx)[bs], vals_[in_idx]); + GetHashes(thrd_idx, bin_idx)[bs] = hashes[k]; + } + } + + for (uint64_t k = 0; i < inputs_span.size(); + ++i, ++in_iter, ++k, ++in_idx) { + hashes[k] = aes_crhasher.Hash(*in_iter); + + auto bin_idx = ModNumBins(hashes[k]); + auto bs = bin_sizes[bin_idx]++; + YACL_ENFORCE(bs < per_thrd_max_bin_size); + + GetInputMapping(thrd_idx, bin_idx)[bs] = in_idx; + h.Assign(GetValues(thrd_idx, bin_idx)[bs], vals_[in_idx]); + GetHashes(thrd_idx, bin_idx)[bs] = hashes[k]; + } + } + + auto paxos_size_per = paxos_param_.size(); + + std::vector<IdxType> rows_data(items_per_bin_ * weight_); + std::vector<IdxType> col_backing_data(items_per_bin_ * weight_); + + std::vector<IdxType> col_weights_data(paxos_param_.sparse_size); + + std::vector<absl::Span<IdxType>> cols_data(paxos_param_.sparse_size); + + // block until all threads have mapped all items. + if (++num_done == num_threads) { + hashing_done_prom.set_value(); + } else { + hashing_done_fu.get(); + } + + Paxos<IdxType> paxos; + + // this thread will iterator over its assigned bins. This thread + // will aggregate all the items mapped to the ith bin (which are currently + // stored in a per thread local). + for (uint64_t bin_idx = thrd_idx; bin_idx < num_bins_; + bin_idx += num_threads) { + // get the actual bin size. + uint64_t bin_size = 0; + for (uint64_t i = 0; i < num_threads; ++i) { + bin_size += thrd_bin_sizes_mtx(i, bin_idx); + } + + YACL_ENFORCE(bin_size <= items_per_bin_); + + paxos.Init(bin_size, paxos_param_, seed_); + + SPDLOG_DEBUG("bin_size:{} weight:{} sparse:{}", bin_size, weight_, + paxos_param_.sparse_size); + + MatrixView<IdxType> rows_mtx(rows_data.data(), bin_size, weight_); + absl::Span<IdxType> col_backing = + absl::MakeSpan(col_backing_data.data(), bin_size * weight_); + absl::Span<IdxType> col_weights = + absl::MakeSpan(col_weights_data.data(), paxos_param_.sparse_size); + absl::Span<absl::Span<IdxType>> cols = absl::MakeSpan(cols_data); + + auto bin_begin = combined_max_bin_size * bin_idx; + auto values = val_backing.subspan(bin_begin, bin_size); + auto hashes = + absl::Span<uint128_t>(hash_backing.data() + bin_begin, bin_size); + auto output = p_.subspan(paxos_size_per * bin_idx, paxos_size_per); + + // for each thread, copy the hashes,values that it mapped + // to this bin. + uint64_t bin_pos = thrd_bin_sizes_mtx(0, bin_idx); + YACL_ENFORCE(bin_pos <= per_thrd_max_bin_size); + YACL_ENFORCE(hashes.data() == GetHashes(0, bin_idx).data()); + + for (uint64_t i = 1; i < num_threads; ++i) { + auto size = thrd_bin_sizes_mtx(i, bin_idx); + YACL_ENFORCE(size <= per_thrd_max_bin_size); + + auto thrd_hashes = GetHashes(i, bin_idx); + auto thrd_vals = GetValues(i, bin_idx); + + std::memmove(hashes.data() + bin_pos, thrd_hashes.data(), + size * sizeof(uint128_t)); + + for (uint64_t j = 0; j < size; ++j) { + h.Assign(values[bin_pos + j], thrd_vals[j]); + } + + bin_pos += size; + } + + // compute the rows and count the column weight. + std::memset(col_weights.data(), 0, col_weights.size() * sizeof(IdxType)); + auto rIter = rows_mtx.data(); + if (weight_ == 3) { + auto main = bin_size / batch_size * batch_size; + + uint64_t i = 0; + for (; i < main; i += batch_size) { + paxos.hasher_.BuildRow32(absl::MakeSpan(&hashes[i], batch_size), + absl::MakeSpan(rIter, weight_ * batch_size)); + for (uint64_t j = 0; j < batch_size; ++j) { + ++col_weights[rIter[0]]; + ++col_weights[rIter[1]]; + ++col_weights[rIter[2]]; + rIter += weight_; + } + } + for (; i < bin_size; ++i) { + paxos.hasher_.BuildRow(hashes[i], absl::MakeSpan(rIter, weight_)); + + ++col_weights[rIter[0]]; + ++col_weights[rIter[1]]; + ++col_weights[rIter[2]]; + rIter += weight_; + } + } else { + for (uint64_t i = 0; i < bin_size; ++i) { + paxos.hasher_.BuildRow(hashes[i], absl::MakeSpan(rIter, weight_)); + for (uint64_t k = 0; k < weight_; ++k) { + ++col_weights[rIter[k]]; + } + rIter += weight_; + } + } + + paxos.SetInput(rows_mtx, hashes, cols, col_backing, col_weights); + + paxos.Encode(values, output, h, prng); + } + }; + + std::vector<std::thread> thrds(num_threads - 1); + + for (uint64_t i = 0; i < thrds.size(); ++i) { + thrds[i] = std::thread(routine, i); + } + + routine(thrds.size()); + + for (uint64_t i = 0; i < thrds.size(); ++i) { + thrds[i].join(); + } +} + +void Baxos::Decode(absl::Span<const uint128_t> inputs, + absl::Span<uint128_t> values, const absl::Span<uint128_t> p, + uint64_t num_threads) { + PxVector V(values); + PxVector P(p); + auto h = V.DefaultHelper(); + + Decode(inputs, V, P, h, num_threads); +} + +void Baxos::Decode(absl::Span<const uint128_t> inputs, PxVector& V, + const PxVector& P, PxVector::Helper& h, + uint64_t num_threads) { + auto bit_length = + RoundUpTo(yacl::math::Log2Ceil(paxos_param_.sparse_size + 1), 8); + + if (bit_length <= 8) { + ImplParDecode<uint8_t>(inputs, V, P, h, num_threads); + } else if (bit_length <= 16) { + ImplParDecode<uint16_t>(inputs, V, P, h, num_threads); + } else if (bit_length <= 32) { + ImplParDecode<uint32_t>(inputs, V, P, h, num_threads); + } else { + ImplParDecode<uint64_t>(inputs, V, P, h, num_threads); + } +} + +template <typename IdxType> +void Baxos::ImplDecodeBin([[maybe_unused]] uint64_t bin_idx, + absl::Span<uint128_t> hashes, PxVector& values, + PxVector& values_buff, absl::Span<uint64_t> in_idxs, + const PxVector& PP, PxVector::Helper& h, + Paxos<IdxType>& paxos) { + constexpr uint64_t batch_size = 32; + constexpr uint64_t max_weight_size = 20; + + auto batch_count = (hashes.size() / batch_size) * batch_size; + + YACL_ENFORCE(weight_ <= max_weight_size); + + yacl::Buffer _backing_buffer(sizeof(IdxType) * max_weight_size * batch_size); + absl::Span<IdxType> _backing = absl::MakeSpan( + (IdxType*)(_backing_buffer.data()), max_weight_size * batch_size); + + MatrixView<IdxType> row(_backing.data(), batch_size, weight_); + + YACL_ENFORCE(values_buff.size() >= batch_size); + + uint64_t i = 0; + + for (; i < batch_count; i += batch_size) { + paxos.hasher_.BuildRow32(absl::MakeSpan(&hashes[i], 32), + absl::MakeSpan(row.data(), batch_size * weight_)); + paxos.Decode32(absl::MakeSpan(row.data(), batch_size * weight_), + absl::MakeSpan(&hashes[i], batch_size), + absl::MakeSpan(values_buff[0], batch_size), PP, h); + + if (add_to_decode_) { + for (uint64_t k = 0; k < batch_size; ++k) { + h.Add(values[in_idxs[i + k]], values_buff[k]); + } + } else { + for (uint64_t k = 0; k < batch_size; ++k) { + h.Assign(values[in_idxs[i + k]], values_buff[k]); + } + } + } + + for (; i < hashes.size(); ++i) { + paxos.hasher_.BuildRow(hashes[i], absl::MakeSpan(row.data(), weight_)); + + auto v = values[in_idxs[i]]; + + if (add_to_decode_) { + paxos.Decode1(absl::MakeSpan(row.data(), weight_), hashes[i], + values_buff[0], PP, h); + h.Add(v, values_buff[0]); + } else { + paxos.Decode1(absl::MakeSpan(row.data(), weight_), hashes[i], v, PP, h); + } + } +} + +template <typename IdxType> +void Baxos::ImplDecodeBatch(absl::Span<const uint128_t> inputs, + PxVector& values, const PxVector& pp, + PxVector::Helper& h) { + uint64_t decode_size = std::min<uint64_t>(512, inputs.size()); + + yacl::Buffer batches_data_buffer(num_bins_ * decode_size * sizeof(uint128_t)); + absl::Span<uint128_t> batches_data = absl::MakeSpan( + (uint128_t*)(batches_data_buffer.data()), num_bins_ * decode_size); + MatrixView<uint128_t> batches_mtx(batches_data.data(), num_bins_, + decode_size); + + yacl::Buffer in_idxs_data_buffer(num_bins_ * decode_size * sizeof(uint64_t)); + absl::Span<uint64_t> in_idxs_data = absl::MakeSpan( + (uint64_t*)(in_idxs_data_buffer.data()), num_bins_ * decode_size); + MatrixView<uint64_t> in_idxs_mtx(in_idxs_data.data(), num_bins_, decode_size); + + std::vector<uint64_t> batch_sizes(num_bins_); + + AesCrHash aes_crhasher(reinterpret_cast<uint128_t>(seed_)); + + auto in_iter = inputs.data(); + Paxos<IdxType> paxos; + auto size_per = size() / num_bins_; + paxos.Init(1, paxos_param_, seed_); + auto buff = h.NewVec(32); + + static const uint32_t batch_size = 32; + auto main = inputs.size() / batch_size * batch_size; + std::array<uint128_t, batch_size> buffer; + std::array<uint64_t, batch_size> bin_idxs; + + uint64_t i = 0; + libdivide::libdivide_u64_t divider = libdivide::libdivide_u64_gen(num_bins_); + + // iterate over the input + for (; i < main; i += batch_size, in_iter += batch_size) { + aes_crhasher.Hash(absl::MakeSpan(in_iter, batch_size), + absl::MakeSpan(buffer)); + + for (uint64_t j = 0; j < batch_size; j += 8) { + bin_idxs[j + 0] = BinIdxCompress(buffer[j + 0]); + bin_idxs[j + 1] = BinIdxCompress(buffer[j + 1]); + bin_idxs[j + 2] = BinIdxCompress(buffer[j + 2]); + bin_idxs[j + 3] = BinIdxCompress(buffer[j + 3]); + bin_idxs[j + 4] = BinIdxCompress(buffer[j + 4]); + bin_idxs[j + 5] = BinIdxCompress(buffer[j + 5]); + bin_idxs[j + 6] = BinIdxCompress(buffer[j + 6]); + bin_idxs[j + 7] = BinIdxCompress(buffer[j + 7]); + } + + DoMod32(bin_idxs.data(), &divider, num_bins_); + + for (uint64_t k = 0; k < batch_size; ++k) { + auto bin_idx = bin_idxs[k]; + + batches_mtx(bin_idx, batch_sizes[bin_idx]) = buffer[k]; + in_idxs_mtx(bin_idx, batch_sizes[bin_idx]) = i + k; + ++batch_sizes[bin_idx]; + + if (batch_sizes[bin_idx] == decode_size) { + auto p = pp.subspan(bin_idx * size_per, size_per); + auto idxs = in_idxs_mtx[bin_idx]; + ImplDecodeBin<IdxType>(bin_idx, batches_mtx[bin_idx], values, buff, + idxs, p, h, paxos); + + batch_sizes[bin_idx] = 0; + } + } + } + + for (; i < inputs.size(); ++i, ++in_iter) { + auto k = 0; + + aes_crhasher.Hash(absl::MakeSpan(in_iter, 1), + absl::MakeSpan(&(buffer[k]), 1)); + + auto bin_idx = ModNumBins(buffer[k]); + + batches_mtx(bin_idx, batch_sizes[bin_idx]) = buffer[k]; + in_idxs_mtx(bin_idx, batch_sizes[bin_idx]) = i + k; + ++batch_sizes[bin_idx]; + + if (batch_sizes[bin_idx] == decode_size) { + auto p = pp.subspan(bin_idx * size_per, size_per); + ImplDecodeBin<IdxType>(bin_idx, batches_mtx[bin_idx], values, buff, + in_idxs_mtx[bin_idx], p, h, paxos); + + batch_sizes[bin_idx] = 0; + } + } + + for (uint64_t bin_idx = 0; bin_idx < num_bins_; ++bin_idx) { + if (batch_sizes[bin_idx]) { + auto p = pp.subspan(bin_idx * size_per, size_per); + auto b = batches_mtx[bin_idx].subspan(0, batch_sizes[bin_idx]); + ImplDecodeBin<IdxType>(bin_idx, b, values, buff, in_idxs_mtx[bin_idx], p, + h, paxos); + } + } +} + +template <typename IdxType> +void Baxos::ImplParDecode(absl::Span<const uint128_t> inputs, PxVector& values, + const PxVector& pp, PxVector::Helper& h, + uint64_t num_threads) { + if (num_bins_ == 1) { + Paxos<IdxType> paxos; + paxos.Init(1, paxos_param_, seed_); + + paxos.Decode(inputs, values, pp, h); + return; + } + + num_threads = std::max<uint64_t>(num_threads, 1ull); + + std::vector<std::thread> thrds(num_threads - 1); + auto routine = [&](uint64_t i) { + auto begin = (inputs.size() * i) / num_threads; + auto end = (inputs.size() * (i + 1)) / num_threads; + absl::Span<const uint128_t> in = + absl::MakeSpan(inputs.begin() + begin, inputs.begin() + end); + auto va = values.subspan(begin, end - begin); + + ImplDecodeBatch<IdxType>(in, va, pp, h); + }; + + for (uint64_t i = 0; i < thrds.size(); ++i) { + thrds[i] = std::thread(routine, i); + } + + routine(thrds.size()); + + for (uint64_t i = 0; i < thrds.size(); ++i) { + thrds[i].join(); + } +} + +} // namespace psi::psi::okvs diff --git a/psi/psi/core/vole_psi/okvs/baxos.h b/psi/psi/core/vole_psi/okvs/baxos.h new file mode 100644 index 00000000..5f28cedf --- /dev/null +++ b/psi/psi/core/vole_psi/okvs/baxos.h @@ -0,0 +1,162 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <array> +#include <memory> +#include <ostream> + +#include "absl/types/span.h" + +#include "psi/psi/core/vole_psi/okvs/dense_mtx.h" +#include "psi/psi/core/vole_psi/okvs/galois128.h" +#include "psi/psi/core/vole_psi/okvs/paxos.h" +#include "psi/psi/core/vole_psi/okvs/paxos_utils.h" + +namespace psi::psi::okvs { + +// a binned version of paxos. Internally calls paxos. +class Baxos { + public: + size_t num_items_ = 0; + size_t num_bins_ = 0; + size_t items_per_bin_ = 0; + size_t weight_ = 0; + size_t ssp_ = 0; + + // the parameters used on a single bim. + PaxosParam paxos_param_; + uint128_t seed_; + + bool debug_ = false; + + // when decoding, add the decoded value to the + // output, as opposed to overwriting. + bool add_to_decode_ = false; + + // initialize the paxos with the given parameter. + void Init(uint64_t num_items, uint64_t bin_size, uint64_t weight, + uint64_t ssp, PaxosParam::DenseType dt, uint128_t seed) { + num_items_ = num_items; + weight_ = weight; + num_bins_ = (num_items + bin_size - 1) / bin_size; + items_per_bin_ = + GetBinSize(num_bins_, num_items_, ssp + std::log2(num_bins_)); + ssp_ = ssp; + seed_ = seed; + paxos_param_.Init(items_per_bin_, weight, ssp, dt); + } + + // solve the system for the given input vectors. + // inputs are the keys + // values are the desired values that inputs should decode to. + // output is the paxos. + // prng should be non-null if randomized paxos is desired. + void Solve(absl::Span<const uint128_t> inputs, + const absl::Span<uint128_t> values, absl::Span<uint128_t> output, + const std::shared_ptr<yacl::crypto::Prg<uint8_t>>& prng = nullptr, + uint64_t num_threads = 0); + + // solve/encode the system. + void Solve(absl::Span<const uint128_t> inputs, const PxVector& values, + PxVector& output, + const std::shared_ptr<yacl::crypto::Prg<uint8_t>>& prng, + uint64_t num_threads, PxVector::Helper& h); + + // decode a single input given the paxos p. + template <typename ValueType> + ValueType Decode(const uint128_t& input, absl::Span<const ValueType> p) { + ValueType r; + decode(absl::Span<uint128_t>(&input, 1), absl::Span<ValueType>(&r, 1), p); + return r; + } + + // decode the given input vector and write the result to values. + // inputs are the keys. + // values are the output. + // p is the paxos vector. + void Decode(absl::Span<const uint128_t> input, absl::Span<uint128_t> values, + const absl::Span<uint128_t> p, uint64_t num_threads = 0); + + void Decode(absl::Span<const uint128_t> inputs, PxVector& values, + const PxVector& p, PxVector::Helper& h, uint64_t num_threads); + + ////////////////////////////////////////// + // private impl + ////////////////////////////////////////// + + // solve/encode the system. + template <typename IdxType> + void ImplParSolve(absl::Span<const uint128_t> inputs, const PxVector& values, + PxVector& output, + const std::shared_ptr<yacl::crypto::Prg<uint8_t>>& prng, + uint64_t num_threads, PxVector::Helper& h); + + // create the desired number of threads and split up the work. + template <typename IdxType> + void ImplParDecode(absl::Span<const uint128_t> inputs, PxVector& values, + const PxVector& p, PxVector::Helper& h, + uint64_t num_threads); + + // decode the given inputs based on the paxos p. The output is written to + // values. + template <typename IdxType> + void ImplDecodeBatch(absl::Span<const uint128_t> inputs, PxVector& values, + const PxVector& p, PxVector::Helper& h); + + // decode the given inputs based on the paxos p. The output is written to + // values. this differs from implDecode in that all inputs must be for the + // same paxos bin. + template <typename IdxType> + void ImplDecodeBin(uint64_t bin_idx, absl::Span<uint128_t> hashes, + PxVector& values, PxVector& valuesBuff, + absl::Span<uint64_t> inIdxs, const PxVector& p, + PxVector::Helper& h, Paxos<IdxType>& paxos); + + // the size of the paxos. + uint64_t size() { + return uint64_t(num_bins_ * + (paxos_param_.sparse_size + paxos_param_.dense_size)); + } + + static uint64_t GetBinSize(uint64_t num_bins, uint64_t num_items, + uint64_t ssp); + + uint64_t BinIdxCompress(const uint128_t& h) { + auto h64 = Galois128(h).get<uint64_t>(); + auto h32 = Galois128(h).get<uint32_t>(); + + return h64[0] ^ h64[1] ^ h32[3]; + } + + uint64_t ModNumBins(const uint128_t& h) { + return BinIdxCompress(h) % num_bins_; + } + + void Check(absl::Span<uint128_t> inputs, PxVector values, PxVector output) { + auto h = values.DefaultHelper(); + auto v2 = h.NewVec(values.size()); + Decode(inputs, v2, output, h, 1); + + for (uint64_t i = 0; i < values.size(); ++i) { + YACL_ENFORCE( + h.eq(v2[i], values[i]), + "paxos failed to encode. inputs[{}]: {} seed:{} n: {} m: {} ", i, + inputs[i], seed_, size(), inputs.size()); + } + } +}; + +} // namespace psi::psi::okvs diff --git a/psi/psi/core/vole_psi/okvs/baxos_test.cc b/psi/psi/core/vole_psi/okvs/baxos_test.cc new file mode 100644 index 00000000..1ff6cef4 --- /dev/null +++ b/psi/psi/core/vole_psi/okvs/baxos_test.cc @@ -0,0 +1,74 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/vole_psi/okvs/baxos.h" + +#include <ostream> +#include <vector> + +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/crypto/tools/prg.h" +#include "yacl/crypto/utils/rand.h" + +namespace psi::psi::okvs { + +class BaxosTest : public testing::TestWithParam<std::size_t> {}; + +TEST_P(BaxosTest, WORKS) { + size_t items_num = GetParam(); + + size_t bin_size = items_num / 4; + size_t weight = 3; + // statistical security parameter + size_t ssp = 40; + + Baxos baxos; + yacl::crypto::Prg<uint128_t> prng(yacl::crypto::RandU128()); + + uint128_t seed; + prng.Fill(absl::MakeSpan(&seed, 1)); + + SPDLOG_INFO("items_num:{}, bin_size:{}", items_num, bin_size); + + baxos.Init(items_num, bin_size, weight, ssp, PaxosParam::DenseType::GF128, + seed); + + SPDLOG_INFO("baxos.size(): {}", baxos.size()); + + std::vector<uint128_t> items(items_num); + std::vector<uint128_t> values(items_num); + std::vector<uint128_t> values2(items_num); + std::vector<uint128_t> p(baxos.size()); + + prng.Fill(absl::MakeSpan(items.data(), items.size())); + prng.Fill(absl::MakeSpan(values.data(), values.size())); + + baxos.Solve(absl::MakeSpan(items), absl::MakeSpan(values), absl::MakeSpan(p)); + + baxos.Decode(absl::MakeSpan(items), absl::MakeSpan(values2), + absl::MakeSpan(p)); + + if (std::memcmp(values2.data(), values.data(), + values.size() * sizeof(uint128_t)) != 0) { + for (uint64_t i = 0; i < items_num; ++i) { + EXPECT_EQ(std::memcmp(&values[i], &values2[i], sizeof(uint128_t)), 0); + } + } +} + +INSTANTIATE_TEST_SUITE_P(Works_Instances, BaxosTest, + testing::Values(16, 32, 64, 128, 2048)); + +} // namespace psi::psi::okvs diff --git a/psi/psi/core/vole_psi/okvs/dense_mtx.cc b/psi/psi/core/vole_psi/okvs/dense_mtx.cc new file mode 100644 index 00000000..b7658feb --- /dev/null +++ b/psi/psi/core/vole_psi/okvs/dense_mtx.cc @@ -0,0 +1,134 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/vole_psi/okvs/dense_mtx.h" + +#include <utility> + +namespace psi::psi::okvs { + +void DenseMtx::Row::swap(const Row& r) { + YACL_ENFORCE(mtx.cols() == r.mtx.cols()); + + for (uint64_t col_idx = 0; col_idx < mtx.cols(); ++col_idx) { + uint8_t bit = r.mtx(r.idx, col_idx); + r.mtx(r.idx, col_idx) = mtx(idx, col_idx); + mtx(idx, col_idx) = bit; + } +} + +// returns true if the row is zero. +bool DenseMtx::Row::IsZero() const { + for (uint64_t col_idx = 0; col_idx < mtx.cols(); ++col_idx) { + uint8_t bit = mtx(idx, col_idx); + if (bit) { + return false; + } + } + return true; +} + +void DenseMtx::Row::operator^=(const Row& r) { + for (uint64_t col_idx = 0; col_idx < mtx.cols(); ++col_idx) { + mtx(idx, col_idx) ^= r.mtx(r.idx, col_idx); + } +} + +void DenseMtx::resize(uint64_t rows, uint64_t cols) { + bit_rows_ = rows; + rows_ = (rows_ + 127) / 128; + cols_ = cols; + data_.resize(cols_ * rows_); + data_mtx_ = MatrixView<uint128_t>(data_.data(), cols_, rows_); +} + +DenseMtx DenseMtx::Identity(uint64_t n) { + DenseMtx I(n, n); + + for (uint64_t i = 0; i < n; ++i) { + I(i, i) = 1; + } + + return I; +} + +// multiply this matrix by m. + +DenseMtx DenseMtx::Mult(const DenseMtx& m) { + YACL_ENFORCE(cols() == m.rows()); + + DenseMtx ret(rows(), m.cols()); + + for (uint64_t i = 0; i < ret.rows(); ++i) { + for (uint64_t j = 0; j < ret.cols(); ++j) { + uint8_t v = 0; + for (uint64_t k = 0; k < cols(); ++k) { + v = v ^ ((*this)(i, k) & m(k, j)); + } + + ret(i, j) = v; + } + } + + return ret; +} + +DenseMtx DenseMtx::Add(DenseMtx& m) { + YACL_ENFORCE(rows() == m.rows() && cols() == m.cols()); + + auto ret = *this; + for (uint64_t i = 0; i < data_.size(); ++i) + ret.data_mtx_(i) = ret.data_mtx_(i) ^ m.data_mtx_(i); + + return ret; +} + +DenseMtx DenseMtx::Invert() const { + YACL_ENFORCE(rows() == cols()); + + auto mtx = *this; + auto n = this->rows(); + + auto Inv = Identity(n); + + for (uint64_t i = 0; i < n; ++i) { + if (mtx(i, i) == 0) { + for (uint64_t j = i + 1; j < n; ++j) { + if (mtx(j, i) == 1) { + mtx.row(i).swap(mtx.row(j)); + Inv.row(i).swap(Inv.row(j)); + break; + } + } + + if (mtx(i, i) == 0) { + // std::cout << mtx << std::endl; + return {}; + } + } + + for (uint64_t j = 0; j < n; ++j) { + if (j != i && mtx(j, i)) { + for (uint64_t k = 0; k < n; ++k) { + mtx(j, k) ^= mtx(i, k); + Inv(j, k) ^= Inv(i, k); + } + } + } + } + + return Inv; +} + +} // namespace psi::psi::okvs diff --git a/psi/psi/core/vole_psi/okvs/dense_mtx.h b/psi/psi/core/vole_psi/okvs/dense_mtx.h new file mode 100644 index 00000000..096a0d27 --- /dev/null +++ b/psi/psi/core/vole_psi/okvs/dense_mtx.h @@ -0,0 +1,276 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <utility> +#include <vector> + +#include "yacl/base/exception.h" + +#include "psi/psi/core/vole_psi/okvs/galois128.h" + +namespace psi::psi::okvs { + +// A class to reference a specific bit. +class BitReference { + public: + // Default copy constructor + BitReference(const BitReference& rhs) = default; + + // Construct a reference to the bit in the provided byte offset by the shift. + BitReference(uint8_t* byte, uint64_t shift) + : bytes_(byte + (shift / 8)), shift_(shift % 8) {} + + // Construct a reference to the bit in the provided byte offset by the shift + // and mask. Shift should be less than 8. and the mask should equal 1 << + // shift. + BitReference(uint8_t* byte, [[maybe_unused]] uint8_t mask, uint8_t shift) + : BitReference(byte, shift) {} + + // Copy the underlying values of the rhs to the lhs. + void operator=(const BitReference& rhs) { *this = (uint8_t)rhs; } + + // Copy the value of the rhs to the lhs. + inline void operator=(uint8_t n) { + bool b = n; + *bytes_ ^= (*bytes_ ^ ((b & 1) << shift_)) & (1 << shift_); + } + + inline void operator^=(bool b) { *bytes_ ^= ((b & 1) << shift_); } + + // Convert the reference to the underlying value + operator uint8_t() const { return (*bytes_ >> shift_) & 1; } + + private: + uint8_t* bytes_; + uint8_t shift_; +}; + +// Function to allow the printing of a BitReference. +inline std::ostream& operator<<(std::ostream& out, const BitReference& bit) { + out << (uint32_t)bit; + return out; +} + +// A class to allow the iteration of bits. +class BitIterator { + public: + // Would be random access with some extra methods. + typedef std::bidirectional_iterator_tag iterator_category; + typedef ptrdiff_t difference_type; + typedef uint8_t value_type; + typedef BitReference reference; + typedef void pointer; // Can't support operator-> + + BitIterator() = default; + // Default copy constructor + BitIterator(const BitIterator& cp) = default; + + // Construct a reference to the bit in the provided byte offset by the shift. + // Shift should be less than 8. + explicit BitIterator(uint8_t* byte, uint64_t shift = 0) + : bytes_(byte + (shift / 8)), shift_(shift & 7) {} + + // Construct a reference to the current bit pointed to by the iterator. + BitReference operator*() { return BitReference(bytes_, shift_); } + + // Pre increment the iterator by 1. + BitIterator& operator++() { + bytes_ += (shift_ == 7) & 1; + ++shift_ &= 7; + return *this; + } + + // Post increment the iterator by 1. Returns a copy of this class. + BitIterator operator++(int) { + BitIterator ret(*this); + + bytes_ += (shift_ == 7) & 1; + ++shift_ &= 7; + + return ret; + } + + // Pre decrement the iterator by 1. + BitIterator& operator--() { + bytes_ -= (shift_ == 0) & 1; + --shift_ &= 7; + return *this; + } + + // Post decrement the iterator by 1. + BitIterator operator--(int) { + auto ret = *this; + --(*this); + return ret; + } + + // Return the Iterator that has been incremented by v. + // v must be possitive. + BitIterator operator+(int64_t v) const { + YACL_ENFORCE(v >= 0); + BitIterator ret(bytes_, shift_ + v); + + return ret; + } + + // Check if two iterators point to the same bit. + bool operator==(const BitIterator& cmp) const { + return bytes_ == cmp.bytes_ && shift_ == cmp.shift_; + } + + bool operator!=(const BitIterator& cmp) const { return !(*this == cmp); } + + uint8_t* bytes_; + uint8_t shift_; +}; + +template <class T> +class MatrixView { + public: + MatrixView() : stride_(0) {} + + MatrixView(T* data, size_t num_rows, size_t stride) + : view_(data, num_rows * stride), stride_(stride) {} + + size_t size() const { return view_.size(); } + size_t stride() const { return stride_; } + + size_t rows() const { return stride() ? size() / stride() : 0; } + size_t cols() const { return stride(); } + + T& operator()(size_t idx) { return view_[idx]; } + const T& operator()(size_t idx) const { return view_[idx]; } + + T& operator()(size_t row_idx, size_t col_idx) { + return view_[row_idx * stride() + col_idx]; + } + + const T& operator()(size_t row_idx, size_t col_idx) const { + return view_[row_idx * stride() + col_idx]; + } + + const absl::Span<T> operator[](size_t row_idx) const { + YACL_ENFORCE(row_idx < rows(), "row_idx:{}, rows():{}", row_idx, rows()); + return absl::Span<T>(view_.data() + row_idx * stride(), stride()); + } + + T* data() const { return view_.data(); } + + T* data(size_t row_idx) const { + YACL_ENFORCE(row_idx < rows()); + + return view_.data() + row_idx * stride(); + } + + protected: + absl::Span<T> view_; + size_t stride_ = 0; +}; + +// a class that represents a dense binary matrix. +// The data is stored in column major format. +class DenseMtx { + public: + // column major, which means we call mData.row(i) + // to get column i and vise versa. + std::vector<uint128_t> data_; + MatrixView<uint128_t> data_mtx_; + + // the number of rows. This will be + // mData.cols() * 128 or a bit less. + uint64_t bit_rows_ = 0; + uint64_t rows_ = 0; + uint64_t cols_ = 0; + + DenseMtx() = default; + DenseMtx(const DenseMtx&) = default; + DenseMtx(DenseMtx&&) = default; + + DenseMtx& operator=(const DenseMtx&) = default; + DenseMtx& operator=(DenseMtx&&) = default; + + DenseMtx(uint64_t rows, uint64_t cols) { resize(rows, cols); } + + // resize the given matrix. If the number of rows + // changed then the data is invalidated. + void resize(uint64_t rows, uint64_t cols); + + // the number of rows. + uint64_t rows() const { return bit_rows_; } + + // the number of columns. + uint64_t cols() const { return data_mtx_.rows(); } + + // returns a refernce to the given bit. + BitReference operator()(uint64_t row, uint64_t col) const { + YACL_ENFORCE(row < rows()); + YACL_ENFORCE(col < cols()); + + return BitReference((uint8_t*)&data_mtx_(col, 0), row); + } + + bool operator==(const DenseMtx& m) const { + return rows() == m.rows() && cols() == m.cols() && + std::memcmp(data_.data(), m.data_.data(), + data_.size() * sizeof(uint128_t)) == 0; + } + + // A referenced to the given row. + struct Row { + uint64_t idx; + DenseMtx& mtx; + + // swap the data of the underlying rows. + void swap(const Row& r); + + // returns true if the row is zero. + bool IsZero() const; + + // xor the given row into this one. + void operator^=(const Row& r); + }; + + // returns a refernce to the given row. + Row row(uint64_t i) const { return Row{i, (DenseMtx&)*this}; } + + // returns a refernce to the given column. + absl::Span<uint128_t> col(uint64_t i) { return data_mtx_[i]; } + + // returns a refernce to the given column. + absl::Span<const uint128_t> col(uint64_t i) const { return data_mtx_[i]; } + + // multiply this matrix by m and return the result. + DenseMtx Mult(const DenseMtx& m); + + // multiply this matrix by m and return the result. + DenseMtx operator*(const DenseMtx& m) { return Mult(m); } + + // add this matrix with m and return the result. + DenseMtx Add(DenseMtx& m); + + // add this matrix with m and return the result. + DenseMtx operator+(DenseMtx& m) { return Add(m); } + + // returns the identity matrix of the given size. + static DenseMtx Identity(uint64_t n); + + // return the inverse of this matrix. + DenseMtx Invert() const; +}; + +std::ostream& operator<<(std::ostream& o, const DenseMtx& H); + +} // namespace psi::psi::okvs diff --git a/psi/psi/core/vole_psi/okvs/galois128.cc b/psi/psi/core/vole_psi/okvs/galois128.cc new file mode 100644 index 00000000..800d6501 --- /dev/null +++ b/psi/psi/core/vole_psi/okvs/galois128.cc @@ -0,0 +1,214 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/vole_psi/okvs/galois128.h" + +#include <utility> + +#include "absl/strings/escaping.h" +#include "yacl/base/exception.h" +#include "yacl/utils/platform_utils.h" + +#ifdef __x86_64__ +#include "cpu_features/cpuinfo_x86.h" +#endif + +namespace psi::psi::okvs { + +// namespace { + +#ifdef __x86_64__ +static const auto kCpuFeatures = cpu_features::GetX86Info().features; +static const bool kHasPCLML = kCpuFeatures.pclmulqdq; +#else +static const bool kHasPCLML = false; +#endif + +bool hasPCLML() { return kHasPCLML; } + +#ifdef __x86_64__ +void mm_gf128Mul(const yacl::block& x, const yacl::block& y, yacl::block& xy1, + yacl::block& xy2) { + yacl::block t1 = _mm_clmulepi64_si128(x, y, (int)0x00); + yacl::block t2 = _mm_clmulepi64_si128(x, y, 0x10); + yacl::block t3 = _mm_clmulepi64_si128(x, y, 0x01); + yacl::block t4 = _mm_clmulepi64_si128(x, y, 0x11); + t2 = (t2 ^ t3); + t3 = _mm_slli_si128(t2, 8); + t2 = _mm_srli_si128(t2, 8); + t1 = (t1 ^ t3); + t4 = (t4 ^ t2); + + xy1 = t1; + xy2 = t4; +} + +yacl::block mm_gf128Reduce(const yacl::block& x, const yacl::block& x1) { + auto mul256_low = x; + auto mul256_high = x1; + static const constexpr std::uint64_t mod = 0b10000111; + + /* reduce w.r.t. high half of mul256_high */ + const __m128i modulus = _mm_loadl_epi64((const __m128i*)&(mod)); + __m128i tmp = _mm_clmulepi64_si128(mul256_high, modulus, 0x01); + mul256_low = _mm_xor_si128(mul256_low, _mm_slli_si128(tmp, 8)); + mul256_high = _mm_xor_si128(mul256_high, _mm_srli_si128(tmp, 8)); + + /* reduce w.r.t. low half of mul256_high */ + tmp = _mm_clmulepi64_si128(mul256_high, modulus, 0x00); + mul256_low = _mm_xor_si128(mul256_low, tmp); + + // std::cout << "redu " << bits(x, 128) << std::endl; + // std::cout << " " << bits(mul256_low, 128) << std::endl; + + return mul256_low; +} +#endif + +// Multiplication in GF2^128, Reference +// The Galois/Counter Mode of Operation (GCM) +// https://csrc.nist.rip/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf +// P9 Algorithm 1 Multiplication in GF2^128 +uint128_t cc_gf128Mul(const uint128_t a, const uint128_t b) { + uint128_t z = yacl::MakeUint128(0, 0); + uint128_t v = a; + + uint128_t mask1 = yacl::MakeUint128(0, 1); + uint128_t mask127 = yacl::MakeUint128(0x8000000000000000, 0); + uint128_t r = yacl::MakeUint128(0, 0x0000000000000087); + + for (size_t i = 0; i < 128; ++i) { + if ((b >> i) & mask1) { + z = z ^ v; + } + + if (v & mask127) { + v = v << 1; + v = v ^ r; + } else { + v = v << 1; + } + } + + return z; +} + +//} // namespace + +Galois128::Galois128(uint64_t a, uint64_t b) { +#ifdef __x86_64__ + if (yacl::hasAVX2()) { + value_ = yacl::block(a, b); + } else { +#endif + value_ = yacl::MakeUint128(a, b); +#ifdef __x86_64__ + } +#endif +} + +Galois128::Galois128(const uint128_t b) { +#ifdef __x86_64__ + if (yacl::hasAVX2()) { + std::pair<uint64_t, uint64_t> b64 = yacl::DecomposeUInt128(b); + value_ = yacl::block(b64.first, b64.second); + } else { +#endif + value_ = b; +#ifdef __x86_64__ + } +#endif +} + +Galois128 Galois128::Mul(const Galois128& rhs) const { +#ifdef __x86_64__ + if (yacl::hasAVX2()) { + yacl::block xy1, xy2; + + mm_gf128Mul(std::get<yacl::block>(value_), + std::get<yacl::block>(rhs.value_), xy1, xy2); + + return Galois128(mm_gf128Reduce(xy1, xy2)); + } else { +#endif + uint128_t z = cc_gf128Mul(std::get<uint128_t>(value_), + std::get<uint128_t>(rhs.value_)); + + return Galois128(z); +#ifdef __x86_64__ + } +#endif +} + +Galois128 Galois128::Pow(std::uint64_t i) const { + Galois128 pow2(*this); + Galois128 zeroblock(0, 0); + + if (std::memcmp(pow2.data(), zeroblock.data(), 16) == 0) + return Galois128(0, 0); + + Galois128 s(0, 1); + while (i) { + if (i & 1) { + // s = 1 * i_0 * x^{2^{1}} * ... * i_j x^{2^{j+1}} + s = s.Mul(pow2); + } + + // pow2 = x^{2^{j+1}} + pow2 = pow2.Mul(pow2); + i >>= 1; + } + + return s; +} + +Galois128 Galois128::Inv() const { + /* calculate el^{-1} as el^{2^{128}-2}. the addition chain below + requires 142 mul/sqr operations total. */ + Galois128 a = *this; + + Galois128 result(0, 0); + for (int64_t i = 0; i <= 6; ++i) { + /* entering the loop a = el^{2^{2^i}-1} */ + Galois128 b(a); + for (int64_t j = 0; j < (1 << i); ++j) { + b = b * b; + } + /* after the loop b = a^{2^i} = el^{2^{2^i}*(2^{2^i}-1)} */ + a = a * b; + /* now a = el^{2^{2^{i+1}}-1} */ + + if (i == 0) { + result = b; + } else { + result = result * b; + } + } + + YACL_ENFORCE(Mul(result).get<uint128_t>(0) == yacl::MakeUint128(0, 1)); + + /* now result = el^{2^128-2} */ + return result; +} + +} // namespace psi::psi::okvs + +namespace std { + +std::ostream& operator<<(std::ostream& os, psi::psi::okvs::Galois128 x) { + return os << absl::BytesToHexString( + absl::string_view((const char*)x.data(), 16)); +} + +} // namespace std diff --git a/psi/psi/core/vole_psi/okvs/galois128.h b/psi/psi/core/vole_psi/okvs/galois128.h new file mode 100644 index 00000000..326cce9f --- /dev/null +++ b/psi/psi/core/vole_psi/okvs/galois128.h @@ -0,0 +1,102 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <ostream> +#include <variant> + +#include "absl/strings/escaping.h" +#include "spdlog/spdlog.h" +#include "yacl/base/block.h" +#include "yacl/base/exception.h" +#include "yacl/base/int128.h" + +// Galois field 2^128 +// polynoimal : x^127+x^7+x^2+x+1 + +namespace psi::psi::okvs { + +using Galois128Type = std::variant<yacl::block, uint128_t>; + +class Galois128 { + public: + Galois128(uint64_t a, uint64_t b); + explicit Galois128(uint64_t v) : Galois128(0, v) {} + + Galois128(const Galois128& v) { value_ = v.value_; } + +#ifdef __x86_64__ + explicit Galois128(const yacl::block& b) { value_ = b; } +#endif + + explicit Galois128(const uint128_t b); + + Galois128& operator=(const Galois128&) = default; + + Galois128 Mul(const Galois128& rhs) const; + Galois128 Pow(std::uint64_t i) const; + Galois128 Inv() const; + + inline Galois128 operator*(const Galois128& rhs) const { + return Galois128(Mul(rhs)); + } + + inline Galois128 operator*(const uint128_t& rhs) const { + return Galois128(Mul(Galois128(rhs))); + } + + inline Galois128 operator*(const uint64_t& rhs) const { + return Galois128(Mul(Galois128(rhs))); + } + + const uint8_t* data() { return (const uint8_t*)&value_; } + + template <typename T> + typename std::enable_if<std::is_standard_layout<T>::value && + std::is_trivial<T>::value && (sizeof(T) <= 16) && + (16 % sizeof(T) == 0), + std::array<T, 16 / sizeof(T)> >::type + get() { + std::array<T, 16 / sizeof(T)> output; + std::memcpy(output.data(), data(), 16); + return output; + } + + template <typename T> + typename std::enable_if<std::is_standard_layout<T>::value && + std::is_trivial<T>::value && (sizeof(T) <= 16) && + (16 % sizeof(T) == 0), + T>::type + get(size_t index) { + YACL_ENFORCE(index < 16 / sizeof(T)); + + T output; + std::memcpy(&output, data() + sizeof(T) * index, sizeof(T)); + return output; + } + + private: + Galois128Type value_; +}; + +uint128_t cc_gf128Mul(const uint128_t a, const uint128_t b); + +} // namespace psi::psi::okvs + +namespace std { + +std::ostream& operator<<(std::ostream& os, psi::psi::okvs::Galois128 x); + +} diff --git a/psi/psi/core/vole_psi/okvs/galois128_test.cc b/psi/psi/core/vole_psi/okvs/galois128_test.cc new file mode 100644 index 00000000..0e96c530 --- /dev/null +++ b/psi/psi/core/vole_psi/okvs/galois128_test.cc @@ -0,0 +1,79 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/vole_psi/okvs/galois128.h" + +#include <sstream> + +#include "absl/strings/escaping.h" +#include "gtest/gtest.h" +#include "openssl/modes.h" +#include "spdlog/spdlog.h" +#include "yacl/crypto/tools/prg.h" +#include "yacl/crypto/utils/rand.h" + +namespace psi::psi::okvs { + +namespace { + +struct TestParams { + uint64_t seed1; + uint64_t seed2; +}; + +} // namespace + +class GaloisTest : public testing::TestWithParam<TestParams> {}; + +TEST_P(GaloisTest, Works) { + auto params = GetParam(); + + Galois128 a(0, params.seed1); + Galois128 b(0, params.seed2); + + Galois128 c = a * b; + + uint128_t a128 = yacl::MakeUint128(0, params.seed1); + uint128_t b128 = yacl::MakeUint128(0, params.seed2); + uint128_t z = cc_gf128Mul(a128, b128); + + EXPECT_EQ(std::memcmp(c.data(), &z, sizeof(uint128_t)), 0); + + uint64_t seed = yacl::crypto::RandU64(); + yacl::crypto::Prg<uint64_t> prg(seed); + + for (size_t i = 0; i < 100000; ++i) { + uint64_t rh = prg(); + uint64_t rl = prg(); + uint64_t lh = prg(); + uint64_t ll = prg(); + Galois128 a(rh, rl), b(lh, ll); + + Galois128 c = a * b; + + uint128_t a128 = yacl::MakeUint128(rh, rl); + uint128_t b128 = yacl::MakeUint128(lh, ll); + uint128_t z = cc_gf128Mul(a128, b128); + + EXPECT_EQ(std::memcmp(c.data(), &z, sizeof(uint128_t)), 0); + } +} + +INSTANTIATE_TEST_SUITE_P(Works_Instances, GaloisTest, + testing::Values(TestParams{1, 2}, TestParams{3, 2}, + TestParams{3, 4}, + TestParams{yacl::crypto::RandU64(), + yacl::crypto::RandU64()})); + +} // namespace psi::psi::okvs diff --git a/psi/psi/core/vole_psi/okvs/paxos.cc b/psi/psi/core/vole_psi/okvs/paxos.cc new file mode 100644 index 00000000..c4726b50 --- /dev/null +++ b/psi/psi/core/vole_psi/okvs/paxos.cc @@ -0,0 +1,1467 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/vole_psi/okvs/paxos.h" + +#include <cmath> +#include <functional> +#include <set> + +#include "yacl/base/exception.h" + +namespace psi::psi::okvs { + +namespace { + +constexpr uint8_t kPaxosBuildRowSize = 32; + +inline std::vector<uint128_t> MatrixGf128Inv(std::vector<uint128_t> mtx, + size_t row_size, size_t col_size) { + YACL_ENFORCE(row_size == col_size); + + auto inv = std::vector<uint128_t>(row_size, col_size); + for (uint64_t i = 0; i < row_size; ++i) { + inv[i * row_size + i] = yacl::MakeUint128(0, 1); + } + + uint128_t zero_block = yacl::MakeUint128(0, 0); + uint128_t one_block = yacl::MakeUint128(0, 1); + + // for the ith row + for (uint64_t i = 0; i < row_size; ++i) { + // if the i,i position is zero, + // then find a lower row to swap with. + + if (mtx[i * row_size + i] == 0) { + // for all lower row j, check if (j,i) + // is non-zero + for (uint64_t j = i + 1; j < row_size; ++j) { + if (mtx[j * row_size + i] == one_block) { + // found one, swap the rows and break + for (uint64_t k = 0; k < row_size; ++k) { + std::swap(mtx[i * row_size + k], mtx[j * row_size + k]); + std::swap(inv[i * row_size + k], inv[j * row_size + k]); + } + break; + } + } + + // double check that we found a swap. If not, + // then this matrix is not invertable. Return + // the empty matrix to denote this. + if (mtx[i * row_size + i] == zero_block) { + return {}; + } + } + + // Make the i'th column the zero column with + // a one on the diagonal. First we will make row + // i have a one on the diagonal by computing + // row(i) = row(i) / entry(i,i) + Galois128 mtx_ii_inv = Galois128(mtx[i * row_size + i]).Inv(); + for (uint64_t j = 0; j < row_size; ++j) { + mtx[i * row_size + j] = + (mtx_ii_inv * mtx[i * row_size + j]).get<uint128_t>(0); + inv[i * row_size + j] = + (mtx_ii_inv * inv[i * row_size + j]).get<uint128_t>(0); + } + + // next we will make all other rows have a zero + // in the i'th column by computing + // row(j) = row(j) - row(i) * entry(j,i) + for (uint64_t j = 0; j < row_size; ++j) { + if (j != i) { + Galois128 mtx_ji(mtx[j * row_size + i]); + for (uint64_t k = 0; k < row_size; ++k) { + mtx[j * row_size + k] = + mtx[j * row_size + k] ^ + (mtx_ji * mtx[i * row_size + k]).get<uint128_t>(0); + inv[j * row_size + k] = + inv[j * row_size + k] ^ + (mtx_ji * inv[i * row_size + k]).get<uint128_t>(0); + } + } + } + } + + return inv; +} + +inline std::vector<uint128_t> MatrixGf128Mul(const std::vector<uint128_t>& m0, + const std::vector<uint128_t>& m1, + size_t row_size, size_t col_size) { + YACL_ENFORCE(row_size == col_size); + + std::vector<uint128_t> ret(row_size, col_size); + + for (uint64_t i = 0; i < row_size; ++i) { + for (uint64_t j = 0; j < col_size; ++j) { + auto& v = ret[i * row_size + j]; + for (uint64_t k = 0; k < col_size; ++k) { + v = v ^ (Galois128(m0[i * row_size + k]) * m1[k * row_size + j]) + .get<uint128_t>(0); + } + } + } + return ret; +} + +void ithCombination(uint64_t index, uint64_t n, std::vector<uint64_t>& set) { + //'''Yields the items of the single combination that would be at the provided + //(0-based) index in a lexicographically sorted list of combinations of + // choices of k items from n items [0,n), given the combinations were sorted + // in + // descending order. Yields in descending order. + //''' + uint64_t nCk = 1; + uint64_t nMinusI = n; + uint64_t iPlus1 = 1; + + auto k = set.size(); + + // nMinusI, iPlus1 in zip(range(n, n - k, -1), range(1, k + 1)): + for (; nMinusI != n - k; --nMinusI, ++iPlus1) { + nCk *= nMinusI; + nCk /= iPlus1; + } + + // std::cout << "nCk " << nCk << std::endl; + + auto cur_index = nCk; + for (auto kk = k; kk != 0ull; --kk) // in range(k, 0, -1): + { + // std::cout << "kk " << kk << " " << nCk << std::endl; + nCk *= kk; + nCk /= n; + while (cur_index - nCk > index) { + cur_index -= nCk; + nCk *= (n - kk); + nCk -= nCk % kk; + n -= 1; + nCk /= n; + } + n -= 1; + + set[kk - 1] = n; + } +} + +std::vector<uint64_t> ithCombination(uint64_t index, uint64_t n, uint64_t k) { + std::vector<uint64_t> set(k); + ithCombination(index, n, set); + return set; +} + +uint64_t Choose(uint64_t n, uint64_t k) { + if (k == 0) return 1; + return (n * Choose(n - 1, k - 1)) / k; +} + +} // namespace + +void PaxosParam::Init(uint64_t num_items, uint64_t weight, uint64_t ssp, + PaxosParam::DenseType dt) { + YACL_ENFORCE(weight >= 2, "weight:{} must be 2 or greater.", weight); + + this->weight = weight; + this->ssp = ssp; + this->dt = dt; + + double logn = std::log2(num_items); + + if (weight == 2) { + double a = 7.529; + double b = 0.61; + double c = 2.556; + double lambda_vs_gap = a / (logn - c) + b; + + // lambda = lambdaVsGap * g - 1.9 * lambdaVsGap + // g = (lambda + 1.9 * lambdaVsGap) / lambdaVsGap + // = lambda / lambdaVsGap + 1.9 + + this->g = static_cast<uint64_t>(std::ceil(ssp / lambda_vs_gap + 1.9)); + + dense_size = + this->g + (this->dt == PaxosParam::DenseType::Binary) * this->ssp; + sparse_size = 2 * num_items; + } else { + double ee = 0; + if (weight == 3) ee = 1.223; + if (weight == 4) ee = 1.293; + if (weight >= 5) ee = 0.1485 * weight + 0.6845; + + double logw = std::log2(weight); + double log_lambda_vs_e = 0.555 * logn + 0.093 * std::pow(logw, 3) - + 1.01 * std::pow(logw, 2) + 2.925 * logw - 0.133; + double lambda_vs_e = std::pow(2, log_lambda_vs_e); + + double b = -9.2 - lambda_vs_e * ee; + + double e = (this->ssp - b) / lambda_vs_e; + this->g = + std::floor(this->ssp / ((this->weight - 2) * std::log2(e * num_items))); + + dense_size = this->g + (dt == PaxosParam::DenseType::Binary) * this->ssp; + sparse_size = num_items * e; + } +} + +template <typename IdxType> +void Paxos<IdxType>::Init(uint64_t num_items, PaxosParam p, uint128_t seed) { + YACL_ENFORCE( + p.sparse_size < uint64_t(std::numeric_limits<IdxType>::max()), + "the size of the paxos is too large for the index type. {} vs {}", + p.sparse_size, uint64_t(std::numeric_limits<IdxType>::max())); + + YACL_ENFORCE( + (p.sparse_size + p.dense_size) >= num_items, + "p.sparse_size:{} + p.dense_size:{} should greater than num_items:{}", + p.sparse_size, p.dense_size, num_items); + + static_cast<PaxosParam&>(*this) = p; + num_items_ = static_cast<IdxType>(num_items); + seed_ = seed; + hasher_.init(seed, weight, sparse_size); +} + +template <typename IdxType> +void Paxos<IdxType>::SetInput(absl::Span<const uint128_t> inputs) { + SPDLOG_DEBUG( + "setInput begin, inputs.size():{}, mNumItems:{}, mSparseSize:{} " + "IdxType:{}", + inputs.size(), num_items_, sparse_size, sizeof(IdxType)); + + YACL_ENFORCE(inputs.size() <= num_items_, "inputs size must equal num_items ", + inputs.size(), num_items_); + + std::vector<IdxType> col_weights(sparse_size); + + dense_.resize(num_items_); + rows_.resize(num_items_ * weight); + cols_.resize(sparse_size); + col_backing_.resize(num_items_ * weight); + + SPDLOG_DEBUG("setInput alloc"); + + { + auto main = inputs.size() / kPaxosBuildRowSize * kPaxosBuildRowSize; + auto in_iter = inputs.data(); + SPDLOG_DEBUG("main:{}, kPaxosBuildRowSize:{}", main, kPaxosBuildRowSize); + + for (uint64_t i = 0; i < main; + i += kPaxosBuildRowSize, in_iter += kPaxosBuildRowSize) { + SPDLOG_DEBUG("i:{}, main:{}", i, main); + auto rr = &rows_[i * weight]; + + YACL_ENFORCE(kPaxosBuildRowSize == 32); + + hasher_.HashBuildRow32(absl::MakeSpan(in_iter, kPaxosBuildRowSize), + absl::MakeSpan(rr, kPaxosBuildRowSize * weight), + absl::MakeSpan(&dense_[i], kPaxosBuildRowSize)); + + absl::Span<IdxType> cols(rr, kPaxosBuildRowSize * weight); + SPDLOG_DEBUG("i:{} cols size:{} sparse_size:{}", i, cols.size(), + sparse_size); + for (auto c : cols) { + SPDLOG_DEBUG("colWeights[{}]: {}", c, col_weights[c]); + ++col_weights[c]; + SPDLOG_DEBUG("colWeights[{}]: {}", c, col_weights[c]); + } + } + + SPDLOG_DEBUG("main:{}, mNumItems:{} mDense size:{}", main, num_items_, + dense_.size()); + + for (size_t i = main; i < num_items_; ++i, ++in_iter) { + hasher_.HashBuildRow1( + *in_iter, absl::MakeSpan(&(rows_[i * weight]), weight), &dense_[i]); + for (size_t j = 0; j < weight; j++) { + auto c = rows_[i * weight + j]; + SPDLOG_DEBUG("colWeights[{}]: {}", c, col_weights[c]); + ++col_weights[c]; + SPDLOG_DEBUG("colWeights[{}]: {}", c, col_weights[c]); + } + } + + for (uint64_t i = 0; i < num_items_; i++) { + SPDLOG_DEBUG("[{}]: {}", i, + (std::ostringstream() << Galois128(dense_[i])).str()); + } + } + + // setTimePoint("setInput buildRow"); + SPDLOG_DEBUG("setInput buildRow"); + + RebuildColumns(absl::MakeSpan(col_weights), weight * num_items_); + // setTimePoint("setInput rebuildColumns"); + SPDLOG_DEBUG("setInput rebuildColumns"); + + weight_sets_.init(absl::MakeSpan(col_weights)); + + SPDLOG_DEBUG(" mWeightSets.mNodes:{}, mWeightSets:{}", + weight_sets_.nodes.size(), weight_sets_.weight_sets.size()); + + SPDLOG_DEBUG("setInput end"); +} + +template <typename IdxType> +void Paxos<IdxType>::SetInput(MatrixView<IdxType> rows, + absl::Span<uint128_t> dense, + absl::Span<absl::Span<IdxType>> cols, + absl::Span<IdxType> col_backing, + absl::Span<IdxType> col_weights) { + YACL_ENFORCE((rows.rows() == num_items_) && (dense.size() == num_items_)); + YACL_ENFORCE(rows.cols() == weight); + YACL_ENFORCE(cols.size() == sparse_size); + YACL_ENFORCE(col_backing.size() == num_items_ * weight); + YACL_ENFORCE(col_weights.size() == sparse_size); + + rows_.resize(rows.size()); + std::memcpy(rows_.data(), rows.data(), rows.size() * sizeof(IdxType)); + + dense_.resize(dense.size()); + std::memcpy(dense_.data(), dense.data(), dense.size() * sizeof(uint128_t)); + + cols_.resize(cols.size()); + for (size_t i = 0; i < cols_.size(); ++i) { + cols_[i] = cols[i]; + } + + col_backing_.resize(col_backing.size()); + std::memcpy(col_backing_.data(), col_backing.data(), + col_backing.size() * sizeof(IdxType)); + + RebuildColumns(col_weights, weight * num_items_); + + weight_sets_.init(col_weights); +} + +template <typename IdxType> +void Paxos<IdxType>::RebuildColumns(absl::Span<IdxType> col_weights, + uint64_t total_weight) { + YACL_ENFORCE(col_backing_.size() == total_weight); + + SPDLOG_DEBUG("mColBacking.size():{} totalWeight:{}", col_backing_.size(), + total_weight); + + auto col_iter = col_backing_.data(); + for (uint64_t i = 0; i < sparse_size; ++i) { + cols_[i] = absl::MakeSpan((IdxType*)(col_iter), (IdxType*)(col_iter)); + col_iter += col_weights[i]; + SPDLOG_DEBUG("colWeights[{}]: {} {}", i, col_weights[i], + col_iter - col_backing_.data()); + } + + SPDLOG_DEBUG("weight:{} {} {}", weight, col_iter - col_backing_.data(), + col_backing_.size()); + YACL_ENFORCE(col_iter == (col_backing_.data() + col_backing_.size())); + + if (weight == 3) { + for (IdxType i = 0; i < num_items_; ++i) { + auto& c0 = cols_[rows_[i * weight + 0]]; + auto& c1 = cols_[rows_[i * weight + 1]]; + auto& c2 = cols_[rows_[i * weight + 2]]; + + auto s0 = c0.size(); + auto s1 = c1.size(); + auto s2 = c2.size(); + + auto d0 = c0.data(); + auto d1 = c1.data(); + auto d2 = c2.data(); + + c0 = absl::Span<IdxType>(d0, s0 + 1); + c1 = absl::Span<IdxType>(d1, s1 + 1); + c2 = absl::Span<IdxType>(d2, s2 + 1); + + SPDLOG_DEBUG("i:{}, s0:{}, s1:{}, s2:{}", i, s0, s1, s2); + + c0[s0] = i; + c1[s1] = i; + c2[s2] = i; + } + } else { + for (IdxType i = 0; i < num_items_; ++i) { + for (size_t j = 0; j < weight; j++) { + auto& col = cols_[rows_[i * weight + j]]; + auto s = col.size(); + col = absl::Span<IdxType>(col.data(), s + 1); + col[s] = i; + SPDLOG_DEBUG("i:{}, j:{}", i, j); + } + } + } +} + +template <typename IdxType> +void Paxos<IdxType>::Encode( + const PxVector& values, PxVector& output, PxVector::Helper& h, + const std::shared_ptr<yacl::crypto::Prg<uint8_t>>& prng) { + YACL_ENFORCE(static_cast<uint64_t>(output.size()) == size(), + "output.size():{} size():{}", output.size(), size()); + + std::vector<IdxType> main_rows, main_cols; + main_rows.reserve(num_items_); + main_cols.reserve(num_items_); + std::vector<std::array<IdxType, 2>> gap_rows; + + Triangulate(main_rows, main_cols, gap_rows); + SPDLOG_DEBUG("mainRows.size():{}, mainCols.size():{}, gapRows.size():{}", + main_rows.size(), main_cols.size(), gap_rows.size()); + for (size_t i = 0; i < main_rows.size(); i++) { + SPDLOG_DEBUG("mainRows[{}]:{} main_cols[{}]:{}", i, main_rows[i], i, + main_cols[i]); + } + + // output.ZeroFill(); + + SPDLOG_DEBUG("prng:{}", prng ? "not NULL" : "NULL"); + if (prng) { + typename WeightData<IdxType>::WeightNode* node = + weight_sets_.weight_sets[0]; + while (node != nullptr) { + auto col_idx = weight_sets_.IdxOf(*node); + // prng->get(output[colIdx], output.stride()); + h.Randomize(output[col_idx], prng); + + if (node->next_weight_node == weight_sets_.NullNode) + node = nullptr; + else + node = weight_sets_.nodes.data() + node->next_weight_node; + } + } + + Backfill(absl::MakeSpan(main_rows), absl::MakeSpan(main_cols), + absl::MakeSpan(gap_rows), values, output, h, prng); +} + +template <typename IdxType> +void Paxos<IdxType>::Triangulate( + std::vector<IdxType>& main_rows, std::vector<IdxType>& main_cols, + std::vector<std::array<IdxType, 2>>& gap_rows) { + SPDLOG_DEBUG("triangulate begin"); + + if (weight_sets_.weight_sets.size() <= 1) { + std::vector<IdxType> col_weights(sparse_size); + for (uint64_t i = 0; i < cols_.size(); ++i) { + col_weights[i] = static_cast<IdxType>(cols_[i].size()); + } + weight_sets_.init(absl::MakeSpan(col_weights)); + } + + std::vector<uint8_t> row_set(num_items_); + while (weight_sets_.weight_sets.size() > 1) { + auto& col = weight_sets_.GetMinWeightNode(); + SPDLOG_DEBUG("colIdx:{} col.mWeight:{}", weight_sets_.IdxOf(col), + col.weight); + + weight_sets_.PopNode(col); + col.weight = 0; + auto col_idx = weight_sets_.IdxOf(col); + + SPDLOG_DEBUG("colIdx:{} col.mWeight:{}", col_idx, col.weight); + + bool first = true; + + // iterate over all the rows in the next column + for (auto row_idx : cols_[col_idx]) { + SPDLOG_DEBUG("rowIdx:{} size:{} rowSet[rowIdx]:{}", row_idx, + cols_[col_idx].size(), row_set[row_idx]); + // check if this row has already been set. + if (row_set[row_idx] == 0) { + row_set[row_idx] = 1; + + // iterate over the other columns in this row. + // for (auto col_idx2 : rows_[row_idx]) { + for (size_t j = 0; j < weight; ++j) { + auto col_idx2 = rows_[row_idx * weight + j]; + auto& node = weight_sets_.nodes[col_idx2]; + SPDLOG_DEBUG("colIdx2:{} node.mWeight:{}", col_idx2, node.weight); + + // if this column still hasn't been fixed, + // then decrement it's weight. + if (node.weight) { + YACL_ENFORCE(node.weight); + + // decrement the weight. + weight_sets_.PopNode(node); + --node.weight; + SPDLOG_DEBUG("node.mWeight:{}", node.weight); + weight_sets_.PushNode(node); + + // as an optimization, prefetch this next + // column if its ready to be used.. + if (node.weight == 1) { + _mm_prefetch((const char*)&cols_[col_idx2], _MM_HINT_T0); + } + } + } + + // if this is the first row, then we will use + // it as the row on the diagonal. Otherwise + // we will place the extra rows in the "gap" + SPDLOG_DEBUG("first: {}-{}", first, !!(first)); + if (!!(first)) { + main_cols.push_back(col_idx); + main_rows.push_back(row_idx); + first = false; + SPDLOG_DEBUG("colIdx:{}, rowIdx:{}", col_idx, row_idx); + } else { + // check that we dont have duplicates + YACL_ENFORCE( + std::memcmp(&(dense_[main_rows.back()]), &(dense_[row_idx]), + sizeof(uint128_t)) != 0, + "Paxos error, Duplicate keys were detected at idx {} {}, key={}", + main_rows.back(), row_idx, dense_[main_rows.back()]); + + SPDLOG_DEBUG("rowIdx:{}, mainRows.back():{}", row_idx, + main_rows.back()); + gap_rows.emplace_back( + std::array<IdxType, 2>{row_idx, main_rows.back()}); + } + } + } + + YACL_ENFORCE(first == false, "first:{}", first); + } + + SPDLOG_DEBUG("triangulate end"); +} + +template <typename IdxType> +void Paxos<IdxType>::Backfill(absl::Span<IdxType> main_rows, + absl::Span<IdxType> main_cols, + absl::Span<std::array<IdxType, 2>> gap_rows, + const PxVector& values, PxVector& output, + PxVector::Helper& h, + const std::shared_ptr<yacl::crypto::Prg<uint8_t>>& + prng) { // We are solving the system + // + // H * P = X + // + // for the unknown "P". Divide the + // matrix into + // + // | A B C | + // H = | D E F | + // + // where C is a square lower-triangular matrix, + // E is a dense g*g matrix with the gap rows. + // + // In particular, C have columns mainCols and + // rows mainCols. Rows of E are indexed by + // { gapRows[i][0] | i in [g] }. The columns of E will + // consists of a subset of the dense columns. + // + // Then compute + // + // |I 0 | |I 0 | + // H' = |-FC^-1 I | * H , X' = |-FC^-1 I | * X + // + // = | A B C | = | x1 | + // | D' E' 0 | | x2' | + // + // where + // + // D' = -FC^-1A + D + // E' = -FC^-1B + E + // x2' = -FC^-1 x1 + x2 + // + // We require E' be invertible. There are a + // few ways of ensuring this. + // + // One is to let E' be binary and then have 40 + // extra dense columns and find an invertible E' + // out of the 2^40 options. This succeeds with + // Pr 1-2^-40. + // + // Another option is to make E,B consist of + // random elements in a large field. Then E' + // is invertible with overwhelming Pr. (1- 1/fieldSize). + // + // Observe that there are many solutions. In + // particular, the first columns corresponding + // to A,D can be set arbitrarially. Let p = | r p1 p2 | + // and rewrite the above as + // + // | B C | * | p1 | = | x1 | - | A | * | r | + // | E' 0 | | p2 | = | x2' | | D' | + // + // Therefore we have + // + // r <- $ or r = 0 + // x2' = x2 - D' r - FC^-1 x1 + // p1 = -E'^-1 x2' + // x1' = x1 - A r - B p1 + // p2 = -C^-1 x1' + // + // P = | r p2 p1 | + // + // Here r is either set to zero or random. + // + // In the common case g will be zero and then we + // have H = | A C | and we solve just using back + // propegation (C is trivially invertible). Ie + // + // r <- $ or r = 0 + // x' = x1 - A r + // p1 = -C^-1 x' + // + // P = | r p1 | + + // setTimePoint("backFill begin"); + SPDLOG_DEBUG("backFill begin {}", + dt == DenseType::GF128 ? "GF128" : "Binary"); + + // select the method based on the dense type. + // Both perform the same basic algorithm, + if (dt == DenseType::GF128) { + BackfillGf128(main_rows, main_cols, gap_rows, values, output, h, prng); + } else { + BackfillBinary(main_rows, main_cols, gap_rows, values, output, h, prng); + } + + SPDLOG_DEBUG("backFill end"); +} + +template <typename IdxType> +void Paxos<IdxType>::BackfillGf128( + absl::Span<IdxType> main_rows, absl::Span<IdxType> main_cols, + absl::Span<std::array<IdxType, 2>> gap_rows, const PxVector& values, + PxVector& output, PxVector::Helper& helper, + const std::shared_ptr<yacl::crypto::Prg<uint8_t>>& prng) { + YACL_ENFORCE(dt == DenseType::GF128); + auto g = gap_rows.size(); + auto p2 = output.subspan(sparse_size); + + SPDLOG_DEBUG("gapRows size:{}", g); + + YACL_ENFORCE(g <= dense_size, "g:{}, dense_size", g, dense_size); + + if (g) { + auto fcinv = GetFCInv(main_rows, main_cols, gap_rows); + auto size = prng ? dense_size : g; + + // |dense[r0]^1, dense[r0]^2, ... | + // E = |dense[r1]^1, dense[r1]^2, ... | + // |dense[r2]^1, dense[r2]^2, ... | + // ... + // EE = E - FC^-1 B + std::vector<uint128_t> EE(size * size); + // Matrix<int128_t> EE(size, size); + + // xx = x' - FC^-1 x + PxVector xx = helper.NewVec(size); + // std::vector<block> xx(size); + std::vector<uint128_t> fcb(size); + + for (uint64_t i = 0; i < g; ++i) { + uint128_t e = dense_[gap_rows[i][0]]; + Galois128 ej(e); + EE[i * size] = e; //^ fcb; + for (uint64_t j = 1; j < size; ++j) { + ej = ej * e; + EE[i * size + j] = ej.get<uint128_t>(0); + } + + helper.Assign(xx[i], values[gap_rows[i][0]]); + for (auto j : fcinv.mtx[i]) { + helper.Add(xx[i], values[j]); + auto fcb = dense_[j]; + Galois128 fcbk(fcb); + EE[i * size] = EE[i * size] ^ fcb; + for (uint64_t k = 1; k < size; ++k) { + fcbk = fcbk * fcb; + EE[i * size + k] = EE[i * size + k] ^ fcbk.get<uint128_t>(0); + } + } + } + + if (prng) { + for (uint64_t i = g; i < dense_size; ++i) { + // prng->get<yacl::block>(EE[i]); + prng->Fill(absl::MakeSpan(&EE[i * size], size * sizeof(uint128_t))); + helper.Randomize(xx[i], prng); + } + } + + EE = MatrixGf128Inv(EE, size, size); + + YACL_ENFORCE(EE.size() > 0); + + // now we compute + // p' = (E - FC^-1 B)^-1 * (x'-FC^-1 x) + // = EE * xx + for (uint64_t i = 0; i < size; ++i) { + auto pp = p2[i]; + for (uint64_t j = 0; j < size; ++j) { + // pp = pp ^ xx[j] * EE(i, j); + helper.MultAdd(pp, xx[j], EE[i * size + j]); + } + } + } else if (prng) { + for (uint64_t i = 0; i < static_cast<uint64_t>(p2.size()); ++i) + helper.Randomize(p2[i], prng); + } + + auto out_col_iter = main_cols.rbegin(); + auto row_iter = main_rows.rbegin(); + bool do_dense = g || prng; + +#define GF128_DENSE_BACKFILL \ + if (do_dense) { \ + uint128_t d = dense_[i]; \ + uint128_t x = d; \ + helper.MultAdd(y, p2[0], x); \ + \ + for (uint64_t i = 1; i < dense_size; ++i) { \ + x = (Galois128(x) * d).get<uint128_t>(0); \ + helper.MultAdd(y, p2[i], x); \ + } \ + } + + auto yy = helper.NewElement(); + auto y = helper.AsPtr(yy); + + if (weight == 3) { + for (uint64_t k = 0; k < main_rows.size(); ++k) { + auto i = *row_iter; + auto c = *out_col_iter; + ++out_col_iter; + ++row_iter; + SPDLOG_DEBUG("k:{}, i:{} c:{}", k, i, c); + + // auto y = X[i]; + helper.Assign(y, values[i]); + SPDLOG_DEBUG("y:{}", absl::BytesToHexString( + absl::string_view((char*)y, sizeof(uint128_t)))); + + auto row = absl::MakeSpan(&rows_[i * weight], weight); + auto cc0 = row[0]; + auto cc1 = row[1]; + auto cc2 = row[2]; + + // y = y ^ P[cc0] ^ P[cc1] ^ P[cc2]; + helper.Add(y, output[cc0]); + + SPDLOG_DEBUG("y:{}, P[{}]:{}", + absl::BytesToHexString( + absl::string_view((char*)y, sizeof(uint128_t))), + cc0, + absl::BytesToHexString(absl::string_view( + (char*)&(*output[cc0]), sizeof(*output[cc0])))); + + helper.Add(y, output[cc1]); + + SPDLOG_DEBUG("y:{}, P[{}]:{}", + absl::BytesToHexString( + absl::string_view((char*)y, sizeof(uint128_t))), + cc1, + absl::BytesToHexString(absl::string_view( + (char*)&(*output[cc1]), sizeof(*output[cc1])))); + helper.Add(y, output[cc2]); + SPDLOG_DEBUG("y:{}, P[{}]:{}", + absl::BytesToHexString( + absl::string_view((char*)y, sizeof(uint128_t))), + cc2, + absl::BytesToHexString(absl::string_view( + (char*)&(*output[cc2]), sizeof(*output[cc2])))); + + SPDLOG_DEBUG("doDense:{}", do_dense); + + // GF128_DENSE_BACKFILL; + if (do_dense) { + uint128_t d = dense_[i]; + uint128_t x = d; + helper.MultAdd(y, p2[0], x); + SPDLOG_DEBUG( + "d:{}, y:{} mDenseSize:{}", + absl::BytesToHexString(absl::string_view((char*)&d, sizeof(d))), + absl::BytesToHexString( + absl::string_view((char*)y, sizeof(uint128_t))), + dense_size); + + for (uint64_t i = 1; i < dense_size; ++i) { + x = (Galois128(x) * d).get<uint128_t>(0); + helper.MultAdd(y, p2[i], x); + SPDLOG_DEBUG( + "c:{}, y:{}", + absl::BytesToHexString(absl::string_view((char*)&c, sizeof(c))), + absl::BytesToHexString( + absl::string_view((char*)y, sizeof(uint128_t)))); + } + } + + // P[c] = y; + helper.Assign(output[c], y); + SPDLOG_DEBUG("P[{}]:{} y:{}", c, + absl::BytesToHexString(absl::string_view( + (char*)&(*output[c]), sizeof(*output[c]))), + absl::BytesToHexString( + absl::string_view((char*)y, sizeof(uint128_t)))); + } + } else { + for (uint64_t k = 0; k < main_rows.size(); ++k) { + auto i = *row_iter; + auto c = *out_col_iter; + ++out_col_iter; + ++row_iter; + + // auto y = X[i]; + helper.Assign(y, values[i]); + + auto row = &rows_[i * weight]; + for (uint64_t j = 0; j < weight; ++j) { + auto cc = row[j]; + helper.Add(y, output[cc]); + // y = y ^ P[cc]; + } + + GF128_DENSE_BACKFILL; + + // P[c] = y; + helper.Assign(output[c], y); + } + } +} + +template <typename IdxType> +void Paxos<IdxType>::BackfillBinary( + absl::Span<IdxType> main_rows, absl::Span<IdxType> main_cols, + absl::Span<std::array<IdxType, 2>> gap_rows, const PxVector& X, PxVector& P, + PxVector::Helper& h, + const std::shared_ptr<yacl::crypto::Prg<uint8_t>>& prng) { + auto gg = gap_rows.size(); + + // the dense columns which index the gap. + std::vector<uint64_t> gap_cols; + + // masks that will be used to select the + // bits of the dense columns. + std::vector<uint64_t> dense_masks(g); + + // the dense part of the paxos. + auto p2 = P.subspan(sparse_size); + + YACL_ENFORCE(gg <= g, "gg:{}<=g:{}", gg, g); + + if (gg) { + auto fcinv = GetFCInv(main_rows, main_cols, gap_rows); + + // get the columns for the gap which define + // B, E and therefore EE. + gap_cols = GetGapCols(fcinv, gap_rows); + + if (prng) { + RandomizeDenseCols(p2, h, absl::MakeSpan(gap_cols), prng); + } + + // auto PP = + // x2' = x2 - D r - FC^-1 x1 + auto xx2 = + GetX2Prime(fcinv, absl::MakeSpan(gap_rows), absl::MakeSpan(gap_cols), X, + prng ? P : PxVector{}, h); + + // E' = E - FC^-1 B + DenseMtx EE = + GetEPrime(fcinv, absl::MakeSpan(gap_rows), absl::MakeSpan(gap_cols)); + auto EEInv = EE.Invert(); + + // now we compute + // p2 = E'^-1 * x2' + // = (-FC^-1B + E)^-1 * (x2 - D r - FC^-1 x1) + for (uint64_t i = 0; i < g; ++i) { + auto pp = p2[gap_cols[i]]; + // if (pp != oc::ZeroBlock) + // throw RTE_LOC; + + for (uint64_t j = 0; j < g; ++j) { + if (EEInv(i, j)) { + // pp = pp ^ xx2[j] + h.Add(pp, xx2[j]); + } + } + } + + for (uint64_t i = 0; i < g; ++i) { + dense_masks[i] = 1ull << gap_cols[i]; + } + + } else if (prng) { + for (uint64_t i = 0; i < static_cast<uint64_t>(p2.size()); ++i) + h.Randomize(p2[i], prng); + } + + auto out_col_iter = main_cols.rbegin(); + auto row_iter = main_rows.rbegin(); + + // get a temporary element. + auto yy = h.NewElement(); + + // get its pointer + auto y = h.AsPtr(yy); + + for (uint64_t k = 0; k < main_rows.size(); ++k) { + auto i = *row_iter; + auto c = *out_col_iter; + ++out_col_iter; + ++row_iter; + + // y = X[i] + h.Assign(y, X[i]); + + auto row = &rows_[i * weight]; + for (uint64_t j = 0; j < weight; ++j) { + auto cc = row[j]; + + // y += X[i] + h.Add(y, P[cc]); + } + + YACL_ENFORCE(dense_size <= 64); + + if (prng) { + auto d = yacl::DecomposeUInt128(dense_[i]).first; + for (uint64_t j = 0; j < dense_size; ++j) { + if (d & 1) { + // y += p2[j] + h.Add(y, p2[j]); + } + d >>= 1; + } + } else { + for (uint64_t j = 0; j < g; ++j) { + YACL_ENFORCE(dense_size <= 64); + + if (yacl::DecomposeUInt128(dense_[i]).first & dense_masks[j]) { + h.Add(y, p2[gap_cols[j]]); + } + } + } + + h.Assign(P[c], y); + } +} + +template <typename IdxType> +typename Paxos<IdxType>::FCInv Paxos<IdxType>::GetFCInv( + absl::Span<IdxType> main_rows, absl::Span<IdxType> main_cols, + absl::Span<std::array<IdxType, 2>> gap_rows) const { + // maps the H column indexes to F,C column Indexes + std::vector<IdxType> col_mapping; + Paxos<IdxType>::FCInv ret(gap_rows.size()); + auto m = main_rows.size(); + + // the input rows are in reverse order compared to the + // logical algorithm. This inverts the row index. + auto invert_row_idx = [m](auto i) { return m - i - 1; }; + + for (uint64_t i = 0; i < gap_rows.size(); ++i) { + if (std::memcmp(&(rows_[gap_rows[i][0] * weight]), + &(rows_[gap_rows[i][1] * weight]), + weight * sizeof(IdxType)) == 0) { + // special/common case where FC^-1 [i] = 0000100000 + // where the 1 is at position gapRows[i][1]. This code is + // used to speed up this common case. + ret.mtx[i].push_back(gap_rows[i][1]); + } else { + // for the general case we need to implicitly create the C + // matrix. The issue is that currently C is defined by mainRows + // and mainCols and this form isn't ideal for the computation + // of computing F C^-1. In particular, we will need to know which + // columns of the overall matrix H live in C. To do this we will construct + // colMapping. For columns of H that are in C, colMapping will give us + // the column in C. We only construct this mapping when its needed. + if (col_mapping.size() == 0) { + col_mapping.resize(size(), -1); + for (uint64_t i = 0; i < m; ++i) { + col_mapping[main_cols[invert_row_idx(i)]] = i; + } + } + + // the current row of F. We initialize this as just F_i + // and then Xor in rows of C until its the zero row. + std::set<IdxType, std::greater<IdxType>> row; + for (uint64_t j = 0; j < weight; ++j) { + auto c1 = rows_[gap_rows[i][0] * weight + j]; + if (col_mapping[c1] != IdxType(-1)) { + row.insert(col_mapping[c1]); + } + } + + while (row.size()) { + // the column of C, F that we will cancel (by adding + // the corresponding row of C to F_i. We will pick the + // row of C as the row with index CCol. + auto CCol = *row.begin(); + + // the row of C we will add to F_i + auto CRow = CCol; + + // the row of H that we will add to F_i + auto HRow = main_rows[invert_row_idx(CRow)]; + ret.mtx[i].push_back(HRow); + + for (size_t j = 0; j < weight; ++j) { + auto HCol = rows_[HRow * weight + j]; + auto CCol2 = col_mapping[HCol]; + SPDLOG_DEBUG("CCol:{}, CCol2:{}", CCol, CCol2); + + if (CCol2 != IdxType(-1)) { + YACL_ENFORCE(CCol2 <= CCol, "CCol:{}, CCol2:{}", CCol, CCol2); + + // Xor in the row CRow from C into the current + // row of F + auto iter = row.find(CCol2); + if (iter == row.end()) { + row.insert(CCol2); + } else { + row.erase(iter); + } + } + } + + YACL_ENFORCE((row.size() == 0) || (*row.begin() != CCol)); + } + } + } + + return ret; +} + +template <typename IdxType> +std::vector<uint64_t> Paxos<IdxType>::GetGapCols( + const FCInv& fcinv, absl::Span<std::array<IdxType, 2>> gap_rows) const { + if (gap_rows.size() == 0) return {}; + + auto g = gap_rows.size(); + uint64_t ci = 0; + uint64_t e = Choose(dense_size, g); + + // E' = -FC^-1B + E + DenseMtx EE; + + while (true) { + // TDOD, make the linear time. + auto gap_cols = ithCombination(ci, dense_size, g); + ++ci; + YACL_ENFORCE(ci <= e, "failed to find invertible matrix. {}"); + + EE.resize(g, g); + for (uint64_t i = 0; i < g; ++i) { + uint128_t FCB = yacl::MakeUint128(0, 0); + for (auto c : fcinv.mtx[i]) { + FCB = FCB ^ dense_[c]; + } + + // EE = E + FC^-1 B + uint128_t EERow = dense_[gap_rows[i][0]] ^ FCB; + for (uint64_t j = 0; j < g; ++j) { + EE(i, j) = *BitIterator((uint8_t*)&EERow, gap_cols[j]); + } + } + + auto EEInv = EE.Invert(); + if (EEInv.rows()) { + return gap_cols; + } + } +} + +template <typename IdxType> +PxVector Paxos<IdxType>::GetX2Prime(const FCInv& fcinv, + absl::Span<std::array<IdxType, 2>> gap_rows, + absl::Span<uint64_t> gap_cols, + const PxVector& X, const PxVector& P, + PxVector::Helper& helper) { + YACL_ENFORCE(X.size() == num_items_); + bool randomized = P.size() != 0; + + auto g = gap_rows.size(); + PxVector xx2 = helper.NewVec(g); + + for (uint64_t i = 0; i < g; ++i) { + // x2' = x2 - FC^-1 x1 + helper.Assign(xx2[i], X[gap_rows[i][0]]); + for (auto j : fcinv.mtx[i]) { + helper.Add(xx2[i], X[j]); + } + } + + // x2' = x2 - D' r - FC^-1 x1 + if (randomized) { + YACL_ENFORCE(P.size() == dense_size + sparse_size); + auto p2 = P.subspan(sparse_size); + + // note that D' only has a dense part because we + // assume only duplcate rows in the gap, can + // therefore the sparse part of D' cancels. + for (uint64_t i = 0; i < dense_size; ++i) { + if (std::find(gap_cols.begin(), gap_cols.end(), i) == gap_cols.end()) { + for (uint64_t j = 0; j < g; ++j) { + auto dense = dense_[gap_rows[j][0]]; //^ mDense[gapRows[j][1]]; + for (auto k : fcinv.mtx[j]) { + dense = dense ^ dense_[k]; + } + + if (*BitIterator((uint8_t*)&dense, i)) { + helper.Add(xx2[j], p2[i]); + } + } + } + } + } + + return xx2; +} + +template <typename IdxType> +DenseMtx Paxos<IdxType>::GetEPrime(const FCInv& fcinv, + absl::Span<std::array<IdxType, 2>> gap_rows, + absl::Span<uint64_t> gap_cols) { + auto g = gap_rows.size(); + + // E' = E - FC^-1 B + DenseMtx EE(g, g); + + for (uint64_t i = 0; i < g; ++i) { + // EERow = E - FC^-1 B + uint128_t EERow = dense_[gap_rows[i][0]]; + for (auto j : fcinv.mtx[i]) { + EERow = EERow ^ dense_[j]; + } + + // select the gap columns bits. + for (uint64_t j = 0; j < g; ++j) { + EE(i, j) = *BitIterator((uint8_t*)&EERow, gap_cols[j]); + } + } + + return EE; +} + +template <typename IdxType> +void Paxos<IdxType>::RandomizeDenseCols( + PxVector& p2, PxVector::Helper& h, absl::Span<uint64_t> gap_cols, + const std::shared_ptr<yacl::crypto::Prg<uint8_t>>& prng) { + YACL_ENFORCE(prng); + + // handle the dense part of D. + for (uint64_t i = 0; i < dense_size; ++i) { + if (std::find(gap_cols.begin(), gap_cols.end(), i) == gap_cols.end()) { + h.Randomize(p2[i], prng); + } + } +} + +template <typename IdxType> +void Paxos<IdxType>::Decode(absl::Span<const uint128_t> inputs, + absl::Span<uint128_t> values, + absl::Span<uint128_t> p) { + PxVector VV(values); + PxVector PP(p); + auto h = PP.DefaultHelper(); + Decode(inputs, VV, PP, h); +} + +template <typename IdxType> +void Paxos<IdxType>::Decode(absl::Span<const uint128_t> inputs, + PxVector& values, const PxVector& PP, + PxVector::Helper& h) { + SPDLOG_DEBUG("decode begin,inputs.size():{}, values.size():{}, PP.size():{}", + inputs.size(), values.size(), PP.size()); + + YACL_ENFORCE(PP.size() == size(), "{} ?= {}", PP.size(), size()); + + auto batch_count = inputs.size() / kPaxosBuildRowSize * kPaxosBuildRowSize; + + auto in_iter = inputs.data(); + + SPDLOG_DEBUG("add_to_decode_:{}, gPaxosBuildRowSize:{}, mWeight:{}", + add_to_decode_, kPaxosBuildRowSize, weight); + + std::vector<IdxType> rows(kPaxosBuildRowSize * weight); + std::vector<uint128_t> dense(kPaxosBuildRowSize); + + if (add_to_decode_) { + PxVector v = h.NewVec(kPaxosBuildRowSize); + + for (uint64_t i = 0; i < batch_count; + i += kPaxosBuildRowSize, in_iter += kPaxosBuildRowSize) { + hasher_.HashBuildRow32(absl::MakeSpan(in_iter, kPaxosBuildRowSize), + absl::MakeSpan(rows), absl::MakeSpan(dense)); + + Decode32(absl::MakeSpan(rows), absl::MakeSpan(dense), + absl::MakeSpan(v[0], kPaxosBuildRowSize), PP, h); + + for (uint64_t j = 0; j < kPaxosBuildRowSize; j += 8) { + h.Add(values[i + j + 0], v[j + 0]); + h.Add(values[i + j + 1], v[j + 1]); + h.Add(values[i + j + 2], v[j + 2]); + h.Add(values[i + j + 3], v[j + 3]); + h.Add(values[i + j + 4], v[j + 4]); + h.Add(values[i + j + 5], v[j + 5]); + h.Add(values[i + j + 6], v[j + 6]); + h.Add(values[i + j + 7], v[j + 7]); + } + } + + for (uint64_t i = batch_count; i < inputs.size(); ++i, ++in_iter) { + hasher_.HashBuildRow1(*in_iter, absl::MakeSpan(rows.data(), weight), + &dense[0]); + Decode1(absl::MakeSpan(rows.data(), weight), dense[0], v[0], PP, h); + h.Add(values[i], v[0]); + } + } else { + for (uint64_t i = 0; i < batch_count; + i += kPaxosBuildRowSize, in_iter += kPaxosBuildRowSize) { + hasher_.HashBuildRow32(absl::MakeSpan(in_iter, kPaxosBuildRowSize), + absl::MakeSpan(rows), absl::MakeSpan(dense)); + Decode32(absl::MakeSpan(rows), absl::MakeSpan(dense), + absl::MakeSpan(values[i], kPaxosBuildRowSize), PP, h); + } + + for (uint64_t i = batch_count; i < inputs.size(); ++i, ++in_iter) { + hasher_.HashBuildRow1(*in_iter, absl::MakeSpan(rows.data(), weight), + &dense[0]); + Decode1(absl::MakeSpan(rows.data(), weight), dense[0], values[i], PP, h); + } + } + + SPDLOG_DEBUG("decode done"); +} + +template <typename IdxType> +void Paxos<IdxType>::Decode32(absl::Span<IdxType> rows_span, + absl::Span<uint128_t> dense_span, + absl::Span<uint128_t> values_span, + const PxVector& p_, const PxVector::Helper& h) { + const uint128_t* __restrict p = p_[0]; + + for (uint64_t j = 0; j < 4; ++j) { + const IdxType* __restrict rows = rows_span.data() + j * 8 * weight; + uint128_t* __restrict values = h.IterPlus(values_span.data(), j * 8); + + auto c00 = rows[weight * 0 + 0]; + auto c01 = rows[weight * 1 + 0]; + auto c02 = rows[weight * 2 + 0]; + auto c03 = rows[weight * 3 + 0]; + auto c04 = rows[weight * 4 + 0]; + auto c05 = rows[weight * 5 + 0]; + auto c06 = rows[weight * 6 + 0]; + auto c07 = rows[weight * 7 + 0]; + + auto v00 = h.IterPlus(values, 0); + auto v01 = h.IterPlus(values, 1); + auto v02 = h.IterPlus(values, 2); + auto v03 = h.IterPlus(values, 3); + auto v04 = h.IterPlus(values, 4); + auto v05 = h.IterPlus(values, 5); + auto v06 = h.IterPlus(values, 6); + auto v07 = h.IterPlus(values, 7); + + auto p00 = h.IterPlus(p, c00); + auto p01 = h.IterPlus(p, c01); + auto p02 = h.IterPlus(p, c02); + auto p03 = h.IterPlus(p, c03); + auto p04 = h.IterPlus(p, c04); + auto p05 = h.IterPlus(p, c05); + auto p06 = h.IterPlus(p, c06); + auto p07 = h.IterPlus(p, c07); + + h.Assign(v00, p00); + h.Assign(v01, p01); + h.Assign(v02, p02); + h.Assign(v03, p03); + h.Assign(v04, p04); + h.Assign(v05, p05); + h.Assign(v06, p06); + h.Assign(v07, p07); + } + + for (uint64_t j = 1; j < weight; ++j) { + for (uint64_t k = 0; k < 4; ++k) { + const IdxType* __restrict rows = rows_span.data() + k * 8 * weight; + uint128_t* __restrict values = h.IterPlus(values_span.data(), k * 8); + + auto c0 = rows[weight * 0 + j]; + auto c1 = rows[weight * 1 + j]; + auto c2 = rows[weight * 2 + j]; + auto c3 = rows[weight * 3 + j]; + auto c4 = rows[weight * 4 + j]; + auto c5 = rows[weight * 5 + j]; + auto c6 = rows[weight * 6 + j]; + auto c7 = rows[weight * 7 + j]; + + auto v0 = h.IterPlus(values, 0); + auto v1 = h.IterPlus(values, 1); + auto v2 = h.IterPlus(values, 2); + auto v3 = h.IterPlus(values, 3); + auto v4 = h.IterPlus(values, 4); + auto v5 = h.IterPlus(values, 5); + auto v6 = h.IterPlus(values, 6); + auto v7 = h.IterPlus(values, 7); + + auto p0 = h.IterPlus(p, c0); + auto p1 = h.IterPlus(p, c1); + auto p2 = h.IterPlus(p, c2); + auto p3 = h.IterPlus(p, c3); + auto p4 = h.IterPlus(p, c4); + auto p5 = h.IterPlus(p, c5); + auto p6 = h.IterPlus(p, c6); + auto p7 = h.IterPlus(p, c7); + + h.Add(v0, p0); + h.Add(v1, p1); + h.Add(v2, p2); + h.Add(v3, p3); + h.Add(v4, p4); + h.Add(v5, p5); + h.Add(v6, p6); + h.Add(v7, p7); + } + } + + if (dt == DenseType::GF128) { + const uint128_t* __restrict p2 = h.IterPlus(p, sparse_size); + + std::array<uint128_t, 32> xx; + memcpy(xx.data(), dense_span.data(), sizeof(uint128_t) * 32); + + for (uint64_t k = 0; k < 4; ++k) { + uint128_t* __restrict values = h.IterPlus(values_span.data(), k * 8); + auto x = xx.data() + k * 8; + + h.MultAdd(h.IterPlus(values, 0), p2, x[0]); + h.MultAdd(h.IterPlus(values, 1), p2, x[1]); + h.MultAdd(h.IterPlus(values, 2), p2, x[2]); + h.MultAdd(h.IterPlus(values, 3), p2, x[3]); + h.MultAdd(h.IterPlus(values, 4), p2, x[4]); + h.MultAdd(h.IterPlus(values, 5), p2, x[5]); + h.MultAdd(h.IterPlus(values, 6), p2, x[6]); + h.MultAdd(h.IterPlus(values, 7), p2, x[7]); + } + + for (uint64_t i = 1; i < dense_size; ++i) { + p2 = h.IterPlus(p2, 1); + + for (uint64_t k = 0; k < 4; ++k) { + auto x = xx.data() + k * 8; + auto dense = dense_span.data() + k * 8; + uint128_t* __restrict values = h.IterPlus(values_span.data(), k * 8); + + x[0] = (Galois128(x[0]) * dense[0]).get<uint128_t>(0); + x[1] = (Galois128(x[1]) * dense[1]).get<uint128_t>(0); + x[2] = (Galois128(x[2]) * dense[2]).get<uint128_t>(0); + x[3] = (Galois128(x[3]) * dense[3]).get<uint128_t>(0); + x[4] = (Galois128(x[4]) * dense[4]).get<uint128_t>(0); + x[5] = (Galois128(x[5]) * dense[5]).get<uint128_t>(0); + x[6] = (Galois128(x[6]) * dense[6]).get<uint128_t>(0); + x[7] = (Galois128(x[7]) * dense[7]).get<uint128_t>(0); + + h.MultAdd(h.IterPlus(values, 0), p2, x[0]); + h.MultAdd(h.IterPlus(values, 1), p2, x[1]); + h.MultAdd(h.IterPlus(values, 2), p2, x[2]); + h.MultAdd(h.IterPlus(values, 3), p2, x[3]); + h.MultAdd(h.IterPlus(values, 4), p2, x[4]); + h.MultAdd(h.IterPlus(values, 5), p2, x[5]); + h.MultAdd(h.IterPlus(values, 6), p2, x[6]); + h.MultAdd(h.IterPlus(values, 7), p2, x[7]); + } + } + } else { + std::array<uint64_t, 8> d2; + std::array<uint8_t, 8> b; + + for (uint64_t k = 0; k < 4; ++k) { + auto values = h.IterPlus(values_span.data(), k * 8); + auto dense = dense_span.data() + k * 8; + + YACL_ENFORCE(dense_size <= 64); + for (uint64_t i = 0; i < 8; ++i) { + d2[i] = yacl::DecomposeUInt128(dense[i]).first; + } + + for (uint64_t i = 0; i < dense_size; ++i) { + b[0] = d2[0] & 1; + b[1] = d2[1] & 1; + b[2] = d2[2] & 1; + b[3] = d2[3] & 1; + b[4] = d2[4] & 1; + b[5] = d2[5] & 1; + b[6] = d2[6] & 1; + b[7] = d2[7] & 1; + + d2[0] = d2[0] >> 1; + d2[1] = d2[1] >> 1; + d2[2] = d2[2] >> 1; + d2[3] = d2[3] >> 1; + d2[4] = d2[4] >> 1; + d2[5] = d2[5] >> 1; + d2[6] = d2[6] >> 1; + d2[7] = d2[7] >> 1; + + auto p2 = h.IterPlus(p, sparse_size + i); + + h.MultAdd(h.IterPlus(values, 0), p2, b[0]); + h.MultAdd(h.IterPlus(values, 1), p2, b[1]); + h.MultAdd(h.IterPlus(values, 2), p2, b[2]); + h.MultAdd(h.IterPlus(values, 3), p2, b[3]); + h.MultAdd(h.IterPlus(values, 4), p2, b[4]); + h.MultAdd(h.IterPlus(values, 5), p2, b[5]); + h.MultAdd(h.IterPlus(values, 6), p2, b[6]); + h.MultAdd(h.IterPlus(values, 7), p2, b[7]); + } + } + } +} + +// decodes one instances. rows should contain the row indicies, dense the +// dense part. values is where the values are written to. p is the Paxos, h is +// the value op. helper. +template <typename IdxType> +void Paxos<IdxType>::Decode1(absl::Span<IdxType> rows, const uint128_t dense, + uint128_t* values, const PxVector& p, + const PxVector::Helper& h) { + h.Assign(values, p[rows[0]]); + for (uint64_t j = 1; j < weight; ++j) { + h.Add(values, p[rows[j]]); + } + + SPDLOG_DEBUG("mSparseSize:{}, mDenseSize:{}, p.size():{}", sparse_size, + dense_size, p.size()); + + if (dt == DenseType::GF128) { + uint128_t x = dense; + h.MultAdd(values, p[sparse_size], x); + + for (uint64_t i = 1; i < dense_size; ++i) { + x = (Galois128(x) * dense).get<uint128_t>(0); + h.MultAdd(values, p[i + sparse_size], x); + } + } else { + for (uint64_t i = 0; i < dense_size; ++i) { + if (*BitIterator((uint8_t*)(&dense), i)) { + h.Add(values, p[i + sparse_size]); + } + } + } +} + +template class Paxos<uint64_t>; +template class Paxos<uint32_t>; +template class Paxos<uint16_t>; +template class Paxos<uint8_t>; + +} // namespace psi::psi::okvs diff --git a/psi/psi/core/vole_psi/okvs/paxos.h b/psi/psi/core/vole_psi/okvs/paxos.h new file mode 100644 index 00000000..3f6ebc19 --- /dev/null +++ b/psi/psi/core/vole_psi/okvs/paxos.h @@ -0,0 +1,226 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <array> +#include <memory> +#include <string> +#include <vector> + +#include "libdivide.h" +#include "yacl/utils/platform_utils.h" + +#include "psi/psi/core/vole_psi/okvs/dense_mtx.h" +#include "psi/psi/core/vole_psi/okvs/galois128.h" +#include "psi/psi/core/vole_psi/okvs/paxos_hash.h" +#include "psi/psi/core/vole_psi/okvs/paxos_utils.h" + +namespace psi::psi::okvs { + +struct PaxosParam { + // the type of dense columns. + enum class DenseType { Binary, GF128 }; + + uint64_t sparse_size = 0; + uint64_t dense_size = 0; + uint64_t weight = 0; + uint64_t g = 0; + uint64_t ssp = 40; + DenseType dt = DenseType::GF128; + + PaxosParam() = default; + PaxosParam(const PaxosParam&) = default; + PaxosParam& operator=(const PaxosParam&) = default; + + PaxosParam(uint64_t num_items, uint64_t weight = 3, uint64_t ssp = 40, + DenseType dt = DenseType::GF128) { + Init(num_items, weight, ssp, dt); + } + + // computes the paxos parameters based the parameters. + void Init(uint64_t num_items, uint64_t weight = 3, uint64_t ssp = 40, + DenseType dt = DenseType::GF128); + + // the size of the paxos data structure. + uint64_t size() const { return sparse_size + dense_size; } +}; + +// [RR22] Blazing Fast PSI from Improved OKVS and Subfield VOLE, CCS 2022 +// https://eprint.iacr.org/2022/320 +// okvs code reference https://github.com/Visa-Research/volepsi +// The core Paxos algorithm. The template parameter +// IdxType should be in {u8,u16,u32,u64} and large +// enough to fit the paxos size value. +template <typename IdxType> +class Paxos : public PaxosParam { + public: + Paxos() = default; + Paxos(const Paxos&) = default; + Paxos(Paxos&&) = default; + Paxos& operator=(const Paxos&) = default; + Paxos& operator=(Paxos&&) = default; + + // initialize the paxos with the given parameters. + void Init(uint64_t num_items, uint64_t weight, uint64_t ssp, + PaxosParam::DenseType dt, uint128_t seed) { + PaxosParam p(num_items, weight, ssp, dt); + Init(num_items, p, seed); + } + + // initialize the paxos with the given parameters. + void Init(uint64_t num_items, PaxosParam p, uint128_t seed); + + // set the input keys which define the paxos matrix. After that, + // encode can be called more than once. + void SetInput(absl::Span<const uint128_t> inputs); + + void SetInput(MatrixView<IdxType> rows, absl::Span<uint128_t> dense, + absl::Span<absl::Span<IdxType>> cols, + absl::Span<IdxType> col_backing, + absl::Span<IdxType> col_weights); + + void Encode( + absl::Span<uint128_t> values, absl::Span<uint128_t> output, + const std::shared_ptr<yacl::crypto::Prg<uint8_t>>& prng = nullptr) { + PxVector V(values); + PxVector P(output); + auto h = P.DefaultHelper(); + Encode(V, P, h, prng); + } + + // encode the given input with the given paxos p. Vec and ConstVec should + // meet the PxVector concept... Helper used to perform operations on values. + void Encode( + const PxVector& values, PxVector& output, PxVector::Helper& h, + const std::shared_ptr<yacl::crypto::Prg<uint8_t>>& prng = nullptr); + + // Decode the given input based on the data paxos structure p. The + // output is written to values. + void Decode(absl::Span<const uint128_t> input, absl::Span<uint128_t> values, + absl::Span<uint128_t> p); + + void Decode(absl::Span<const uint128_t> inputs, PxVector& values, + const PxVector& PP, PxVector::Helper& h); + + // decodes 32 instances. rows should contain the row indicies, dense the dense + // part. values is where the values are written to. p is the Paxos, h is the + // value op. helper. + void Decode32(absl::Span<IdxType> rows_span, absl::Span<uint128_t> dense_span, + absl::Span<uint128_t> values_span, const PxVector& p, + const PxVector::Helper& h); + + // decodes one instances. rows should contain the row indicies, dense the + // dense part. values is where the values are written to. p is the Paxos, h is + // the value op. helper. + void Decode1(absl::Span<IdxType> rows, const uint128_t dense, + uint128_t* values, const PxVector& p, const PxVector::Helper& h); + + // A sparse representation of the F * C^-1 matrix. + struct FCInv { + explicit FCInv(uint64_t n) : mtx(n) {} + std::vector<std::vector<IdxType>> mtx; + }; + + // the method for generating the row data based on the input value. + PaxosHash<IdxType> hasher_; + + private: + // helper function that generates the column data given that + // the row data has been populated (via setInput(...)). + void RebuildColumns(absl::Span<IdxType> col_weights, uint64_t total_weight); + + // perform the tringulization algorithm for encoding. This + // populates mainRows,mainCols with the rows/columns of C + // gapRows are all the rows that are in the gap. + void Triangulate(std::vector<IdxType>& main_rows, + std::vector<IdxType>& main_cols, + std::vector<std::array<IdxType, 2>>& gap_rows); + + // once triangulated, this is used to assign values + // to output (paxos). + void Backfill(absl::Span<IdxType> main_rows, absl::Span<IdxType> main_cols, + absl::Span<std::array<IdxType, 2>> gap_rows, + const PxVector& values, PxVector& output, PxVector::Helper& h, + const std::shared_ptr<yacl::crypto::Prg<uint8_t>>& prng); + + // once triangulated, this is used to assign values + // to output (paxos). Use the gf128 dense algorithm. + void BackfillGf128(absl::Span<IdxType> main_rows, + absl::Span<IdxType> main_cols, + absl::Span<std::array<IdxType, 2>> gap_rows, + const PxVector& values, PxVector& output, + PxVector::Helper& h, + const std::shared_ptr<yacl::crypto::Prg<uint8_t>>& prng); + + // once triangulated, this is used to assign values + // to output (paxos). Use the classic binary dense algorithm. + void BackfillBinary(absl::Span<IdxType> main_rows, + absl::Span<IdxType> main_cols, + absl::Span<std::array<IdxType, 2>> gap_rows, + const PxVector& values, PxVector& output, + PxVector::Helper& h, + const std::shared_ptr<yacl::crypto::Prg<uint8_t>>& prng); + + // returns the sparse representation of the F * C^-1 matrix. + FCInv GetFCInv(absl::Span<IdxType> main_rows, absl::Span<IdxType> main_cols, + absl::Span<std::array<IdxType, 2>> gap_rows) const; + + // returns which columns are used for the gap. This + // is only used for binary dense method. + std::vector<uint64_t> GetGapCols( + const FCInv& fcinv, absl::Span<std::array<IdxType, 2>> gap_rows) const; + + // returns x2' = x2 - D' r - FC^-1 x1 + PxVector GetX2Prime(const FCInv& fcinv, + absl::Span<std::array<IdxType, 2>> gap_rows, + absl::Span<uint64_t> gap_cols, const PxVector& X, + const PxVector& P, PxVector::Helper& h); + + // returns E' = -FC^-1B + E + DenseMtx GetEPrime(const FCInv& fcinv, + absl::Span<std::array<IdxType, 2>> gap_rows, + absl::Span<uint64_t> gap_cols); + + void RandomizeDenseCols( + PxVector&, PxVector::Helper&, absl::Span<uint64_t> gap_cols, + const std::shared_ptr<yacl::crypto::Prg<uint8_t>>& prng); + + // the number of items to be encoded. + IdxType num_items_ = 0; + + // the encoding/decoding seed. + uint128_t seed_ = 0; + + // The dense part of the paxos matrix + std::vector<uint128_t> dense_; + + // matrix number_items * weight + std::vector<IdxType> rows_; + + // the sparse columns of the matrix + std::vector<absl::Span<IdxType>> cols_; + + // the memory used to store the column data. + std::vector<IdxType> col_backing_; + + // A data structure used to track the current weight of the rows.s + WeightData<IdxType> weight_sets_; + + // when decoding, add the decoded value to the + // output, as opposed to overwriting. + bool add_to_decode_ = false; +}; + +} // namespace psi::psi::okvs diff --git a/psi/psi/core/vole_psi/okvs/paxos_hash.cc b/psi/psi/core/vole_psi/okvs/paxos_hash.cc new file mode 100644 index 00000000..405af8a9 --- /dev/null +++ b/psi/psi/core/vole_psi/okvs/paxos_hash.cc @@ -0,0 +1,318 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/vole_psi/okvs/paxos_hash.h" + +#include <algorithm> + +#include "yacl/base/int128.h" +#include "yacl/utils/platform_utils.h" + +namespace psi::psi::okvs { + +template <typename IdxType> +void PaxosHash<IdxType>::mod32(uint64_t* vals, uint64_t mod_idx) const { + auto divider = &mods[mod_idx]; + auto mod_val = mod_vals[mod_idx]; + + DoMod32(vals, divider, mod_val); +} + +template <typename IdxType> +void PaxosHash<IdxType>::BuildRow32(const absl::Span<uint128_t> hash, + absl::Span<IdxType> rows) const { + if ((weight == 3) && yacl::hasAVX2()) { +#ifdef __x86_64__ + yacl::block row128_[3][16]; + + for (uint64_t i = 0; i < weight; ++i) { + auto ll = (uint64_t*)row128_[i]; + + for (uint64_t j = 0; j < 32; ++j) { + std::memcpy(&ll[j], (uint8_t*)(&hash[j]) + sizeof(uint32_t) * i, + sizeof(uint64_t)); + } + mod32(ll, i); + } + + for (uint64_t i = 0; i < 2; ++i) { + std::array<yacl::block, 8> mask, max, min; + + std::array<yacl::block*, 3> row128{row128_[0] + i * 8, row128_[1] + i * 8, + row128_[2] + i * 8}; + + // mask = a > b ? -1 : 0; + mask[0] = _mm_cmpgt_epi64(row128[0][0], row128[1][0]); + mask[1] = _mm_cmpgt_epi64(row128[0][1], row128[1][1]); + mask[2] = _mm_cmpgt_epi64(row128[0][2], row128[1][2]); + mask[3] = _mm_cmpgt_epi64(row128[0][3], row128[1][3]); + mask[4] = _mm_cmpgt_epi64(row128[0][4], row128[1][4]); + mask[5] = _mm_cmpgt_epi64(row128[0][5], row128[1][5]); + mask[6] = _mm_cmpgt_epi64(row128[0][6], row128[1][6]); + mask[7] = _mm_cmpgt_epi64(row128[0][7], row128[1][7]); + + min[0] = row128[0][0] ^ row128[1][0]; + min[1] = row128[0][1] ^ row128[1][1]; + min[2] = row128[0][2] ^ row128[1][2]; + min[3] = row128[0][3] ^ row128[1][3]; + min[4] = row128[0][4] ^ row128[1][4]; + min[5] = row128[0][5] ^ row128[1][5]; + min[6] = row128[0][6] ^ row128[1][6]; + min[7] = row128[0][7] ^ row128[1][7]; + + // max = max(a,b) + max[0] = (min[0]) & mask[0]; + max[1] = (min[1]) & mask[1]; + max[2] = (min[2]) & mask[2]; + max[3] = (min[3]) & mask[3]; + max[4] = (min[4]) & mask[4]; + max[5] = (min[5]) & mask[5]; + max[6] = (min[6]) & mask[6]; + max[7] = (min[7]) & mask[7]; + max[0] = max[0] ^ row128[1][0]; + max[1] = max[1] ^ row128[1][1]; + max[2] = max[2] ^ row128[1][2]; + max[3] = max[3] ^ row128[1][3]; + max[4] = max[4] ^ row128[1][4]; + max[5] = max[5] ^ row128[1][5]; + max[6] = max[6] ^ row128[1][6]; + max[7] = max[7] ^ row128[1][7]; + + // min = min(a,b) + min[0] = min[0] ^ max[0]; + min[1] = min[1] ^ max[1]; + min[2] = min[2] ^ max[2]; + min[3] = min[3] ^ max[3]; + min[4] = min[4] ^ max[4]; + min[5] = min[5] ^ max[5]; + min[6] = min[6] ^ max[6]; + min[7] = min[7] ^ max[7]; + + // if (max == b) + // ++b + // ++max + mask[0] = _mm_cmpeq_epi64(max[0], row128[1][0]); + mask[1] = _mm_cmpeq_epi64(max[1], row128[1][1]); + mask[2] = _mm_cmpeq_epi64(max[2], row128[1][2]); + mask[3] = _mm_cmpeq_epi64(max[3], row128[1][3]); + mask[4] = _mm_cmpeq_epi64(max[4], row128[1][4]); + mask[5] = _mm_cmpeq_epi64(max[5], row128[1][5]); + mask[6] = _mm_cmpeq_epi64(max[6], row128[1][6]); + mask[7] = _mm_cmpeq_epi64(max[7], row128[1][7]); + row128[1][0] = _mm_sub_epi64(row128[1][0], mask[0]); + row128[1][1] = _mm_sub_epi64(row128[1][1], mask[1]); + row128[1][2] = _mm_sub_epi64(row128[1][2], mask[2]); + row128[1][3] = _mm_sub_epi64(row128[1][3], mask[3]); + row128[1][4] = _mm_sub_epi64(row128[1][4], mask[4]); + row128[1][5] = _mm_sub_epi64(row128[1][5], mask[5]); + row128[1][6] = _mm_sub_epi64(row128[1][6], mask[6]); + row128[1][7] = _mm_sub_epi64(row128[1][7], mask[7]); + max[0] = _mm_sub_epi64(max[0], mask[0]); + max[1] = _mm_sub_epi64(max[1], mask[1]); + max[2] = _mm_sub_epi64(max[2], mask[2]); + max[3] = _mm_sub_epi64(max[3], mask[3]); + max[4] = _mm_sub_epi64(max[4], mask[4]); + max[5] = _mm_sub_epi64(max[5], mask[5]); + max[6] = _mm_sub_epi64(max[6], mask[6]); + max[7] = _mm_sub_epi64(max[7], mask[7]); + + // if (c >= min) + // ++c + mask[0] = _mm_cmpgt_epi64(min[0], row128[2][0]); + mask[1] = _mm_cmpgt_epi64(min[1], row128[2][1]); + mask[2] = _mm_cmpgt_epi64(min[2], row128[2][2]); + mask[3] = _mm_cmpgt_epi64(min[3], row128[2][3]); + mask[4] = _mm_cmpgt_epi64(min[4], row128[2][4]); + mask[5] = _mm_cmpgt_epi64(min[5], row128[2][5]); + mask[6] = _mm_cmpgt_epi64(min[6], row128[2][6]); + mask[7] = _mm_cmpgt_epi64(min[7], row128[2][7]); + mask[0] = mask[0] ^ yacl::Uint128Max(); + mask[1] = mask[1] ^ yacl::Uint128Max(); + mask[2] = mask[2] ^ yacl::Uint128Max(); + mask[3] = mask[3] ^ yacl::Uint128Max(); + mask[4] = mask[4] ^ yacl::Uint128Max(); + mask[5] = mask[5] ^ yacl::Uint128Max(); + mask[6] = mask[6] ^ yacl::Uint128Max(); + mask[7] = mask[7] ^ yacl::Uint128Max(); + row128[2][0] = _mm_sub_epi64(row128[2][0], mask[0]); + row128[2][1] = _mm_sub_epi64(row128[2][1], mask[1]); + row128[2][2] = _mm_sub_epi64(row128[2][2], mask[2]); + row128[2][3] = _mm_sub_epi64(row128[2][3], mask[3]); + row128[2][4] = _mm_sub_epi64(row128[2][4], mask[4]); + row128[2][5] = _mm_sub_epi64(row128[2][5], mask[5]); + row128[2][6] = _mm_sub_epi64(row128[2][6], mask[6]); + row128[2][7] = _mm_sub_epi64(row128[2][7], mask[7]); + + mask[0] = _mm_cmpgt_epi64(max[0], row128[2][0]); + mask[1] = _mm_cmpgt_epi64(max[1], row128[2][1]); + mask[2] = _mm_cmpgt_epi64(max[2], row128[2][2]); + mask[3] = _mm_cmpgt_epi64(max[3], row128[2][3]); + mask[4] = _mm_cmpgt_epi64(max[4], row128[2][4]); + mask[5] = _mm_cmpgt_epi64(max[5], row128[2][5]); + mask[6] = _mm_cmpgt_epi64(max[6], row128[2][6]); + mask[7] = _mm_cmpgt_epi64(max[7], row128[2][7]); + mask[0] = mask[0] ^ yacl::Uint128Max(); + mask[1] = mask[1] ^ yacl::Uint128Max(); + mask[2] = mask[2] ^ yacl::Uint128Max(); + mask[3] = mask[3] ^ yacl::Uint128Max(); + mask[4] = mask[4] ^ yacl::Uint128Max(); + mask[5] = mask[5] ^ yacl::Uint128Max(); + mask[6] = mask[6] ^ yacl::Uint128Max(); + mask[7] = mask[7] ^ yacl::Uint128Max(); + row128[2][0] = _mm_sub_epi64(row128[2][0], mask[0]); + row128[2][1] = _mm_sub_epi64(row128[2][1], mask[1]); + row128[2][2] = _mm_sub_epi64(row128[2][2], mask[2]); + row128[2][3] = _mm_sub_epi64(row128[2][3], mask[3]); + row128[2][4] = _mm_sub_epi64(row128[2][4], mask[4]); + row128[2][5] = _mm_sub_epi64(row128[2][5], mask[5]); + row128[2][6] = _mm_sub_epi64(row128[2][6], mask[6]); + row128[2][7] = _mm_sub_epi64(row128[2][7], mask[7]); + + for (uint64_t j = 0; j < weight; ++j) { + IdxType* __restrict rowi = rows.data() + weight * 16 * i; + uint64_t* __restrict row64 = (uint64_t*)(row128[j]); + rowi[weight * 0 + j] = row64[0]; + rowi[weight * 1 + j] = row64[1]; + rowi[weight * 2 + j] = row64[2]; + rowi[weight * 3 + j] = row64[3]; + rowi[weight * 4 + j] = row64[4]; + rowi[weight * 5 + j] = row64[5]; + rowi[weight * 6 + j] = row64[6]; + rowi[weight * 7 + j] = row64[7]; + + rowi += 8 * weight; + row64 += 8; + + rowi[weight * 0 + j] = row64[0]; + rowi[weight * 1 + j] = row64[1]; + rowi[weight * 2 + j] = row64[2]; + rowi[weight * 3 + j] = row64[3]; + rowi[weight * 4 + j] = row64[4]; + rowi[weight * 5 + j] = row64[5]; + rowi[weight * 6 + j] = row64[6]; + rowi[weight * 7 + j] = row64[7]; + } + } +#endif + } else { + auto rows_ptr = rows.data(); + + for (uint64_t k = 0; k < 32; ++k) { + BuildRow(hash[k], absl::MakeSpan(rows_ptr, weight)); + rows_ptr += weight; + } + } +} + +template <typename IdxType> +void PaxosHash<IdxType>::BuildRow(const uint128_t& hash, + absl::Span<IdxType> rows) const { + SPDLOG_DEBUG("weight:{}", weight); + + if (weight == 3) { + uint32_t* rr = (uint32_t*)&hash; + auto rr0 = *(uint64_t*)(&rr[0]); + auto rr1 = *(uint64_t*)(&rr[1]); + auto rr2 = *(uint64_t*)(&rr[2]); + rows[0] = (IdxType)(rr0 % sparse_size); + rows[1] = (IdxType)(rr1 % (sparse_size - 1)); + rows[2] = (IdxType)(rr2 % (sparse_size - 2)); + + SPDLOG_DEBUG("rr0:{}, rr1:{}, rr2:{}", rr0, rr1, rr2); + SPDLOG_DEBUG("rr0:{}, row[1]:{}, row[2]:{}", rows[0], rows[1], rows[2]); + + YACL_ENFORCE(rows[0] < sparse_size); + YACL_ENFORCE(rows[1] < sparse_size); + YACL_ENFORCE(rows[2] < sparse_size); + + auto min = std::min<IdxType>(rows[0], rows[1]); + auto max = rows[0] + rows[1] - min; + + SPDLOG_DEBUG("max:{}, min:{}", max, min); + + if (max == rows[1]) { + ++rows[1]; + ++max; + } + + if (rows[2] >= min) { + ++rows[2]; + } + + if (rows[2] >= max) { + ++rows[2]; + } + + SPDLOG_DEBUG("max:{}, min:{}", max, min); + SPDLOG_DEBUG("rr0:{}, row[1]:{}, row[2]:{}", rows[0], rows[1], rows[2]); + } else { + Galois128 hh(hash); + for (uint64_t j = 0; j < weight; ++j) { + auto modulus = (sparse_size - j); + + hh = hh * hh; + + auto col_idx = hh.get<uint64_t>(0) % modulus; + + auto iter = rows.begin(); + auto end = rows.begin() + j; + while (iter != end) { + if (*iter <= col_idx) { + ++col_idx; + } else { + break; + } + ++iter; + } + + while (iter != end) { + end[0] = end[-1]; + --end; + } + + *iter = static_cast<IdxType>(col_idx); + } + } +} + +template <typename IdxType> +void PaxosHash<IdxType>::HashBuildRow32( + const absl::Span<const uint128_t> in_iter, absl::Span<IdxType> rows, + absl::Span<uint128_t> hash) const { + YACL_ENFORCE(in_iter.size() == 32); + + YACL_ENFORCE(rows.size() == 32 * weight); + + aes_crhash->Hash(in_iter, hash); + BuildRow32(hash, rows); +} + +template <typename IdxType> +void PaxosHash<IdxType>::HashBuildRow1(const uint128_t& input, + absl::Span<IdxType> rows, + uint128_t* hash) const { + YACL_ENFORCE(rows.size() == weight); + + aes_crhash->Hash(absl::MakeSpan(&input, 1), absl::MakeSpan(hash, 1)); + + BuildRow(*hash, rows); +} + +template class PaxosHash<uint8_t>; +template class PaxosHash<uint16_t>; +template class PaxosHash<uint32_t>; +template class PaxosHash<uint64_t>; +template class PaxosHash<uint128_t>; + +} // namespace psi::psi::okvs diff --git a/psi/psi/core/vole_psi/okvs/paxos_hash.h b/psi/psi/core/vole_psi/okvs/paxos_hash.h new file mode 100644 index 00000000..5dba9f17 --- /dev/null +++ b/psi/psi/core/vole_psi/okvs/paxos_hash.h @@ -0,0 +1,195 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <memory> +#include <string> +#include <vector> + +#include "absl/types/span.h" +#include "libdivide.h" +#include "yacl/math/gadget.h" +#include "yacl/utils/platform_utils.h" + +#include "psi/psi/core/vole_psi/okvs/aes_crhash.h" +#include "psi/psi/core/vole_psi/okvs/galois128.h" + +namespace psi::psi::okvs { + +namespace { + +constexpr uint64_t DivCeil(uint64_t val, uint64_t d) { + return (val + d - 1) / d; +} +constexpr uint64_t RoundUpTo(uint64_t val, uint64_t step) { + return DivCeil(val, step) * step; +} + +using block256 = std::array<uint128_t, 2>; + +inline block256 _mm256_loadu_si256(block256* p) { + block256 b256; + std::memcpy(&b256, p, sizeof(block256)); + return b256; +} + +inline block256 my_libdivide_u64_do_vec256( + const block256& x, const libdivide::libdivide_u64_t* divider) { + block256 y; + + auto x64 = (uint64_t*)&x; + auto y64 = (uint64_t*)&y; + + y64[0] = libdivide::libdivide_u64_do(x64[0], divider); + y64[1] = libdivide::libdivide_u64_do(x64[1], divider); + y64[2] = libdivide::libdivide_u64_do(x64[2], divider); + y64[3] = libdivide::libdivide_u64_do(x64[3], divider); + + return y; +} + +inline void DoMod32(uint64_t* vals, const libdivide::libdivide_u64_t* divider, + const uint64_t& mod_val) { + uint64_t i = 0; + +#ifdef __x86_64__ + block256 row256a = _mm256_loadu_si256((block256*)&vals[i]); + block256 row256b = _mm256_loadu_si256((block256*)&vals[i + 4]); + block256 row256c = _mm256_loadu_si256((block256*)&vals[i + 8]); + block256 row256d = _mm256_loadu_si256((block256*)&vals[i + 12]); + block256 row256e = _mm256_loadu_si256((block256*)&vals[i + 16]); + block256 row256f = _mm256_loadu_si256((block256*)&vals[i + 20]); + block256 row256g = _mm256_loadu_si256((block256*)&vals[i + 24]); + block256 row256h = _mm256_loadu_si256((block256*)&vals[i + 28]); +#else + block256 row256a; + block256 row256b; + block256 row256c; + block256 row256d; + block256 row256e; + block256 row256f; + block256 row256g; + block256 row256h; + std::memcpy(&row256a, &vals[i], sizeof(block256)); + std::memcpy(&row256b, &vals[i + 4], sizeof(block256)); + std::memcpy(&row256c, &vals[i + 8], sizeof(block256)); + std::memcpy(&row256d, &vals[i + 12], sizeof(block256)); + std::memcpy(&row256e, &vals[i + 16], sizeof(block256)); + std::memcpy(&row256f, &vals[i + 20], sizeof(block256)); + std::memcpy(&row256g, &vals[i + 24], sizeof(block256)); + std::memcpy(&row256h, &vals[i + 28], sizeof(block256)); +#endif + + auto tempa = my_libdivide_u64_do_vec256(row256a, divider); + auto tempb = my_libdivide_u64_do_vec256(row256b, divider); + auto tempc = my_libdivide_u64_do_vec256(row256c, divider); + auto tempd = my_libdivide_u64_do_vec256(row256d, divider); + auto tempe = my_libdivide_u64_do_vec256(row256e, divider); + auto tempf = my_libdivide_u64_do_vec256(row256f, divider); + auto tempg = my_libdivide_u64_do_vec256(row256g, divider); + auto temph = my_libdivide_u64_do_vec256(row256h, divider); + + auto temp64a = (uint64_t*)&tempa; + auto temp64b = (uint64_t*)&tempb; + auto temp64c = (uint64_t*)&tempc; + auto temp64d = (uint64_t*)&tempd; + auto temp64e = (uint64_t*)&tempe; + auto temp64f = (uint64_t*)&tempf; + auto temp64g = (uint64_t*)&tempg; + auto temp64h = (uint64_t*)&temph; + + vals[i + 0] -= temp64a[0] * mod_val; + vals[i + 1] -= temp64a[1] * mod_val; + vals[i + 2] -= temp64a[2] * mod_val; + vals[i + 3] -= temp64a[3] * mod_val; + vals[i + 4] -= temp64b[0] * mod_val; + vals[i + 5] -= temp64b[1] * mod_val; + vals[i + 6] -= temp64b[2] * mod_val; + vals[i + 7] -= temp64b[3] * mod_val; + vals[i + 8] -= temp64c[0] * mod_val; + vals[i + 9] -= temp64c[1] * mod_val; + vals[i + 10] -= temp64c[2] * mod_val; + vals[i + 11] -= temp64c[3] * mod_val; + vals[i + 12] -= temp64d[0] * mod_val; + vals[i + 13] -= temp64d[1] * mod_val; + vals[i + 14] -= temp64d[2] * mod_val; + vals[i + 15] -= temp64d[3] * mod_val; + vals[i + 16] -= temp64e[0] * mod_val; + vals[i + 17] -= temp64e[1] * mod_val; + vals[i + 18] -= temp64e[2] * mod_val; + vals[i + 19] -= temp64e[3] * mod_val; + vals[i + 20] -= temp64f[0] * mod_val; + vals[i + 21] -= temp64f[1] * mod_val; + vals[i + 22] -= temp64f[2] * mod_val; + vals[i + 23] -= temp64f[3] * mod_val; + vals[i + 24] -= temp64g[0] * mod_val; + vals[i + 25] -= temp64g[1] * mod_val; + vals[i + 26] -= temp64g[2] * mod_val; + vals[i + 27] -= temp64g[3] * mod_val; + vals[i + 28] -= temp64h[0] * mod_val; + vals[i + 29] -= temp64h[1] * mod_val; + vals[i + 30] -= temp64h[2] * mod_val; + vals[i + 31] -= temp64h[3] * mod_val; + + return; +} + +}; // namespace + +template <typename IdxType> +struct PaxosHash { + uint64_t weight; + uint64_t sparse_size; + uint64_t idx_size; + + std::shared_ptr<AesCrHash> aes_crhash; + + std::vector<libdivide::libdivide_u64_t> mods; + + std::vector<uint64_t> mod_vals; + + void init(uint128_t seed, uint64_t weight, uint64_t paxos_size) { + this->weight = weight; + this->sparse_size = paxos_size; + this->idx_size = static_cast<IdxType>( + RoundUpTo(yacl::math::Log2Ceil(sparse_size), 8) / 8); + + aes_crhash = std::make_shared<AesCrHash>(seed); + + mod_vals.resize(weight); + mods.resize(weight); + + for (uint64_t i = 0; i < weight; ++i) { + mod_vals[i] = sparse_size - i; + mods[i] = libdivide::libdivide_u64_gen(mod_vals[i]); + } + } + + void mod32(uint64_t* vals, uint64_t mod_idx) const; + + void HashBuildRow32(const absl::Span<const uint128_t> input, + absl::Span<IdxType> rows, + absl::Span<uint128_t> hash) const; + + void BuildRow32(const absl::Span<uint128_t> hash, + absl::Span<IdxType> rows) const; + + void HashBuildRow1(const uint128_t& input, absl::Span<IdxType> rows, + uint128_t* hash) const; + + void BuildRow(const uint128_t& hash, absl::Span<IdxType> rows) const; +}; + +} // namespace psi::psi::okvs diff --git a/psi/psi/core/vole_psi/okvs/paxos_hash_test.cc b/psi/psi/core/vole_psi/okvs/paxos_hash_test.cc new file mode 100644 index 00000000..39e42e0a --- /dev/null +++ b/psi/psi/core/vole_psi/okvs/paxos_hash_test.cc @@ -0,0 +1,62 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/vole_psi/okvs/paxos_hash.h" + +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/crypto/utils/rand.h" + +namespace psi::psi::okvs { + +class PaxosHashTest : public testing::TestWithParam<uint128_t> {}; + +TEST_P(PaxosHashTest, Works) { + uint128_t seed = GetParam(); + + yacl::crypto::Prg<uint128_t> prng(seed); + + PaxosHash<uint32_t> hasher; + + uint128_t paxos_seed; + prng.Fill(absl::MakeSpan(&paxos_seed, 1)); + + size_t weight = 3; + + size_t sparse_size = 10; + + hasher.init(seed, weight, sparse_size); + + uint128_t data; + prng.Fill(absl::MakeSpan(&data, 1)); + + uint128_t hash; + + std::vector<uint32_t> rows(weight); + + hasher.HashBuildRow1(data, absl::MakeSpan(rows), &hash); + + SPDLOG_INFO("hash: {}", hash); + for (size_t i = 0; i < rows.size(); i++) { + SPDLOG_INFO("[{}]={}", i, rows[i]); + } +} + +// yacl::MakeUint128(0x1234, 0x5678) + +INSTANTIATE_TEST_SUITE_P(Works_Instances, PaxosHashTest, + testing::Values(yacl::MakeUint128(0x1234, 0x5678), + yacl::crypto::RandU128())); + +} // namespace psi::psi::okvs diff --git a/psi/psi/core/vole_psi/okvs/paxos_test.cc b/psi/psi/core/vole_psi/okvs/paxos_test.cc new file mode 100644 index 00000000..ba8c7c0d --- /dev/null +++ b/psi/psi/core/vole_psi/okvs/paxos_test.cc @@ -0,0 +1,91 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/vole_psi/okvs/paxos.h" + +#include "absl/strings/escaping.h" +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/crypto/tools/prg.h" + +namespace psi::psi::okvs { + +TEST(PaxosTest, SolveTest) { + for (auto dt : + {PaxosParam::DenseType::Binary, PaxosParam::DenseType::GF128}) { + SPDLOG_INFO("=== dt:{}", + dt == PaxosParam::DenseType::Binary ? "binary" : "gf128"); + + [[maybe_unused]] uint64_t n = 15; + + [[maybe_unused]] uint64_t w = 3; + [[maybe_unused]] uint64_t s = 0; + uint64_t t = 1; + + for (uint64_t tt = 0; tt < t; ++tt) { + SPDLOG_INFO("=== tt:{} t:{}", tt, t); + Paxos<uint16_t> paxos; + Paxos<uint32_t> px2; + + paxos.Init(n, w, 40, dt, yacl::MakeUint128(0, 0)); + px2.Init(n, w, 40, dt, yacl::MakeUint128(0, 0)); + + std::vector<uint128_t> items(n); + std::vector<uint128_t> values(n); + std::vector<uint128_t> values2(n); + std::vector<uint128_t> p(paxos.size()); + + SPDLOG_INFO("n:{}, paxos.size():{}", n, paxos.size()); + + yacl::crypto::Prg<uint128_t> prng(yacl::MakeUint128(tt, s)); + + prng.Fill(absl::MakeSpan(items.data(), items.size())); + prng.Fill(absl::MakeSpan(values.data(), values.size())); + + for (auto &item : items) { + SPDLOG_INFO("{}", (std::ostringstream() << Galois128(item)).str()); + } + for (auto &value : values) { + SPDLOG_INFO("{}", (std::ostringstream() << Galois128(value)).str()); + } + + paxos.SetInput(absl::MakeSpan(items)); + px2.SetInput(absl::MakeSpan(items)); + + SPDLOG_INFO("===encode==="); + paxos.Encode(absl::MakeSpan(values), absl::MakeSpan(p)); + SPDLOG_INFO("===decode==="); + paxos.Decode(absl::MakeSpan(items), absl::MakeSpan(values2), + absl::MakeSpan(p)); + + for (size_t i = 0; i < p.size(); ++i) { + SPDLOG_INFO("P[{}]:{}", i, + (std::ostringstream() << Galois128(p[i])).str()); + } + + for (auto &value : values) { + SPDLOG_INFO("{}", (std::ostringstream() << Galois128(value)).str()); + } + for (auto &value : values2) { + SPDLOG_INFO("{}", (std::ostringstream() << Galois128(value)).str()); + } + + EXPECT_EQ(std::memcmp(values2.data(), values.data(), + sizeof(uint128_t) * values.size()), + 0); + } + } +} + +} // namespace psi::psi::okvs diff --git a/psi/psi/core/vole_psi/okvs/paxos_utils.cc b/psi/psi/core/vole_psi/okvs/paxos_utils.cc new file mode 100644 index 00000000..6e5a3ddc --- /dev/null +++ b/psi/psi/core/vole_psi/okvs/paxos_utils.cc @@ -0,0 +1,17 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/vole_psi/okvs/paxos_utils.h" + +namespace psi::psi::okvs {} diff --git a/psi/psi/core/vole_psi/okvs/paxos_utils.h b/psi/psi/core/vole_psi/okvs/paxos_utils.h new file mode 100644 index 00000000..84f45534 --- /dev/null +++ b/psi/psi/core/vole_psi/okvs/paxos_utils.h @@ -0,0 +1,325 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <cstdint> +#include <memory> +#include <set> +#include <vector> + +#include "absl/types/span.h" +#include "yacl/crypto/tools/prg.h" + +#include "psi/psi/core/vole_psi/okvs/galois128.h" + +namespace psi::psi::okvs { + +// An efficient data structure for tracking the weight of the +// paxos columns (node), which excludes the rows which have +// already been fixed (assigned to C). +template <typename IdxType> +struct WeightData { + static constexpr IdxType NullNode = ~IdxType(0); + + struct WeightNode { + // The current of this column. + IdxType weight; + + // The previous column with the same weight. + IdxType prev_weight_node = NullNode; + + // the next column with the same weight + IdxType next_weight_node = NullNode; + }; + + std::vector<WeightNode*> weight_sets; + + std::vector<WeightNode> nodes; + + uint64_t node_alloc_size = 0; + + // returns the index of the node + IdxType IdxOf(const WeightNode& node) { + return static_cast<IdxType>(&node - nodes.data()); + } + + // returns the size of the set of weight w columns. + uint64_t SetSize(uint64_t w) { + uint64_t s = 0; + auto cur = weight_sets[w]; + while (cur) { + ++s; + + if (cur->next_weight_node != NullNode) + cur = &nodes[cur->next_weight_node]; + else + cur = nullptr; + } + + return s; + } + + // add the node/column to the data structure. Assumes + // it is no already in it. + void PushNode(WeightNode& node) { + YACL_ENFORCE(node.next_weight_node == NullNode); + YACL_ENFORCE(node.prev_weight_node == NullNode); + + if (weight_sets.size() <= node.weight) { + weight_sets.resize(node.weight + 1, nullptr); + } + + if (weight_sets[node.weight] == nullptr) { + weight_sets[node.weight] = &node; + } else { + YACL_ENFORCE(weight_sets[node.weight]->prev_weight_node == NullNode); + + weight_sets[node.weight]->prev_weight_node = IdxOf(node); + node.next_weight_node = IdxOf(*weight_sets[node.weight]); + weight_sets[node.weight] = &node; + } + } + + // remove the given node/column from the data structure. + void PopNode(WeightNode& node) { + if (node.prev_weight_node == NullNode) { + YACL_ENFORCE(weight_sets[node.weight] == &node); + + if (node.next_weight_node == NullNode) { + weight_sets[node.weight] = nullptr; + while (weight_sets.back() == nullptr) { + weight_sets.pop_back(); + } + } else { + weight_sets[node.weight] = &nodes[node.next_weight_node]; + weight_sets[node.weight]->prev_weight_node = NullNode; + } + } else { + auto& prev = nodes[node.prev_weight_node]; + + if (node.next_weight_node == NullNode) { + prev.next_weight_node = NullNode; + } else { + auto& next = nodes[node.next_weight_node]; + prev.next_weight_node = IdxOf(next); + next.prev_weight_node = IdxOf(prev); + } + } + + node.prev_weight_node = NullNode; + node.next_weight_node = NullNode; + } + + // decrease the weight of a given node/column + void DecementWeight(WeightNode& node) { + YACL_ENFORCE(node.weight); + + PopNode(node); + --node.weight; + PushNode(node); + } + + // returns the node with minimum weight. + WeightNode& GetMinWeightNode() { + for (size_t i = 1; i < weight_sets.size(); ++i) { + if (weight_sets[i]) { + auto& node = *weight_sets[i]; + return node; + } + } + + YACL_THROW("func:{} error {}", __func__, __LINE__); + } + + // initialize the data structure with the current set of + // node/column weights. + void init(absl::Span<IdxType> weights) { + SPDLOG_DEBUG("node_alloc_size:{} weights.size():{}", node_alloc_size, + weights.size()); + + if (node_alloc_size < weights.size()) { + node_alloc_size = weights.size(); + nodes.resize(node_alloc_size); + + SPDLOG_DEBUG("node_alloc_size:{} weights.size():{}", node_alloc_size, + weights.size()); + } + + weight_sets.clear(); + weight_sets.resize(200); + // mNodes.resize(weights.size()); + + for (IdxType i = 0; i < weights.size(); ++i) { + nodes[i].weight = weights[i]; + + auto& node = nodes[i]; + node.next_weight_node = NullNode; + node.prev_weight_node = NullNode; + + YACL_ENFORCE(node.weight < weight_sets.size(), + "something went wrong, maybe duplicate inputs."); + + auto& ws = weight_sets[node.weight]; + if (!!(ws != nullptr)) { + YACL_ENFORCE(ws->prev_weight_node == NullNode); + + ws->prev_weight_node = IdxOf(node); + node.next_weight_node = IdxOf(*ws); + } + + ws = &node; + } + + for (uint64_t i = weight_sets.size() - 1; i < weight_sets.size(); --i) { + if (weight_sets[i]) { + weight_sets.resize(i + 1); + break; + } + } + } +}; + +// A Paxos vector type when the elements are of type T. +// This differs from PxMatrix which has elements that +// each a vector of type T's. PxVector are more efficient +// since we can remove an "inner for-loop." + +struct PxVector { + using value_type = uint128_t; + using iterator = uint128_t*; + using const_iterator = const uint128_t*; + + std::vector<value_type> owning; + absl::Span<value_type> elements; + + PxVector() = default; + PxVector(const PxVector& v) : PxVector(v.owning.size()) { + std::memcpy(owning.data(), v.owning.data(), + v.owning.size() * sizeof(value_type)); + } + + PxVector(PxVector&& v) : PxVector(v.owning.size()) { + std::memcpy(owning.data(), v.owning.data(), + v.owning.size() * sizeof(value_type)); + } + + PxVector(absl::Span<value_type> e) { elements = e; } + + PxVector(uint64_t size) { + owning.resize(size); + + elements = absl::MakeSpan(owning.data(), owning.size()); + } + + // return a iterator to the i'th element. Should be pointer symmatics + inline iterator operator[](uint64_t i) { return &elements[i]; } + + // return a iterator to the i'th element. Should be pointer symmatics + inline const_iterator operator[](uint64_t i) const { return &elements[i]; } + + // return the size of the vector + inline auto size() const { return elements.size(); } + + // return a subset of the vector, starting at index offset and of size count. + // if count = -1, then get the rest of the vector. + inline PxVector subspan(uint64_t offset, uint64_t count = -1) { + return elements.subspan(offset, count); + } + + // return a subset of the vector, starting at index offset and of size count. + // if count = -1, then get the rest of the vector. + inline PxVector subspan(uint64_t offset, uint64_t count = -1) const { + return PxVector(elements.subspan(offset, count)); + } + + // populate the vector with the zero element. + inline void ZeroFill() { + // memset(mElements.data(), 0, mElements.size_bytes()); + memset(elements.data(), 0, elements.size() * sizeof(value_type)); + } + + // The default implementation of helper for PxVector. + // This class performs operations of the elements of PxVector. + struct Helper { + // mutable version of value_type + using mut_value_type = std::remove_const_t<value_type>; + using mut_iterator = mut_value_type*; + + // internal mask used to multiply a value with a bit. + // Assumes the zero bit string is the zero element. + std::array<mut_value_type, 2> zeroOneMask; + + Helper() { + memset(&zeroOneMask[0], 0, sizeof(value_type)); + memset(&zeroOneMask[1], -1, sizeof(value_type)); + } + + Helper(const Helper&) = default; + + // return a element that the user can use. + inline static mut_value_type NewElement() { return {}; } + + // return the iterator for the return type of newElement(). + mut_iterator AsPtr(mut_value_type& t) { return &t; } + + // return a vector of elements that the user can use. + inline static PxVector NewVec(uint64_t size) { return {size}; } + + // assign the src to the dst, ie *dst = *src. + inline static void Assign(mut_iterator dst, const_iterator src) { + *dst = *src; + } + + // add the src to the dst, ie *dst += *src. + inline static void Add(mut_iterator dst, const_iterator src1) { + *dst = *dst ^ *src1; + } + + // multiply src1 with m and add the result to the dst, ie *dst += (*src) * + // m. + inline static void MultAdd(mut_iterator dst, const_iterator src1, + const uint128_t& m) { + *dst = *dst ^ (Galois128(*src1) * m).get<uint128_t>(0); + } + + // multiply src1 with bit and add the result to the dst, ie *dst += (*src) * + // bit. + inline void MultAdd(mut_iterator dst, const_iterator src1, + const uint8_t& bit) { + YACL_ENFORCE(bit < 2, "bit:{}", bit); + *dst = *dst ^ (*src1 & zeroOneMask[bit]); + } + + // return the iterator plus the given number of rows + inline static auto IterPlus(const_iterator p, uint64_t i) { return p + i; } + + // return the iterator plus the given number of rows + inline static auto IterPlus(mut_iterator p, uint64_t i) { return p + i; } + + // randomize the given element. + inline static void Randomize( + mut_iterator p, + const std::shared_ptr<yacl::crypto::Prg<uint8_t>>& prng) { + prng->Fill(absl::MakeSpan(p, 1)); + } + + inline static auto eq(iterator p0, iterator p1) { return *p0 == *p1; } + }; + + // return the default helper for this vector type. + static Helper DefaultHelper() { return {}; } +}; + +} // namespace psi::psi::okvs diff --git a/psi/psi/core/vole_psi/okvs/simple_index.cc b/psi/psi/core/vole_psi/okvs/simple_index.cc new file mode 100644 index 00000000..24337af3 --- /dev/null +++ b/psi/psi/core/vole_psi/okvs/simple_index.cc @@ -0,0 +1,967 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/vole_psi/okvs/simple_index.h" + +#include <algorithm> +#include <cmath> +#include <limits> +#include <vector> + +#include "boost/math/special_functions/binomial.hpp" +#include "boost/multiprecision/cpp_bin_float.hpp" +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" + +namespace psi::psi::okvs { + +namespace { +// template<unsigned int N = 16> +double GetBinOverflowProb(uint64_t num_bins, uint64_t num_balls, + uint64_t get_bin_size, double epsilon = 0.0001) { + if (num_balls <= get_bin_size) return std::numeric_limits<double>::max(); + + YACL_ENFORCE(num_balls <= std::numeric_limits<int32_t>::max(), + "boost::math::binomial_coefficient(...) only supports {} bit " + "inputs which was exceeded.", + std::to_string(sizeof(unsigned) * 8)); + + // std::cout << numBalls << " " << num_bins << " " << binSize << std::endl; + typedef boost::multiprecision::number< + boost::multiprecision::backends::cpp_bin_float<16>> + T; + T sum = 0.0; + T sec = 0.0; // minSec + 1; + T diff = 1; + uint64_t i = get_bin_size + 1; + + while (diff > T(epsilon) && num_balls >= i /*&& sec > minSec*/) { + sum += + num_bins * + boost::math::binomial_coefficient<T>(int32_t(num_balls), int32_t(i)) * + boost::multiprecision::pow(T(1.0) / num_bins, i) * + boost::multiprecision::pow(1 - T(1.0) / num_bins, num_balls - i); + + T sec2 = boost::multiprecision::log2(sum); + diff = boost::multiprecision::abs(sec - sec2); + + sec = sec2; + + i++; + } + + return std::max<double>(0, static_cast<double>(-sec)); +} + +// log2(bin size) of mapping n balls to m bins, for 40 bit sec. +// row i has n=2^i bins, column j has m=2^j balls. +std::vector<std::vector<double>> logbin_sizes_40bit{{ + // log #bins , log #balls + // 0, 1, 2, 3, 4, 5, 6,... + /*0*/ {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30}}, + /*1*/ {{0, 1, 2, 3, 4, + 5, 5.857980995, 6.686500527, 7.523561956, 8.392317423, + 9.290018847, 10.21067134, 11.15228484, 12.10950422, 13.07831762, + 14.05579081, 15.03969019, 16.02818667, 17.01999243, 18.01416217, + 19.01002812, 20.00709846, 21.00502277, 22.00355291, 23.00251306, + 24.00177721, 25.00125665, 26.00088843, 27.00062802, 28.00044386, + 29.00031363}}, + /*2*/ {{0, 1, 2, 3, 4, + 4.754887502, 5.459431619, 6.129283017, 6.87036472, 7.665335917, + 8.491853096, 9.361943774, 10.2632692, 11.18982456, 12.13602985, + 13.09737377, 14.06936585, 15.04929552, 16.03499235, 17.02481502, + 18.01757508, 19.01244722, 20.00880883, 21.00623293, 22.00440908, + 23.00311846, 24.00220528, 25.00155925, 26.00110233, 27.00077917, + 28.00055063}}, + /*3*/ {{0, 1, 2, 3, 3.906890596, + 4.459431619, 5, 5.614709844, 6.247927513, 6.965784285, + 7.727920455, 8.539158811, 9.394462695, 10.28771238, 11.20762447, + 12.14879432, 13.10639936, 14.07581338, 15.05392588, 16.03827601, + 17.02713927, 18.01923236, 19.0136171, 20.00963729, 21.00681914, + 22.00482395, 23.00341171, 24.00241262, 25.00170588, 26.00120598, + 27.00085241}}, + /*4*/ {{0, 1, 2, 3, 3.700439718, + 4.087462841, 4.584962501, 5.129283017, 5.672425342, 6.321928095, + 7.011227255, 7.761551232, 8.566054038, 9.413627929, 10.30149619, + 11.21735191, 12.15608308, 13.11146174, 14.07948478, 15.05651071, + 16.04011845, 17.0284457, 18.02015526, 19.01427116, 20.0101019, + 21.00714706, 22.00505602, 23.0035759, 24.00252877, 25.00178798, + 26.00126401}}, + /*5*/ {{0, 1, 2, 3, 3.459431619, + 3.807354922, 4.247927513, 4.700439718, 5.169925001, 5.727920455, + 6.357552005, 7.033423002, 7.781359714, 8.581200582, 9.426264755, + 10.30947635, 11.22339841, 12.16050175, 13.11471839, 14.08173308, + 15.06149777, 16.04127413, 17.02926566, 18.02074126, 19.01468524, + 20.01039425, 21.00735446, 22.00520271, 23.00367969, 24.00260216, + 25.0018399}}, + /*6*/ {{0, 1, 2, 2.807354922, 3.169925001, + 3.584962501, 3.906890596, 4.321928095, 4.700439718, 5.209453366, + 5.754887502, 6.375039431, 7.055282436, 7.794415866, 8.592457037, + 9.434628228, 10.31514956, 11.22821744, 12.16364968, 13.11699368, + 14.08339622, 15.05930221, 16.04210821, 17.02985876, 18.0211535, + 19.01497939, 20.01060323, 21.00750229, 22.00530724, 23.00375379, + 24.00265452}}, + /*7*/ {{0, 1, 2, 2.807354922, 3, + 3.321928095, 3.584962501, 4, 4.321928095, 4.754887502, + 5.247927513, 5.781359714, 6.392317423, 7.06608919, 7.807354922, + 8.599912842, 9.440869168, 10.32080055, 11.23122118, 12.16616308, + 13.11877889, 14.08472536, 15.06023151, 16.04277086, 17.03032228, + 18.02148428, 19.0152163, 20.01076984, 21.00762, 22.00539086, + 23.0038128}}, + /*8*/ {{0, 1, 2, 2.584962501, 2.807354922, + 3.169925001, 3.321928095, 3.700439718, 4, 4.392317423, + 4.807354922, 5.247927513, 5.807354922, 6.409390936, 7.076815597, + 7.813781191, 8.607330314, 9.445014846, 10.32418055, 11.23421868, + 12.16835873, 13.1203999, 14.08580438, 15.0610336, 16.04332639, + 17.03073179, 18.02177162, 19.01541777, 20.01091459, 21.00772264, + 22.00546316}}, + /*9*/ {{0, 1, 2, 2.321928095, 2.584962501, + 3, 3.169925001, 3.459431619, 3.700439718, 4, + 4.392317423, 4.807354922, 5.285402219, 5.807354922, 6.426264755, + 7.087462841, 7.826548487, 8.614709844, 9.451211112, 10.32755264, + 11.23720996, 12.17023805, 13.12185725, 14.08679969, 15.06175089, + 16.04386035, 17.03109809, 18.02203723, 19.01560561, 20.01104704, + 21.00781707}}, + /*10*/ {{0, 1, 2, 2.321928095, 2.584962501, + 2.807354922, 3, 3.169925001, 3.459431619, 3.700439718, + 4.087462841, 4.392317423, 4.807354922, 5.285402219, 5.832890014, + 6.426264755, 7.098032083, 7.832890014, 8.618385502, 9.45532722, + 10.33203655, 11.23959853, 12.17211493, 13.12315143, 14.0877943, + 15.06246782, 16.04435143, 17.03145353, 18.02229195, 19.01578526, + 20.01117265}}, + /*11*/ {{0, 1, 2, 2.321928095, 2.321928095, + 2.584962501, 2.807354922, 3, 3.169925001, 3.459431619, + 3.700439718, 4.087462841, 4.459431619, 4.857980995, 5.321928095, + 5.832890014, 6.442943496, 7.108524457, 7.839203788, 8.625708843, + 9.459431619, 10.33539035, 11.24198315, 12.17398937, 13.12444446, + 14.08878824, 15.06314225, 16.04484233, 17.03179811, 18.02253579, + 19.01595672}}, + /*12*/ {{0, 1, 2, 2, 2.321928095, + 2.321928095, 2.584962501, 2.807354922, 3, 3.169925001, + 3.459431619, 3.807354922, 4.087462841, 4.459431619, 4.857980995, + 5.321928095, 5.857980995, 6.459431619, 7.118941073, 7.845490051, + 8.62935662, 9.463524373, 10.33873638, 11.24436384, 12.17586138, + 13.12573633, 14.08969874, 15.06381637, 16.04531174, 17.03213185, + 18.02277416}}, + /*13*/ {{0, 1, 2, 2, 2.321928095, + 2.321928095, 2.584962501, 2.584962501, 2.807354922, 3, + 3.321928095, 3.459431619, 3.807354922, 4.087462841, 4.459431619, + 4.857980995, 5.357552005, 5.882643049, 6.459431619, 7.129283017, + 7.851749041, 8.636624621, 9.46760555, 10.34207467, 11.2467406, + 12.17741954, 13.12702704, 14.09060867, 15.06444807, 16.04575966, + 17.0324655}}, + /*14*/ {{0, 1, 1.584962501, 2, 2, + 2.321928095, 2.321928095, 2.584962501, 2.584962501, 2.807354922, + 3, 3.321928095, 3.584962501, 3.807354922, 4.169925001, + 4.459431619, 4.906890596, 5.357552005, 5.882643049, 6.475733431, + 7.139551352, 7.857980995, 8.64385619, 9.47370575, 10.34429591, + 11.24911345, 12.1792871, 13.1283166, 14.09151803, 15.06507949, + 16.04622877}}, + /*15*/ {{0, 1, 1.584962501, 2, 2, + 2, 2.321928095, 2.321928095, 2.584962501, 2.807354922, + 2.807354922, 3.169925001, 3.321928095, 3.584962501, 3.807354922, + 4.169925001, 4.523561956, 4.906890596, 5.357552005, 5.906890596, + 6.491853096, 7.14974712, 7.87036472, 8.647458426, 9.477758266, + 10.34762137, 11.25148241, 12.18084157, 13.12944402, 14.09234422, + 15.06571064}}, + /*16*/ {{0, 1, 1.584962501, 1.584962501, 2, + 2, 2, 2.321928095, 2.321928095, 2.584962501, + 2.807354922, 3, 3.169925001, 3.321928095, 3.584962501, + 3.906890596, 4.169925001, 4.523561956, 4.906890596, 5.392317423, + 5.906890596, 6.491853096, 7.159871337, 7.876516947, 8.654636029, + 9.481799432, 10.35093918, 11.25384748, 12.18270471, 13.13073143, + 14.09325249}}, + /*17*/ {{0, 1, 1.584962501, 1.584962501, 1.584962501, + 2, 2, 2, 2.321928095, 2.321928095, + 2.584962501, 2.807354922, 3, 3.169925001, 3.321928095, + 3.584962501, 3.906890596, 4.169925001, 4.523561956, 4.95419631, + 5.392317423, 5.930737338, 6.50779464, 7.159871337, 7.882643049, + 8.658211483, 9.485829309, 10.35424938, 11.25620869, 12.1842555, + 13.13185696}}, + /*18*/ {{0, 1, 1.584962501, 1.584962501, 1.584962501, + 2, 2, 2, 2.321928095, 2.321928095, + 2.584962501, 2.584962501, 2.807354922, 3, 3.169925001, + 3.321928095, 3.584962501, 3.906890596, 4.169925001, 4.584962501, + 4.95419631, 5.426264755, 5.930737338, 6.523561956, 7.169925001, + 7.888743249, 8.665335917, 9.48984796, 10.357552, 11.25856603, + 12.18580462}}, + /*19*/ {{0, 1, 1.584962501, 1.584962501, 1.584962501, + 1.584962501, 2, 2, 2, 2.321928095, + 2.321928095, 2.584962501, 2.584962501, 2.807354922, 3, + 3.169925001, 3.459431619, 3.700439718, 3.906890596, 4.247927513, + 4.584962501, 4.95419631, 5.426264755, 5.95419631, 6.523561956, + 7.17990909, 7.894817763, 8.668884984, 9.493855449, 10.35974956, + 11.26091953}}, + /*20*/ {{0, 1, 1.584962501, 1.584962501, 1.584962501, + 1.584962501, 1.584962501, 2, 2, 2, + 2.321928095, 2.321928095, 2.584962501, 2.584962501, 2.807354922, + 3, 3.169925001, 3.459431619, 3.700439718, 3.906890596, + 4.247927513, 4.584962501, 5, 5.426264755, 5.95419631, + 6.539158811, 7.189824559, 7.900866808, 8.675957033, 9.497851837, + 10.36303963}}, + /*21*/ {{0, 1, 1, 1.584962501, 1.584962501, + 1.584962501, 1.584962501, 2, 2, 2, + 2, 2.321928095, 2.321928095, 2.584962501, 2.584962501, + 2.807354922, 3, 3.169925001, 3.459431619, 3.700439718, + 4, 4.247927513, 4.584962501, 5, 5.459431619, + 5.977279923, 6.554588852, 7.199672345, 7.906890596, 8.6794801, + 9.501837185}}, + /*22*/ {{0, 1, 1, 1.584962501, 1.584962501, + 1.584962501, 1.584962501, 1.584962501, 2, 2, + 2, 2.321928095, 2.321928095, 2.321928095, 2.584962501, + 2.807354922, 2.807354922, 3, 3.321928095, 3.459431619, + 3.700439718, 4, 4.247927513, 4.64385619, 5, + 5.459431619, 5.977279923, 6.554588852, 7.209453366, 7.912889336, + 8.682994584}}, + /*23*/ {{0, 1, 1, 1, 1.584962501, + 1.584962501, 1.584962501, 1.584962501, 1.584962501, 2, + 2, 2, 2.321928095, 2.321928095, 2.321928095, + 2.584962501, 2.807354922, 2.807354922, 3, 3.321928095, + 3.459431619, 3.700439718, 4, 4.321928095, 4.64385619, + 5.044394119, 5.491853096, 6, 6.569855608, 7.209453366, + 7.918863237}}, + /*24*/ {{0, 1, 1, 1, 1.584962501, + 1.584962501, 1.584962501, 1.584962501, 1.584962501, 1.584962501, + 2, 2, 2, 2.321928095, 2.321928095, + 2.584962501, 2.584962501, 2.807354922, 3, 3.169925001, + 3.321928095, 3.459431619, 3.700439718, 4, 4.321928095, + 4.64385619, 5.044394119, 5.491853096, 6, 6.584962501, + 7.21916852}}, + /*25*/ {{0, 1, 1, 1, 1, + 1.584962501, 1.584962501, 1.584962501, 1.584962501, 1.584962501, + 2, 2, 2, 2, 2.321928095, + 2.321928095, 2.584962501, 2.584962501, 2.807354922, 3, + 3.169925001, 3.321928095, 3.459431619, 3.807354922, 4, + 4.321928095, 4.64385619, 5.044394119, 5.491853096, 6.022367813, + 6.584962501}}, + /*26*/ {{0, 1, 1, 1, 1, + 1.584962501, 1.584962501, 1.584962501, 1.584962501, 1.584962501, + 1.584962501, 2, 2, 2, 2, + 2.321928095, 2.321928095, 2.584962501, 2.584962501, 2.807354922, + 3, 3.169925001, 3.321928095, 3.584962501, 3.807354922, + 4, 4.321928095, 4.700439718, 5.087462841, 5.523561956, + 6.022367813}}, + /*27*/ {{0, 1, 1, 1, 1, + 1, 1.584962501, 1.584962501, 1.584962501, 1.584962501, + 1.584962501, 1.584962501, 2, 2, 2, + 2.321928095, 2.321928095, 2.321928095, 2.584962501, 2.584962501, + 2.807354922, 3, 3.169925001, 3.321928095, 3.584962501, + 3.807354922, 4.087462841, 4.392317423, 4.700439718, 5.087462841, + 5.523561956}}, + /*28*/ {{0, 1, 1, 1, 1, + 1, 1, 1.584962501, 1.584962501, 1.584962501, + 1.584962501, 1.584962501, 1.584962501, 2, 2, + 2, 2.321928095, 2.321928095, 2.321928095, 2.584962501, + 2.584962501, 2.807354922, 3, 3.169925001, 3.321928095, + 3.584962501, 3.807354922, 4.087462841, 4.392317423, 4.700439718, + 5.087462841}}, + /*29*/ {{0, 1, 1, 1, 1, + 1, 1, 1.584962501, 1.584962501, 1.584962501, + 1.584962501, 1.584962501, 1.584962501, 2, 2, + 2, 2, 2.321928095, 2.321928095, 2.321928095, + 2.584962501, 2.807354922, 2.807354922, 3, 3.169925001, + 3.321928095, 3.584962501, 3.807354922, 4.087462841, 4.392317423, + 4.754887502}}, + /*30*/ {{0, 1, 1, 1, 1, + 1, 1, 1, 1.584962501, 1.584962501, + 1.584962501, 1.584962501, 1.584962501, 1.584962501, 2, + 2, 2, 2, 2.321928095, 2.321928095, + 2.584962501, 2.584962501, 2.807354922, 2.807354922, 3, + 3.169925001, 3.459431619, 3.584962501, 3.807354922, 4.087462841, + 4.392317423}}, + /*31*/ {{0, 1, 1, 1, 1, + 1, 1, 1, 1, 1.584962501, + 1.584962501, 1.584962501, 1.584962501, 1.584962501, 1.584962501, + 2, 2, 2, 2, 2.321928095, + 2.321928095, 2.584962501, 2.584962501, 2.807354922, 2.807354922, + 3, 3.169925001, 3.459431619, 3.584962501, 3.906890596, + 4.087462841}}, + /*32*/ {{0, 1, 1, 1, 1, + 1, 1, 1, 1, 1.584962501, + 1.584962501, 1.584962501, 1.584962501, 1.584962501, 1.584962501, + 1.584962501, 2, 2, 2, 2.321928095, + 2.321928095, 2.321928095, 2.584962501, 2.584962501, 2.807354922, + 3, 3, 3.321928095, 3.459431619, 3.700439718, + 3.906890596}}, +}}; + +std::vector<std::vector<double>> logbin_sizes_50bit{ + {/*0 */ {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30}}, + /*1 */ {{0, 1, 2, 3, 4, + 5, 5.977279923, 6.794415866, 7.622051819, 8.471675214, + 9.350939182, 10.25738784, 11.18673329, 12.13442632, 13.09638573, + 14.06886223, 15.04904021, 16.03484194, 17.02472847, 18.01752615, + 19.01241449, 20.00878969, 21.00622128, 22.00440154, 23.00311348, + 24.0022021, 25.00155732, 26.00110119, 27.00077858, 28.00055042, + 29.00038906}}, + /*2 */ {{0, 1, 2, 3, 4, + 4.95419631, 5.64385619, 6.321928095, 7.033423002, 7.787902559, + 8.588714636, 9.436711542, 10.31967212, 11.23122118, 12.16647695, + 13.11926538, 14.08514046, 15.0606115, 16.04307002, 17.03055938, + 18.02165778, 19.01534154, 20.01085997, 21.00768569, 22.00543781, + 23.00384642, 24.00272043, 25.0019238, 26.0013603, 27.00096173, + 28.00067986}}, + /*3 */ {{0, 1, 2, 3, 4, + 4.700439718, 5.247927513, 5.832890014, 6.459431619, 7.129283017, + 7.857980995, 8.64385619, 9.475733431, 10.34762137, 11.25207404, + 12.18146288, 13.12992693, 14.09275714, 15.06604713, 16.04691083, + 17.03328318, 18.02359194, 19.01671037, 20.01183055, 21.00837254, + 22.00592363, 23.00419011, 24.00296348, 25.00209568, 26.00148182, + 27.00104763}}, + /*4 */ {{0, 1, 2, 3, 3.906890596, + 4.459431619, 4.906890596, 5.392317423, 5.930737338, 6.523561956, + 7.17990909, 7.894817763, 8.672425342, 9.495855027, 10.36303963, + 11.26268214, 12.18920683, 13.13554898, 14.0967975, 15.06890421, + 16.04895509, 17.03474524, 18.02462568, 19.01744187, 20.01234901, + 21.00873909, 22.00618292, 23.00437376, 24.00309332, 25.00218746, + 26.00154671}}, + /*5 */ {{0, 1, 2, 3, 3.807354922, + 4.169925001, 4.584962501, 5, 5.459431619, 5.977279923, + 6.554588852, 7.209453366, 7.918863237, 8.686500527, 9.50779464, + 10.37177664, 11.26912668, 12.19383333, 13.13891172, 14.09918341, + 15.06149777, 16.05018877, 17.03560458, 18.02523691, 19.01787679, + 20.01265727, 21.0089572, 22.00633738, 23.00448281, 24.00317045, + 25.00224203}}, + /*6 */ {{0, 1, 2, 3, 3.584962501, + 3.906890596, 4.247927513, 4.64385619, 5.044394119, 5.491853096, + 6, 6.584962501, 7.22881869, 7.930737338, 8.696967526, + 9.515699838, 10.37721053, 11.2737956, 12.19690944, 13.1411492, + 14.10082657, 15.07175563, 16.05099646, 17.03618435, 18.02564786, + 19.01816757, 20.01286183, 21.00910213, 22.00644011, 23.0045555, + 24.00322193}}, + /*7 */ {{0, 1, 2, 3, 3.459431619, + 3.700439718, 4, 4.321928095, 4.700439718, 5.087462841, + 5.523561956, 6.022367813, 6.599912842, 7.238404739, 7.942514505, + 8.707359132, 9.52160044, 10.38154295, 11.27670602, 12.19936562, + 13.14290479, 14.10197567, 15.07263508, 16.05159132, 17.03661365, + 18.025956, 19.01838494, 20.01301592, 21.0092115, 22.0065175, + 23.00461019}}, + /*8 */ {{0, 1, 2, 3, 3.321928095, + 3.459431619, 3.807354922, 4.087462841, 4.392317423, 4.700439718, + 5.087462841, 5.523561956, 6.044394119, 6.614709844, 7.247927513, + 7.948367232, 8.710806434, 9.527477006, 10.3858624, 11.27961058, + 12.20120501, 13.14418024, 14.10295989, 15.07330478, 16.052101, + 17.03697846, 18.02621002, 19.01856696, 20.01314408, 21.00930241, + 22.00658153}}, + /*9 */ {{0, 1, 2, 2.807354922, 3.169925001, + 3.321928095, 3.584962501, 3.807354922, 4.087462841, 4.392317423, + 4.700439718, 5.129283017, 5.554588852, 6.044394119, 6.62935662, + 7.257387843, 7.960001932, 8.717676423, 9.531381461, 10.38801729, + 11.28193003, 12.20304206, 13.14545456, 14.10386149, 15.07397416, + 16.05254682, 17.03728955, 18.02643699, 19.01872722, 20.0132586, + 21.00938375}}, + /*10*/ {{0, 1, 2, 2.807354922, 3, + 3.169925001, 3.321928095, 3.584962501, 3.807354922, 4.087462841, + 4.392317423, 4.754887502, 5.129283017, 5.554588852, 6.06608919, + 6.62935662, 7.266786541, 7.965784285, 8.724513853, 9.535275377, + 10.39124359, 11.28366717, 12.20457114, 13.14656868, 14.10468065, + 15.07455962, 16.05297129, 17.03760057, 18.02665311, 19.01887933, + 20.0133663}}, + /*11*/ {{0, 1, 2, 2.584962501, 2.807354922, + 3, 3.169925001, 3.459431619, 3.584962501, 3.906890596, + 4.087462841, 4.392317423, 4.754887502, 5.129283017, 5.584962501, + 6.087462841, 6.64385619, 7.276124405, 7.971543554, 8.727920455, + 9.539158811, 10.39446269, 11.28598011, 12.20609861, 13.14768193, + 14.10549934, 15.07510305, 16.05339563, 17.03789009, 18.0268584, + 19.01902598}}, + /*12*/ {{0, 1, 2, 2.584962501, 2.807354922, + 2.807354922, 3, 3.169925001, 3.459431619, 3.700439718, + 3.906890596, 4.169925001, 4.459431619, 4.754887502, 5.169925001, + 5.584962501, 6.087462841, 6.64385619, 7.276124405, 7.977279923, + 8.731319031, 9.54303182, 10.39660478, 11.28771238, 12.20762447, + 13.14879432, 14.10623576, 15.07568805, 16.05377743, 17.03816882, + 18.02705826}}, + /*13*/ {{0, 1, 2, 2.321928095, 2.584962501, + 2.807354922, 3, 3.169925001, 3.321928095, 3.459431619, + 3.700439718, 3.906890596, 4.169925001, 4.459431619, 4.807354922, + 5.169925001, 5.614709844, 6.087462841, 6.658211483, 7.285402219, + 7.982993575, 8.73809226, 9.544964433, 10.39981196, 11.29001885, + 12.20884399, 13.14990586, 14.10705357, 15.07623105, 16.05418033, + 17.0384475}}, + /*14*/ {{0, 1, 2, 2.321928095, 2.584962501, + 2.584962501, 2.807354922, 3, 3.169925001, 3.321928095, + 3.459431619, 3.700439718, 3.906890596, 4.169925001, 4.459431619, + 4.807354922, 5.169925001, 5.614709844, 6.108524457, 6.672425342, + 7.294620749, 7.988684687, 8.741466986, 9.548821908, 10.40194612, + 11.29174628, 12.21036695, 13.15085792, 14.1077892, 15.07677385, + 16.05456193}}, + /*15*/ {{0, 1, 2, 2.321928095, 2.321928095, + 2.584962501, 2.584962501, 2.807354922, 3, 3.169925001, + 3.321928095, 3.459431619, 3.700439718, 3.906890596, 4.169925001, + 4.459431619, 4.807354922, 5.209453366, 5.614709844, 6.108524457, + 6.672425342, 7.303780748, 7.994353437, 8.744833837, 9.552669098, + 10.40514146, 11.29404631, 12.21188829, 13.15196787, 14.10852446, + 15.07731645}}, + /*16*/ {{0, 1, 2, 2.321928095, 2.321928095, + 2.321928095, 2.584962501, 2.807354922, 2.807354922, 3, + 3.169925001, 3.321928095, 3.459431619, 3.700439718, 3.906890596, + 4.169925001, 4.523561956, 4.807354922, 5.209453366, 5.64385619, + 6.129283017, 6.686500527, 7.312882955, 8, 8.751544059, + 9.556506055, 10.40726776, 11.29576893, 12.21340804, 13.15291858, + 14.10925934}}, + /*17*/ {{0, 1, 2, 2, 2.321928095, + 2.321928095, 2.584962501, 2.584962501, 2.807354922, 2.807354922, + 3, 3.169925001, 3.321928095, 3.584962501, 3.700439718, + 4, 4.247927513, 4.523561956, 4.857980995, 5.209453366, + 5.64385619, 6.129283017, 6.686500527, 7.312882955, 8.005624549, + 8.754887502, 9.560332834, 10.41045135, 11.2974895, 12.21462269, + 13.15402694}}, + /*18*/ {{0, 1, 2, 2, 2.321928095, + 2.321928095, 2.321928095, 2.584962501, 2.584962501, 2.807354922, + 2.807354922, 3, 3.169925001, 3.321928095, 3.584962501, + 3.700439718, 4, 4.247927513, 4.523561956, 4.857980995, + 5.209453366, 5.64385619, 6.14974712, 6.700439718, 7.321928095, + 8.011227255, 8.758223215, 9.562242424, 10.41256985, 11.2997804, + 12.21613956}}, + /*19*/ {{0, 1, 2, 2, 2, + 2.321928095, 2.321928095, 2.321928095, 2.584962501, 2.584962501, + 2.807354922, 2.807354922, 3, 3.169925001, 3.321928095, + 3.584962501, 3.807354922, 4, 4.247927513, 4.523561956, + 4.857980995, 5.247927513, 5.672425342, 6.14974712, 6.714245518, + 7.330916878, 8.016808288, 8.764871591, 9.566054038, 10.41468524, + 11.30149619}}, + /*20*/ {{0, 1, 1.584962501, 2, 2, + 2, 2.321928095, 2.321928095, 2.321928095, 2.584962501, + 2.584962501, 2.807354922, 3, 3, 3.169925001, + 3.321928095, 3.584962501, 3.807354922, 4, 4.247927513, + 4.523561956, 4.857980995, 5.247927513, 5.672425342, 6.169925001, + 6.714245518, 7.339850003, 8.022367813, 8.768184325, 9.569855608, + 10.41785251}}, + /*21*/ {{0, 1, 1.584962501, 2, 2, + 2, 2, 2.321928095, 2.321928095, 2.321928095, + 2.584962501, 2.584962501, 2.807354922, 3, 3, + 3.169925001, 3.459431619, 3.584962501, 3.807354922, 4, + 4.247927513, 4.584962501, 4.906890596, 5.247927513, 5.700439718, + 6.169925001, 6.727920455, 7.339850003, 8.027905997, 8.77148947, + 9.573647187}}, + /*22*/ {{0, 1, 1.584962501, 2, 2, + 2, 2, 2, 2.321928095, 2.321928095, + 2.584962501, 2.584962501, 2.584962501, 2.807354922, 3, + 3.169925001, 3.321928095, 3.459431619, 3.584962501, 3.807354922, + 4, 4.321928095, 4.584962501, 4.906890596, 5.285402219, + 5.700439718, 6.189824559, 6.727920455, 7.348728154, 8.033423002, + 8.77478706}}, + /*23*/ {{0, 1, 1.584962501, 1.584962501, 2, + 2, 2, 2, 2.321928095, 2.321928095, + 2.321928095, 2.584962501, 2.584962501, 2.807354922, 2.807354922, + 3, 3.169925001, 3.321928095, 3.459431619, 3.584962501, + 3.807354922, 4.087462841, 4.321928095, 4.584962501, 4.906890596, + 5.285402219, 5.700439718, 6.189824559, 6.741466986, 7.357552005, + 8.038918989}}, + /*24*/ {{0, 1, 1.584962501, 1.584962501, 1.584962501, + 2, 2, 2, 2, 2.321928095, + 2.321928095, 2.321928095, 2.584962501, 2.584962501, 2.807354922, + 2.807354922, 3, 3.169925001, 3.321928095, 3.459431619, + 3.584962501, 3.807354922, 4.087462841, 4.321928095, 4.584962501, + 4.906890596, 5.285402219, 5.727920455, 6.209453366, 6.741466986, + 7.357552005}}, + /*25*/ {{0, 1, 1.584962501, 1.584962501, 1.584962501, + 2, 2, 2, 2, 2, + 2.321928095, 2.321928095, 2.321928095, 2.584962501, 2.584962501, + 2.807354922, 2.807354922, 3, 3.169925001, 3.321928095, + 3.459431619, 3.700439718, 3.807354922, 4.087462841, 4.321928095, + 4.584962501, 4.95419631, 5.321928095, 5.727920455, 6.209453366, + 6.754887502}}, + /*26*/ {{0, 1, 1.584962501, 1.584962501, 1.584962501, + 1.584962501, 2, 2, 2, 2, + 2, 2.321928095, 2.321928095, 2.321928095, 2.584962501, + 2.584962501, 2.807354922, 2.807354922, 3, 3.169925001, + 3.321928095, 3.459431619, 3.700439718, 3.906890596, 4.087462841, + 4.321928095, 4.64385619, 4.95419631, 5.321928095, 5.727920455, + 6.209453366}}, + /*27*/ {{0, 1, 1.584962501, 1.584962501, 1.584962501, + 1.584962501, 1.584962501, 2, 2, 2, + 2, 2.321928095, 2.321928095, 2.321928095, 2.321928095, + 2.584962501, 2.584962501, 2.807354922, 2.807354922, 3, + 3.169925001, 3.321928095, 3.459431619, 3.700439718, 3.906890596, + 4.087462841, 4.321928095, 4.64385619, 4.95419631, 5.321928095, + 5.754887502}}, + /*28*/ {{0, 1, 1.584962501, 1.584962501, 1.584962501, + 1.584962501, 1.584962501, 1.584962501, 2, 2, + 2, 2, 2.321928095, 2.321928095, 2.321928095, + 2.584962501, 2.584962501, 2.584962501, 2.807354922, 3, + 3, 3.169925001, 3.321928095, 3.459431619, 3.700439718, + 3.906890596, 4.087462841, 4.392317423, 4.64385619, 4.95419631, + 5.321928095}}, + /*29*/ + {{0, + 1, + 1.584962501, + 1.584962501, + 1.584962501, + 1.584962501, + 1.584962501, + 1.584962501, + 2, + 2, + 2, + 2, + 2, + 2.321928095, + 2.321928095, + 2.321928095, + 2.584962501, + 2.584962501, + 2.584962501, + 2.807354922, + 3, + 3, + 3.169925001, + 3.321928095, + 3.584962501, + 3.700439718, + 3.906890596, + 4.169925001, + 4.392317423, + 4.64385619, + 5}}, + /*30*/ {{0, 1, 1.584962501, 1.584962501, 1.584962501, + 1.584962501, 1.584962501, 1.584962501, 1.584962501, 2, + 2, 2, 2, 2, 2.321928095, + 2.321928095, 2.321928095, 2.584962501, 2.584962501, 2.807354922, + 2.807354922, 3, 3, 3.169925001, 3.321928095, + 3.584962501, 3.700439718, 3.906890596, 4.169925001, 4.392317423, + 4.700439718}}, + /*31*/ {{0, 1, 1, 1.584962501, 1.584962501, + 1.584962501, 1.584962501, 1.584962501, 1.584962501, 1.584962501, + 2, 2, 2, 2, 2, + 2.321928095, 2.321928095, 2.321928095, 2.584962501, 2.584962501, + 2.807354922, 2.807354922, 3, 3.169925001, 3.169925001, + 3.321928095, 3.584962501, 3.700439718, 3.906890596, 4.169925001, + 4.392317423}}, + /*32*/ {{0, 1, 1, 1.584962501, 1.584962501, + 1.584962501, 1.584962501, 1.584962501, 1.584962501, 1.584962501, + 1.584962501, 2, 2, 2, 2, + 2.321928095, 2.321928095, 2.321928095, 2.321928095, 2.584962501, + 2.584962501, 2.807354922, 2.807354922, 3, 3.169925001, + 3.321928095, 3.459431619, 3.584962501, 3.700439718, 3.906890596, + 4.169925001}}}}; +std::vector<std::vector<double>> logbin_sizes_60bit{ + {/*0*/ {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}}, + /*1*/ {{0, 1, 2, 3, 4, 5, 5.97728, + 6.79442, 7.62205, 8.47168, 9.35094, 10.2574, 11.1867, 12.1344, + 13.0964, 14.0689, 15.049, 16.0348, 17.0247, 18.0175, 19.0124, + 20.0088, 21.0062, 22.0044, 23.0031, 24.0022, 25.0016, 26.0011, + 27.0008, 28.0006, 29.0004}}, + /*2*/ {{0, 1, 2, 3, 4, 4.9542, 5.64386, + 6.32193, 7.03342, 7.7879, 8.58871, 9.43671, 10.3197, 11.2312, + 12.1665, 13.1193, 14.0851, 15.0606, 16.0431, 17.0306, 18.0217, + 19.0153, 20.0109, 21.0077, 22.0054, 23.0038, 24.0027, 25.0019, + 26.0014, 27.001, 28.0007}}, + /*3*/ {{0, 1, 2, 3, 4, 4.70044, 5.24793, + 5.83289, 6.45943, 7.12928, 7.85798, 8.64386, 9.47573, 10.3476, + 11.2521, 12.1815, 13.1299, 14.0928, 15.066, 16.0469, 17.0333, + 18.0236, 19.0167, 20.0118, 21.0084, 22.0059, 23.0042, 24.003, + 25.0021, 26.0015, 27.001}}, + /*4*/ {{0, 1, 2, 3, 3.90689, 4.45943, 4.90689, + 5.39232, 5.93074, 6.52356, 7.17991, 7.89482, 8.67243, 9.49586, + 10.363, 11.2627, 12.1892, 13.1355, 14.0968, 15.0689, 16.049, + 17.0347, 18.0246, 19.0174, 20.0123, 21.0087, 22.0062, 23.0044, + 24.0031, 25.0022, 26.0015}}, + /*5*/ {{0, 1, 2, 3, 3.80735, 4.16993, 4.58496, + 5, 5.45943, 5.97728, 6.55459, 7.20945, 7.91886, 8.6865, + 9.50779, 10.3718, 11.2691, 12.1938, 13.1389, 14.0992, 15.0615, + 16.0502, 17.0356, 18.0252, 19.0179, 20.0127, 21.009, 22.0063, + 23.0045, 24.0032, 25.0022}}, + /*6*/ {{0, 1, 2, 3, 3.58496, 3.90689, 4.24793, + 4.64386, 5.04439, 5.49185, 6, 6.58496, 7.22882, 7.93074, + 8.69697, 9.5157, 10.3772, 11.2738, 12.1969, 13.1411, 14.1008, + 15.0718, 16.051, 17.0362, 18.0256, 19.0182, 20.0129, 21.0091, + 22.0064, 23.0046, 24.0032}}, + /*7*/ {{0, 1, 2, 3, 3.45943, 3.70044, 4, + 4.32193, 4.70044, 5.08746, 5.52356, 6.02237, 6.59991, 7.2384, + 7.94251, 8.70736, 9.5216, 10.3815, 11.2767, 12.1994, 13.1429, + 14.102, 15.0726, 16.0516, 17.0366, 18.026, 19.0184, 20.013, + 21.0092, 22.0065, 23.0046}}, + /*8*/ {{0, 1, 2, 3, 3.32193, 3.45943, 3.80735, + 4.08746, 4.39232, 4.70044, 5.08746, 5.52356, 6.04439, 6.61471, + 7.24793, 7.94837, 8.71081, 9.52748, 10.3859, 11.2796, 12.2012, + 13.1442, 14.103, 15.0733, 16.0521, 17.037, 18.0262, 19.0186, + 20.0131, 21.0093, 22.0066}}, + /*9*/ {{0, 1, 2, 2.80735, 3.16993, 3.32193, 3.58496, + 3.80735, 4.08746, 4.39232, 4.70044, 5.12928, 5.55459, 6.04439, + 6.62936, 7.25739, 7.96, 8.71768, 9.53138, 10.388, 11.2819, + 12.203, 13.1455, 14.1039, 15.074, 16.0525, 17.0373, 18.0264, + 19.0187, 20.0133, 21.0094}}, + /*10*/ {{0, 1, 2, 2.80735, 3, 3.16993, 3.32193, + 3.58496, 3.80735, 4.08746, 4.39232, 4.75489, 5.12928, 5.55459, + 6.06609, 6.62936, 7.26679, 7.96578, 8.72451, 9.53528, 10.3912, + 11.2837, 12.2046, 13.1466, 14.1047, 15.0746, 16.053, 17.0376, + 18.0267, 19.0189, 20.0134}}, + /*11*/ {{0, 1, 2, 2.58496, 2.80735, 3, 3.16993, + 3.45943, 3.58496, 3.90689, 4.08746, 4.39232, 4.75489, 5.12928, + 5.58496, 6.08746, 6.64386, 7.27612, 7.97154, 8.72792, 9.53916, + 10.3945, 11.286, 12.2061, 13.1477, 14.1055, 15.0751, 16.0534, + 17.0379, 18.0269, 19.019}}, + /*12*/ {{0, 1, 2, 2.58496, 2.80735, 2.80735, 3, + 3.16993, 3.45943, 3.70044, 3.90689, 4.16993, 4.45943, 4.75489, + 5.16993, 5.58496, 6.08746, 6.64386, 7.27612, 7.97728, 8.73132, + 9.54303, 10.3966, 11.2877, 12.2076, 13.1488, 14.1062, 15.0757, + 16.0538, 17.0382, 18.0271}}, + /*13*/ {{0, 1, 2, 2.32193, 2.58496, 2.80735, 3, + 3.16993, 3.32193, 3.45943, 3.70044, 3.90689, 4.16993, 4.45943, + 4.80735, 5.16993, 5.61471, 6.08746, 6.65821, 7.2854, 7.98299, + 8.73809, 9.54496, 10.3998, 11.29, 12.2088, 13.1499, 14.1071, + 15.0762, 16.0542, 17.0384}}, + /*14*/ {{0, 1, 2, 2.32193, 2.58496, 2.58496, 2.80735, + 3, 3.16993, 3.32193, 3.45943, 3.70044, 3.90689, 4.16993, + 4.45943, 4.80735, 5.16993, 5.61471, 6.10852, 6.67243, 7.29462, + 7.98868, 8.74147, 9.54882, 10.4019, 11.2917, 12.2104, 13.1509, + 14.1078, 15.0768, 16.0546}}, + /*15*/ {{0, 1, 2, 2.32193, 2.32193, 2.58496, 2.58496, + 2.80735, 3, 3.16993, 3.32193, 3.45943, 3.70044, 3.90689, + 4.16993, 4.45943, 4.80735, 5.20945, 5.61471, 6.10852, 6.67243, + 7.30378, 7.99435, 8.74483, 9.55267, 10.4051, 11.294, 12.2119, + 13.152, 14.1085, 15.0773}}, + /*16*/ {{0, 1, 2, 2.32193, 2.32193, 2.32193, 2.58496, + 2.80735, 2.80735, 3, 3.16993, 3.32193, 3.45943, 3.70044, + 3.90689, 4.16993, 4.52356, 4.80735, 5.20945, 5.64386, 6.12928, + 6.6865, 7.31288, 8, 8.75154, 9.55651, 10.4073, 11.2958, + 12.2134, 13.1529, 14.1093}}, + /*17*/ {{0, 1, 2, 2, 2.32193, 2.32193, 2.58496, + 2.58496, 2.80735, 2.80735, 3, 3.16993, 3.32193, 3.58496, + 3.70044, 4, 4.24793, 4.52356, 4.85798, 5.20945, 5.64386, + 6.12928, 6.6865, 7.31288, 8.00562, 8.75489, 9.56033, 10.4105, + 11.2975, 12.2146, 13.154}}, + /*18*/ {{0, 1, 2, 2, 2.32193, 2.32193, 2.32193, + 2.58496, 2.58496, 2.80735, 2.80735, 3, 3.16993, 3.32193, + 3.58496, 3.70044, 4, 4.24793, 4.52356, 4.85798, 5.20945, + 5.64386, 6.14975, 6.70044, 7.32193, 8.01123, 8.75822, 9.56224, + 10.4126, 11.2998, 12.2161}}, + /*19*/ {{0, 1, 2, 2, 2, 2.32193, 2.32193, + 2.32193, 2.58496, 2.58496, 2.80735, 2.80735, 3, 3.16993, + 3.32193, 3.58496, 3.80735, 4, 4.24793, 4.52356, 4.85798, + 5.24793, 5.67243, 6.14975, 6.71425, 7.33092, 8.01681, 8.76487, + 9.56605, 10.4147, 11.3015}}, + /*20*/ {{0, 1, 1.58496, 2, 2, 2, 2.32193, + 2.32193, 2.32193, 2.58496, 2.58496, 2.80735, 3, 3, + 3.16993, 3.32193, 3.58496, 3.80735, 4, 4.24793, 4.52356, + 4.85798, 5.24793, 5.67243, 6.16993, 6.71425, 7.33985, 8.02237, + 8.76818, 9.56986, 10.4179}}, + /*21*/ {{0, 1, 1.58496, 2, 2, 2, 2, + 2.32193, 2.32193, 2.32193, 2.58496, 2.58496, 2.80735, 3, + 3, 3.16993, 3.45943, 3.58496, 3.80735, 4, 4.24793, + 4.58496, 4.90689, 5.24793, 5.70044, 6.16993, 6.72792, 7.33985, + 8.02791, 8.77149, 9.57365}}, + /*22*/ {{0, 1, 1.58496, 2, 2, 2, 2, + 2, 2.32193, 2.32193, 2.58496, 2.58496, 2.58496, 2.80735, + 3, 3.16993, 3.32193, 3.45943, 3.58496, 3.80735, 4, + 4.32193, 4.58496, 4.90689, 5.2854, 5.70044, 6.18982, 6.72792, + 7.34873, 8.03342, 8.77479}}, + /*23*/ {{0, 1, 1.58496, 1.58496, 2, 2, 2, + 2, 2.32193, 2.32193, 2.32193, 2.58496, 2.58496, 2.80735, + 2.80735, 3, 3.16993, 3.32193, 3.45943, 3.58496, 3.80735, + 4.08746, 4.32193, 4.58496, 4.90689, 5.2854, 5.70044, 6.18982, + 6.74147, 7.35755, 8.03892}}, + /*24*/ {{0, 1, 1.58496, 1.58496, 1.58496, 2, 2, + 2, 2, 2.32193, 2.32193, 2.32193, 2.58496, 2.58496, + 2.80735, 2.80735, 3, 3.16993, 3.32193, 3.45943, 3.58496, + 3.80735, 4.08746, 4.32193, 4.58496, 4.90689, 5.2854, 5.72792, + 6.20945, 6.74147, 7.35755}}, + /*25*/ {{0, 1, 1.58496, 1.58496, 1.58496, 2, 2, + 2, 2, 2, 2.32193, 2.32193, 2.32193, 2.58496, + 2.58496, 2.80735, 2.80735, 3, 3.16993, 3.32193, 3.45943, + 3.70044, 3.80735, 4.08746, 4.32193, 4.58496, 4.9542, 5.32193, + 5.72792, 6.20945, 6.75489}}, + /*26*/ {{0, 1, 1.58496, 1.58496, 1.58496, 1.58496, 2, + 2, 2, 2, 2, 2.32193, 2.32193, 2.32193, + 2.58496, 2.58496, 2.80735, 2.80735, 3, 3.16993, 3.32193, + 3.45943, 3.70044, 3.90689, 4.08746, 4.32193, 4.64386, 4.9542, + 5.32193, 5.72792, 6.20945}}, + /*27*/ {{0, 1, 1.58496, 1.58496, 1.58496, 1.58496, 1.58496, + 2, 2, 2, 2, 2.32193, 2.32193, 2.32193, + 2.32193, 2.58496, 2.58496, 2.80735, 2.80735, 3, 3.16993, + 3.32193, 3.45943, 3.70044, 3.90689, 4.08746, 4.32193, 4.64386, + 4.9542, 5.32193, 5.75489}}, + /*28*/ {{0, 1, 1.58496, 1.58496, 1.58496, 1.58496, 1.58496, + 1.58496, 2, 2, 2, 2, 2.32193, 2.32193, + 2.32193, 2.58496, 2.58496, 2.58496, 2.80735, 3, 3, + 3.16993, 3.32193, 3.45943, 3.70044, 3.90689, 4.08746, 4.39232, + 4.64386, 4.9542, 5.32193}}, + /*29*/ {{0, 1, 1.58496, 1.58496, 1.58496, 1.58496, 1.58496, + 1.58496, 2, 2, 2, 2, 2, 2.32193, + 2.32193, 2.32193, 2.58496, 2.58496, 2.58496, 2.80735, 3, + 3, 3.16993, 3.32193, 3.58496, 3.70044, 3.90689, 4.16993, + 4.39232, 4.64386, 5}}, + /*30*/ {{0, 1, 1.58496, 1.58496, 1.58496, 1.58496, 1.58496, + 1.58496, 1.58496, 2, 2, 2, 2, 2, + 2.32193, 2.32193, 2.32193, 2.58496, 2.58496, 2.80735, 2.80735, + 3, 3, 3.16993, 3.32193, 3.58496, 3.70044, 3.90689, + 4.16993, 4.39232, 4.70044}}, + /*31*/ {{0, 1, 1, 1.58496, 1.58496, 1.58496, 1.58496, + 1.58496, 1.58496, 1.58496, 2, 2, 2, 2, + 2, 2.32193, 2.32193, 2.32193, 2.58496, 2.58496, 2.80735, + 2.80735, 3, 3.16993, 3.16993, 3.32193, 3.58496, 3.70044, + 3.90689, 4.16993, 4.39232}}, + /*32*/ {{0, 1, 1, 1.58496, 1.58496, 1.58496, 1.58496, + 1.58496, 1.58496, 1.58496, 1.58496, 2, 2, 2, + 2, 2.32193, 2.32193, 2.32193, 2.32193, 2.58496, 2.58496, + 2.80735, 2.80735, 3, 3.16993, 3.32193, 3.45943, 3.58496, + 3.70044, 3.90689, 4.16993}}}}; +std::vector<std::vector<double>> logbin_sizes_80bit{ + {/*0*/ {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}}, + /*1*/ {{0, 1, 2, 3, 4, 5, 6, + 6.88264, 7.69349, 8.53138, 9.39874, 10.2946, 11.2143, 12.1551, + 13.1115, 14.0797, 15.0568, 16.0404, 17.0287, 18.0203, 19.0144, + 20.0102, 21.0072, 22.0051, 23.0036, 24.0026, 25.0018, 26.0013, + 27.0009, 28.0006, 29.0005}}, + /*2*/ {{0, 1, 2, 3, 4, 5, 5.78136, + 6.45943, 7.14975, 7.88264, 8.66888, 9.49785, 10.3652, 11.2656, + 12.1917, 13.1376, 14.0984, 15.0701, 16.0498, 17.0354, 18.0251, + 19.0178, 20.0126, 21.0089, 22.0063, 23.0045, 24.0032, 25.0022, + 26.0016, 27.0011, 28.0008}}, + /*3*/ {{0, 1, 2, 3, 4, 4.90689, 5.45943, + 6.02237, 6.61471, 7.25739, 7.96578, 8.72792, 9.5411, 10.3977, + 11.2889, 12.2085, 13.1496, 14.107, 15.0762, 16.0542, 17.0385, + 18.0273, 19.0193, 20.0137, 21.0097, 22.0069, 23.0048, 24.0034, + 25.0024, 26.0017, 27.0012}}, + /*4*/ {{0, 1, 2, 3, 4, 4.64386, 5.12928, + 5.58496, 6.10852, 6.6865, 7.31288, 8.00562, 8.75822, 9.56415, + 10.4136, 11.3009, 12.217, 13.1558, 14.1114, 15.0794, 16.0564, + 17.0401, 18.0284, 19.0201, 20.0143, 21.0101, 22.0071, 23.005, + 24.0036, 25.0025, 26.0018}}, + /*5*/ {{0, 1, 2, 3, 4, 4.45943, 4.80735, + 5.20945, 5.67243, 6.16993, 6.72792, 7.34873, 8.02791, 8.77479, + 9.57554, 10.4231, 11.3078, 12.2219, 13.1594, 14.1139, 15.0615, + 16.0577, 17.041, 18.0291, 19.0206, 20.0146, 21.0103, 22.0073, + 23.0052, 24.0037, 25.0026}}, + /*6*/ {{0, 1, 2, 3, 3.80735, 4.24793, 4.52356, + 4.90689, 5.2854, 5.70044, 6.18982, 6.74147, 7.36632, 8.04439, + 8.7879, 9.58496, 10.4284, 11.3117, 12.2252, 13.1616, 14.1155, + 15.0823, 16.0585, 17.0416, 18.0295, 19.0209, 20.0148, 21.0105, + 22.0074, 23.0052, 24.0037}}, + /*7*/ {{0, 1, 2, 3, 3.70044, 4, 4.32193, + 4.58496, 4.9542, 5.32193, 5.72792, 6.20945, 6.76818, 7.37504, + 8.05528, 8.79442, 9.59059, 10.4325, 11.3151, 12.2273, 13.1632, + 14.1167, 15.0831, 16.0591, 17.042, 18.0298, 19.0211, 20.0149, + 21.0106, 22.0075, 23.0053}}, + /*8*/ {{0, 1, 2, 3, 3.58496, 3.80735, 4.08746, + 4.32193, 4.64386, 4.9542, 5.32193, 5.75489, 6.22882, 6.76818, + 7.3837, 8.0607, 8.8009, 9.59432, 10.4357, 11.3174, 12.2291, + 13.1644, 14.1176, 15.0838, 16.0596, 17.0423, 18.03, 19.0213, + 20.0151, 21.0107, 22.0075}}, + /*9*/ {{0, 1, 2, 3, 3.45943, 3.70044, 3.90689, + 4.08746, 4.39232, 4.64386, 4.9542, 5.35755, 5.75489, 6.24793, + 6.78136, 7.39232, 8.06609, 8.80413, 9.59805, 10.4388, 11.3191, + 12.2306, 13.1655, 14.1184, 15.0844, 16.06, 17.0426, 18.0302, + 19.0214, 20.0152, 21.0107}}, + /*10*/ {{0, 1, 2, 3, 3.32193, 3.45943, 3.70044, + 3.90689, 4.16993, 4.39232, 4.64386, 5, 5.35755, 5.78136, + 6.24793, 6.79442, 7.40088, 8.07146, 8.80735, 9.60177, 10.4419, + 11.3214, 12.2321, 13.1666, 14.1191, 15.0849, 16.0604, 17.0429, + 18.0304, 19.0215, 20.0153}}, + /*11*/ {{0, 1, 2, 3, 3.16993, 3.32193, 3.58496, + 3.70044, 3.90689, 4.16993, 4.39232, 4.70044, 5, 5.35755, + 5.78136, 6.26679, 6.79442, 7.40088, 8.07682, 8.81378, 9.60363, + 10.444, 11.3231, 12.2333, 13.1676, 14.1198, 15.0854, 16.0607, + 17.0431, 18.0306, 19.0217}}, + /*12*/ {{0, 1, 2, 2.80735, 3, 3.16993, 3.32193, + 3.58496, 3.70044, 3.90689, 4.16993, 4.39232, 4.70044, 5, + 5.39232, 5.78136, 6.26679, 6.80735, 7.40939, 8.08215, 8.81698, + 9.60733, 10.446, 11.3247, 12.2345, 13.1685, 14.1205, 15.0859, + 16.0611, 17.0434, 18.0308}}, + /*13*/ {{0, 1, 2, 2.80735, 3, 3.16993, 3.32193, + 3.45943, 3.58496, 3.80735, 4, 4.16993, 4.45943, 4.70044, + 5.04439, 5.39232, 5.80735, 6.2854, 6.80735, 7.41785, 8.08746, + 8.82018, 9.61102, 10.4481, 11.3264, 12.236, 13.1695, 14.1211, + 15.0864, 16.0614, 17.0436}}, + /*14*/ {{0, 1, 2, 2.58496, 2.80735, 3, 3.16993, + 3.32193, 3.45943, 3.58496, 3.80735, 4, 4.16993, 4.45943, + 4.70044, 5.04439, 5.39232, 5.80735, 6.2854, 6.82018, 7.42626, + 8.09276, 8.82337, 9.61287, 10.4512, 11.3281, 12.2372, 13.1704, + 14.1219, 15.0868, 16.0618}}, + /*15*/ {{0, 1, 2, 2.58496, 2.80735, 2.80735, 3, + 3.16993, 3.32193, 3.45943, 3.58496, 3.80735, 4, 4.16993, + 4.45943, 4.75489, 5.04439, 5.42626, 5.83289, 6.2854, 6.83289, + 7.42626, 8.09803, 8.82972, 9.61655, 10.4533, 11.3298, 12.2384, + 13.1712, 14.1225, 15.0873}}, + /*16*/ {{0, 1, 2, 2.58496, 2.58496, 2.80735, 3, + 3, 3.16993, 3.32193, 3.45943, 3.58496, 3.80735, 4, + 4.24793, 4.45943, 4.75489, 5.04439, 5.42626, 5.83289, 6.30378, + 6.83289, 7.43463, 8.10329, 8.83289, 9.61839, 10.4553, 11.3315, + 12.2396, 13.1721, 14.1232}}, + /*17*/ {{0, 1, 2, 2.32193, 2.58496, 2.58496, 2.80735, + 3, 3, 3.16993, 3.32193, 3.45943, 3.58496, 3.80735, + 4, 4.24793, 4.45943, 4.75489, 5.08746, 5.42626, 5.83289, + 6.30378, 6.84549, 7.44294, 8.10852, 8.83605, 9.62205, 10.4574, + 11.3332, 12.2408, 13.1731}}, + /*18*/ {{0, 1, 2, 2.32193, 2.58496, 2.58496, 2.80735, + 2.80735, 3, 3, 3.16993, 3.32193, 3.45943, 3.70044, + 3.80735, 4, 4.24793, 4.45943, 4.75489, 5.08746, 5.42626, + 5.85798, 6.32193, 6.84549, 7.44294, 8.10852, 8.8392, 9.62571, + 10.4594, 11.3348, 12.242}}, + /*19*/ {{0, 1, 2, 2.32193, 2.32193, 2.58496, 2.58496, + 2.80735, 2.80735, 3, 3, 3.16993, 3.32193, 3.45943, + 3.70044, 3.80735, 4, 4.24793, 4.52356, 4.75489, 5.08746, + 5.45943, 5.85798, 6.32193, 6.85798, 7.45121, 8.11374, 8.84235, + 9.62753, 10.4625, 11.3365}}, + /*20*/ {{0, 1, 2, 2.32193, 2.32193, 2.32193, 2.58496, + 2.58496, 2.80735, 2.80735, 3, 3.16993, 3.16993, 3.32193, + 3.45943, 3.70044, 3.80735, 4.08746, 4.24793, 4.52356, 4.80735, + 5.08746, 5.45943, 5.85798, 6.33985, 6.85798, 7.45943, 8.11894, + 8.84549, 9.63118, 10.4645}}, + /*21*/ {{0, 1, 2, 2.32193, 2.32193, 2.32193, 2.58496, + 2.58496, 2.58496, 2.80735, 2.80735, 3, 3.16993, 3.16993, + 3.32193, 3.58496, 3.70044, 3.90689, 4.08746, 4.24793, 4.52356, + 4.80735, 5.12928, 5.45943, 5.88264, 6.33985, 6.87036, 7.45943, + 8.12412, 8.85175, 9.633}}, + /*22*/ {{0, 1, 2, 2, 2.32193, 2.32193, 2.32193, + 2.58496, 2.58496, 2.58496, 2.80735, 2.80735, 3, 3.16993, + 3.32193, 3.32193, 3.58496, 3.70044, 3.90689, 4.08746, 4.32193, + 4.52356, 4.80735, 5.12928, 5.49185, 5.88264, 6.33985, 6.87036, + 7.46761, 8.12928, 8.85487}}, + /*23*/ {{0, 1, 2, 2, 2.32193, 2.32193, 2.32193, + 2.32193, 2.58496, 2.58496, 2.58496, 2.80735, 2.80735, 3, + 3.16993, 3.32193, 3.45943, 3.58496, 3.70044, 3.90689, 4.08746, + 4.32193, 4.52356, 4.80735, 5.12928, 5.49185, 5.88264, 6.35755, + 6.88264, 7.47573, 8.13443}}, + /*24*/ {{0, 1, 2, 2, 2, 2.32193, 2.32193, + 2.32193, 2.32193, 2.58496, 2.58496, 2.80735, 2.80735, 3, + 3, 3.16993, 3.32193, 3.45943, 3.58496, 3.70044, 3.90689, + 4.08746, 4.32193, 4.58496, 4.80735, 5.12928, 5.49185, 5.90689, + 6.35755, 6.88264, 7.47573}}, + /*25*/ {{0, 1, 2, 2, 2, 2, 2.32193, + 2.32193, 2.32193, 2.32193, 2.58496, 2.58496, 2.80735, 2.80735, + 3, 3, 3.16993, 3.32193, 3.45943, 3.58496, 3.70044, + 3.90689, 4.08746, 4.32193, 4.58496, 4.85798, 5.16993, 5.49185, + 5.90689, 6.37504, 6.89482}}, + /*26*/ {{0, 1, 2, 2, 2, 2, 2, + 2.32193, 2.32193, 2.32193, 2.58496, 2.58496, 2.58496, 2.80735, + 2.80735, 3, 3, 3.16993, 3.32193, 3.45943, 3.58496, + 3.70044, 3.90689, 4.08746, 4.32193, 4.58496, 4.85798, 5.16993, + 5.52356, 5.90689, 6.37504}}, + /*27*/ {{0, 1, 1.58496, 2, 2, 2, 2, + 2, 2.32193, 2.32193, 2.32193, 2.58496, 2.58496, 2.58496, + 2.80735, 2.80735, 3, 3, 3.16993, 3.32193, 3.45943, + 3.58496, 3.80735, 3.90689, 4.16993, 4.32193, 4.58496, 4.85798, + 5.16993, 5.52356, 5.93074}}, + /*28*/ {{0, 1, 1.58496, 2, 2, 2, 2, + 2, 2.32193, 2.32193, 2.32193, 2.32193, 2.58496, 2.58496, + 2.58496, 2.80735, 2.80735, 3, 3, 3.16993, 3.32193, + 3.45943, 3.58496, 3.80735, 3.90689, 4.16993, 4.39232, 4.58496, + 4.85798, 5.16993, 5.52356}}, + /*29*/ {{0, 1, 1.58496, 1.58496, 2, 2, 2, + 2, 2, 2.32193, 2.32193, 2.32193, 2.32193, 2.58496, + 2.58496, 2.58496, 2.80735, 2.80735, 3, 3.16993, 3.16993, + 3.32193, 3.45943, 3.58496, 3.80735, 4, 4.16993, 4.39232, + 4.58496, 4.85798, 5.16993}}, + /*30*/ {{0, 1, 1.58496, 1.58496, 2, 2, 2, + 2, 2, 2, 2.32193, 2.32193, 2.32193, 2.32193, + 2.58496, 2.58496, 2.58496, 2.80735, 2.80735, 3, 3.16993, + 3.16993, 3.32193, 3.45943, 3.58496, 3.80735, 4, 4.16993, + 4.39232, 4.64386, 4.90689}}, + /*31*/ {{0, 1, 1.58496, 1.58496, 1.58496, 2, 2, + 2, 2, 2, 2, 2.32193, 2.32193, 2.32193, + 2.32193, 2.58496, 2.58496, 2.80735, 2.80735, 2.80735, 3, + 3.16993, 3.16993, 3.32193, 3.45943, 3.70044, 3.80735, 4, + 4.16993, 4.39232, 4.64386}}, + /*32*/ {{0, 1, 1.58496, 1.58496, 1.58496, 1.58496, 2, + 2, 2, 2, 2, 2.32193, 2.32193, 2.32193, + 2.32193, 2.58496, 2.58496, 2.58496, 2.80735, 2.80735, 3, + 3, 3.16993, 3.32193, 3.32193, 3.45943, 3.70044, 3.80735, + 4, 4.16993, 4.39232}}}}; + +} // namespace + +uint64_t SimpleIndex::GetBinSize(uint64_t num_bins, uint64_t num_balls, + uint64_t stat_sec_param, bool approx) { + SPDLOG_DEBUG("num_bins:{}, num_balls:{}, approx:{}", num_bins, num_balls, + approx); + + if (num_bins < 2) { + return num_balls; + } + + if (approx) { + auto& sizes = [&]() -> std::vector<std::vector<double>>& { + if (stat_sec_param <= 40) { + return logbin_sizes_40bit; + } + if (stat_sec_param <= 50) { + return logbin_sizes_50bit; + } + if (stat_sec_param <= 60) { + return logbin_sizes_60bit; + } + return logbin_sizes_80bit; + }(); + + auto num_bins_low = std::floor(std::log2(num_bins)); + auto num_bins_hgh = std::ceil(std::log2(num_bins)); + auto num_balls_low = std::floor(std::log2(num_balls)); + auto num_balls_hgh = std::ceil(std::log2(num_balls)); + + auto diff_bin = std::log2(num_bins) - num_bins_low; + auto diff_ball = std::log2(num_balls) - num_balls_low; + + SPDLOG_DEBUG( + "num_bins_low:{}, num_bins_hgh:{}, num_balls_low:{}, num_balls_hgh:{}", + num_bins_low, num_bins_hgh, num_balls_low, num_balls_hgh); + SPDLOG_DEBUG("diff_bin:{}, diff_ball:{}", diff_bin, diff_ball); + + // interpolate a linear 2d spline between the serounding points (a surface). + // Then evalate the surface at out bin,ball coordinate. + if (num_bins_hgh < sizes.size() && + num_balls_hgh < sizes[num_bins_hgh].size()) { + auto a0 = (diff_bin)*sizes[num_bins_low][num_balls_low] + + (1 - diff_bin) * sizes[num_bins_low][num_balls_hgh]; + auto a1 = (diff_bin)*sizes[num_bins_hgh][num_balls_low] + + (1 - diff_bin) * sizes[num_bins_hgh][num_balls_hgh]; + + auto b0 = (diff_ball)*a0 + (1 - diff_ball) * a1; + + auto B = std::ceil(std::pow(2, b0)); + +#if !defined(NDEBUG) && defined(ENABLE_BOOST) + auto B2 = get_bin_size(numBins, numBalls, statSecParam, false); + YACL_ENFORCE(B2 <= B); +#endif + + return B; + } + } + + auto B = std::max<uint64_t>(1, num_balls / num_bins); + double current_prob = GetBinOverflowProb(num_bins, num_balls, B); + uint64_t step = 1; + + bool doubling = true; + + while (current_prob < stat_sec_param || step > 1) { + YACL_ENFORCE(step, "setp:{} current_prob:{}", step, current_prob); + + if (stat_sec_param > current_prob) { + if (doubling) { + step = std::max<uint64_t>(1, step * 2); + } else { + step = std::max<uint64_t>(1, step / 2); + } + + B += step; + } else { + doubling = false; + step = std::max<uint64_t>(1, step / 2); + B -= step; + } + current_prob = GetBinOverflowProb(num_bins, num_balls, B); + } + + return B; +} + +} // namespace psi::psi::okvs diff --git a/psi/psi/core/vole_psi/okvs/simple_index.h b/psi/psi/core/vole_psi/okvs/simple_index.h new file mode 100644 index 00000000..6904c32a --- /dev/null +++ b/psi/psi/core/vole_psi/okvs/simple_index.h @@ -0,0 +1,27 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <cstdint> + +namespace psi::psi::okvs { + +class SimpleIndex { + public: + static uint64_t GetBinSize(uint64_t num_bins, uint64_t num_balls, + uint64_t stat_sec_param, bool approx = true); +}; + +} // namespace psi::psi::okvs diff --git a/psi/psi/core/vole_psi/rr22_oprf.cc b/psi/psi/core/vole_psi/rr22_oprf.cc new file mode 100644 index 00000000..2daef595 --- /dev/null +++ b/psi/psi/core/vole_psi/rr22_oprf.cc @@ -0,0 +1,564 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/vole_psi/rr22_oprf.h" + +#include <algorithm> +#include <future> + +#include "fmt/format.h" +#include "spdlog/spdlog.h" +#include "yacl/base/buffer.h" +#include "yacl/base/byte_container_view.h" +#include "yacl/crypto/tools/prg.h" +#include "yacl/crypto/utils/rand.h" +#include "yacl/math/f2k/f2k.h" +#include "yacl/utils/parallel.h" + +#include "psi/psi/core/vole_psi/okvs/galois128.h" + +namespace psi::psi { + +namespace { + +constexpr uint128_t kAesHashSeed = + yacl::MakeUint128(0x99e096a63468f39f, 0x9ceaad9f20cc8233); + +constexpr size_t kPaxosWeight = 3; + +} // namespace + +#define USE_MOCK 0 + +MocRr22VoleSender::MocRr22VoleSender(uint128_t seed) : seed_(seed) { + uint128_t seed1 = yacl::MakeUint128(0xe0, 0x0f); + yacl::crypto::Prg<uint128_t> prng1(seed1); + + prng1.Fill(absl::MakeSpan(&delta_, 1)); +} + +void MocRr22VoleSender::Send( + [[maybe_unused]] const std::shared_ptr<yacl::link::Context>& lctx, + absl::Span<uint128_t> c) { + size_t vole_num = c.size(); + + std::vector<uint128_t> a(vole_num); + + std::vector<uint128_t> b(vole_num); + + yacl::crypto::Prg<uint128_t> prng2(seed_); + prng2.Fill(absl::MakeSpan(a)); + + prng2.Fill(absl::MakeSpan(b)); + + okvs::Galois128 delta_gf128(delta_); + + for (size_t i = 0; i < vole_num; ++i) { + c[i] = b[i] ^ (delta_gf128 * a[i]).get<uint128_t>(0); + } +} + +void MocRr22VoleSender::SendF64( + [[maybe_unused]] const std::shared_ptr<yacl::link::Context>& lctx, + absl::Span<uint128_t> c) { + size_t vole_num = c.size(); + + std::vector<uint64_t> a(vole_num); + + std::vector<uint128_t> b(vole_num); + + yacl::crypto::Prg<uint64_t> prng(seed_); + yacl::crypto::Prg<uint128_t> prng2(seed_ + 1); + + prng.Fill(absl::MakeSpan(a)); + + prng2.Fill(absl::MakeSpan(b)); + + okvs::Galois128 delta_gf128(delta_); + + for (size_t i = 0; i < vole_num; ++i) { + c[i] = b[i] ^ (delta_gf128 * a[i]).get<uint128_t>(0); + } +} + +MocRr22VoleReceiver::MocRr22VoleReceiver(uint128_t seed) : seed_(seed) {} + +void MocRr22VoleReceiver::Recv( + [[maybe_unused]] const std::shared_ptr<yacl::link::Context>& lctx, + absl::Span<uint128_t> a, absl::Span<uint128_t> b) { + yacl::crypto::Prg<uint128_t> prng(seed_); + + SPDLOG_INFO("rank:{}", lctx->Rank()); + + prng.Fill(a); + prng.Fill(b); +} + +void MocRr22VoleReceiver::RecvF64( + [[maybe_unused]] const std::shared_ptr<yacl::link::Context>& lctx, + absl::Span<uint64_t> a, absl::Span<uint128_t> b) { + SPDLOG_INFO("rank:{}", lctx->Rank()); + yacl::crypto::Prg<uint64_t> prng(seed_); + yacl::crypto::Prg<uint128_t> prng2(seed_ + 1); + + prng.Fill(a); + prng2.Fill(b); +} + +void Rr22OprfSender::Send(const std::shared_ptr<yacl::link::Context>& lctx, + size_t paxos_init_size, + absl::Span<const uint128_t> inputs, + absl::Span<uint128_t> hash_outputs, + [[maybe_unused]] size_t num_threads) { + if (mode_ == Rr22PsiMode::FastMode) { + SendFast(lctx, paxos_init_size, inputs, hash_outputs, num_threads); + } else if (mode_ == Rr22PsiMode::LowCommMode) { + SendLowComm(lctx, paxos_init_size, inputs, hash_outputs, num_threads); + } +} + +void Rr22OprfSender::SendFast(const std::shared_ptr<yacl::link::Context>& lctx, + size_t paxos_init_size, + absl::Span<const uint128_t> inputs, + absl::Span<uint128_t> hash_outputs, + [[maybe_unused]] size_t num_threads) { + uint128_t baxos_seed; + SPDLOG_INFO("recv paxos seed..."); + + yacl::Buffer baxos_seed_buf = + lctx->Recv(lctx->NextRank(), fmt::format("recv paxos seed")); + YACL_ENFORCE(baxos_seed_buf.size() == sizeof(uint128_t)); + + SPDLOG_INFO("recv paxos seed finished"); + + std::memcpy(&baxos_seed, baxos_seed_buf.data(), baxos_seed_buf.size()); + + baxos_.Init(paxos_init_size, bin_size_, kPaxosWeight, ssp_, + okvs::PaxosParam::DenseType::GF128, baxos_seed); + + paxos_size_ = baxos_.size(); + + // vole send function + yacl::crypto::SilentVoleSender vole_sender(code_type_); + + b_ = yacl::Buffer(std::max<size_t>(256, baxos_.size()) * sizeof(uint128_t)); + absl::Span<uint128_t> b128_span = + absl::MakeSpan(reinterpret_cast<uint128_t*>(b_.data()), + std::max<size_t>(256, baxos_.size())); + + SPDLOG_INFO("begin vole send"); + + vole_sender.Send(lctx, b128_span); + delta_ = vole_sender.GetDelta(); + + SPDLOG_INFO("end vole send"); + + auto hash_inputs_proc = + std::async([&] { HashInputMulDelta(inputs, hash_outputs); }); + + SPDLOG_INFO("recv paxos solve ..."); + + yacl::Buffer paxos_solve_buffer(paxos_size_ * sizeof(uint128_t)); + absl::Span<uint128_t> paxos_solve_vec = + absl::MakeSpan((uint128_t*)paxos_solve_buffer.data(), paxos_size_); + + size_t recv_item_count = 0; + + while (true) { + yacl::Buffer paxos_solve_buf = + lctx->Recv(lctx->NextRank(), fmt::format("recv paxos_solve")); + + std::memcpy(&paxos_solve_vec[recv_item_count], paxos_solve_buf.data(), + paxos_solve_buf.size()); + + recv_item_count += paxos_solve_buf.size() / sizeof(uint128_t); + if (recv_item_count == paxos_size_) { + break; + } + } + + SPDLOG_INFO("recv paxos solve finished. bytes:{}", + paxos_solve_vec.size() * sizeof(uint128_t)); + + hash_inputs_proc.get(); + + okvs::Galois128 delta_gf128(delta_); + + SPDLOG_INFO("begin b xor delta a"); + + yacl::parallel_for( + 0, paxos_solve_vec.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + b128_span[idx] = + b128_span[idx] ^ + (delta_gf128 * paxos_solve_vec[idx]).get<uint128_t>(0); + } + }); + + SPDLOG_INFO("end b xor delta a"); +} + +void Rr22OprfSender::SendLowComm( + const std::shared_ptr<yacl::link::Context>& lctx, size_t paxos_init_size, + absl::Span<const uint128_t> inputs, absl::Span<uint128_t> hash_outputs, + [[maybe_unused]] size_t num_threads) { + uint128_t paxos_seed; + SPDLOG_INFO("recv paxos seed..."); + + yacl::Buffer paxos_seed_buf = + lctx->Recv(lctx->NextRank(), fmt::format("recv paxos seed")); + YACL_ENFORCE(paxos_seed_buf.size() == sizeof(uint128_t)); + + std::memcpy(&paxos_seed, paxos_seed_buf.data(), paxos_seed_buf.size()); + + paxos_.Init(paxos_init_size, kPaxosWeight, ssp_, + okvs::PaxosParam::DenseType::GF128, paxos_seed); + + paxos_size_ = paxos_.size(); + + // vole send function + + yacl::crypto::SilentVoleSender vole_sender(code_type_); + + b_ = yacl::Buffer(std::max<size_t>(256, paxos_.size()) * sizeof(uint128_t)); + absl::Span<uint128_t> b128_span = + absl::MakeSpan(reinterpret_cast<uint128_t*>(b_.data()), + std::max<size_t>(256, paxos_.size())); + + SPDLOG_INFO("begin vole send"); + + vole_sender.SfSend(lctx, b128_span); + delta_ = vole_sender.GetDelta(); + + SPDLOG_INFO("end vole send"); + + HashInputMulDelta(inputs, hash_outputs); + + SPDLOG_INFO("recv paxos solve ..."); + yacl::Buffer paxos_solve_buf = + lctx->Recv(lctx->NextRank(), fmt::format("recv paxos_solve")); + YACL_ENFORCE(paxos_solve_buf.size() / sizeof(uint64_t) == paxos_size_); + + SPDLOG_INFO("recv paxos solve finished. bytes:{}", paxos_solve_buf.size()); + + absl::Span<uint64_t> paxos_solve_u64 = + absl::MakeSpan(reinterpret_cast<uint64_t*>(paxos_solve_buf.data()), + paxos_solve_buf.size() / sizeof(uint64_t)); + + SPDLOG_INFO("paxos_solve_u64 size:{}", paxos_solve_u64.size()); + + okvs::Galois128 delta_gf128(delta_); + + for (size_t i = 0; i < paxos_solve_u64.size(); ++i) { + b128_span[i] = + b128_span[i] ^ (delta_gf128 * paxos_solve_u64[i]).get<uint128_t>(0); + } +} + +void Rr22OprfSender::Eval(absl::Span<const uint128_t> inputs, + absl::Span<uint128_t> outputs, uint64_t num_threads) { + SPDLOG_INFO("paxos decode ..."); + + YACL_ENFORCE(b_.size() > 0, "Must use Send() first"); + + absl::Span<uint128_t> b128_span = + absl::MakeSpan(reinterpret_cast<uint128_t*>(b_.data()), paxos_size_); + + if (mode_ == Rr22PsiMode::FastMode) { + baxos_.Decode(inputs, outputs, b128_span, num_threads); + } else if (mode_ == Rr22PsiMode::LowCommMode) { + paxos_.Decode(inputs, outputs, b128_span); + } else { + YACL_THROW("unsupported rr22 psi mode"); + } + + SPDLOG_INFO("paxos decode finished"); + + okvs::AesCrHash aes_crhash(kAesHashSeed); + + okvs::Galois128 delta_gf128(delta_); + + if (mode_ == Rr22PsiMode::FastMode) { + yacl::parallel_for(0, inputs.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + uint128_t h = aes_crhash.Hash(inputs[idx]); + outputs[idx] = outputs[idx] ^ (delta_gf128 * h).get<uint128_t>(0); + } + }); + } else if (mode_ == Rr22PsiMode::LowCommMode) { + yacl::parallel_for(0, inputs.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + uint64_t h = + yacl::DecomposeUInt128(aes_crhash.Hash(inputs[idx])).second; + outputs[idx] = outputs[idx] ^ (delta_gf128 * h).get<uint128_t>(0); + } + }); + } + aes_crhash.Hash(outputs, outputs); +} + +void Rr22OprfSender::HashInputMulDelta(absl::Span<const uint128_t> inputs, + absl::Span<uint128_t> hash_outputs) { + YACL_ENFORCE(hash_outputs.size() == inputs.size()); + + okvs::Galois128 delta_gf128(delta_); + okvs::AesCrHash aes_crhash(kAesHashSeed); + + if (mode_ == Rr22PsiMode::FastMode) { + for (size_t i = 0; i < inputs.size(); ++i) { + hash_outputs[i] = + (delta_gf128 * aes_crhash.Hash(inputs[i])).get<uint128_t>(0); + } + } else if (mode_ == Rr22PsiMode::LowCommMode) { + for (size_t i = 0; i < inputs.size(); ++i) { + hash_outputs[i] = + (delta_gf128 * + yacl::DecomposeUInt128(aes_crhash.Hash(inputs[i])).second) + .get<uint128_t>(0); + } + } else { + YACL_THROW("unsupported rr22 psi mode"); + } +} + +void Rr22OprfSender::Eval(absl::Span<const uint128_t> inputs, + absl::Span<const uint128_t> inputs_hash, + absl::Span<uint128_t> outputs, uint64_t num_threads) { + YACL_ENFORCE(b_.size() > 0, "Must use Send() first"); + + absl::Span<uint128_t> b128_span = + absl::MakeSpan(reinterpret_cast<uint128_t*>(b_.data()), paxos_size_); + + SPDLOG_INFO("paxos decode (mode:{}) ...", + mode_ == Rr22PsiMode::FastMode ? "Fast" : "LowComm"); + + if (mode_ == Rr22PsiMode::FastMode) { + baxos_.Decode(inputs, outputs, b128_span, num_threads); + } else if (mode_ == Rr22PsiMode::LowCommMode) { + paxos_.Decode(inputs, outputs, b128_span); + } else { + YACL_THROW("unsupported rr22 psi mode"); + } + + SPDLOG_INFO("paxos decode finished"); + + yacl::parallel_for(0, inputs.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + outputs[idx] = outputs[idx] ^ inputs_hash[idx]; + } + }); + + okvs::AesCrHash aes_crhash(kAesHashSeed); + + aes_crhash.Hash(outputs, outputs); +} + +void Rr22OprfReceiver::Recv(const std::shared_ptr<yacl::link::Context>& lctx, + size_t paxos_init_size, + const std::vector<uint128_t>& inputs, + absl::Span<uint128_t> outputs, size_t num_threads) { + if (mode_ == Rr22PsiMode::FastMode) { + RecvFast(lctx, paxos_init_size, inputs, outputs, num_threads); + } else if (mode_ == Rr22PsiMode::LowCommMode) { + RecvLowComm(lctx, paxos_init_size, inputs, outputs, num_threads); + } +} + +void Rr22OprfReceiver::RecvFast( + const std::shared_ptr<yacl::link::Context>& lctx, size_t paxos_init_size, + const std::vector<uint128_t>& inputs, absl::Span<uint128_t> outputs, + size_t num_threads) { + YACL_ENFORCE(inputs.size() <= paxos_init_size); + + okvs::Baxos paxos; + + uint128_t paxos_seed = yacl::crypto::SecureRandU128(); + + yacl::ByteContainerView paxos_seed_buf(&paxos_seed, sizeof(uint128_t)); + + lctx->SendAsyncThrottled(lctx->NextRank(), paxos_seed_buf, + fmt::format("send paxos_seed_buf")); + paxos.Init(paxos_init_size, bin_size_, kPaxosWeight, ssp_, + okvs::PaxosParam::DenseType::GF128, paxos_seed); + + paxos_size_ = paxos.size(); + + // c + b = a * delta + yacl::Buffer a; + yacl::Buffer c; + absl::Span<uint128_t> a128_span; + absl::Span<uint128_t> c128_span; + + // vole recv function + auto vole_recv_proc = std::async([&] { + yacl::crypto::SilentVoleReceiver vole_receiver(code_type_); + + a = yacl::Buffer(std::max<size_t>(256, paxos.size()) * sizeof(uint128_t)); + c = yacl::Buffer(std::max<size_t>(256, paxos.size()) * sizeof(uint128_t)); + + a128_span = absl::MakeSpan(reinterpret_cast<uint128_t*>(a.data()), + std::max<size_t>(256, paxos.size())); + c128_span = absl::MakeSpan(reinterpret_cast<uint128_t*>(c.data()), + std::max<size_t>(256, paxos.size())); + + SPDLOG_INFO("a_,b_ size:{} {}", a128_span.size(), c128_span.size()); + + SPDLOG_INFO("begin vole recv"); + + vole_receiver.Recv(lctx, a128_span, c128_span); + + SPDLOG_INFO("end vole recv"); + }); + + okvs::AesCrHash aes_crhash(kAesHashSeed); + + aes_crhash.Hash(absl::MakeSpan(inputs), outputs); + + yacl::Buffer p_buffer(paxos.size() * sizeof(uint128_t)); + std::memset((uint8_t*)p_buffer.data(), 0, p_buffer.size()); + + absl::Span<uint128_t> p128_span = + absl::MakeSpan((uint128_t*)(p_buffer.data()), paxos.size()); + + SPDLOG_INFO("solve begin"); + paxos.Solve(absl::MakeSpan(inputs), outputs, p128_span, nullptr, num_threads); + SPDLOG_INFO("solve end"); + + vole_recv_proc.get(); + + auto oprf_eval_proc = std::async([&] { + SPDLOG_INFO("begin compute self oprf"); + paxos.Decode(absl::MakeSpan(inputs), outputs, + absl::MakeSpan(c128_span.data(), paxos.size()), num_threads); + + SPDLOG_INFO("call aes hash"); + aes_crhash.Hash(outputs, outputs); + SPDLOG_INFO("end compute self oprf"); + }); + + SPDLOG_INFO("begin p xor a"); + + yacl::parallel_for(0, p128_span.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + p128_span[idx] = p128_span[idx] ^ a128_span[idx]; + } + }); + + SPDLOG_INFO("end p xor a"); + + for (size_t i = 0; i < p128_span.size(); i += 100000) { + size_t batch_size = std::min<size_t>(100000, p128_span.size() - i); + yacl::ByteContainerView paxos_solve_byteview( + &p128_span[i], batch_size * sizeof(uint128_t)); + + lctx->SendAsyncThrottled(lctx->NextRank(), paxos_solve_byteview, + fmt::format("send paxos_solve_byteview")); + } + + oprf_eval_proc.get(); +} + +void Rr22OprfReceiver::RecvLowComm( + const std::shared_ptr<yacl::link::Context>& lctx, size_t paxos_init_size, + const std::vector<uint128_t>& inputs, absl::Span<uint128_t> outputs, + [[maybe_unused]] size_t num_threads) { + YACL_ENFORCE(inputs.size() <= paxos_init_size); + + okvs::Paxos<uint32_t> paxos; + + uint128_t paxos_seed = yacl::crypto::SecureRandU128(); + + yacl::ByteContainerView paxos_seed_buf(&paxos_seed, sizeof(uint128_t)); + + lctx->SendAsyncThrottled(lctx->NextRank(), paxos_seed_buf, + fmt::format("send paxos_seed_buf")); + + paxos.Init(paxos_init_size, kPaxosWeight, ssp_, + okvs::PaxosParam::DenseType::GF128, paxos_seed); + + paxos_size_ = paxos.size(); + + // c + b = a * delta + yacl::Buffer a; + yacl::Buffer c; + absl::Span<uint64_t> a64_span; + absl::Span<uint128_t> c128_span; + + // vole recv function + auto vole_recv_proc = std::async([&] { + SPDLOG_INFO("use SilentVoleReceiver"); + yacl::crypto::SilentVoleReceiver vole_receiver(code_type_); + + a = yacl::Buffer(std::max<size_t>(256, paxos.size()) * sizeof(uint64_t)); + c = yacl::Buffer(std::max<size_t>(256, paxos.size()) * sizeof(uint128_t)); + + a64_span = absl::MakeSpan(reinterpret_cast<uint64_t*>(a.data()), + std::max<size_t>(256, paxos.size())); + c128_span = absl::MakeSpan(reinterpret_cast<uint128_t*>(c.data()), + std::max<size_t>(256, paxos.size())); + SPDLOG_INFO("a_,b_ size:{} {} ", a64_span.size(), c128_span.size()); + + SPDLOG_INFO("begin vole recv"); + + vole_receiver.SfRecv(lctx, absl::MakeSpan(a64_span), c128_span); + + SPDLOG_INFO("end vole recv"); + }); + + okvs::AesCrHash aes_crhash(kAesHashSeed); + + aes_crhash.Hash(absl::MakeSpan(inputs), outputs); + + yacl::Buffer p_buffer(paxos.size() * sizeof(uint128_t)); + std::memset((uint8_t*)p_buffer.data(), 0, p_buffer.size()); + + absl::Span<uint128_t> p128_span((uint128_t*)p_buffer.data(), paxos.size()); + absl::Span<uint64_t> p64_span((uint64_t*)p_buffer.data(), paxos.size()); + + SPDLOG_INFO("solve begin"); + paxos.SetInput(absl::MakeSpan(inputs)); + + paxos.Encode(outputs, p128_span, nullptr); + + SPDLOG_INFO("solve end"); + + vole_recv_proc.get(); + + auto oprf_eval_proc = std::async([&] { + SPDLOG_INFO("begin receiver oprf"); + paxos.Decode(absl::MakeSpan(inputs), outputs, + absl::MakeSpan(c128_span.data(), paxos.size())); + + aes_crhash.Hash(outputs, outputs); + SPDLOG_INFO("end receiver oprf"); + }); + + SPDLOG_INFO("begin p xor a"); + + for (size_t i = 0; i < p64_span.size(); ++i) { + p64_span[i] = a64_span[i] ^ yacl::DecomposeUInt128(p128_span[i]).second; + } + + SPDLOG_INFO("end p xor a"); + + yacl::ByteContainerView paxos_solve_byteview( + p64_span.data(), p64_span.size() * sizeof(uint64_t)); + + lctx->SendAsyncThrottled(lctx->NextRank(), paxos_solve_byteview, + fmt::format("send paxos_solve_byteview")); + + oprf_eval_proc.get(); +} + +} // namespace psi::psi diff --git a/psi/psi/core/vole_psi/rr22_oprf.h b/psi/psi/core/vole_psi/rr22_oprf.h new file mode 100644 index 00000000..1d89eb6b --- /dev/null +++ b/psi/psi/core/vole_psi/rr22_oprf.h @@ -0,0 +1,167 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <memory> +#include <vector> + +#include "yacl/base/int128.h" +#include "yacl/crypto/primitives/vole/f2k/silent_vole.h" +#include "yacl/link/context.h" + +#include "psi/psi/core/vole_psi/okvs/baxos.h" + +namespace psi::psi { + +enum class Rr22PsiMode { + FastMode, + LowCommMode, +}; + +class MocRr22VoleSender { + public: + explicit MocRr22VoleSender(uint128_t seed); + + void Send(const std::shared_ptr<yacl::link::Context>& lctx, + absl::Span<uint128_t> c); + + void SendF64(const std::shared_ptr<yacl::link::Context>& lctx, + absl::Span<uint128_t> c); + + uint128_t GetDelta() { return delta_; } + + private: + uint128_t delta_; + uint128_t seed_; +}; + +class MocRr22VoleReceiver { + public: + explicit MocRr22VoleReceiver(uint128_t seed); + + void Recv(const std::shared_ptr<yacl::link::Context>& lctx, + absl::Span<uint128_t> a, absl::Span<uint128_t> b); + + void RecvF64(const std::shared_ptr<yacl::link::Context>& lctx, + absl::Span<uint64_t> a, absl::Span<uint128_t> b); + + uint128_t seed_; +}; + +class Rr22Oprf { + public: + Rr22Oprf( + size_t bin_size, size_t ssp, Rr22PsiMode mode = Rr22PsiMode::FastMode, + const yacl::crypto::CodeType& code_type = yacl::crypto::CodeType::Silver5, + bool malicious = false) + : bin_size_(bin_size), + ssp_(ssp), + code_type_(code_type), + mode_(mode), + malicious_(malicious) {} + + uint64_t GetBinSize() { return bin_size_; } + + uint64_t GetSsp() { return ssp_; } + + Rr22PsiMode GetMode() { return mode_; } + + yacl::crypto::CodeType GetCodeType() { return code_type_; } + + size_t GetPaxosSize() { return paxos_size_; } + + protected: + // + uint64_t bin_size_ = 0; + // ssp i.e.statistical security parameter. + // must >= 30bit + uint64_t ssp_ = 40; + + // Silver & ExAcc code + yacl::crypto::CodeType code_type_; + + // fase or lowcomm mode + Rr22PsiMode mode_; + + bool malicious_ = false; + + bool debug_ = false; + + size_t paxos_size_ = 0; +}; + +class Rr22OprfSender : public Rr22Oprf { + public: + Rr22OprfSender( + size_t bin_size, size_t ssp, Rr22PsiMode mode = Rr22PsiMode::FastMode, + const yacl::crypto::CodeType& code_type = yacl::crypto::CodeType::Silver5, + bool malicious = false) + : Rr22Oprf(bin_size, ssp, mode, code_type, malicious) {} + + void Send(const std::shared_ptr<yacl::link::Context>& lctx, size_t n, + absl::Span<const uint128_t> inputs, + absl::Span<uint128_t> hash_outputs, size_t num_threads); + + void SendFast(const std::shared_ptr<yacl::link::Context>& lctx, size_t n, + absl::Span<const uint128_t> inputs, + absl::Span<uint128_t> hash_outputs, size_t num_threads); + + void SendLowComm(const std::shared_ptr<yacl::link::Context>& lctx, size_t n, + absl::Span<const uint128_t> inputs, + absl::Span<uint128_t> hash_outputs, size_t num_threads); + + void HashInputMulDelta(absl::Span<const uint128_t> inputs, + absl::Span<uint128_t> hash_outputs); + + void Eval(absl::Span<const uint128_t> inputs, absl::Span<uint128_t> outputs, + uint64_t num_threads = 0); + + void Eval(absl::Span<const uint128_t> inputs, + absl::Span<const uint128_t> inputs_hash, + absl::Span<uint128_t> outputs, uint64_t num_threads = 0); + + private: + okvs::Baxos baxos_; + okvs::Paxos<uint32_t> paxos_; + + // b = delta * a + c + uint128_t delta_ = 0; + yacl::Buffer b_; +}; + +class Rr22OprfReceiver : public Rr22Oprf { + public: + Rr22OprfReceiver( + size_t bin_size, size_t ssp, Rr22PsiMode mode = Rr22PsiMode::FastMode, + const yacl::crypto::CodeType& code_type = yacl::crypto::CodeType::Silver5, + bool malicious = false) + : Rr22Oprf(bin_size, ssp, mode, code_type, malicious) {} + + void Recv(const std::shared_ptr<yacl::link::Context>& lctx, + size_t paxos_init_size, const std::vector<uint128_t>& inputs, + absl::Span<uint128_t> outputs, size_t num_threads); + + void RecvFast(const std::shared_ptr<yacl::link::Context>& lctx, + size_t paxos_init_size, const std::vector<uint128_t>& inputs, + absl::Span<uint128_t> outputs, size_t num_threads); + + void RecvLowComm(const std::shared_ptr<yacl::link::Context>& lctx, + size_t paxos_init_size, const std::vector<uint128_t>& inputs, + absl::Span<uint128_t> outputs, size_t num_threads); + + private: +}; + +} // namespace psi::psi diff --git a/psi/psi/core/vole_psi/rr22_oprf_test.cc b/psi/psi/core/vole_psi/rr22_oprf_test.cc new file mode 100644 index 00000000..a8e39eb2 --- /dev/null +++ b/psi/psi/core/vole_psi/rr22_oprf_test.cc @@ -0,0 +1,160 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/vole_psi/rr22_oprf.h" + +#include <future> +#include <vector> + +#include "gtest/gtest.h" +#include "yacl/crypto/tools/prg.h" +#include "yacl/link/test_util.h" + +#include "psi/psi/core/vole_psi/okvs/galois128.h" + +namespace psi::psi { + +namespace { + +constexpr size_t kRr22OprfBinSize = 1 << 14; +constexpr size_t kRr22DefaultSsp = 40; + +struct TestParams { + uint64_t items_num = 16; + + Rr22PsiMode mode = Rr22PsiMode::FastMode; +}; + +} // namespace + +class Rr22OprfTest : public testing::TestWithParam<TestParams> {}; + +TEST(Rr22OprfTest, MocVoleTest) { + uint128_t seed2 = yacl::MakeUint128(0, 1); + + auto lctxs = yacl::link::test::SetupWorld("ab", 2); + + size_t vole_num = 100; + + std::vector<uint128_t> a(vole_num); + std::vector<uint128_t> b(vole_num); + std::vector<uint128_t> c(vole_num); + uint128_t delta = 0; + + MocRr22VoleSender sender(seed2); + + MocRr22VoleReceiver receiver(seed2); + + sender.Send(lctxs[0], absl::MakeSpan(c)); + delta = sender.GetDelta(); + receiver.Recv(lctxs[1], absl::MakeSpan(a), absl::MakeSpan(b)); + + okvs::Galois128 delta_gf128(delta); + + for (size_t i = 0; i < vole_num; ++i) { + EXPECT_EQ(c[i] ^ b[i], (delta_gf128 * a[i]).get<uint128_t>(0)); + } +} + +TEST(Rr22OprfTest, SilverVoleTest) { + auto lctxs = yacl::link::test::SetupWorld(2); // setup network + + const auto codetype = yacl::crypto::CodeType::Silver5; + const uint64_t num = 10000; + + std::vector<uint128_t> a(num); + std::vector<uint128_t> b(num); + std::vector<uint128_t> c(num); + uint128_t delta = 0; + + auto sender = std::async([&] { + auto sv_sender = yacl::crypto::SilentVoleSender(codetype); + sv_sender.Send(lctxs[0], absl::MakeSpan(c)); + delta = sv_sender.GetDelta(); + }); + + auto receiver = std::async([&] { + auto sv_receiver = yacl::crypto::SilentVoleReceiver(codetype); + sv_receiver.Recv(lctxs[1], absl::MakeSpan(a), absl::MakeSpan(b)); + }); + + sender.get(); + receiver.get(); + + okvs::Galois128 delta_gf128(delta); + + SPDLOG_INFO("delta:{}", delta); + SPDLOG_INFO("a[i]:{}, b[i]:{}, c[0]:{}", a[0], b[0], c[0]); + SPDLOG_INFO("a[i]*delta ^ b[0]:{}", + (delta_gf128 * a[0]).get<uint128_t>(0) ^ b[0]); + + for (uint64_t i = 0; i < num; ++i) { + EXPECT_EQ((delta_gf128 * a[i]).get<uint128_t>(0) ^ b[i], c[i]); + } +} + +TEST_P(Rr22OprfTest, OprfTest) { + auto params = GetParam(); + + auto lctxs = yacl::link::test::SetupWorld("ab", 2); + + lctxs[0]->SetRecvTimeout(30 * 6 * 1000); + lctxs[1]->SetRecvTimeout(30 * 6 * 1000); + + uint128_t seed = yacl::MakeUint128(0, 0); + yacl::crypto::Prg<uint128_t> prng(seed); + + size_t item_size = params.items_num; + std::vector<uint128_t> values_a(item_size); + std::vector<uint128_t> values_b(item_size); + + prng.Fill(absl::MakeSpan(values_a)); + prng.Fill(absl::MakeSpan(values_b)); + + Rr22OprfSender oprf_sender(kRr22OprfBinSize, kRr22DefaultSsp, params.mode); + Rr22OprfReceiver oprf_receiver(kRr22OprfBinSize, kRr22DefaultSsp, + params.mode); + + std::vector<uint128_t> oprf_a(item_size); + std::vector<uint128_t> oprf_b(item_size); + + auto oprf_sender_proc = std::async([&] { + std::vector<uint128_t> sender_inputs_hash(item_size); + oprf_sender.Send(lctxs[0], item_size, absl::MakeSpan(values_b), + absl::MakeSpan(sender_inputs_hash), 1); + + oprf_sender.Eval(absl::MakeSpan(values_b), absl::MakeSpan(oprf_a)); + }); + auto oprf_receiver_proc = std::async([&] { + oprf_receiver.Recv(lctxs[1], item_size, values_b, absl::MakeSpan(oprf_b), + 1); + }); + + oprf_sender_proc.get(); + oprf_receiver_proc.get(); + + for (size_t i = 0; i < item_size; ++i) { + SPDLOG_INFO("{} {}", oprf_a[i], oprf_b[i]); + EXPECT_EQ(oprf_a[i], oprf_b[i]); + } + + EXPECT_EQ(oprf_a, oprf_b); +} + +INSTANTIATE_TEST_SUITE_P(OprfTest_Instances, Rr22OprfTest, + testing::Values(TestParams{15, Rr22PsiMode::FastMode}, + TestParams{15, + Rr22PsiMode::LowCommMode})); + +} // namespace psi::psi diff --git a/psi/psi/core/vole_psi/rr22_psi.cc b/psi/psi/core/vole_psi/rr22_psi.cc new file mode 100644 index 00000000..d6b340bc --- /dev/null +++ b/psi/psi/core/vole_psi/rr22_psi.cc @@ -0,0 +1,172 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/vole_psi/rr22_psi.h" + +#include <algorithm> +#include <cmath> +#include <future> +#include <ostream> + +#include "sparsehash/dense_hash_map" +#include "yacl/base/byte_container_view.h" + +#include "psi/psi/core/vole_psi/okvs/galois128.h" +#include "psi/psi/core/vole_psi/rr22_oprf.h" +#include "psi/psi/core/vole_psi/rr22_utils.h" +#include "psi/psi/utils/utils.h" + +namespace psi::psi { + +namespace { + +size_t ComputeTruncateSize(size_t self_size, size_t peer_size, size_t ssp, + bool malicious) { + size_t truncate_size = + malicious + ? sizeof(uint128_t) + : std::min<size_t>( + std::ceil((ssp + std::ceil(std::log2(self_size * peer_size))) / + 8), + sizeof(uint128_t)); + + return truncate_size; +} + +constexpr size_t kRr22OprfBinSize = 1 << 14; + +} // namespace + +void Rr22PsiSender(const Rr22PsiOptions& options, + const std::shared_ptr<yacl::link::Context>& lctx, + const std::vector<uint128_t>& inputs) { + YACL_ENFORCE(lctx->WorldSize() == 2); + + // Gather Items Size + std::vector<size_t> items_size = AllGatherItemsSize(lctx, inputs.size()); + + size_t sender_size = items_size[lctx->Rank()]; + size_t receiver_size = items_size[lctx->NextRank()]; + + YACL_ENFORCE(sender_size == inputs.size()); + + if ((sender_size == 0) || (receiver_size == 0)) { + return; + } + + size_t mask_size = sizeof(uint128_t); + if (options.compress) { + mask_size = ComputeTruncateSize(sender_size, receiver_size, options.ssp, + options.malicious); + } + + Rr22OprfSender oprf_sender(kRr22OprfBinSize, options.ssp, options.mode); + + yacl::Buffer inputs_hash_buffer(inputs.size() * sizeof(uint128_t)); + absl::Span<uint128_t> inputs_hash = + absl::MakeSpan((uint128_t*)inputs_hash_buffer.data(), inputs.size()); + + oprf_sender.Send(lctx, receiver_size, absl::MakeSpan(inputs), inputs_hash, + options.num_threads); + + yacl::Buffer sender_oprf_buffer(inputs.size() * sizeof(uint128_t)); + absl::Span<uint128_t> sender_oprfs = + absl::MakeSpan((uint128_t*)(sender_oprf_buffer.data()), inputs.size()); + + SPDLOG_INFO("oprf eval begin"); + for (size_t i = 0; i < sender_size; i += receiver_size) { + size_t current_batch_size = std::min(receiver_size, sender_size - i); + oprf_sender.Eval(absl::MakeSpan(&inputs[i], current_batch_size), + absl::MakeSpan(&inputs_hash[i], current_batch_size), + absl::MakeSpan(&sender_oprfs[i], current_batch_size), + options.num_threads); + } + SPDLOG_INFO("oprf eval end"); + + yacl::ByteContainerView oprf_byteview; + if (options.compress) { + uint128_t* src = sender_oprfs.data(); + uint8_t* dest = (uint8_t*)sender_oprfs.data(); + + for (size_t i = 0; i < sender_oprfs.size(); ++i) { + std::memmove(dest, src, mask_size); + dest += mask_size; + src += 1; + } + + oprf_byteview = yacl::ByteContainerView(sender_oprfs.data(), + sender_oprfs.size() * mask_size); + } else { + oprf_byteview = yacl::ByteContainerView( + sender_oprfs.data(), sender_oprfs.size() * sizeof(uint128_t)); + } + + SPDLOG_INFO("send rr22 oprf: {} vector:{}", oprf_byteview.size(), + sender_oprfs.size()); + + for (size_t i = 0; i < sender_oprfs.size(); i += 100000) { + size_t send_size = std::min<size_t>(100000, sender_oprfs.size() - i); + yacl::ByteContainerView send_byteview = yacl::ByteContainerView( + oprf_byteview.data() + i * mask_size, send_size * mask_size); + + lctx->SendAsyncThrottled(lctx->NextRank(), send_byteview, + fmt::format("send oprf_buf")); + } + + SPDLOG_INFO("send rr22 oprf finished"); +} + +std::vector<size_t> Rr22PsiReceiver( + const Rr22PsiOptions& options, + const std::shared_ptr<yacl::link::Context>& lctx, + const std::vector<uint128_t>& inputs) { + YACL_ENFORCE(lctx->WorldSize() == 2); + + // Gather Items Size + std::vector<size_t> items_size = AllGatherItemsSize(lctx, inputs.size()); + + size_t receiver_size = items_size[lctx->Rank()]; + size_t sender_size = items_size[lctx->NextRank()]; + + YACL_ENFORCE(receiver_size == inputs.size()); + + if ((sender_size == 0) || (receiver_size == 0)) { + return {}; + } + + size_t mask_size = ComputeTruncateSize(sender_size, receiver_size, + options.ssp, options.malicious); + + Rr22OprfReceiver oprf_receiver(kRr22OprfBinSize, options.ssp, options.mode); + + SPDLOG_INFO("out buffer begin"); + yacl::Buffer outputs_buffer(inputs.size() * sizeof(uint128_t)); + absl::Span<uint128_t> outputs = + absl::MakeSpan((uint128_t*)(outputs_buffer.data()), inputs.size()); + SPDLOG_INFO("out buffer end"); + + oprf_receiver.Recv(lctx, receiver_size, inputs, outputs, options.num_threads); + + SPDLOG_INFO("compute intersection begin, threads:{}", options.num_threads); + + std::vector<size_t> indices = + GetIntersection(outputs, sender_size, lctx, options.num_threads, + options.compress, mask_size); + + SPDLOG_INFO("compute intersection end"); + + return indices; +} + +} // namespace psi::psi diff --git a/psi/psi/core/vole_psi/rr22_psi.h b/psi/psi/core/vole_psi/rr22_psi.h new file mode 100644 index 00000000..5b36168e --- /dev/null +++ b/psi/psi/core/vole_psi/rr22_psi.h @@ -0,0 +1,74 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <algorithm> +#include <memory> +#include <string> +#include <vector> + +#include "yacl/base/exception.h" +#include "yacl/base/int128.h" +#include "yacl/link/context.h" + +#include "psi/psi/core/vole_psi/rr22_oprf.h" + +// [RR22] Blazing Fast PSI from Improved OKVS and Subfield VOLE, CCS 2022 +// https://eprint.iacr.org/2022/320 +// okvs code reference https://github.com/Visa-Research/volepsi + +namespace psi::psi { + +struct Rr22PsiOptions { + Rr22PsiOptions(size_t ssp_params, size_t num_threads_params, + bool compress_params, bool malicious_params = false) + : ssp(ssp_params), + num_threads(num_threads_params), + compress(compress_params), + malicious(malicious_params) { + YACL_ENFORCE(ssp >= 30, "ssp:{}", ssp); + + num_threads = std::max<size_t>(1, num_threads_params); + } + + // ssp i.e. statistical security parameter. + // must >= 30bit + size_t ssp = 30; + + // number of threads + // value 0 will use 1 thread + size_t num_threads = 0; + + // psi mode: fast or low communication + Rr22PsiMode mode = Rr22PsiMode::FastMode; + + // wether compress the OPRF outputs + bool compress = true; + + // run the protocol with malicious security + // not supported by now + bool malicious = false; +}; + +void Rr22PsiSender(const Rr22PsiOptions& options, + const std::shared_ptr<yacl::link::Context>& lctx, + const std::vector<uint128_t>& inputs); + +std::vector<size_t> Rr22PsiReceiver( + const Rr22PsiOptions& options, + const std::shared_ptr<yacl::link::Context>& lctx, + const std::vector<uint128_t>& inputs); + +} // namespace psi::psi diff --git a/psi/psi/core/vole_psi/rr22_psi_bench.cc b/psi/psi/core/vole_psi/rr22_psi_bench.cc new file mode 100644 index 00000000..1319d821 --- /dev/null +++ b/psi/psi/core/vole_psi/rr22_psi_bench.cc @@ -0,0 +1,154 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <omp.h> + +#include <future> +#include <random> +#include <tuple> + +#include "absl/strings/str_split.h" +#include "benchmark/benchmark.h" +#include "spdlog/spdlog.h" +#include "yacl/crypto/tools/prg.h" +#include "yacl/crypto/utils/rand.h" +#include "yacl/link/context.h" +#include "yacl/link/test_util.h" + +#include "psi/psi/core/vole_psi/rr22_psi.h" + +namespace { + +std::tuple<std::vector<uint128_t>, std::vector<uint128_t>, std::vector<size_t>> +GenerateTestData(size_t item_size, [[maybe_unused]] double p = 0.5) { + // uint128_t seed = yacl::MakeUint128(0, 0); + uint128_t seed = yacl::crypto::RandSeed(); + yacl::crypto::Prg<uint128_t> prng(seed); + + std::vector<uint128_t> inputs_a(item_size); + std::vector<uint128_t> inputs_b(item_size); + + prng.Fill(absl::MakeSpan(inputs_a)); + prng.Fill(absl::MakeSpan(inputs_b)); + + std::mt19937 std_rand(yacl::crypto::RandU64()); + std::bernoulli_distribution dist(p); + + std::vector<size_t> indices; + for (size_t i = 0; i < item_size; ++i) { + if (dist(std_rand)) { + // if (i % 3 == 0) { + inputs_b[i] = inputs_a[i]; + indices.push_back(i); + } + } + + return std::make_tuple(inputs_a, inputs_b, indices); +} + +std::shared_ptr<yacl::link::Context> CreateLinkContext( + const std::string& party_ips, size_t self_rank) { + std::vector<std::string> ip_list = absl::StrSplit(party_ips, ','); + YACL_ENFORCE(ip_list.size() > 1); + + yacl::link::ContextDesc ctx_desc; + for (size_t i = 0; i < ip_list.size(); ++i) { + ctx_desc.parties.push_back({std::to_string(i), ip_list[i]}); + } + + return yacl::link::FactoryBrpc().CreateContext(ctx_desc, self_rank); +} + +} // namespace + +static void BM_Rr22FastPsi(benchmark::State& state) { + for (auto _ : state) { + state.PauseTiming(); + size_t n = state.range(0); + + size_t mode = state.range(1); + + std::string party_ips = "127.0.0.1:9307,127.0.0.1:9308"; + + // auto lctxs = yacl::link::test::SetupWorld("ab", 2); + std::vector<std::shared_ptr<yacl::link::Context>> lctxs(2); + auto link0_proc = std::async([&] { + lctxs[0] = CreateLinkContext(party_ips, 0); + lctxs[0]->ConnectToMesh(); + }); + auto link1_proc = std::async([&] { + lctxs[1] = CreateLinkContext(party_ips, 1); + lctxs[1]->ConnectToMesh(); + }); + + link0_proc.get(); + link1_proc.get(); + + lctxs[0]->SetRecvTimeout(30 * 6 * 1000); + lctxs[1]->SetRecvTimeout(30 * 6 * 1000); + + std::vector<uint128_t> inputs_a; + std::vector<uint128_t> inputs_b; + std::vector<size_t> indices; + + size_t thread_num = omp_get_num_procs(); + + std::tie(inputs_a, inputs_b, indices) = GenerateTestData(n); + + state.ResumeTiming(); + + psi::psi::Rr22PsiOptions psi_options(40, thread_num, true); + if (mode == 1) { + psi_options.mode = psi::psi::Rr22PsiMode::LowCommMode; + } + + auto psi_sender_proc = std::async( + [&] { psi::psi::Rr22PsiSender(psi_options, lctxs[0], inputs_a); }); + + auto psi_receiver_proc = std::async([&] { + return psi::psi::Rr22PsiReceiver(psi_options, lctxs[1], inputs_b); + }); + + psi_sender_proc.get(); + std::vector<size_t> indices_psi = psi_receiver_proc.get(); + + SPDLOG_INFO("intersection size: {}", indices_psi.size()); + + YACL_ENFORCE(indices_psi.size() == indices.size(), "{}!={}", + indices_psi.size(), indices.size()); + + auto stats0 = lctxs[0]->GetStats(); + auto stats1 = lctxs[1]->GetStats(); + + SPDLOG_INFO("stats0->sent_bytes:{},stats0->recv_bytes:{}, total:{}", + stats0->sent_bytes.load(), stats0->recv_bytes.load(), + (stats0->sent_bytes.load() + stats0->recv_bytes.load())); + SPDLOG_INFO("stats1->sent_bytes:{},stats1->recv_bytes:{}, total:{}", + stats1->sent_bytes.load(), stats1->recv_bytes.load(), + (stats1->sent_bytes.load() + stats1->recv_bytes.load())); + } +} + +BENCHMARK(BM_Rr22FastPsi) + ->Unit(benchmark::kMillisecond) + ->Args({1 << 20, 0}) // fast mode + ->Args({1 << 21, 0}) + ->Args({1 << 22, 0}) + ->Args({1 << 23, 0}) + ->Args({1 << 24, 0}) + ->Args({1 << 20, 1}) // low comm mode + ->Args({1 << 21, 1}) + ->Args({1 << 22, 1}) + ->Args({1 << 23, 1}) + ->Args({1 << 24, 1}); diff --git a/psi/psi/core/vole_psi/rr22_psi_test.cc b/psi/psi/core/vole_psi/rr22_psi_test.cc new file mode 100644 index 00000000..07655f38 --- /dev/null +++ b/psi/psi/core/vole_psi/rr22_psi_test.cc @@ -0,0 +1,109 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/vole_psi/rr22_psi.h" + +#include <random> +#include <tuple> + +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/crypto/tools/prg.h" +#include "yacl/crypto/utils/rand.h" +#include "yacl/link/test_util.h" + +namespace psi::psi { + +namespace { + +std::tuple<std::vector<uint128_t>, std::vector<uint128_t>, std::vector<size_t>> +GenerateTestData(size_t item_size, double p = 0.5) { + uint128_t seed = yacl::MakeUint128(0, 0); + yacl::crypto::Prg<uint128_t> prng(seed); + + std::vector<uint128_t> inputs_a(item_size); + std::vector<uint128_t> inputs_b(item_size); + + prng.Fill(absl::MakeSpan(inputs_a)); + prng.Fill(absl::MakeSpan(inputs_b)); + + std::mt19937 std_rand(yacl::crypto::RandU64()); + std::bernoulli_distribution dist(p); + + std::vector<size_t> indices; + for (size_t i = 0; i < item_size; ++i) { + if (dist(std_rand)) { + inputs_b[i] = inputs_a[i]; + indices.push_back(i); + } + } + + return std::make_tuple(inputs_a, inputs_b, indices); +} + +struct TestParams { + uint64_t items_num; + + Rr22PsiMode mode = Rr22PsiMode::FastMode; +}; + +} // namespace + +class Rr22PsiTest : public testing::TestWithParam<TestParams> {}; + +TEST_P(Rr22PsiTest, CorrectTest) { + auto params = GetParam(); + + auto lctxs = yacl::link::test::SetupWorld("ab", 2); + + uint128_t seed = yacl::MakeUint128(0, 0); + yacl::crypto::Prg<uint128_t> prng(seed); + + size_t item_size = params.items_num; + + std::vector<uint128_t> inputs_a; + std::vector<uint128_t> inputs_b; + std::vector<size_t> indices; + + std::tie(inputs_a, inputs_b, indices) = GenerateTestData(item_size); + + Rr22PsiOptions psi_options(40, 0, true); + + psi_options.mode = params.mode; + + auto psi_sender_proc = + std::async([&] { Rr22PsiSender(psi_options, lctxs[0], inputs_a); }); + auto psi_receiver_proc = std::async( + [&] { return Rr22PsiReceiver(psi_options, lctxs[1], inputs_b); }); + + psi_sender_proc.get(); + std::vector<size_t> indices_psi = psi_receiver_proc.get(); + + SPDLOG_INFO("{}?={}", indices.size(), indices_psi.size()); + EXPECT_EQ(indices.size(), indices_psi.size()); + + for (size_t i = 0; i < indices.size(); ++i) { + SPDLOG_INFO("i:{} index:{} a:{}, b:{}", i, indices_psi[i], + inputs_a[indices_psi[i]], inputs_b[indices_psi[i]]); + } + + EXPECT_EQ(indices_psi, indices); +} + +INSTANTIATE_TEST_SUITE_P(CorrectTest_Instances, Rr22PsiTest, + testing::Values(TestParams{35, Rr22PsiMode::FastMode}, + TestParams{35, + Rr22PsiMode::LowCommMode})); + +} // namespace psi::psi diff --git a/psi/psi/core/vole_psi/rr22_utils.cc b/psi/psi/core/vole_psi/rr22_utils.cc new file mode 100644 index 00000000..c4c9e541 --- /dev/null +++ b/psi/psi/core/vole_psi/rr22_utils.cc @@ -0,0 +1,191 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/core/vole_psi/rr22_utils.h" + +#include <algorithm> +#include <array> +#include <future> +#include <utility> + +#include "absl/types/span.h" +#include "libdivide.h" +#include "sparsehash/dense_hash_map" + +#include "psi/psi/core/vole_psi/okvs/galois128.h" +#include "psi/psi/core/vole_psi/okvs/simple_index.h" + +namespace psi::psi { + +namespace { + +struct NoHash { + inline size_t operator()(const uint128_t& v) const { + uint32_t v32; + std::memcpy(&v32, &v, sizeof(uint32_t)); + + return v32; + } +}; + +} // namespace +std::vector<size_t> GetIntersection( + absl::Span<const uint128_t> self_oprfs, size_t peer_items_num, + const std::shared_ptr<yacl::link::Context>& lctx, size_t num_threads, + bool compress, size_t mask_size, size_t ssp) { + num_threads = std::max<size_t>(1, num_threads); + + int128_t truncate_mask = yacl::MakeUint128(0, 0); + for (size_t i = 0; i < mask_size; ++i) { + truncate_mask = 0xff | (truncate_mask << 8); + SPDLOG_DEBUG( + "{}, truncate_mask:{}", i, + (std::ostringstream() << okvs::Galois128(truncate_mask)).str()); + } + + auto divider = libdivide::libdivide_u32_gen(num_threads); + + std::vector<std::thread> thrds(num_threads); + std::mutex merge_mtx; + + size_t bin_size = okvs::SimpleIndex::GetBinSize( + num_threads, std::max(self_oprfs.size(), peer_items_num), ssp); + + SPDLOG_INFO("bin_size:{} {}?={} compress:{} mask_size:{}", bin_size, + self_oprfs.size(), peer_items_num, compress, mask_size); + + std::atomic<uint64_t> hash_done = 0; + std::promise<void> sync_prom; + std::promise<void> hash_done_promise; + + std::shared_future<void> hashing_done_future = + hash_done_promise.get_future().share(); + + std::shared_future<void> hash_sync_future = sync_prom.get_future().share(); + + std::vector<size_t> indices; + + yacl::Buffer peer_buffer(peer_items_num * mask_size); + static const size_t batch_size = 128; + + auto proc = [&](size_t thread_idx) -> void { + google::dense_hash_map<uint128_t, size_t, NoHash> dense_map(bin_size); + dense_map.set_empty_key(yacl::MakeUint128(0, 0)); + + std::array<std::pair<uint128_t, size_t>, batch_size> hh; + + for (size_t i = 0; i < self_oprfs.size();) { + size_t j = 0; + while ((j != batch_size) && i < self_oprfs.size()) { + uint32_t v = 0; + if (num_threads > 1) { + std::memcpy(&v, &self_oprfs[i], sizeof(uint32_t)); + auto k = libdivide::libdivide_u32_do(v, &divider); + v -= k * num_threads; + } + if (v == thread_idx) { + if (compress) { + hh[j] = {self_oprfs[i] & truncate_mask, i}; + } else { + hh[j] = {self_oprfs[i], i}; + } + ++j; + } + ++i; + } + dense_map.insert(hh.begin(), hh.begin() + j); + } + if (++hash_done == num_threads) { + hash_done_promise.set_value(); + } else { + hashing_done_future.get(); + } + + hash_sync_future.get(); + + SPDLOG_DEBUG("thread_idx map size:{}", dense_map.size()); + SPDLOG_DEBUG("peer_buffer size:{}", peer_buffer.size()); + + if (dense_map.size() == 0) { + return; + } + + size_t intersection_size = 0; + size_t begin = thread_idx * self_oprfs.size() / num_threads; + size_t* intersection = (size_t*)(&self_oprfs[begin]); + + uint8_t* peer_data_ptr = (uint8_t*)(peer_buffer.data()); + + uint128_t h = yacl::MakeUint128(0, 0); + + for (size_t i = 0; i < peer_items_num; ++i) { + std::memcpy(&h, peer_data_ptr, mask_size); + peer_data_ptr += mask_size; + + uint32_t v = 0; + if (num_threads > 1) { + std::memcpy(&v, &h, sizeof(uint32_t)); + auto k = libdivide::libdivide_u32_do(v, &divider); + v -= k * num_threads; + } + + if (v == thread_idx) { + auto iter = dense_map.find(h); + if (iter != dense_map.end()) { + intersection[intersection_size] = iter->second; + + ++intersection_size; + } + } + } + + if (intersection_size) { + std::lock_guard<std::mutex> lock(merge_mtx); + indices.insert(indices.end(), intersection, + intersection + intersection_size); + } + }; + + for (size_t i = 0; i < num_threads; ++i) { + thrds[i] = std::thread(proc, i); + } + + SPDLOG_INFO("recv rr22 oprf begin"); + + size_t recv_count = 0; + while (true) { + auto recv_buffer = + lctx->Recv(lctx->NextRank(), fmt::format("recv paxos_solve")); + + std::memcpy((uint8_t*)peer_buffer.data() + (recv_count * mask_size), + recv_buffer.data(), recv_buffer.size()); + recv_count += recv_buffer.size() / mask_size; + if (recv_count == peer_items_num) { + break; + } + } + + sync_prom.set_value(); + + SPDLOG_INFO("recv rr22 oprf finished: {} vector:{}", peer_buffer.size(), + peer_buffer.size() / mask_size); + + for (size_t i = 0; i < num_threads; ++i) { + thrds[i].join(); + } + + return indices; +} + +} // namespace psi::psi diff --git a/psi/psi/core/vole_psi/rr22_utils.h b/psi/psi/core/vole_psi/rr22_utils.h new file mode 100644 index 00000000..19fbe928 --- /dev/null +++ b/psi/psi/core/vole_psi/rr22_utils.h @@ -0,0 +1,30 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <memory> +#include <vector> + +#include "yacl/base/buffer.h" +#include "yacl/base/int128.h" +#include "yacl/link/context.h" + +namespace psi::psi { + +std::vector<size_t> GetIntersection( + absl::Span<const uint128_t> self_oprfs, size_t peer_items_num, + const std::shared_ptr<yacl::link::Context>& lctx, size_t num_threads, + bool compress, size_t mask_size, size_t ssp = 40); +} diff --git a/psi/psi/core/vole_psi/sparseconfig.h b/psi/psi/core/vole_psi/sparseconfig.h new file mode 100644 index 00000000..18f4b2dc --- /dev/null +++ b/psi/psi/core/vole_psi/sparseconfig.h @@ -0,0 +1,46 @@ +/* + * NOTE: This file is for internal use only. + * Do not use these #defines in your own program! + */ + +/* Namespace for Google classes */ +#define GOOGLE_NAMESPACE ::google + +/* the location of the header defining hash functions */ +#define HASH_FUN_H <functional> + +/* the namespace of the hash<> function */ +#define HASH_NAMESPACE std + +/* Define to 1 if you have the <inttypes.h> header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if the system has the type `long long'. */ +#define HAVE_LONG_LONG 1 + +/* Define to 1 if you have the `memcpy' function. */ +#define HAVE_MEMCPY 1 + +/* Define to 1 if you have the <stdint.h> header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the <sys/types.h> header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if the system has the type `uint16_t'. */ +#define HAVE_UINT16_T 1 + +/* Define to 1 if the system has the type `u_int16_t'. */ +#define HAVE_U_INT16_T 1 + +/* Define to 1 if the system has the type `__uint16'. */ +/* #undef HAVE___UINT16 */ + +/* The system-provided hash function including the namespace. */ +#define SPARSEHASH_HASH HASH_NAMESPACE::hash + +/* Stops putting the code inside the Google namespace */ +#define _END_GOOGLE_NAMESPACE_ } + +/* Puts following code inside the Google namespace */ +#define _START_GOOGLE_NAMESPACE_ namespace google { diff --git a/psi/psi/cryptor/BUILD.bazel b/psi/psi/cryptor/BUILD.bazel new file mode 100644 index 00000000..c9517f10 --- /dev/null +++ b/psi/psi/cryptor/BUILD.bazel @@ -0,0 +1,152 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//bazel:psi.bzl", "psi_cc_library", "psi_cc_test") + +package(default_visibility = ["//visibility:public"]) + +psi_cc_library( + name = "ecc_cryptor", + srcs = ["ecc_cryptor.cc"], + hdrs = ["ecc_cryptor.h"], + deps = [ + "//psi/psi:psi_cc_proto", + "@com_google_absl//absl/types:span", + "@yacl//yacl/base:exception", + "@yacl//yacl/crypto/base/hash:hash_utils", + "@yacl//yacl/utils:parallel", + ], +) + +psi_cc_library( + name = "sodium_curve25519_cryptor", + srcs = ["sodium_curve25519_cryptor.cc"], + hdrs = ["sodium_curve25519_cryptor.h"], + deps = [ + ":ecc_cryptor", + "@com_github_libsodium//:libsodium", + "@com_github_openssl_openssl//:openssl", + "@yacl//yacl/base:exception", + "@yacl//yacl/crypto/base/hash:hash_utils", + "@yacl//yacl/link", + "@yacl//yacl/utils:parallel", + ], +) + +psi_cc_library( + name = "ipp_ecc_cryptor", + srcs = ["ipp_ecc_cryptor.cc"], + hdrs = ["ipp_ecc_cryptor.h"], + target_compatible_with = [ + "@platforms//cpu:x86_64", + ], + deps = [ + ":ecc_cryptor", + "@com_github_intel_ipp//:ipp", + "@com_github_openssl_openssl//:openssl", + "@yacl//yacl/base:exception", + "@yacl//yacl/utils:parallel", + ], +) + +psi_cc_library( + name = "fourq_cryptor", + srcs = ["fourq_cryptor.cc"], + hdrs = ["fourq_cryptor.h"], + defines = [ + "__LINUX__", + ] + select({ + "@bazel_tools//src/conditions:linux_x86_64": [ + "_AMD64_", + "_ASM_", + ], + "@bazel_tools//src/conditions:darwin_arm64": [ + "_ARM64_", + ], + "//conditions:default": [ + "_AMD64_", + ], + }), + deps = [ + ":ecc_cryptor", + "@com_github_microsoft_apsi//:apsi", + "@com_github_openssl_openssl//:openssl", + "@yacl//yacl/base:exception", + "@yacl//yacl/utils:parallel", + ], +) + +psi_cc_library( + name = "cryptor_selector", + srcs = ["cryptor_selector.cc"], + hdrs = ["cryptor_selector.h"], + deps = [ + ":fourq_cryptor", + ":sm2_cryptor", + ":sodium_curve25519_cryptor", + "//psi/psi:prelude", + "@yacl//yacl/utils:platform_utils", + "//psi/proto:psi_cc_proto", + ] + select({ + "@platforms//cpu:x86_64": [ + ":ipp_ecc_cryptor", + ], + "//conditions:default": [], + }), +) + +psi_cc_library( + name = "ecc_utils", + hdrs = ["ecc_utils.h"], + # Openssl::libcrypto requires `dlopen`... + linkopts = ["-ldl"], + deps = [ + "@com_github_openssl_openssl//:openssl", + "@yacl//yacl/base:exception", + "@yacl//yacl/crypto/base/hash:hash_utils", + "@yacl//yacl/utils:parallel", + ], +) + +psi_cc_test( + name = "ecc_utils_test", + srcs = ["ecc_utils_test.cc"], + deps = [ + ":ecc_utils", + "@com_google_googletest//:gtest", + ], +) + +psi_cc_library( + name = "sm2_cryptor", + srcs = ["sm2_cryptor.cc"], + hdrs = ["sm2_cryptor.h"], + deps = [ + ":ecc_cryptor", + ":ecc_utils", + "@com_github_openssl_openssl//:openssl", + "@yacl//yacl/base:exception", + "@yacl//yacl/utils:parallel", + ], +) + +psi_cc_test( + name = "sm2_cryptor_test", + srcs = ["sm2_cryptor_test.cc"], + deps = [ + ":sm2_cryptor", + "@yacl//yacl/crypto/base/hash:hash_utils", + "@yacl//yacl/crypto/tools:prg", + ], +) diff --git a/psi/psi/cryptor/cryptor_selector.cc b/psi/psi/cryptor/cryptor_selector.cc new file mode 100644 index 00000000..4c0ced2b --- /dev/null +++ b/psi/psi/cryptor/cryptor_selector.cc @@ -0,0 +1,98 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/cryptor/cryptor_selector.h" + +#include <cstdlib> + +#include "spdlog/spdlog.h" +#include "yacl/utils/platform_utils.h" + +#include "psi/psi/cryptor/fourq_cryptor.h" +#include "psi/psi/cryptor/sm2_cryptor.h" +#include "psi/psi/cryptor/sodium_curve25519_cryptor.h" +#include "psi/psi/prelude.h" + +#ifdef __x86_64__ +#include "psi/psi/cryptor/ipp_ecc_cryptor.h" +#endif + +namespace psi::psi { + +namespace { + +std::unique_ptr<IEccCryptor> GetIppCryptor() { +#ifdef __x86_64__ + if (yacl::hasAVX512ifma()) { + SPDLOG_INFO("Using IPPCP"); + return std::make_unique<IppEccCryptor>(); + } +#endif + return {}; +} + +std::unique_ptr<IEccCryptor> GetSodiumCryptor() { + SPDLOG_INFO("Using libSodium"); + return std::make_unique<SodiumCurve25519Cryptor>(); +} + +std::unique_ptr<IEccCryptor> GetFourQCryptor() { +#ifdef __x86_64__ + if (yacl::hasAVX2()) { +#endif + SPDLOG_INFO("Using FourQ"); + return std::make_unique<FourQEccCryptor>(); // fourq has an arm impl, + // so always works on ARM + // platform +#ifdef __x86_64__ + } +#endif + return {}; +} +} // namespace + +std::unique_ptr<IEccCryptor> CreateEccCryptor(CurveType type) { + std::unique_ptr<IEccCryptor> cryptor; + switch (type) { + case CurveType::CURVE_25519: { + cryptor = GetIppCryptor(); + if (cryptor == nullptr) { + cryptor = GetSodiumCryptor(); + } + break; + } + case CurveType::CURVE_FOURQ: { + cryptor = GetFourQCryptor(); + YACL_ENFORCE(cryptor != nullptr, "FourQ requires AVX2 instruction"); + break; + } + case CurveType::CURVE_SM2: { + SPDLOG_INFO("Using SM2"); + cryptor = std::make_unique<Sm2Cryptor>(type); + break; + } + case CurveType::CURVE_SECP256K1: { + SPDLOG_INFO("Using Secp256k1"); + cryptor = std::make_unique<Sm2Cryptor>(type); + break; + } + default: { + YACL_THROW("Invaild curve type: {}", type); + } + } + YACL_ENFORCE(cryptor != nullptr, "Cryptor should not be nullptr"); + return cryptor; +} + +} // namespace psi::psi diff --git a/psi/psi/cryptor/cryptor_selector.h b/psi/psi/cryptor/cryptor_selector.h new file mode 100644 index 00000000..76d7c4f6 --- /dev/null +++ b/psi/psi/cryptor/cryptor_selector.h @@ -0,0 +1,25 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <memory> + +#include "psi/psi/cryptor/ecc_cryptor.h" + +namespace psi::psi { + +std::unique_ptr<IEccCryptor> CreateEccCryptor(CurveType type); + +} // namespace psi::psi diff --git a/psi/psi/cryptor/ecc_cryptor.cc b/psi/psi/cryptor/ecc_cryptor.cc new file mode 100644 index 00000000..8a91271e --- /dev/null +++ b/psi/psi/cryptor/ecc_cryptor.cc @@ -0,0 +1,110 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/cryptor/ecc_cryptor.h" + +#include <vector> + +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/utils/parallel.h" + +namespace psi::psi { + +namespace { +std::string CreateFlattenEccBuffer(const std::vector<std::string>& items, + size_t item_size, + size_t chosen_size = kEccKeySize) { + std::string ret; + ret.reserve(items.size() * item_size); + size_t size = std::min<size_t>(chosen_size, item_size); + for (const auto& item : items) { + YACL_ENFORCE(item.size() == item_size, "item.size:{}, item_size:{}", + item.size(), item_size); + ret.append(item.data(), size); + } + return ret; +} + +std::string CreateFlattenEccBuffer(const std::vector<absl::string_view>& items, + size_t item_size, + size_t chosen_size = kEccKeySize) { + std::string ret; + ret.reserve(items.size() * item_size); + size_t size = std::min<size_t>(chosen_size, item_size); + for (const auto& item : items) { + YACL_ENFORCE(item.size() == item_size, "item.size:{}, item_size:{}", + item.size(), item_size); + ret.append(item.data(), size); + } + return ret; +} + +std::vector<std::string> CreateItemsFromFlattenEccBuffer( + std::string_view buf, size_t item_size = kEccKeySize) { + YACL_ENFORCE(buf.size() % item_size == 0); + size_t num_item = buf.size() / item_size; + std::vector<std::string> ret; + ret.reserve(num_item); + for (size_t i = 0; i < num_item; i++) { + ret.emplace_back(buf.data() + i * item_size, item_size); + } + return ret; +} + +} // namespace + +std::vector<uint8_t> IEccCryptor::HashToCurve( + absl::Span<const char> input) const { + auto d = yacl::crypto::Sha256(input); + return {d.begin(), d.end()}; +} + +std::vector<std::string> Mask(const std::shared_ptr<IEccCryptor>& cryptor, + const std::vector<std::string>& items) { + std::string batch_points = CreateFlattenEccBuffer( + items, cryptor->GetMaskLength(), cryptor->GetMaskLength()); + std::string out_points(batch_points.size(), '\0'); + cryptor->EccMask(batch_points, absl::MakeSpan(out_points)); + return CreateItemsFromFlattenEccBuffer(out_points, cryptor->GetMaskLength()); +} + +std::vector<std::string> Mask(const std::shared_ptr<IEccCryptor>& cryptor, + const std::vector<absl::string_view>& items) { + std::string batch_points = CreateFlattenEccBuffer( + items, cryptor->GetMaskLength(), cryptor->GetMaskLength()); + std::string out_points(batch_points.size(), '\0'); + cryptor->EccMask(batch_points, absl::MakeSpan(out_points)); + return CreateItemsFromFlattenEccBuffer(out_points, cryptor->GetMaskLength()); +} + +std::string HashInput(const std::shared_ptr<IEccCryptor>& cryptor, + const std::string& item) { + auto sha_bytes = cryptor->HashToCurve(item); + std::string ret(sha_bytes.size(), '\0'); + std::memcpy(ret.data(), sha_bytes.data(), sha_bytes.size()); + return ret; +} + +std::vector<std::string> HashInputs(const std::shared_ptr<IEccCryptor>& cryptor, + const std::vector<std::string>& items) { + std::vector<std::string> ret(items.size()); + yacl::parallel_for(0, items.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + ret[idx] = HashInput(cryptor, items[idx]); + } + }); + return ret; +} + +} // namespace psi::psi diff --git a/psi/psi/cryptor/ecc_cryptor.h b/psi/psi/cryptor/ecc_cryptor.h new file mode 100644 index 00000000..7182d2fa --- /dev/null +++ b/psi/psi/cryptor/ecc_cryptor.h @@ -0,0 +1,83 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <array> +#include <cstring> +#include <memory> +#include <string> +#include <string_view> +#include <vector> + +#include "absl/types/span.h" +#include "openssl/crypto.h" +#include "openssl/rand.h" +#include "yacl/base/exception.h" + +#include "psi/psi/psi.pb.h" + +namespace psi::psi { + +inline constexpr int kEccKeySize = 32; + +// Make ECDH implementation plugable. +class IEccCryptor { + public: + IEccCryptor() { + YACL_ENFORCE(RAND_bytes(&private_key_[0], kEccKeySize) == 1, + "Cannot create random private key"); + } + + virtual ~IEccCryptor() { OPENSSL_cleanse(&private_key_[0], kEccKeySize); } + + virtual void SetPrivateKey(absl::Span<const uint8_t> key) { + YACL_ENFORCE(key.size() == kEccKeySize); + std::memcpy(private_key_, key.data(), key.size()); + } + + /// Get current curve type + virtual CurveType GetCurveType() const = 0; + + // Perform the ECC mask for a batch of items. + // + // The `base_points` contains a series of base point. Each point occupies + // 256bit, i.e. 32 bytes. + virtual void EccMask(absl::Span<const char> batch_points, + absl::Span<char> dest_points) const = 0; + + virtual size_t GetMaskLength() const { return kEccKeySize; } + + // Perform hash on input + virtual std::vector<uint8_t> HashToCurve(absl::Span<const char> input) const; + + [[nodiscard]] const uint8_t* GetPrivateKey() const { return private_key_; } + + protected: + uint8_t private_key_[kEccKeySize]; +}; + +std::vector<std::string> Mask(const std::shared_ptr<IEccCryptor>& cryptor, + const std::vector<std::string>& items); + +std::vector<std::string> Mask(const std::shared_ptr<IEccCryptor>& cryptor, + const std::vector<absl::string_view>& items); + +std::string HashInput(const std::shared_ptr<IEccCryptor>& cryptor, + const std::string& item); + +std::vector<std::string> HashInputs(const std::shared_ptr<IEccCryptor>& cryptor, + const std::vector<std::string>& items); + +} // namespace psi::psi diff --git a/psi/psi/cryptor/ecc_utils.h b/psi/psi/cryptor/ecc_utils.h new file mode 100644 index 00000000..2154b9e0 --- /dev/null +++ b/psi/psi/cryptor/ecc_utils.h @@ -0,0 +1,261 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <memory> +#include <string> +#include <vector> + +#include "absl/types/span.h" +#include "openssl/bn.h" +#include "openssl/ec.h" +#include "yacl/base/exception.h" +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/utils/parallel.h" + +namespace psi::psi { + +inline constexpr size_t kEcPointCompressLength = 33; +inline constexpr size_t kEc256KeyLength = 32; + +// Deletes a BN_CTX. +struct BnCtxDeleter { + public: + void operator()(BN_CTX* bn_ctx) { BN_CTX_free(bn_ctx); } +}; +using BnCtxPtr = std::unique_ptr<BN_CTX, BnCtxDeleter>; + +// Deletes a BIGNUM. +struct BnDeleter { + public: + void operator()(BIGNUM* bn) { BN_clear_free(bn); } +}; +using BigNumPtr = std::unique_ptr<BIGNUM, BnDeleter>; + +// Deletes a EC_GROUP. +struct ECGroupDeleter { + public: + void operator()(EC_GROUP* group) { EC_GROUP_free(group); } +}; +using ECGroupPtr = std::unique_ptr<EC_GROUP, ECGroupDeleter>; + +// Deletes an EC_POINT. +struct ECPointDeleter { + public: + void operator()(EC_POINT* point) { EC_POINT_clear_free(point); } +}; +using ECPointPtr = std::unique_ptr<EC_POINT, ECPointDeleter>; + +struct BigNumSt { + BigNumSt() : bn_ptr(BN_new()) {} + + explicit BigNumSt(absl::string_view bytes) : bn_ptr(BN_new()) { + FromBytes(bytes); + } + + explicit BigNumSt(absl::Span<const uint8_t> bytes) : bn_ptr(BN_new()) { + FromBytes(bytes); + } + + BIGNUM* get() { return bn_ptr.get(); } + + const BIGNUM* get() const { return bn_ptr.get(); } + + std::string ToBytes() { + std::string bytes(kEc256KeyLength, '\0'); + + BN_bn2binpad(bn_ptr.get(), reinterpret_cast<uint8_t*>(bytes.data()), + kEc256KeyLength); + + return bytes; + } + + void FromBytes(absl::Span<const uint8_t> bytes) { + YACL_ENFORCE(nullptr != + BN_bin2bn(bytes.data(), bytes.size(), bn_ptr.get())); + } + + void FromBytes(absl::string_view bytes) { + YACL_ENFORCE(nullptr != + BN_bin2bn(reinterpret_cast<const uint8_t*>(bytes.data()), + bytes.size(), bn_ptr.get())); + } + + void FromBytes(absl::Span<const uint8_t> bytes, const BigNumSt& p) { + BigNumSt bn_m(bytes); + BnCtxPtr bn_ctx(yacl::CheckNotNull(BN_CTX_new())); + + YACL_ENFORCE(BN_nnmod(bn_ptr.get(), bn_m.get(), p.get(), bn_ctx.get()) == + 1); + } + + void FromBytes(absl::string_view bytes, const BigNumSt& p) { + BigNumSt bn_m(bytes); + BnCtxPtr bn_ctx(yacl::CheckNotNull(BN_CTX_new())); + + YACL_ENFORCE(BN_nnmod(bn_ptr.get(), bn_m.get(), p.get(), bn_ctx.get()) == + 1); + } + + BigNumSt Inverse(const BigNumSt& p) { + BnCtxPtr bn_ctx(yacl::CheckNotNull(BN_CTX_new())); + + BigNumSt bn_inv; + + BN_mod_inverse(bn_inv.get(), bn_ptr.get(), p.get(), bn_ctx.get()); + return bn_inv; + } + + BigNumPtr bn_ptr; +}; + +struct EcGroupSt { + explicit EcGroupSt(int ec_group_nid = NID_sm2) + : EcGroupSt(EC_GROUP_new_by_curve_name(ec_group_nid)) {} + + explicit EcGroupSt(EC_GROUP* group) : group_ptr(group) { + BnCtxPtr bn_ctx(yacl::CheckNotNull(BN_CTX_new())); + + YACL_ENFORCE(EC_GROUP_get_curve(group_ptr.get(), bn_p.get(), bn_a.get(), + bn_b.get(), bn_ctx.get()) == 1); + YACL_ENFORCE( + EC_GROUP_get_order(group_ptr.get(), bn_n.get(), bn_ctx.get()) == 1); + } + + EC_GROUP* get() { return group_ptr.get(); } + + const EC_GROUP* get() const { return group_ptr.get(); } + + BigNumSt bn_p; + + BigNumSt bn_a; + BigNumSt bn_b; + BigNumSt bn_n; + + ECGroupPtr group_ptr; +}; + +inline constexpr size_t kHashToCurveCounterGuard = 100; + +struct EcPointSt { + explicit EcPointSt(const EcGroupSt& group) + : group_ref(group), point_ptr(EC_POINT_new(group_ref.get())) {} + + // + // https://eprint.iacr.org/2009/226.pdf + // 1.1 Try-and-Increment Method + // + static EcPointSt CreateEcPointByHashToCurve(absl::string_view m, + const EcGroupSt& ec_group) { + BnCtxPtr bn_ctx(yacl::CheckNotNull(BN_CTX_new())); + + EcPointSt ec_point(ec_group); + + BigNumSt bn_x; + + bn_x.FromBytes(yacl::crypto::Sha256(m), ec_group.bn_p); + + size_t counter = 0; + + while (true) { + int ret = EC_POINT_set_compressed_coordinates( + ec_group.get(), ec_point.get(), bn_x.get(), 0, bn_ctx.get()); + + if (ret == 1) { + break; + } else { + std::string bn_x_bytes = bn_x.ToBytes(); + + // warn: if you change this hash method, do update bucket_psi.cc as well + // std::vector<uint8_t> hash = yacl::crypto::Sm3(bn_x_bytes); + const auto hash = yacl::crypto::Sha256(bn_x_bytes); + bn_x.FromBytes( + absl::string_view(reinterpret_cast<const char*>(hash.data()), + hash.size()), + ec_group.bn_p); + } + counter++; + YACL_ENFORCE(counter < kHashToCurveCounterGuard, + "HashToCurve exceed max loop({})", kHashToCurveCounterGuard); + } + + return ec_point; + } + + static EcPointSt CreateEcPointByHashToCurve(absl::Span<const uint8_t> m, + const EcGroupSt& ec_group) { + return CreateEcPointByHashToCurve( + absl::string_view(reinterpret_cast<const char*>(m.data()), m.size()), + ec_group); + } + + EC_POINT* get() { return point_ptr.get(); } + const EC_POINT* get() const { return point_ptr.get(); } + const EcGroupSt& get_group() { return group_ref; } + + /** + * @brief EC_POINT to bytes, compressed form + * + * @param bytes + * @return size_t + */ + size_t ToBytes(absl::Span<uint8_t> bytes) { + BnCtxPtr bn_ctx(yacl::CheckNotNull(BN_CTX_new())); + size_t length = EC_POINT_point2oct(group_ref.get(), point_ptr.get(), + POINT_CONVERSION_COMPRESSED, nullptr, 0, + bn_ctx.get()); + + YACL_ENFORCE(length == kEcPointCompressLength, "{}!={}", length, + kEcPointCompressLength); + + std::vector<uint8_t> point_compress_bytes(length); + length = EC_POINT_point2oct( + group_ref.get(), point_ptr.get(), POINT_CONVERSION_COMPRESSED, + reinterpret_cast<uint8_t*>(point_compress_bytes.data()), + point_compress_bytes.size(), bn_ctx.get()); + + std::memcpy(bytes.data(), point_compress_bytes.data(), bytes.size()); + + return length; + } + + EcPointSt PointMul(const EcGroupSt& ec_group, const BigNumSt& bn_sk) { + BnCtxPtr bn_ctx(yacl::CheckNotNull(BN_CTX_new())); + EcPointSt ec_point(ec_group); + int ret = EC_POINT_mul(ec_group.get(), ec_point.get(), nullptr, + point_ptr.get(), bn_sk.get(), bn_ctx.get()); + YACL_ENFORCE(ret == 1); + + return ec_point; + } + + static EcPointSt BasePointMul(const EcGroupSt& group, const BigNumSt& bn_sk) { + BnCtxPtr bn_ctx(yacl::CheckNotNull(BN_CTX_new())); + EcPointSt ec_point(group); + + int ret = EC_POINT_mul(group.get(), ec_point.get(), bn_sk.get(), nullptr, + nullptr, bn_ctx.get()); + + YACL_ENFORCE(ret == 1); + + return ec_point; + } + + const EcGroupSt& group_ref; + + ECPointPtr point_ptr; +}; + +} // namespace psi::psi diff --git a/psi/psi/cryptor/ecc_utils_test.cc b/psi/psi/cryptor/ecc_utils_test.cc new file mode 100644 index 00000000..611396e9 --- /dev/null +++ b/psi/psi/cryptor/ecc_utils_test.cc @@ -0,0 +1,38 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/cryptor/ecc_utils.h" + +#include "gtest/gtest.h" + +namespace psi::psi::test { + +TEST(EcPointStTest, HashToCurveWorks) { + EcGroupSt curve(NID_sm2); + + for (int i = 0; i < 100; ++i) { + EcPointSt point = EcPointSt::CreateEcPointByHashToCurve( + fmt::format("id{}", i).c_str(), curve); + + BigNumSt x, y; + ASSERT_EQ(EC_POINT_get_affine_coordinates(curve.get(), point.get(), x.get(), + y.get(), nullptr), + 1); + char *str = BN_bn2hex(x.get()); + fmt::print("\"id{}\" -> point_x:{}\n", i, str); + OPENSSL_free(str); + } +} + +} // namespace psi::psi::test diff --git a/psi/psi/cryptor/fourq_cryptor.cc b/psi/psi/cryptor/fourq_cryptor.cc new file mode 100644 index 00000000..5307149a --- /dev/null +++ b/psi/psi/cryptor/fourq_cryptor.cc @@ -0,0 +1,75 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/cryptor/fourq_cryptor.h" + +#include <vector> + +#include "apsi/fourq/FourQ_api.h" +#include "apsi/fourq/FourQ_internal.h" +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/utils/parallel.h" + +namespace psi::psi { + +void FourQEccCryptor::EccMask(absl::Span<const char> batch_points, + absl::Span<char> dest_points) const { + YACL_ENFORCE(batch_points.size() % kEccKeySize == 0); + + using Item = std::array<unsigned char, kEccKeySize>; + static_assert(sizeof(Item) == kEccKeySize); + + auto mask_functor = [this](const Item& in, Item& out) { + ECCRYPTO_STATUS status = + CompressedSecretAgreement(this->private_key_, in.data(), out.data()); + + YACL_ENFORCE( + status == ECCRYPTO_SUCCESS, + "FourQ CompressedSecretAgreement Error: ", static_cast<int>(status)); + }; + + absl::Span<const Item> input( + reinterpret_cast<const Item*>(batch_points.data()), + batch_points.size() / sizeof(Item)); + absl::Span<Item> output(reinterpret_cast<Item*>(dest_points.data()), + dest_points.size() / sizeof(Item)); + + yacl::parallel_for(0, input.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + mask_functor(input[idx], output[idx]); + } + }); +} + +std::vector<uint8_t> FourQEccCryptor::HashToCurve( + absl::Span<const char> input) const { + point_t P; + std::vector<uint8_t> sha_bytes = + yacl::crypto::SslHash(yacl::crypto::HashAlgorithm::SHA512) + .Update(input) + .CumulativeHash(); + auto* f2elmt = reinterpret_cast<f2elm_t*>(sha_bytes.data()); + mod1271((reinterpret_cast<felm_t*>(f2elmt))[0]); + mod1271((reinterpret_cast<felm_t*>(f2elmt))[1]); + ECCRYPTO_STATUS status = ECCRYPTO_SUCCESS; + // Hash GF(p^2) element to curve + status = ::HashToCurve(reinterpret_cast<felm_t*>(f2elmt), P); + YACL_ENFORCE(status == ECCRYPTO_SUCCESS, + "FourQ HashToCurve Error: ", static_cast<int>(status)); + std::vector<uint8_t> ret(kEccKeySize, 0); + encode(P, static_cast<unsigned char*>(ret.data())); // Encode public key + return ret; +} + +} // namespace psi::psi diff --git a/psi/psi/cryptor/fourq_cryptor.h b/psi/psi/cryptor/fourq_cryptor.h new file mode 100644 index 00000000..3adaff5e --- /dev/null +++ b/psi/psi/cryptor/fourq_cryptor.h @@ -0,0 +1,39 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "openssl/crypto.h" +#include "openssl/rand.h" +#include "yacl/base/exception.h" + +#include "psi/psi/cryptor/ecc_cryptor.h" + +namespace psi::psi { + +class FourQEccCryptor : public IEccCryptor { + public: + FourQEccCryptor() = default; + + ~FourQEccCryptor() override = default; + + CurveType GetCurveType() const override { return CurveType::CURVE_FOURQ; } + + void EccMask(absl::Span<const char> batch_points, + absl::Span<char> dest_points) const override; + + std::vector<uint8_t> HashToCurve(absl::Span<const char> input) const override; +}; + +} // namespace psi::psi \ No newline at end of file diff --git a/psi/psi/cryptor/fpga_ecc_cryptor.h b/psi/psi/cryptor/fpga_ecc_cryptor.h new file mode 100644 index 00000000..92156073 --- /dev/null +++ b/psi/psi/cryptor/fpga_ecc_cryptor.h @@ -0,0 +1,28 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "psi/psi/cryptor/ecc_cryptor.h" + +namespace psi::psi { + +class FPGAEccCryptor : public IEccCryptor { + public: + // TODO: Add FPGA-based ECC cryptor here. + void EccMask(absl::Span<const char> batch_points, + absl::Span<char> dest_points) const override; +}; + +} // namespace psi::psi \ No newline at end of file diff --git a/psi/psi/cryptor/ipp_ecc_cryptor.cc b/psi/psi/cryptor/ipp_ecc_cryptor.cc new file mode 100644 index 00000000..1db0b2b9 --- /dev/null +++ b/psi/psi/cryptor/ipp_ecc_cryptor.cc @@ -0,0 +1,74 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/cryptor/ipp_ecc_cryptor.h" + +#include <array> + +#include "crypto_mb/x25519.h" +#include "yacl/utils/parallel.h" + +namespace psi::psi { + +void IppEccCryptor::EccMask(absl::Span<const char> batch_points, + absl::Span<char> dest_points) const { + YACL_ENFORCE(batch_points.size() % kEccKeySize == 0); + + using Item = std::array<unsigned char, kEccKeySize>; + static_assert(sizeof(Item) == kEccKeySize); + + std::array<const int8u *, 8> ptr_sk; + std::fill(ptr_sk.begin(), ptr_sk.end(), + static_cast<const int8u *>(&private_key_[0])); + + int8u key_data[8][32]; // Junk buffer + + auto mask_functor = [&ptr_sk, &key_data](absl::Span<const Item> in, + absl::Span<Item> out) { + size_t current_batch_size = in.size(); + + std::array<const int8u *, 8> ptr_pk; + std::array<int8u *, 8> ptr_key; + + for (size_t i = 0; i < 8; i++) { + if (i < current_batch_size) { + ptr_pk[i] = static_cast<const int8u *>(in[i].data()); + ptr_key[i] = static_cast<int8u *>(out[i].data()); + } else { + ptr_pk[i] = static_cast<const int8u *>(in[0].data()); + ptr_key[i] = static_cast<int8u *>(key_data[i]); + } + } + mbx_status status = + mbx_x25519_mb8(ptr_key.data(), ptr_sk.data(), ptr_pk.data()); + YACL_ENFORCE(status == 0, "ippc mbx_x25519_mb8 Error: ", status); + }; + + absl::Span<const Item> input( + reinterpret_cast<const Item *>(batch_points.data()), + batch_points.size() / sizeof(Item)); + absl::Span<Item> output(reinterpret_cast<Item *>(dest_points.data()), + dest_points.size() / sizeof(Item)); + + yacl::parallel_for(0, input.size(), 8, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; idx += 8) { + int64_t current_batch_size = + std::min(static_cast<int64_t>(8), end - begin); + mask_functor(input.subspan(idx, current_batch_size), + output.subspan(idx, current_batch_size)); + } + }); +} + +} // namespace psi::psi diff --git a/psi/psi/cryptor/ipp_ecc_cryptor.h b/psi/psi/cryptor/ipp_ecc_cryptor.h new file mode 100644 index 00000000..f85fe05b --- /dev/null +++ b/psi/psi/cryptor/ipp_ecc_cryptor.h @@ -0,0 +1,37 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "openssl/crypto.h" +#include "openssl/rand.h" +#include "yacl/base/exception.h" + +#include "psi/psi/cryptor/ecc_cryptor.h" + +namespace psi::psi { + +class IppEccCryptor : public IEccCryptor { + public: + IppEccCryptor() = default; + + ~IppEccCryptor() override = default; + + CurveType GetCurveType() const override { return CurveType::CURVE_25519; } + + void EccMask(absl::Span<const char> batch_points, + absl::Span<char> dest_points) const override; +}; + +} // namespace psi::psi \ No newline at end of file diff --git a/psi/psi/cryptor/sm2_cryptor.cc b/psi/psi/cryptor/sm2_cryptor.cc new file mode 100644 index 00000000..628c2910 --- /dev/null +++ b/psi/psi/cryptor/sm2_cryptor.cc @@ -0,0 +1,89 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/cryptor/sm2_cryptor.h" + +#include "absl/types/span.h" +#include "openssl/bn.h" +#include "openssl/ec.h" +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/utils/parallel.h" + +#include "psi/psi/cryptor/ecc_utils.h" + +namespace psi::psi { + +void Sm2Cryptor::EccMask(absl::Span<const char> batch_points, + absl::Span<char> dest_points) const { + YACL_ENFORCE(batch_points.size() % kEcPointCompressLength == 0, "{} % {}!=0", + batch_points.size(), kEcPointCompressLength); + + using Item = std::array<char, kEcPointCompressLength>; + static_assert(sizeof(Item) == kEcPointCompressLength); + + auto mask_functor = [this](const Item& in, Item& out) { + BnCtxPtr bn_ctx(yacl::CheckNotNull(BN_CTX_new())); + + EcGroupSt ec_group(GetEcGroupId(curve_type_)); + + EcPointSt ec_point(ec_group); + + EC_POINT_oct2point(ec_group.get(), ec_point.get(), + reinterpret_cast<const unsigned char*>(in.data()), + in.size(), bn_ctx.get()); + + BigNumSt bn_sk; + bn_sk.FromBytes( + absl::string_view(reinterpret_cast<const char*>(&this->private_key_[0]), + kEccKeySize), + ec_group.bn_p); + + // pointmul + + EcPointSt ec_point2 = ec_point.PointMul(ec_group, bn_sk); + + ec_point2.ToBytes( + absl::MakeSpan(reinterpret_cast<uint8_t*>(out.data()), out.size())); + }; + + absl::Span<const Item> input( + reinterpret_cast<const Item*>(batch_points.data()), + batch_points.size() / sizeof(Item)); + absl::Span<Item> output(reinterpret_cast<Item*>(dest_points.data()), + dest_points.size() / sizeof(Item)); + + yacl::parallel_for(0, input.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + mask_functor(input[idx], output[idx]); + } + }); +} + +size_t Sm2Cryptor::GetMaskLength() const { return kEcPointCompressLength; } + +std::vector<uint8_t> Sm2Cryptor::HashToCurve( + absl::Span<const char> item_data) const { + EcGroupSt ec_group(GetEcGroupId(curve_type_)); + + EcPointSt ec_point = EcPointSt::CreateEcPointByHashToCurve( + absl::string_view(item_data.data(), item_data.size()), ec_group); + + std::vector<uint8_t> out(kEcPointCompressLength, 0); + + ec_point.ToBytes(absl::MakeSpan(out.data(), out.size())); + + return out; +} + +} // namespace psi::psi diff --git a/psi/psi/cryptor/sm2_cryptor.h b/psi/psi/cryptor/sm2_cryptor.h new file mode 100644 index 00000000..49bb0158 --- /dev/null +++ b/psi/psi/cryptor/sm2_cryptor.h @@ -0,0 +1,66 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <random> +#include <vector> + +#include "openssl/evp.h" +#include "yacl/base/exception.h" + +#include "psi/psi/cryptor/ecc_cryptor.h" + +namespace psi::psi { +class Sm2Cryptor : public IEccCryptor { + public: + explicit Sm2Cryptor(CurveType type = CurveType::CURVE_SM2) + : curve_type_(type) {} + + explicit Sm2Cryptor(absl::Span<const uint8_t> key, + CurveType type = CurveType::CURVE_SM2) + : curve_type_(type) { + YACL_ENFORCE(key.size() == kEccKeySize); + std::memcpy(private_key_, key.data(), key.size()); + } + + ~Sm2Cryptor() override { OPENSSL_cleanse(&private_key_[0], kEccKeySize); } + + void EccMask(absl::Span<const char> batch_points, + absl::Span<char> dest_points) const override; + + CurveType GetCurveType() const override { return curve_type_; } + + size_t GetMaskLength() const override; + + std::vector<uint8_t> HashToCurve( + absl::Span<const char> item_data) const override; + + static int GetEcGroupId(CurveType type) { + switch (type) { + case CurveType::CURVE_SECP256K1: + return NID_secp256k1; + case CurveType::CURVE_SM2: + return NID_sm2; + default: + YACL_THROW("wrong curve type:{}", static_cast<int>(type)); + return -1; + } + } + + private: + CurveType curve_type_ = CurveType::CURVE_SM2; +}; + +} // namespace psi::psi diff --git a/psi/psi/cryptor/sm2_cryptor_test.cc b/psi/psi/cryptor/sm2_cryptor_test.cc new file mode 100644 index 00000000..15965ae2 --- /dev/null +++ b/psi/psi/cryptor/sm2_cryptor_test.cc @@ -0,0 +1,104 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/cryptor/sm2_cryptor.h" + +#include <future> +#include <iostream> + +#include "absl/strings/escaping.h" +#include "gtest/gtest.h" +#include "yacl/base/exception.h" +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/crypto/tools/prg.h" + +namespace psi::psi { + +struct TestParams { + size_t items_size; + CurveType type = CurveType::CURVE_SM2; +}; + +class Sm2CryptorTest : public ::testing::TestWithParam<TestParams> {}; + +TEST_P(Sm2CryptorTest, Works) { + auto params = GetParam(); + std::random_device rd; + yacl::crypto::Prg<uint64_t> prg(rd()); + + std::shared_ptr<Sm2Cryptor> sm2_cryptor_a = + std::make_shared<Sm2Cryptor>(params.type); + std::shared_ptr<Sm2Cryptor> sm2_cryptor_b = + std::make_shared<Sm2Cryptor>(params.type); + + std::string items_a(params.items_size * kEccKeySize, '\0'); + std::string items_b(params.items_size * kEccKeySize, '\0'); + std::string masked_a(params.items_size * (kEccKeySize + 1), '\0'); + std::string masked_b(params.items_size * (kEccKeySize + 1), '\0'); + + std::string masked_ab(params.items_size * (kEccKeySize + 1), '\0'); + std::string masked_ba(params.items_size * (kEccKeySize + 1), '\0'); + + prg.Fill(absl::MakeSpan(items_a.data(), items_a.length())); + + items_b = items_a; + + std::string items_a_point(params.items_size * (kEccKeySize + 1), '\0'); + std::string items_b_point(params.items_size * (kEccKeySize + 1), '\0'); + + for (size_t idx = 0; idx < params.items_size; ++idx) { + absl::Span<const char> items_span = + absl::MakeSpan(&items_a[idx * kEccKeySize], kEccKeySize); + std::vector<uint8_t> point_data = sm2_cryptor_a->HashToCurve(items_span); + std::memcpy(&items_a_point[idx * (kEccKeySize + 1)], point_data.data(), + point_data.size()); + + items_span = absl::MakeSpan(&items_b[idx * kEccKeySize], kEccKeySize); + point_data = sm2_cryptor_b->HashToCurve(items_span); + std::memcpy(&items_b_point[idx * (kEccKeySize + 1)], point_data.data(), + point_data.size()); + } + + // g^a + sm2_cryptor_a->EccMask( + absl::MakeSpan(items_a_point.data(), items_a_point.length()), + absl::MakeSpan(masked_a.data(), masked_a.length())); + + // (g^a)^b + sm2_cryptor_b->EccMask(absl::MakeSpan(masked_a.data(), masked_a.length()), + absl::MakeSpan(masked_ab.data(), masked_ab.length())); + + // g^b + sm2_cryptor_b->EccMask( + absl::MakeSpan(items_b_point.data(), items_b_point.length()), + absl::MakeSpan(masked_b.data(), masked_b.length())); + + // (g^b)^a + sm2_cryptor_a->EccMask(absl::MakeSpan(masked_b.data(), masked_b.length()), + absl::MakeSpan(masked_ba.data(), masked_ba.length())); + + EXPECT_EQ(masked_ab, masked_ba); +} + +INSTANTIATE_TEST_SUITE_P( + Works_Instances, Sm2CryptorTest, + testing::Values(TestParams{1}, TestParams{10}, TestParams{50}, + TestParams{100}, + // CURVE_SECP256K1 + TestParams{1, CurveType::CURVE_SECP256K1}, + TestParams{10, CurveType::CURVE_SECP256K1}, + TestParams{50, CurveType::CURVE_SECP256K1}, + TestParams{100, CurveType::CURVE_SECP256K1})); + +} // namespace psi::psi \ No newline at end of file diff --git a/psi/psi/cryptor/sodium_curve25519_cryptor.cc b/psi/psi/cryptor/sodium_curve25519_cryptor.cc new file mode 100644 index 00000000..4c9bc700 --- /dev/null +++ b/psi/psi/cryptor/sodium_curve25519_cryptor.cc @@ -0,0 +1,75 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/cryptor/sodium_curve25519_cryptor.h" + +extern "C" { +#include "sodium.h" +} + +#include <iostream> + +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/utils/parallel.h" + +namespace psi::psi { + +void SodiumCurve25519Cryptor::EccMask(absl::Span<const char> batch_points, + absl::Span<char> dest_points) const { + YACL_ENFORCE(batch_points.size() % kEccKeySize == 0); + + using Item = std::array<unsigned char, kEccKeySize>; + static_assert(sizeof(Item) == kEccKeySize); + + auto mask_functor = [this](const Item& in, Item& out) { + YACL_ENFORCE(out.size() == kEccKeySize); + YACL_ENFORCE(in.size() == kEccKeySize); + + YACL_ENFORCE(0 == crypto_scalarmult_curve25519( + out.data(), this->private_key_, in.data())); + }; + + absl::Span<const Item> input( + reinterpret_cast<const Item*>(batch_points.data()), + batch_points.size() / sizeof(Item)); + absl::Span<Item> output(reinterpret_cast<Item*>(dest_points.data()), + dest_points.size() / sizeof(Item)); + + yacl::parallel_for(0, input.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + mask_functor(input[idx], output[idx]); + } + }); +} + +std::vector<uint8_t> SodiumCurve25519Cryptor::KeyExchange( + const std::shared_ptr<yacl::link::Context>& link_ctx) { + std::array<uint8_t, kEccKeySize> self_public_key; + crypto_scalarmult_curve25519_base(self_public_key.data(), this->private_key_); + yacl::Buffer self_pubkey_buf(self_public_key.data(), self_public_key.size()); + link_ctx->SendAsyncThrottled( + link_ctx->NextRank(), self_pubkey_buf, + fmt::format("send rank-{} public key", link_ctx->Rank())); + yacl::Buffer peer_pubkey_buf = link_ctx->Recv( + link_ctx->NextRank(), + fmt::format("recv rank-{} public key", link_ctx->NextRank())); + std::vector<uint8_t> dh_key(kEccKeySize); + YACL_ENFORCE(0 == crypto_scalarmult_curve25519( + dh_key.data(), this->private_key_, + (const unsigned char*)peer_pubkey_buf.data())); + const auto shared_key = yacl::crypto::Blake3(dh_key); + return {shared_key.begin(), shared_key.end()}; +} + +} // namespace psi::psi \ No newline at end of file diff --git a/psi/psi/cryptor/sodium_curve25519_cryptor.h b/psi/psi/cryptor/sodium_curve25519_cryptor.h new file mode 100644 index 00000000..899ad52f --- /dev/null +++ b/psi/psi/cryptor/sodium_curve25519_cryptor.h @@ -0,0 +1,62 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "openssl/crypto.h" +#include "openssl/rand.h" +#include "yacl/base/exception.h" +#include "yacl/link/link.h" + +#include "psi/psi/cryptor/ecc_cryptor.h" + +namespace psi::psi { + +// +// avx2 asm code reference: +// https://eprint.iacr.org/2015/943.pdf +// Sandy2x: New Curve_25519 Speed Records +// Table1 Performance results compare with “floodyberry” [7]. +// +// fix Side Channel Attack in +// https://eprint.iacr.org/2017/806 +// May the Fourth Be With You: A Microarchitectural Side Channel Attack on +// Several Real-World Applications of Curve_25519 +// The vulnerability has been assigned CVE-2017-0379 +// +class SodiumCurve25519Cryptor : public IEccCryptor { + public: + SodiumCurve25519Cryptor() { + // + // curve25519 secret keys range: + // 2^254 + 8*{0,1,2, 2^251-1} + // convert 32byte randombytes to curve25519 secret key range + // + private_key_[0] &= 248; + private_key_[31] &= 127; + private_key_[31] |= 64; + } + + ~SodiumCurve25519Cryptor() override = default; + + CurveType GetCurveType() const override { return CurveType::CURVE_25519; } + + void EccMask(absl::Span<const char> batch_points, + absl::Span<char> dest_points) const override; + + std::vector<uint8_t> KeyExchange( + const std::shared_ptr<yacl::link::Context> &link_ctx); +}; + +} // namespace psi::psi \ No newline at end of file diff --git a/psi/psi/demo/BUILD.bazel b/psi/psi/demo/BUILD.bazel new file mode 100644 index 00000000..03757fd7 --- /dev/null +++ b/psi/psi/demo/BUILD.bazel @@ -0,0 +1,26 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//bazel:psi.bzl", "psi_cc_binary") + +package(default_visibility = ["//visibility:public"]) + +psi_cc_binary( + name = "psi_demo", + srcs = ["psi_demo.cc"], + deps = [ + "//psi/psi:bucket_psi", + "//psi/psi/utils:resource", + ], +) diff --git a/psi/psi/demo/README.md b/psi/psi/demo/README.md new file mode 100644 index 00000000..d23c8f0e --- /dev/null +++ b/psi/psi/demo/README.md @@ -0,0 +1,46 @@ +# Demos + +> All the commands should run at the root of the repo. + +## 2P PSI + +### ECDH + +1. Compile the binary + +```bash +$ bazel build //psi/psi:main -c opt +``` + +2. Generate mock data + +```bash +$ python psi/psi/demo/data/test_data_generator.py --reciver_item_cnt 1e6 --sender_item_cnt 1e6 --intersection_cnt 8e4 --id_cnt 2 --receiver_path /tmp/receiver_input.csv --sender_path /tmp/sender_input.csv --intersection_path /tmp/intersection.csv +``` + +3. Run PSI + +In two terminals, + +For **receiver** terminal, + +```bash +$ ./bazel-bin/psi/psi/main --config $(pwd)/psi/psi/demo/config/ecdh_receiver.json +``` + +For **sender** terminal, + +```bash +$ ./bazel-bin/psi/psi/main --config $(pwd)/psi/psi/demo/config/ecdh_sender.json +``` + +4. Verify results. + +```bash + +# compare outputs between receiver and sender. +$ cmp --silent /tmp/receiver_output.csv /tmp/sender_output.csv && echo '### SUCCESS: Outputs between receiver and sender are identical! ###' || echo '### WARNING: Outputs between receiver and sender are different! ###' + +# check whether receiver output is correct. +$ cmp --silent /tmp/receiver_output.csv /tmp/intersection.csv && echo '### SUCCESS: Receiver output and intersection are identical! ###' || echo '### WARNING: Receiver output and intersection are different! ###' +``` \ No newline at end of file diff --git a/psi/psi/demo/config/ecdh_receiver_inner_join.json b/psi/psi/demo/config/ecdh_receiver_inner_join.json new file mode 100644 index 00000000..e9efbca1 --- /dev/null +++ b/psi/psi/demo/config/ecdh_receiver_inner_join.json @@ -0,0 +1,41 @@ +{ + "protocol_config": { + "protocol": "PROTOCOL_ECDH", + "ecdh_config": { + "curve": "CURVE_25519" + }, + "role": "ROLE_RECEIVER", + "broadcast_result": true + }, + "input_config": { + "type": "IO_TYPE_FILE_CSV", + "path": "/tmp/receiver_input.csv" + }, + "output_config": { + "type": "IO_TYPE_FILE_CSV", + "path": "/tmp/ecdh_receiver_inner_join_output.csv" + }, + "link_config": { + "parties": [ + { + "id": "receiver", + "host": "127.0.0.1:5300" + }, + { + "id": "sender", + "host": "127.0.0.1:5400" + } + ] + }, + "self_link_party": "receiver", + "keys": [ + "id0", + "id1" + ], + "debug_options": { + "trace_path": "/tmp/ecdh_receiver_inner_join.trace" + }, + "check_duplicates": true, + "sort_output": false, + "advanced_join_type": "ADVANCED_JOIN_TYPE_INNER_JOIN" +} \ No newline at end of file diff --git a/psi/psi/demo/config/ecdh_receiver_recovery.json b/psi/psi/demo/config/ecdh_receiver_recovery.json new file mode 100644 index 00000000..486b1056 --- /dev/null +++ b/psi/psi/demo/config/ecdh_receiver_recovery.json @@ -0,0 +1,44 @@ +{ + "protocol_config": { + "protocol": "PROTOCOL_ECDH", + "ecdh_config": { + "curve": "CURVE_25519" + }, + "role": "ROLE_RECEIVER", + "broadcast_result": true + }, + "input_config": { + "type": "IO_TYPE_FILE_CSV", + "path": "/tmp/receiver_input.csv" + }, + "output_config": { + "type": "IO_TYPE_FILE_CSV", + "path": "/tmp/ecdh_receiver_recovery_output.csv" + }, + "link_config": { + "parties": [ + { + "id": "receiver", + "host": "127.0.0.1:5300" + }, + { + "id": "sender", + "host": "127.0.0.1:5400" + } + ] + }, + "self_link_party": "receiver", + "keys": [ + "id0", + "id1" + ], + "debug_options": { + "trace_path": "/tmp/ecdh_receiver_recovery.trace" + }, + "check_duplicates": true, + "sort_output": false, + "recovery_config": { + "enabled": true, + "folder": "/tmp/ecdh_receiver_cache" + } +} \ No newline at end of file diff --git a/psi/psi/demo/config/ecdh_sender_inner_join.json b/psi/psi/demo/config/ecdh_sender_inner_join.json new file mode 100644 index 00000000..114d4295 --- /dev/null +++ b/psi/psi/demo/config/ecdh_sender_inner_join.json @@ -0,0 +1,41 @@ +{ + "protocol_config": { + "protocol": "PROTOCOL_ECDH", + "ecdh_config": { + "curve": "CURVE_25519" + }, + "role": "ROLE_SENDER", + "broadcast_result": true + }, + "input_config": { + "type": "IO_TYPE_FILE_CSV", + "path": "/tmp/sender_input.csv" + }, + "output_config": { + "type": "IO_TYPE_FILE_CSV", + "path": "/tmp/ecdh_sender_inner_join_output.csv" + }, + "link_config": { + "parties": [ + { + "id": "receiver", + "host": "127.0.0.1:5300" + }, + { + "id": "sender", + "host": "127.0.0.1:5400" + } + ] + }, + "self_link_party": "sender", + "keys": [ + "id0", + "id1" + ], + "debug_options": { + "trace_path": "/tmp/ecdh_sender_inner_join.trace" + }, + "check_duplicates": true, + "sort_output": false, + "advanced_join_type": "ADVANCED_JOIN_TYPE_INNER_JOIN" +} \ No newline at end of file diff --git a/psi/psi/demo/config/ecdh_sender_recovery.json b/psi/psi/demo/config/ecdh_sender_recovery.json new file mode 100644 index 00000000..597c370f --- /dev/null +++ b/psi/psi/demo/config/ecdh_sender_recovery.json @@ -0,0 +1,44 @@ +{ + "protocol_config": { + "protocol": "PROTOCOL_ECDH", + "ecdh_config": { + "curve": "CURVE_25519" + }, + "role": "ROLE_SENDER", + "broadcast_result": true + }, + "input_config": { + "type": "IO_TYPE_FILE_CSV", + "path": "/tmp/sender_input.csv" + }, + "output_config": { + "type": "IO_TYPE_FILE_CSV", + "path": "/tmp/ecdh_sender_recovery_output.csv" + }, + "link_config": { + "parties": [ + { + "id": "receiver", + "host": "127.0.0.1:5300" + }, + { + "id": "sender", + "host": "127.0.0.1:5400" + } + ] + }, + "self_link_party": "sender", + "keys": [ + "id0", + "id1" + ], + "debug_options": { + "trace_path": "/tmp/ecdh_sender_recovery.trace" + }, + "check_duplicates": true, + "sort_output": false, + "recovery_config": { + "enabled": true, + "folder": "/tmp/ecdh_sender_cache" + } +} \ No newline at end of file diff --git a/psi/psi/demo/config/kkrt_receiver_recovery.json b/psi/psi/demo/config/kkrt_receiver_recovery.json new file mode 100644 index 00000000..08d606db --- /dev/null +++ b/psi/psi/demo/config/kkrt_receiver_recovery.json @@ -0,0 +1,44 @@ +{ + "protocol_config": { + "protocol": "PROTOCOL_KKRT", + "kkrt_config": { + "bucket_size": 4096 + }, + "role": "ROLE_RECEIVER", + "broadcast_result": true + }, + "input_config": { + "type": "IO_TYPE_FILE_CSV", + "path": "/tmp/receiver_input.csv" + }, + "output_config": { + "type": "IO_TYPE_FILE_CSV", + "path": "/tmp/kkrt_receiver_recovery_output.csv" + }, + "link_config": { + "parties": [ + { + "id": "receiver", + "host": "127.0.0.1:5300" + }, + { + "id": "sender", + "host": "127.0.0.1:5400" + } + ] + }, + "self_link_party": "receiver", + "keys": [ + "id0", + "id1" + ], + "debug_options": { + "trace_path": "/tmp/kkrt_receiver_recovery.trace" + }, + "check_duplicates": true, + "sort_output": false, + "recovery_config": { + "enabled": true, + "folder": "/tmp/kkrt_receiver_cache" + } +} \ No newline at end of file diff --git a/psi/psi/demo/config/kkrt_sender_recovery.json b/psi/psi/demo/config/kkrt_sender_recovery.json new file mode 100644 index 00000000..ed00cfcb --- /dev/null +++ b/psi/psi/demo/config/kkrt_sender_recovery.json @@ -0,0 +1,44 @@ +{ + "protocol_config": { + "protocol": "PROTOCOL_KKRT", + "kkrt_config": { + "bucket_size": 4096 + }, + "role": "ROLE_SENDER", + "broadcast_result": true + }, + "input_config": { + "type": "IO_TYPE_FILE_CSV", + "path": "/tmp/sender_input.csv" + }, + "output_config": { + "type": "IO_TYPE_FILE_CSV", + "path": "/tmp/kkrt_sender_recovery_output.csv" + }, + "link_config": { + "parties": [ + { + "id": "receiver", + "host": "127.0.0.1:5300" + }, + { + "id": "sender", + "host": "127.0.0.1:5400" + } + ] + }, + "self_link_party": "sender", + "keys": [ + "id0", + "id1" + ], + "debug_options": { + "trace_path": "/tmp/kkrt_sender_recovery.trace" + }, + "check_duplicates": true, + "sort_output": false, + "recovery_config": { + "enabled": true, + "folder": "/tmp/kkrt_sender_cache" + } +} \ No newline at end of file diff --git a/psi/psi/demo/config/rr22_receiver_recovery.json b/psi/psi/demo/config/rr22_receiver_recovery.json new file mode 100644 index 00000000..eb78af9f --- /dev/null +++ b/psi/psi/demo/config/rr22_receiver_recovery.json @@ -0,0 +1,41 @@ +{ + "protocol_config": { + "protocol": "PROTOCOL_RR22", + "role": "ROLE_RECEIVER", + "broadcast_result": true + }, + "input_config": { + "type": "IO_TYPE_FILE_CSV", + "path": "/tmp/receiver_input.csv" + }, + "output_config": { + "type": "IO_TYPE_FILE_CSV", + "path": "/tmp/rr22_receiver_recovery_output.csv" + }, + "link_config": { + "parties": [ + { + "id": "receiver", + "host": "127.0.0.1:5300" + }, + { + "id": "sender", + "host": "127.0.0.1:5400" + } + ] + }, + "self_link_party": "receiver", + "keys": [ + "id0", + "id1" + ], + "debug_options": { + "trace_path": "/tmp/rr22_receiver_recovery.trace" + }, + "check_duplicates": false, + "sort_output": false, + "recovery_config": { + "enabled": true, + "folder": "/tmp/rr22_receiver_cache" + } +} \ No newline at end of file diff --git a/psi/psi/demo/config/rr22_sender_recovery.json b/psi/psi/demo/config/rr22_sender_recovery.json new file mode 100644 index 00000000..c94ebd35 --- /dev/null +++ b/psi/psi/demo/config/rr22_sender_recovery.json @@ -0,0 +1,41 @@ +{ + "protocol_config": { + "protocol": "PROTOCOL_RR22", + "role": "ROLE_SENDER", + "broadcast_result": true + }, + "input_config": { + "type": "IO_TYPE_FILE_CSV", + "path": "/tmp/sender_input.csv" + }, + "output_config": { + "type": "IO_TYPE_FILE_CSV", + "path": "/tmp/rr22_sender_recovery_output.csv" + }, + "link_config": { + "parties": [ + { + "id": "receiver", + "host": "127.0.0.1:5300" + }, + { + "id": "sender", + "host": "127.0.0.1:5400" + } + ] + }, + "self_link_party": "sender", + "keys": [ + "id0", + "id1" + ], + "debug_options": { + "trace_path": "/tmp/rr22_sender_recovery.trace" + }, + "check_duplicates": false, + "sort_output": false, + "recovery_config": { + "enabled": true, + "folder": "/tmp/rr22_sender_cache" + } +} \ No newline at end of file diff --git a/psi/psi/demo/data/BUILD.bazel b/psi/psi/demo/data/BUILD.bazel new file mode 100644 index 00000000..110d2316 --- /dev/null +++ b/psi/psi/demo/data/BUILD.bazel @@ -0,0 +1,24 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "data", + data = [ + "alice.csv", + "bob.csv", + "carol.csv", + ], +) diff --git a/psi/psi/demo/data/alice.csv b/psi/psi/demo/data/alice.csv new file mode 100644 index 00000000..886020a1 --- /dev/null +++ b/psi/psi/demo/data/alice.csv @@ -0,0 +1,1547 @@ +idx,id,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,y +1,user808,-0.8099145,0.4462118,0.1761337,-0.7801104,-0.08067331,0.7585569,-0.8945493,0.5814661,0.7284426,-0.49955,0 +1,user809,0.4934607,0.7054121,-0.1574063,0.7852814,0.2822348,-0.561065,0.9825531,0.9842912,-0.5361243,0.002944904,1 +1,user810,-0.4455727,0.1096046,0.7971783,-0.809021,-0.3520915,0.5535826,0.4581953,0.5794417,-0.05707829,0.6540501,1 +1,user811,-0.6533987,0.3539579,-0.3587303,0.8346875,0.6286925,-0.1950118,-0.4451231,0.6455893,0.9310575,-0.1095324,0 +1,user812,0.8754278,-0.8758362,-0.3460935,0.8932284,-0.642694,-0.05567194,-0.2448348,0.2983404,0.8497086,0.1724393,0 +1,user813,0.521724,-0.05313766,0.9928341,0.03107372,-0.4891709,0.1941637,-0.7153092,0.9643407,-0.2657519,-0.6666494,0 +1,user814,-0.8066372,0.5781631,-0.2901214,0.6979083,-0.178344,0.1897566,0.940709,0.5751678,-0.9243125,-0.05450757,1 +1,user815,0.9622172,-0.7043077,-0.3575484,-0.2052793,-0.3687039,0.1328102,-0.5295284,-0.3103822,0.5080793,-0.1328084,0 +1,user816,0.690546,-0.08426545,-0.1599133,-0.2011958,-0.3137166,0.870989,0.4561126,-0.07437644,0.6073897,-0.3269641,0 +1,user817,-0.3169206,-0.1366755,0.1174815,0.08131936,0.851398,0.8183566,0.0422993,0.4044457,0.7042426,0.06313821,1 +1,user818,0.3849269,0.4867548,-0.1223454,-0.8912337,-0.4097909,-0.5727595,-0.2915226,0.4472808,0.8229814,0.1831892,0 +1,user819,-0.08697165,0.6906938,0.3771156,-0.4271958,0.785355,0.05667296,0.004340217,0.8738961,0.1948712,-0.6383243,1 +1,user820,-0.1312038,0.6694249,-0.3397621,-0.4799921,-0.4270271,-0.5114425,-0.6043312,0.2539954,0.3165913,-0.06506931,1 +1,user821,0.3080573,0.9655738,-0.2766812,-0.1129542,0.043186,0.6846753,0.9675457,0.487555,-0.05149228,0.4636286,1 +1,user822,-0.3520335,-0.3561707,-0.6313637,-0.5809777,-0.8543196,0.6412349,-0.7065133,0.9944458,0.5175707,0.7448945,0 +1,user823,0.2009843,0.6655176,0.6424807,0.8378194,0.7117212,-0.05046725,0.3359142,-0.09656665,-0.4601094,0.7318065,1 +1,user824,-0.740048,0.7233268,0.4834039,0.3525356,0.3272907,-0.7316941,-0.3026422,0.9637273,-0.6124589,0.517382,1 +1,user825,-0.8374699,-0.5083535,-0.393351,-0.5736121,0.7014543,-0.7790873,0.08236182,0.9109072,-0.6729191,-0.1054133,1 +1,user826,-0.2440057,-0.6889369,0.330833,0.3956872,0.1440119,-0.04508694,-0.2026948,0.6013526,-0.7872165,-0.8516819,1 +1,user827,-0.7260878,0.802059,-0.08069788,-0.1896452,0.3083663,0.6111099,-0.5500415,0.6077421,-0.147716,-0.1669271,0 +1,user828,0.3197552,-0.7987179,-0.6887786,-0.8024025,0.2708701,-0.5516676,-0.9141367,0.9821867,0.8289979,0.6111208,0 +1,user829,-0.7710819,-0.8683111,-0.2496897,0.525204,0.01599458,0.2965901,-0.6656607,0.4262887,0.325822,0.3734652,1 +1,user830,0.7613663,-0.02278171,-0.6784374,-0.5153762,0.9961851,0.1548892,0.6029286,-0.4627568,0.7541725,-0.4478448,0 +1,user831,0.1648993,-0.6916826,-0.5325412,-0.3925771,-0.9910317,-0.2350418,-0.9953441,0.1082341,-0.7382345,0.4369607,0 +1,user832,-0.5782737,-0.8828514,-0.4195128,0.6996937,-0.3217338,-0.2843976,0.8039846,-0.8726473,-0.06669384,-0.2185943,1 +1,user833,0.3366511,0.06871843,0.37171,0.5393258,0.3860445,0.7817579,-0.0648113,0.6584632,-0.4038879,0.166373,0 +1,user834,0.05776969,0.2788839,-0.445801,0.7398712,0.9277476,-0.9593452,-0.3734773,-0.3302453,0.3210399,0.5713849,0 +1,user835,-0.375313,0.7882173,-0.8107915,0.7788946,0.2892233,-0.7745151,0.9246034,0.7714521,-0.8208778,0.9069961,1 +1,user836,0.8864442,-0.1335258,0.144761,-0.7616299,0.2000875,-0.4375915,-0.7459037,-0.3810684,-0.2374698,0.7379727,1 +1,user837,0.5364112,-0.6645929,0.2577899,-0.006069696,-0.1593979,-0.0927395,0.8242167,0.1042705,0.2458247,-0.6762502,0 +1,user838,-0.7558275,-0.3152245,0.4431601,-0.06596496,0.01259006,0.04701278,0.7091518,-0.1350912,0.1491242,0.8817156,1 +1,user839,-0.9234703,-0.6873141,-0.6791054,-0.5417403,-0.8805858,-0.6790346,-0.640453,-0.7996024,-0.5090272,-0.7615773,0 +1,user840,0.02987191,-0.9591808,-0.8996164,-0.2207883,-0.877163,0.3461955,0.8067698,0.08856173,0.7097004,0.3266947,0 +1,user841,-0.2014002,0.7943801,0.2403384,0.1250141,0.6604985,-0.3994046,0.167347,-0.5556495,-0.9079541,0.5357657,1 +1,user842,-0.5768691,0.6666438,-0.03783567,-0.7070528,0.7481067,0.1259537,-0.08557616,0.8459869,-0.5779697,0.1288903,1 +1,user843,-0.09470034,-0.835017,-0.2457099,-0.3275599,-0.519857,-0.7094764,-0.438065,-0.6130979,0.559409,-0.5008659,1 +1,user844,-0.6796762,-0.2587575,0.2331724,-0.8439122,-0.8286724,0.794759,0.4520379,-0.5913088,-0.173706,0.8691162,0 +1,user845,-0.3835063,0.2448069,0.672043,0.9908555,-0.4302373,-0.6842897,-0.1448672,0.4211547,-0.5022821,-0.9256172,1 +1,user846,-0.1324832,-0.5393246,0.3967417,0.4671608,0.111439,0.4233338,0.03240663,0.07651987,0.06748831,0.3663257,0 +1,user847,-0.9891302,0.656977,-0.9267408,-0.04510798,-0.142389,0.6657481,-0.09184958,0.3343148,-0.5663162,-0.4578479,1 +1,user848,0.2995731,-0.8918686,-0.2104756,0.0721749,-0.5788393,-0.8659331,0.8974321,-0.1743996,-0.7980395,0.137521,1 +1,user849,-0.7475562,0.9474302,-0.7256037,0.575927,0.7016481,0.8505743,0.7408841,-0.4761993,-0.1095303,-0.4504852,1 +1,user850,-0.07610187,0.3476708,0.4503747,0.5276962,-0.3570339,-0.277579,0.9124906,0.2082109,0.628555,-0.09617213,0 +1,user851,-0.8316307,0.7775563,0.4497623,0.5921828,-0.005866439,-0.3773755,-0.7068991,-0.9204041,0.5185518,-0.9275483,1 +1,user852,0.560501,0.913004,-0.002284909,-0.5370272,-0.2551659,0.5352496,0.7084298,-0.9886443,0.8389775,-0.9868566,0 +1,user853,0.5718646,0.9915001,0.819011,0.9467185,-0.2113535,-0.6363441,-0.7940227,0.2026567,0.1461257,-0.3512776,1 +1,user854,0.3693536,0.4430739,0.09224309,0.4300022,-0.2941452,0.5721572,0.6290151,-0.01697078,-0.9415576,0.8042582,0 +1,user855,0.820453,0.6363308,-0.518881,0.8155085,-0.9278752,0.8035555,-0.5942125,0.9750829,-0.7734814,0.5305254,0 +1,user856,0.7343947,-0.5168534,-0.57434,-0.6268936,-0.5098993,-0.4154314,0.2883391,0.1135639,0.4732066,0.543309,0 +1,user857,-0.8746521,0.754137,-0.5769239,-0.1743106,0.8498667,-0.4729297,-0.5736797,-0.4156182,-0.7287741,0.9525762,0 +1,user858,-0.9056348,0.4383898,0.4004212,-0.3741367,0.380491,0.4146654,-0.1442539,0.5828251,0.07880255,-0.6364017,1 +1,user859,0.05414991,-0.3155713,-0.2631186,-0.4292961,0.7609708,0.03290103,0.3742024,0.0957506,0.3022045,0.1544299,1 +1,user860,-0.645734,0.8858258,0.1733864,-0.6491066,-0.1341387,0.8236604,-0.2393404,-0.9893295,0.597048,0.3260414,0 +1,user861,0.8557316,-0.5843919,0.7219837,0.1104871,0.3766762,-0.4304454,-0.5413254,-0.8799317,-0.167025,-0.0842465,1 +1,user862,-0.7809508,-0.007253856,0.2043402,0.1781269,0.7699391,0.7978592,0.3788584,-0.7960153,0.56397,-0.4086094,0 +1,user863,-0.2240077,-0.9970255,0.7538737,-0.9494129,0.5441275,-0.4607372,-0.4353558,-0.8619768,-0.4696459,-0.8925528,1 +1,user864,0.1923827,0.4843265,0.09369374,-0.3501872,-0.2372793,-0.6486875,0.3938633,0.7785315,0.4290871,-0.9178735,0 +1,user865,0.2768189,-0.72837,0.7585393,-0.08200199,0.6976867,0.838514,-0.994619,-0.1262606,-0.1149901,-0.8372245,1 +1,user866,0.4006792,0.7911918,0.9430822,0.8294817,-0.1666492,-0.2352523,-0.5107525,0.9094753,-0.2905236,-0.9855567,0 +1,user867,0.07882684,-0.6491994,-0.7615453,-0.1118171,0.9628082,-0.08627894,0.6479596,-0.602537,-0.8083827,0.8200992,0 +1,user868,-0.1867699,-0.3929628,0.01632915,0.9119283,-0.4617112,-0.2542255,0.8295978,0.9780099,-0.8691654,-0.5134747,1 +1,user869,0.6448517,-0.5240326,0.3862423,-0.2364833,0.8459408,0.8117605,-0.8016007,-0.2256159,0.8586006,0.8961589,0 +1,user870,0.1553565,-0.3365134,-0.4406507,0.3464426,-0.9177776,0.2346865,-0.9924934,-0.4021393,-0.3174099,-0.941478,0 +1,user871,0.843102,-0.3521436,0.1167127,-0.30886,-0.3388742,-0.90803,0.6363676,0.06657163,0.840535,0.81322,0 +1,user872,-0.5565485,-0.7296525,-0.3734193,0.8885308,0.5064394,-0.5876441,0.3657463,0.2187346,0.9506465,0.4319246,1 +1,user873,0.5784874,-0.6698696,0.5215137,0.6393898,0.8303291,-0.6393599,-0.07806954,-0.5561524,0.1046205,0.1874123,0 +1,user874,-0.2515983,-0.1871606,0.8710029,0.36358,0.1412687,-0.6175064,-0.8016974,0.4534738,0.399944,-0.6876459,0 +1,user875,-0.2362247,0.01158995,0.8597531,-0.9553814,0.677767,-0.7928851,-0.1822158,0.6274259,-0.2230594,0.3010408,1 +1,user876,-0.8050189,0.5749374,0.1935566,0.6302453,-0.5999082,-0.3236495,0.7770633,0.8650023,0.6023383,0.2617951,1 +1,user877,0.6159185,0.2735148,0.2677446,-0.1692592,-0.7472923,0.8058274,0.2307092,-0.4700064,-0.5325677,0.6786798,0 +1,user878,-0.2253549,-0.331433,0.9330123,-0.0004893928,-0.464622,0.872863,0.7259346,-0.03825933,0.2106243,0.8431929,1 +1,user879,0.4945542,0.6830688,0.983081,-0.2975798,-0.1787475,-0.1895826,0.6744954,-0.3093972,0.8042988,-0.6006839,0 +1,user880,0.8683623,0.2209451,0.5421409,-0.5933322,0.9543558,0.6564017,-0.02840674,0.05379428,0.357902,-0.7718054,0 +1,user881,0.6985432,-0.9837622,0.383387,-0.4727932,0.1783441,-0.404716,0.6384253,-0.8300484,-0.1608207,-0.2529792,0 +1,user882,0.6629235,0.4606251,0.4328434,-0.705397,0.815386,0.4330419,0.9675963,-0.2298014,0.3228507,-0.5282322,1 +1,user883,0.4288633,0.1339491,-0.460144,-0.1303593,-0.3008101,0.1916513,-0.319977,0.06514994,0.1968795,-0.758662,0 +1,user884,0.2704078,-0.9922621,0.2023981,-0.5260747,0.9669905,-0.04106012,0.8444026,0.3726083,0.9853051,0.3957432,1 +1,user885,0.03227714,-0.09630102,-0.4749135,0.7246052,-0.4787592,0.005199087,0.5966115,0.7532279,0.3812931,-0.7239741,1 +1,user886,0.2493163,-0.2297201,0.02097501,-0.3148509,-0.2286853,-0.00479319,0.08581058,0.04023288,0.4233981,0.7718635,1 +1,user887,0.004802535,-0.5091156,0.6280581,-0.1529683,-0.5429087,0.5435085,0.1327417,-0.5138278,0.4585117,-0.06094781,0 +1,user888,0.157625,-0.342164,-0.05183744,-0.4497054,-0.6288924,0.5322694,-0.9770682,-0.6623904,0.652519,-0.7713978,0 +1,user889,0.3436815,-0.7913304,-0.5786038,0.3110124,-0.8481943,-0.5901278,0.9415567,-0.3769421,-0.4977994,-0.8645382,0 +1,user890,-0.9410476,0.1753132,-0.6350605,0.4177357,-0.781938,-0.4235905,-0.4930559,0.5819228,-0.2392838,-0.9065179,0 +1,user891,0.511891,-0.4563382,-0.878451,-0.09881207,0.2369689,0.3559298,-0.2164086,-0.6517199,0.249567,0.5546436,0 +1,user892,0.1994131,-0.3757223,-0.8566201,-0.5785006,0.5284819,-0.02057313,-0.5997687,-0.2568738,0.3351756,0.05121528,0 +1,user893,-0.7219984,-0.8319407,0.5692798,-0.4041375,0.9880012,-0.6257313,0.8858025,0.7859075,-0.6753138,-0.3151274,1 +1,user894,-0.7121167,-0.4533637,0.8754227,-0.04822497,-0.2189037,0.8951925,0.3482356,-0.5136967,0.7799211,0.6620908,1 +1,user895,-0.6082043,-0.8913958,0.2370736,0.07131227,-0.7087974,0.3307394,0.7940946,-0.4783423,-0.2357373,0.1333418,1 +1,user896,0.5548205,-0.5603107,0.3278191,0.5138605,0.6856879,-0.7872173,0.8911835,-0.3403531,0.2096961,-0.1523519,1 +1,user897,0.6885625,-0.6621719,0.8185049,-0.2187433,0.6144471,-0.3400598,0.8374831,-0.6042214,-0.5106025,0.6765341,0 +1,user898,0.4706226,-0.5405952,0.4755283,0.9594952,-0.7459892,-0.7555396,0.4420542,-0.08087927,-0.04411996,-0.04655894,1 +1,user899,-0.6319494,0.04672649,-0.6558518,0.4257888,-0.7760233,-0.04144279,0.7207813,-0.3623432,0.3405307,0.3341734,0 +1,user900,0.3334142,-0.1862045,0.2047472,0.5447734,0.460388,-0.5282993,-0.9641176,0.1701628,-0.6520019,0.572693,0 +1,user901,-0.3740209,0.1228914,-0.9651223,0.3059378,-0.6637669,0.4791469,0.4495609,0.5169814,0.6384702,0.01196305,0 +1,user902,-0.7888474,0.6945829,0.4608609,-0.8830712,-0.1148976,0.05052722,0.3571489,0.7042284,0.1810657,0.1473935,0 +1,user903,0.7768657,0.08414296,0.8313278,0.4333042,-0.03317266,-0.1159434,0.4016288,-0.6111026,-0.7013554,0.004617579,0 +1,user904,-0.7955335,0.4530218,0.5563913,-0.0546724,-0.8334378,0.8397871,-0.6285087,0.960829,-0.2569094,-0.8006246,0 +1,user905,-0.04044572,-0.4925777,0.3318638,0.4805088,-0.9736288,0.4330208,0.5554515,0.1577022,-0.4189904,0.4597476,1 +1,user906,-0.459359,-0.9042671,0.691081,0.4779228,-0.3554057,0.09117145,-0.780587,-0.9836768,0.07558517,-0.6943416,1 +1,user907,-0.6005523,0.02795921,-0.250052,-0.4244271,-0.433346,-0.4838625,-0.8514454,0.8258313,-0.6545711,0.4611705,1 +1,user908,-0.4245272,0.7809372,-0.4003916,-0.6887504,-0.7209211,0.2388482,-0.2138393,0.6876958,0.04844189,0.1384274,0 +1,user909,0.3152861,-0.2357001,0.6240933,-0.5225666,0.1799724,-0.03596559,0.9453476,-0.02193608,-0.7137905,-0.8511487,1 +1,user910,0.8940019,-0.288972,-0.266971,0.2779932,0.3879065,0.3265549,0.82305,-0.4835659,-0.8502722,0.8604866,0 +1,user911,-0.5561649,0.001882227,-0.8582507,-0.2820826,-0.7665653,-0.1047501,0.7577539,-0.2585099,-0.5936561,0.366622,0 +1,user912,0.01382931,-0.2194624,0.007480319,0.004640242,-0.6416836,0.5593184,0.5837729,0.1480155,0.1253888,-0.1041279,0 +1,user913,0.5569255,-0.8283469,-0.8341276,0.5725962,0.2032925,-0.2404032,0.7906463,0.2866327,0.4725785,-0.6677456,1 +1,user914,0.8726984,-0.8641687,-0.3183947,0.5875581,-0.06737535,-0.9130988,-0.562223,0.80664,0.6032234,0.60796,0 +1,user915,-0.7157629,-0.2117245,-0.7901216,0.4785656,-0.674693,-0.4817417,0.4281754,-0.4793761,0.1106939,-0.7083847,1 +1,user916,-0.4107974,0.07535208,-0.3090412,0.2972013,0.7245334,0.7647959,0.3872577,0.03986057,-0.1461285,-0.3917197,0 +1,user917,0.1220147,-0.09388884,0.7025803,-0.7272928,0.7039393,0.08210806,0.5235875,-0.1531271,0.02662147,0.3798235,0 +1,user918,0.2890397,0.2791599,0.8379365,-0.6744027,-0.2176018,-0.9382332,-0.4390828,0.006796102,-0.4307945,0.2306675,1 +1,user919,0.7468276,0.733188,0.6391214,0.8474959,-0.9043591,0.2970652,0.4101895,0.3774702,-0.4936094,-0.1631175,0 +1,user920,-0.5343038,0.1147808,-0.8760235,0.5837196,0.8557451,0.4919803,0.4651442,0.4699308,0.5288221,0.5152852,1 +1,user921,0.3479921,-0.5455269,-0.797124,0.743333,0.000460261,-0.3618237,0.06786128,-0.4112811,0.3299217,0.3241496,1 +1,user922,0.2587186,-0.7231501,0.7606704,-0.2513162,0.3326098,-0.347005,-0.806219,0.7257503,0.7559576,-0.6084739,1 +1,user923,0.6651093,0.7390585,-0.7326436,-0.994781,0.384227,-0.5285928,0.8653755,-0.786943,-0.1360023,-0.4334995,1 +1,user924,0.6259937,-0.3774676,0.7721558,-0.6608045,-0.01153858,0.01244503,-0.04633624,-0.6253735,0.6546079,-0.9909778,0 +1,user925,0.5466019,-0.1765139,0.6360931,0.7004589,-0.8862939,-0.4518125,0.5420165,-0.7879464,0.5358787,-0.9463831,0 +1,user926,-0.9430949,0.8476627,0.50443,0.07653131,0.6754295,0.8021466,0.6594701,-0.2652853,0.6282605,0.6998423,1 +1,user927,0.1808142,0.06222175,0.09997487,0.8530561,-0.3258507,0.2252278,-0.1551527,0.03427337,-0.135696,-0.1433297,1 +1,user928,0.2351644,0.1613143,0.4545979,-0.5182844,0.7281532,0.2081277,0.3794997,-0.3921677,-0.9747238,0.7301511,1 +1,user929,0.5275277,-0.6929325,-0.02004163,0.03602649,0.9294403,-0.953393,0.1015243,0.6538355,-0.4158595,-0.3467166,1 +1,user930,0.5488648,-0.8910518,0.4441231,0.2788449,-0.1018741,-0.816215,-0.4343715,0.6719302,-0.7951653,-0.8091562,1 +1,user931,-0.4314214,0.9751097,-0.3406549,-0.973511,0.1885412,0.6798284,0.4153821,0.777995,-0.6267257,0.3028441,1 +1,user932,-0.8464932,0.4299589,0.01483605,-0.6580357,-0.7343266,0.5257539,-0.4489148,0.1708168,-0.7773893,0.6652464,1 +1,user933,0.7600174,0.8035311,-0.09501598,0.3957737,0.7832284,0.2343122,0.9227774,0.3761586,0.3859003,0.3382373,0 +1,user934,-0.6545557,0.0592527,-0.509327,0.4597933,-0.8446315,-0.436115,-0.1829891,-0.8331076,-0.3280811,-0.6925384,0 +1,user935,-0.6420267,-0.1170193,-0.4287726,0.2872919,-0.5677644,0.365541,-0.07742352,0.1316458,-0.03429873,0.8646218,0 +1,user936,-0.2804283,-0.6890466,-0.7631521,-0.1237174,0.8095996,-0.332667,0.4782289,-0.4661392,0.9669099,-0.2020151,0 +1,user937,-0.1139147,0.1549856,-0.8182461,-0.06228389,-0.2000371,0.6550565,0.0364239,-0.8167843,0.7475041,-0.38688,0 +1,user938,-0.242579,0.9109399,0.3211754,0.8628648,-0.001110376,0.8816785,0.07113106,-0.0425229,0.3111302,0.3257923,0 +1,user939,0.2950445,-0.9081094,-0.1635437,0.1875322,-0.9113215,0.9061812,-0.7356105,-0.7784435,0.01535183,0.9364122,0 +1,user940,-0.7986286,0.9192855,0.8058472,0.4151496,0.9799352,-0.3809091,-0.01822849,0.1612796,-0.9662865,-0.2380286,1 +1,user941,-0.3485771,-0.3780321,-0.9457956,0.140858,-0.6132039,0.2082334,-0.105819,0.4739112,0.460858,0.1862789,0 +1,user942,0.7388796,0.09377284,-0.0217944,0.9054496,-0.6778868,-0.1985688,-0.9778566,-0.03695337,0.4216957,0.3030342,0 +1,user943,0.2152007,-0.3001769,-0.1866725,-0.5802102,-0.6617484,-0.8215907,-0.4344556,-0.6907049,0.1591024,0.6578435,0 +1,user944,-0.7916516,-0.206379,-0.7799233,-0.2865458,0.5900886,0.9678302,-0.3151727,-0.2394561,-0.06656356,0.5185333,0 +1,user945,0.611578,0.2296041,0.6598109,0.4930077,0.2547378,-0.1116676,-0.5400796,-0.2303134,0.02491914,-0.08900583,1 +1,user946,0.4994378,0.4880986,0.02320593,0.8983554,-0.3364414,-0.3033324,0.9937198,-0.170081,-0.7302038,0.9494588,0 +1,user947,-0.202449,0.8689731,-0.08896444,-0.9893445,0.314622,0.7326261,-0.9279149,0.8004044,0.787308,-0.8731864,0 +1,user948,-0.2664073,-0.8642847,0.3623912,0.7657149,-0.04132284,0.9704405,0.9835079,0.6165595,-0.9484594,-0.7091824,1 +1,user949,-0.2115225,-0.2327415,-0.1388576,-0.7760473,0.4459568,-0.2415657,-0.445363,0.8367151,-0.1609983,0.1801263,1 +1,user950,-0.4556214,0.6021612,-0.449843,0.8581514,0.4102629,0.02969132,0.4822746,0.1778747,-0.7063014,-0.0363039,1 +1,user951,0.1992889,0.2504961,0.4863677,0.3494345,-0.1855778,0.4624208,0.4486521,0.08649037,0.5803627,0.8061029,0 +1,user952,-0.8635304,0.2217316,0.06401849,0.9672857,-0.5535829,0.3966106,0.6224983,-0.574566,-0.8310765,-0.4957242,0 +1,user953,0.8030972,0.879011,-0.6891726,-0.3931647,-0.2571273,0.6826863,0.6760555,-0.09637502,-0.9503438,0.3552222,0 +1,user954,-0.1356018,-0.01044545,0.7537241,0.3546535,-0.8013508,0.933828,0.3140276,0.2995474,-0.5556396,-0.6273966,1 +1,user955,0.7624634,0.844264,-0.1638257,-0.6935188,0.4348785,-0.5909443,-0.423838,-0.1999395,0.8235314,-0.4867019,1 +1,user956,0.3496991,-0.2975028,0.9469204,-0.6927059,-0.1434212,-0.7691262,0.2180721,0.1156786,0.5855349,0.4088392,1 +1,user957,-0.0786967,-0.1627828,0.2581541,-0.5688152,0.8740787,0.7359745,-0.02650234,-0.9657378,-0.9273791,-0.9275543,1 +1,user958,-0.0567224,-0.09351421,0.9361492,-0.8404627,-0.8909722,0.6342834,0.4210093,0.8343339,-0.3121646,0.3699684,0 +1,user959,-0.4151365,0.8638114,0.4015184,-0.2109903,-0.415268,0.4390016,-0.4024282,0.7235109,0.6108111,0.1389902,0 +1,user960,-0.551169,0.1442847,-0.7618876,0.4672113,0.803519,0.7825815,-0.924978,0.6880976,-0.3432386,-0.2742709,1 +1,user961,-0.5078576,0.01543403,0.3802723,0.4383822,0.007153695,0.8180684,0.9866378,0.506264,-0.10733,0.5608122,1 +1,user962,0.1534421,0.8389212,-0.9391365,-0.1845013,0.7732732,0.11883,-0.9870461,0.5015059,0.9840854,-0.5581657,0 +1,user963,-0.3976623,-0.4257564,0.2529485,0.8091756,-0.9308075,0.3083355,-0.3738929,-0.1410855,-0.120628,-0.6090245,0 +1,user964,-0.7478402,-0.1810349,-0.7147437,-0.1658441,-0.2096179,0.05238059,0.9094152,-0.1175774,-0.7214297,-0.1009506,1 +1,user965,0.4988863,-0.1018261,-0.4484635,-0.724708,0.9286417,0.682715,-0.1700352,0.6683983,-0.3439957,-0.2507041,1 +1,user966,-0.039689,0.4572243,0.8241759,0.09646746,-0.4985719,-0.3261235,0.5486836,0.9905603,0.8450733,-0.7444026,1 +1,user967,-0.02826848,0.1299186,-0.4778959,0.7104384,-0.4000184,0.7197136,0.3876441,0.4162833,-0.7545197,0.6970343,0 +1,user968,-0.6150284,-0.9468405,-0.2667096,0.2130081,-0.2713955,0.3377715,0.8663887,0.851614,-0.5964916,0.3624159,0 +1,user969,0.717732,0.3681643,0.1453512,-0.04066771,0.5003177,-0.444445,-0.3801853,-0.05196262,0.1562035,0.5813897,1 +1,user970,-0.733224,0.2218092,0.3585604,-0.1020294,-0.3113399,0.6258949,0.6520336,0.6378399,0.2608321,0.6334465,1 +1,user971,-0.413657,0.972445,-0.4608624,-0.3718423,-0.2914603,0.9568623,-0.1518398,0.01289359,-0.5627781,-0.8756127,0 +1,user972,-0.6308451,0.9901322,0.1995556,-0.8998097,0.8871138,0.7637884,0.5139957,-0.5780515,-0.3829385,-0.2323314,1 +1,user973,-0.9943444,-0.6844179,-0.663234,-0.1965798,0.01077328,-0.572674,0.674177,-0.3991135,-0.3174722,-0.0635193,1 +1,user974,0.8015436,-0.3277319,0.3524652,0.04794746,0.04679139,-0.8647284,0.4137046,0.3221887,0.5963243,0.7822308,1 +1,user975,-0.4224968,-0.2162468,0.4196323,-0.1863555,0.4772024,0.7316187,-0.801177,0.1824924,0.5504979,-0.7137981,0 +1,user976,0.6172335,0.5451862,0.9965769,-0.7035722,-0.7344889,0.3156585,-0.8659026,0.3705732,0.707447,0.8474749,0 +1,user977,0.3009814,-0.8396333,-0.6243289,-0.05369717,0.71035,-0.1680608,0.4074244,-0.8478923,0.8661205,0.7316896,1 +1,user978,0.3750542,-0.3472736,-0.6693321,-0.1757,-0.2081756,0.4642447,-0.7290919,-0.01710315,0.3378059,-0.5869846,0 +1,user979,-0.6491738,0.6809015,0.3589681,-0.9378573,0.2241883,0.286099,-0.8823946,-0.0128673,0.7589876,-0.8617075,1 +1,user980,-0.9105411,-0.07237483,0.2368135,0.1702555,0.1563068,0.5903735,0.9620614,0.9888228,-0.2948778,-0.0881841,0 +1,user981,0.9194328,-0.7451125,-0.1191751,-0.3175486,-0.7979127,-0.5060639,0.7531827,-0.8392285,0.6315045,0.3767115,0 +1,user982,0.5501151,-0.06860243,-0.1546642,0.4115772,-0.9613895,-0.2514803,0.5662575,-0.9263769,0.3393502,0.9443953,0 +1,user983,-0.7740714,-0.8506432,-0.699168,0.1375412,0.6027239,-0.01301588,0.5845597,-0.5857432,-0.1259543,0.4160917,1 +1,user984,0.72253,-0.8661014,0.1916522,0.2892867,-0.05504008,-0.8233776,0.4292382,0.06439648,0.6811607,-0.2680662,1 +1,user985,-0.5854867,0.9209521,-0.4009402,-0.2337693,-0.7627403,-0.3176523,-0.1197149,0.3731705,0.7837107,-0.6830013,1 +1,user986,0.9883919,0.9936208,0.1370063,0.4440224,0.03760236,0.3960398,-0.8392783,0.2143173,-0.3024229,0.9293898,0 +1,user987,0.07222915,-0.1636043,0.1385726,0.5965808,0.8015387,-0.5925038,-0.3526897,-0.8199249,0.2666956,-0.8592271,1 +1,user988,0.3358166,-0.2418307,0.8572139,0.1974155,-0.8886616,-0.5816778,0.8537827,0.4074326,0.8563315,-0.6105556,1 +1,user989,-0.06833046,-0.09989339,0.07315552,0.6035596,0.1466301,0.0303232,0.581731,0.04865112,0.3854124,0.2993582,0 +1,user990,0.6570926,-0.2997928,-0.459909,-0.6144095,-0.6137293,0.8464977,0.2448821,0.903586,-0.1224933,0.2797631,0 +1,user991,0.7846476,0.902454,-0.9046737,-0.3353732,0.9148574,-0.7990962,0.9288047,0.09553025,-0.4869071,0.1151735,1 +1,user992,0.423812,0.9155406,-0.5465722,0.04194178,-0.8462162,-0.1516084,0.5683688,-0.4450849,-0.7219176,-0.1398296,1 +1,user993,-0.1894653,-0.4608716,-0.3990455,0.2010892,-0.8404562,-0.03467225,0.257836,0.4050919,-0.1384078,0.7215974,0 +1,user994,-0.6130147,-0.5233024,0.3482748,-0.5261977,0.9840499,0.5092392,-0.4450882,0.9544447,0.392465,0.5061491,0 +1,user995,0.6759718,-0.2654942,-0.261316,0.8760977,-0.05583411,0.9007722,0.477784,0.4373377,-0.4433472,0.7592198,0 +1,user996,-0.690579,0.4373022,0.1524909,0.4763812,-0.9118145,-0.3519572,-0.9121992,0.07349021,0.5175965,-0.5291067,0 +1,user997,0.3472963,0.9339219,0.1724507,0.5702698,-0.514522,-0.8168843,-0.8964045,0.945005,0.2375383,0.7617464,1 +1,user998,-0.3522967,0.8644244,0.2607882,0.5865361,0.5441475,0.6204858,-0.1345719,-0.1463789,-0.197867,0.4562541,0 +1,user999,-0.3056074,0.4904617,0.8857813,-0.3106107,-0.18321,0.9858142,0.9541895,-0.07489578,0.9211048,0.8333093,0 +1,user1000,0.06502829,0.3020861,-0.682198,-0.4703979,0.9857957,-0.2613293,-0.2765899,-0.1069576,-0.6062582,0.3431361,0 +1,user1001,-0.08552068,0.0862336,-0.3806514,-0.5154933,-0.7671924,0.2463806,-0.4825383,-0.508539,-0.9370349,0.08970062,0 +1,user1002,0.2807356,0.4629067,-0.575081,0.317547,0.5253298,0.9426766,-0.1976503,0.9379978,-0.6416733,0.9576966,0 +0,user1003,0.4341832,0.2922183,0.5173576,-0.3702076,0.8729095,-0.4975409,-0.7625941,0.3149909,0.01080328,-0.8891953,0 +0,user1004,-0.07986509,0.4018156,-0.04388542,0.2879268,0.2435809,0.6737067,-0.8083613,0.09234749,-0.2545071,-0.9738187,0 +0,user1005,0.08227918,-0.8648252,0.7773841,-0.6345055,-0.4278788,-0.9220518,-0.7839456,0.2601865,0.954651,0.7399274,0 +0,user1006,-0.9883136,-0.9240284,-0.0630101,0.4434369,0.3501119,-0.7659222,-0.5637711,-0.5025167,-0.4386988,-0.6029935,0 +0,user1007,-0.4626315,-0.05299816,-0.04730851,0.5843547,0.509092,-0.01063488,-0.6742638,-0.5370793,-0.5470601,0.8736562,0 +0,user1008,-0.6167394,-0.7044586,-0.8469448,0.3117973,-0.7175289,-0.09011264,0.6234788,0.4122942,0.8207715,0.471617,0 +0,user1009,0.3867406,-0.2713021,0.2676578,-0.7322631,-0.8580637,0.6983226,-0.292863,0.4803802,0.8991072,-0.189978,0 +0,user1010,-0.1118053,-0.3720967,-0.6883404,0.6464974,-0.2667197,-0.7245359,-0.5566585,0.4500533,-0.7880726,-0.9880513,0 +0,user1011,-0.5272804,0.2231666,0.3898688,-0.5179472,0.438778,-0.4997391,0.5855402,0.401117,-0.4741063,-0.6165671,1 +0,user1012,0.3061734,-0.01641454,-0.8515174,-0.04981173,-0.6559764,-0.8077414,-0.5396803,0.6411517,0.5306117,-0.8132665,0 +0,user1013,-0.5616902,0.5593009,0.1569954,0.05807454,-0.2281092,0.02398382,-0.990401,0.5236764,0.5512777,0.956344,0 +0,user1014,-0.3013519,0.3725234,0.6907008,0.619594,0.04150182,0.487245,0.1700999,0.8153737,0.3999394,0.7995247,0 +0,user1015,0.02870339,0.117484,0.3401348,-0.7605251,0.2889835,-0.631119,0.8895579,-0.2944519,0.2117724,-0.08133269,1 +0,user1016,-0.1471769,0.480253,0.7560552,0.8243052,0.00915049,0.7063315,-0.1101159,-0.1031531,0.3349883,-0.7266573,0 +0,user1017,-0.3129599,0.3661442,-0.1722929,0.06361635,-0.9208958,-0.1167152,0.3308216,0.02969099,-0.9024836,0.7289145,0 +0,user1018,-0.8990675,0.9538797,-0.5212925,0.8360557,0.09052218,-0.2236228,-0.4631318,-0.1143768,-0.521532,0.05944025,1 +0,user1019,-0.8113602,-0.7615777,0.6132691,0.02172066,0.1204889,-0.8753462,-0.2563331,-0.6957205,0.1913199,-0.3372128,1 +0,user1020,0.6187096,-0.7337492,0.9008626,-0.332824,0.2257343,0.9136079,-0.08744743,-0.9216579,0.4829289,0.02827266,0 +0,user1021,0.7580252,-0.3459131,0.01879848,-0.7783538,0.4767928,-0.3771251,0.7817502,-0.2107908,0.3559748,-0.6607966,1 +0,user1022,0.9732874,-0.8591237,0.7085954,0.6863474,0.03534629,-0.6744424,-0.3275284,0.3998098,0.7044128,0.7779607,1 +0,user1023,0.04252157,-0.8182086,-0.6457096,0.7091178,0.3795181,-0.2380005,-0.5190786,-0.3667428,0.7610113,0.888443,1 +0,user1024,-0.4314401,0.1932153,0.619753,0.4227354,0.6363367,0.5882027,0.03958622,-0.8056989,-0.7824331,-0.9391992,1 +0,user1025,-0.6397273,-0.3824261,0.05687028,-0.8398502,0.01939615,0.8347968,0.2273834,0.3542545,0.09687776,0.2841098,0 +0,user1026,-0.2815066,-0.08370277,0.09297443,0.5852154,-0.676316,-0.3372283,0.9587054,-0.9294051,-0.6823359,0.6476628,1 +0,user1027,-0.1220191,-0.3694825,-0.2277561,-0.1008834,0.7245222,-0.7637545,0.127387,0.2677913,0.7351634,-0.4683059,1 +0,user1028,0.707569,-0.4485042,-0.770679,0.7304196,0.5048741,-0.9820875,0.3309789,0.2992595,-0.665584,0.0458562,0 +0,user1029,0.3661967,-0.2192784,-0.6462374,0.1717515,0.8678315,-0.7167425,-0.1758665,-0.07578399,0.1197971,0.1039169,1 +0,user1030,0.5723735,-0.8790208,-0.3419747,0.588506,-0.4586878,-0.7779403,0.08157653,-0.8071045,0.6562682,-0.6349966,0 +0,user1031,-0.2274027,0.8535819,-0.452877,-0.7399783,0.4906698,-0.2434168,-0.945611,-0.8076982,-0.2718422,-0.6110077,0 +0,user1032,-0.719324,0.8669552,-0.02688884,0.6562582,-0.8993608,0.5296381,0.3415952,0.415677,0.1827622,-0.8063825,0 +0,user1,0.6803754,-0.9170587,-0.207233,-0.7962762,0.8797078,0.1600331,0.486172,-0.7701991,0.40677,0.0806263,0 +0,user2,-0.2112341,0.7136786,0.7761897,-0.784765,0.5327111,-0.865918,-0.09517225,0.4532904,0.7758448,-0.5650532,1 +0,user3,0.5661984,-0.6016046,0.5577553,0.03334887,0.6771007,-0.1020502,-0.9816581,0.7031257,0.7474771,-0.8898751,1 +0,user4,0.5968801,-0.2897011,-0.9858211,0.9535864,-0.9216152,0.02126774,-0.1625436,-0.8815696,0.08211747,0.3534081,0 +0,user5,0.8232947,-0.3084135,0.2365242,0.9735567,0.03594764,0.3121172,0.9779206,-0.7007437,-0.5988425,0.7338892,0 +0,user6,-0.6048973,0.3476536,-0.04058368,0.2318033,0.3397171,0.4176953,0.8614932,-0.2967283,-0.8569799,0.1563771,0 +0,user7,-0.3295545,0.7732886,-0.5762567,-0.1174395,-0.8418461,0.14928,-0.4186488,-0.8171929,-0.07867056,-0.620229,0 +0,user8,0.5364592,0.5515529,0.467357,0.5008509,0.6771718,0.428888,-0.7818895,-0.9397281,0.6708866,0.6148423,0 +0,user9,-0.4444506,0.6418815,-0.1542364,-0.4036515,-0.6863929,-0.1351666,0.970202,0.3681662,-0.1604439,-0.6350624,1 +0,user10,0.1079399,0.1385511,-0.08673854,-0.1876371,0.2032164,-0.5911911,-0.8480415,0.1234173,-0.9827996,0.4741438,0 +0,user11,-0.0452059,-0.5663492,-0.6519337,-0.322268,0.4543092,-0.80811,0.8598654,0.657211,0.1215299,-0.451977,0 +0,user12,0.2577418,-0.2390026,-0.1566818,0.1810069,-0.7527846,-0.1039135,0.953497,0.263368,-0.8507715,0.9740726,0 +0,user13,-0.2704311,-0.9334348,0.2221209,-0.4580202,-0.5841137,-0.8471561,0.5524263,0.7642086,-0.1869387,-0.9559491,1 +0,user14,0.02680182,0.7578574,-0.8703259,0.3195951,-0.7569592,-0.3577094,0.5861964,0.9406209,0.1577761,0.9861861,0 +0,user15,0.9044595,-0.517485,0.06506755,0.166942,0.9043772,-0.9376502,0.8085998,0.1720038,-0.1430939,-0.6355447,1 +0,user16,0.8323901,0.6969836,0.5174526,0.002881795,-0.7327637,-0.7534716,0.3346954,-0.5626065,-0.177963,-0.5556766,0 +0,user17,0.2714235,-0.3672456,-0.5510418,-0.8464799,0.08230376,-0.3867757,-0.121717,-0.9990787,0.1685743,-0.6624105,0 +0,user18,0.4345939,0.01091322,-0.2964537,0.9188272,-0.8757026,-0.2442153,-0.05405024,-0.009703513,0.221854,-0.03409319,1 +0,user19,-0.7167949,-0.2380975,0.6292679,0.5585957,0.7830851,-0.8883631,0.7757779,0.2209573,0.5120352,0.59766,1 +0,user20,0.2139378,0.4536859,0.904069,-0.3840925,-0.4035037,0.1806027,0.3411108,-0.2502235,0.2160612,0.7532788,0 +0,user21,-0.9673989,-0.776015,-0.3751726,-0.344859,-0.5527871,-0.4037405,-0.3726096,-0.823573,0.5011755,0.5214331,0 +0,user22,-0.5142265,-0.8621846,-0.7721123,0.8992383,-0.687887,-0.3829023,0.0001422348,-0.8248689,-0.6627313,-0.4932564,0 +0,user23,-0.7255368,0.7300104,-0.3250185,0.01211033,0.9081354,0.3553996,-0.1008384,-0.3892036,-0.2140539,0.04209468,0 +0,user24,0.6083535,0.4822437,-0.7303912,0.2385965,-0.8980671,0.9243831,0.2230517,0.1580526,-0.4701273,0.221743,0 +0,user25,-0.6866418,-0.07110234,-0.8232226,0.9704827,0.3824537,-0.1840883,0.5240514,-0.8113087,0.5929028,-0.247682,1 +0,user26,-0.1981112,0.1604702,-0.7750984,0.2021192,-0.3508538,-0.4957494,-0.5004914,0.519569,-0.1722448,-0.5771232,0 +0,user27,-0.7404191,0.2129091,0.9144766,-0.3914959,-0.2706789,-0.8008082,0.6046109,0.6727768,-0.5589966,0.713149,1 +0,user28,-0.7823824,-0.4147637,0.9504743,-0.2690521,-0.5213919,-0.1397811,-0.2015295,0.9631303,-0.2756577,0.6100759,1 +0,user29,0.997849,0.7825195,0.03807413,0.7752595,-0.5193691,0.08236991,-0.3680984,-0.4992064,-0.1906117,-0.2504204,0 +0,user30,-0.5634862,0.9593262,0.09109492,-0.1234783,0.8140523,-0.5478411,-0.4105027,-0.6529009,-0.7501124,0.8351239,1 +0,user31,0.02586479,-0.6372327,0.5449427,-0.5329596,0.7105877,-0.8405277,-0.5980976,-0.1835063,0.4380646,-0.9106403,1 +0,user32,0.6782245,0.8654608,0.8308411,0.9789833,-0.6396613,-0.757597,-0.8819263,-0.2694055,-0.7838417,0.8302059,0 +0,user33,0.2252797,0.6730048,-0.1327154,0.09175675,0.3467635,-0.4137591,0.4943251,0.8003894,-0.9742676,-0.7299294,0 +0,user34,-0.4079368,-0.2388373,0.1026979,0.5003893,0.3876884,0.05742214,-0.5797557,-0.4803806,0.1855417,-0.8005154,0 +0,user35,0.2751045,-0.4242403,0.84502,0.9325697,-0.5612765,0.2636708,-0.04446993,-0.150975,0.2982757,0.183614,1 +0,user36,0.04857438,-0.6354087,-0.8961912,0.06531344,-0.6172889,0.8983581,0.4722457,-0.9003542,-0.57311,-0.9960402,0 +0,user37,-0.01283403,-0.8911837,-0.9378858,-0.2678074,-0.2725945,-0.5248825,-0.7182625,0.2228912,0.3285618,0.3558616,0 +0,user38,0.94555,-0.6509517,-0.7312368,-0.1848698,-0.4031226,-0.5870492,0.5368813,0.03183207,-0.7803948,0.563385,0 +0,user39,-0.4149664,0.9161442,0.5711658,-0.4338356,-0.9401171,0.327246,0.6903561,-0.8400823,-0.9022234,0.6188021,1 +0,user40,0.5427154,0.7506978,-0.09212218,0.3285411,0.04101259,0.3399509,-0.7480604,-0.4089426,-0.8318821,0.7207993,0 +0,user41,0.05348996,0.4875994,0.1820247,0.6274931,0.8000938,-0.1782404,0.6888398,-0.8447506,-0.7631944,0.03752872,0 +0,user42,0.5398277,-0.650205,0.9192321,0.2438964,0.5141921,0.519136,0.5502215,0.8171287,0.2193065,-0.8331749,1 +0,user43,-0.1995428,-0.4883048,0.751196,-0.490452,0.288228,-0.7639626,-0.7945635,0.8544253,-0.6826536,0.6948719,1 +0,user44,0.7830589,0.5541646,-0.5958544,-0.8305271,-0.7840199,-0.02539646,0.2412661,0.919458,0.0498669,0.08157958,0 +0,user45,-0.4333705,-0.8923477,-0.9510938,-0.4365086,0.757233,-0.8385734,0.1364179,0.7577496,-0.6229173,-0.8469888,1 +0,user46,-0.2950833,-0.005789782,-0.1837364,0.6764901,0.1926052,-0.7016128,-0.9859637,0.0264291,0.1742525,-0.9406728,0 +0,user47,0.615449,0.2511482,0.9215982,0.1723547,-0.5167836,0.2211319,-0.4240385,-0.6431485,0.8719039,0.525903,0 +0,user48,0.8380529,-0.2595932,-0.5021356,-0.2829884,-0.1604633,-0.2253491,-0.985299,0.7586708,0.545657,-0.5093993,0 +0,user49,-0.8604894,-0.9948766,0.5198099,0.5953172,0.3169027,0.05417191,-0.04001393,-0.9832744,-0.6038935,0.02523397,1 +0,user50,0.8986542,-0.9869493,0.5508661,-0.2690497,-0.7336985,0.3327688,-0.6482606,0.5778087,0.3839391,0.123563,0 +0,user51,0.0519907,-0.8059073,-0.5980666,0.332919,0.436033,0.9552536,0.3558117,-0.4915526,-0.2382818,-0.7561204,0 +0,user52,-0.8278883,-0.7708915,-0.8553627,-0.7495418,0.7641156,0.6504314,0.5873765,-0.8068474,0.8972819,-0.4533329,0 +0,user53,-0.6155723,-0.8491339,0.7787538,-0.3698113,-0.4215855,0.9498665,0.3518816,0.7529398,0.7212078,0.6303066,0 +0,user54,0.3264539,0.9241031,0.07691493,-0.6549706,0.3441685,0.3106533,-0.7450267,0.1192438,0.5476643,0.2859742,0 +0,user55,0.7804652,0.7113522,-0.5857539,0.4890548,0.8660484,0.5748145,-0.1895719,0.3512052,-0.5728454,0.7684101,1 +0,user56,-0.3022141,0.07976374,0.9555312,-0.3993286,0.9608682,-0.2342218,-0.124067,0.9416312,0.3141106,-0.6173754,1 +0,user57,-0.8716574,0.08457332,0.3018165,0.5471486,0.9933146,0.8149039,-0.2455181,-0.3611872,-0.6245805,0.708851,1 +0,user58,-0.9599539,-0.07573876,-0.6712773,-0.9024411,-0.4046305,0.7740063,-0.5849609,0.02398196,-0.131842,0.4815591,0 +0,user59,-0.08459653,0.665,0.9060055,0.3316193,-0.5605237,0.6259971,0.6744035,0.9047615,-0.9615472,0.9927005,1 +0,user60,-0.8738083,-0.1329072,-0.6601093,0.322408,-0.5260544,-0.1027262,0.3863835,0.1396064,0.1848078,-0.5415694,0 +0,user61,-0.5234401,-0.1164125,0.4198177,-0.02591939,-0.5905782,-0.7738348,0.00453637,0.371081,0.1180456,0.3166829,1 +0,user62,0.9412683,-0.9722327,0.4509481,0.7986597,-0.849936,0.7854693,-0.9236941,-0.2787448,0.4765174,-0.9179398,0 +0,user63,0.8044161,-0.2674464,-0.8292682,0.3013913,-0.1657157,0.1396769,0.5044572,0.8702009,0.400966,-0.7113635,0 +0,user64,0.7018396,-0.4434077,-0.7128977,-0.9341626,0.7561853,-0.187594,-0.5011386,0.1714705,0.143778,0.5867536,0 +0,user65,-0.4666685,-0.21107,-0.4463539,0.299049,0.5377523,-0.1571085,-0.5034498,0.2408747,-0.3379408,-0.7184553,0 +0,user66,0.07952068,0.3083133,-0.9842483,0.233961,0.2730077,-0.5966523,-0.5400127,-0.2807741,-0.3007582,0.4722504,0 +0,user67,-0.249586,-0.07881639,-0.6090889,0.1311508,-0.8611036,-0.2892359,0.9711071,0.2711163,0.570668,0.5907134,0 +0,user68,0.5204975,-0.1022538,-0.3842397,-0.9687584,-0.7348422,0.318009,-0.2217123,-0.5362341,0.9906209,0.6374064,0 +0,user69,0.02507073,0.6573615,-0.715485,-0.9509087,0.8698851,-0.1837016,0.9968685,0.7510579,-0.08115308,0.03563536,1 +0,user70,0.3354475,-0.1626722,0.9620769,0.6973152,-0.8012207,-0.9619899,0.6614632,0.431034,0.6684446,0.2095155,1 +0,user71,0.06321287,-0.351556,0.5236381,0.3597827,0.3061704,-0.3420401,0.03022724,0.05482321,-0.8412612,0.3582056,1 +0,user72,-0.9214393,0.1449609,0.4665397,0.6765843,0.6699789,0.638058,0.6857083,0.9063073,0.1556525,-0.9268359,1 +0,user73,-0.1247248,0.1871228,0.881309,-0.05878846,0.7129715,0.5571461,0.2116847,0.2481626,-0.1122489,0.3763406,1 +0,user74,0.8636701,0.1601392,0.2748342,0.8693308,-0.4056016,-0.1060027,0.2356638,-0.09075145,-0.5239148,0.05307749,0 +0,user75,0.8616196,-0.3008745,0.8706853,0.8460572,0.885959,-0.3873384,-0.07302559,0.8257653,-0.7944806,0.1547437,1 +0,user76,0.4419047,0.2947751,0.9302152,0.504703,0.4702044,0.7185728,-0.6518973,0.00591221,0.2648338,0.5293518,0 +0,user77,-0.4314132,-0.8456505,-0.9089022,0.5458208,0.7870037,0.1923845,0.2497001,0.9356777,0.6503377,0.1124046,1 +0,user78,0.4770686,0.9502737,0.7922835,0.01841183,-0.6308247,0.8337935,0.5029359,-0.8173832,-0.9225767,-0.3193534,1 +0,user79,0.2799576,-0.9648181,-0.5719203,-0.7782855,-0.6902589,-0.5067763,-0.6371964,-0.2354169,-0.1895092,-0.9800475,0 +0,user80,-0.2919026,-0.8405271,0.6109077,0.141138,0.1039063,-0.7534436,-0.7903138,0.9524032,-0.9535558,-0.8623614,1 +0,user81,0.3757228,0.9633243,0.3431496,0.7493622,-0.3645232,0.1665623,0.8546753,0.7604255,0.4613624,0.8042096,1 +0,user82,-0.6680517,-0.7707254,-0.1699869,0.5546336,0.7457742,-0.5515227,0.7186154,0.2730304,0.5722089,-0.7361679,0 +0,user83,-0.1197909,-0.6114186,0.755545,0.3915963,-0.1319781,0.8969878,0.7970626,-0.8544442,0.9437261,-0.3156943,1 +0,user84,0.7601505,-0.8858096,0.1219034,-0.6204492,0.2138913,0.1164288,0.2065569,0.5133653,0.1825702,0.4345162,1 +0,user85,0.6584022,-0.8466223,0.906928,0.8996629,0.08994263,0.7591306,0.9735887,-0.6077258,0.1198732,0.5498063,1 +0,user86,-0.3393257,-0.9000665,-0.8302089,-0.1193489,-0.2659296,0.4718022,-0.3925092,0.496761,-0.6291193,-0.5472842,0 +0,user87,-0.5420637,0.1939541,0.0774346,-0.0197778,0.1747596,0.882207,-0.9175101,0.4549965,-0.5033192,0.8171408,0 +0,user88,0.7867448,0.237951,0.2087445,0.4468115,0.08325726,0.5740345,-0.2719294,0.03108695,0.4952927,0.2586573,0 +0,user89,-0.2992796,0.02419476,-0.5014862,-0.02179007,0.3294399,0.2458085,0.02252982,-0.4792571,0.2390387,0.9342749,1 +0,user90,0.3733398,-0.1410459,-0.01655992,-0.6881585,0.6142358,0.5082041,0.7568934,0.359758,-0.4648664,0.8098413,1 +0,user91,0.9129365,-0.8949562,0.5486352,-0.2307805,0.5572028,-0.5286916,-0.8855459,-0.8293067,-0.3198995,0.7170879,0 +0,user92,0.1772803,0.9077822,0.9183315,0.9522905,0.7388617,0.4719737,-0.9729338,0.891824,-0.6429158,0.2509578,1 +0,user93,0.3146081,-0.1132786,-0.5656118,-0.8894989,0.7642998,0.2936734,0.8331994,-0.9189868,-0.988349,0.8919015,0 +0,user94,0.7173527,-0.1624026,0.719367,-0.9293891,-0.6085129,0.6109852,0.6189113,-0.9591058,-0.9189335,-0.9942757,1 +0,user95,-0.1208802,-0.5356255,-0.7945662,-0.9818721,0.495047,-0.7156203,-0.4740724,0.06329444,0.5008623,-0.1622886,0 +0,user96,0.8479396,0.6756514,-0.0119657,0.4095501,0.3020522,-0.8634351,-0.6702505,0.3218879,-0.3262898,-0.8265538,0 +0,user97,-0.2031267,-0.8540894,0.7351187,0.3045719,0.6644948,-0.9856671,-0.9211014,-0.2398799,-0.2196917,0.4779747,1 +0,user98,0.6295338,0.3855581,-0.4036551,0.1492787,0.6339434,-0.004856235,-0.5029653,-0.6655893,0.07153024,-0.5715752,0 +0,user99,0.3684371,-0.4266024,0.6037946,0.4407917,0.56721,0.4545739,0.1080372,0.7856538,-0.3356689,0.8108526,1 +0,user100,0.8219441,0.8032722,-0.9803663,0.3536632,0.5343799,-0.1693687,-0.9242329,-0.488822,0.6991552,-0.4863899,0 +0,user101,-0.03501869,-0.7771141,-0.4415782,-0.1534061,0.8327228,0.03315388,-0.8415021,0.7654447,-0.2600252,0.6379403,1 +0,user102,-0.5683501,0.2218416,0.1274328,-0.1994255,-0.1266196,-0.8874663,-0.8617355,-0.159523,-0.1769301,0.1690582,0 +0,user103,0.9005047,-0.05176694,0.4861734,0.03024749,0.2043589,-0.5313107,0.7614754,-0.5825147,-0.1451923,-0.4132258,0 +0,user104,0.8402565,0.4100087,-0.5602692,0.7878054,0.5456942,-0.4097,0.3701826,0.01360728,0.6277259,0.0142809,0 +0,user105,-0.70468,-0.6180191,-0.597733,-0.3300947,0.4677789,0.006531011,0.3739283,0.7497255,0.2991552,-0.7778643,1 +0,user106,0.7621243,0.6473586,0.3568586,-0.1236953,0.09031784,0.0813509,-0.3115502,-0.7567494,0.06032711,0.7415178,0 +0,user107,0.2821612,-0.2952162,-0.630054,0.2925084,0.01589862,-0.6911272,0.7182853,-0.9804805,-0.1074403,-0.4563673,1 +0,user108,-0.1360932,-0.4636697,-0.5066352,-0.7842739,0.2547825,-0.8010845,-0.3763717,0.6854032,-0.05050715,0.3345404,1 +0,user109,0.239193,0.5976322,0.1491421,0.8947165,0.4594932,-0.08485561,-0.8086143,-0.5741326,0.1377504,-0.5778355,0 +0,user110,-0.4378812,-0.2600343,-0.2019743,0.5142229,0.3256397,-0.1979035,-0.9189111,-0.2158975,0.7030505,-0.4364148,0 +0,user111,0.5720042,-0.3041968,-0.8957276,0.3568641,-0.6413111,-0.5545282,-0.1666855,0.6378064,-0.004062989,0.472179,0 +0,user112,-0.3850843,0.5609565,-0.5077082,0.6440787,-0.90503,-0.9182933,-0.953939,-0.8137072,-0.4008872,-0.7736259,0 +0,user113,-0.1059328,-0.03075969,0.6280388,0.06885651,0.07141391,0.2505738,0.7997043,-0.942867,0.2752595,-0.1725827,0 +0,user114,-0.5477867,0.08438457,0.8598174,-0.2515396,0.2267108,-0.6575404,-0.3696229,0.7833622,-0.0603369,-0.8435153,1 +0,user115,-0.6249338,0.6751469,0.6141952,-0.9763705,0.3088614,0.1981355,0.2526179,0.6996582,0.781683,0.6608903,0 +0,user116,-0.4475307,0.122618,0.5349668,-0.03148054,-0.8386435,0.0097044,0.773293,-0.5505928,-0.6048673,-0.6227764,1 +0,user117,0.1128875,0.1843181,-0.9703915,0.6291115,0.9607811,0.8142618,0.2378679,0.2801232,0.3105438,-0.3907995,0 +0,user118,-0.1669974,-0.1308989,-0.3083702,0.003851678,-0.5163791,0.08034255,0.3351078,0.1546547,-0.7216362,0.4780312,1 +0,user119,-0.6607858,-0.639431,-0.2562886,-0.584669,0.2446138,-0.4162611,-0.4986364,0.4804941,0.8904254,0.6358809,0 +0,user120,0.8136079,-0.7914872,-0.4718776,-0.3926786,0.290221,0.06007033,-0.7396023,0.8008661,-0.4504175,-0.4565246,1 +0,user121,-0.7936576,0.7280552,0.6750699,0.3156931,-0.9021432,-0.4114534,0.09200127,-0.4855873,-0.1865026,0.2878725,0 +0,user122,-0.7478493,-0.5343872,-0.7076534,0.1845505,-0.1981834,0.05504729,-0.3841822,0.6511874,-0.4294741,0.3529688,1 +0,user123,-0.009111867,-0.8837049,-0.5535461,-0.4403881,0.0290827,-0.467956,-0.7125361,0.6926901,-0.0933333,0.7944333,0 +0,user124,0.5209505,-0.3852233,-0.8905419,0.4261943,0.8621566,0.8822201,-0.07479938,-0.4045741,-0.1748516,0.179774,0 +0,user125,0.9695033,0.3032102,-0.9882864,0.2551614,0.1933037,-0.3339675,-0.7652709,0.6920816,-0.3484076,0.3586931,0 +0,user126,0.870008,-0.4193304,-0.3481124,-0.4222601,-0.4758703,-0.1835763,-0.1866085,-0.2440155,-0.592471,-0.3678553,0 +0,user127,0.36889,-0.709572,0.0974924,-0.1642556,0.1642088,-0.981215,0.2549502,0.9173138,0.4988586,0.3532202,0 +0,user128,-0.2336233,0.4491208,0.7468323,-0.4402667,-0.1422015,-0.3196346,-0.6863723,-0.5477983,0.4319007,-0.1633321,1 +0,user129,0.4995418,0.9662277,0.2482325,0.7270186,-0.8419269,0.8115674,0.3104262,0.09039524,0.4790592,0.0605695,0 +0,user130,-0.2626729,-0.1361744,-0.298713,-0.7234638,-0.2685812,0.4733589,-0.6370126,0.7029676,-0.8368103,0.1640728,1 +0,user131,-0.4116793,0.2523929,0.766466,0.9133965,-0.6078215,0.5109967,-0.6106052,-0.03662036,0.1310559,0.350278,0 +0,user132,-0.5354769,-0.8108863,0.8066543,-0.4263875,0.9907959,-0.1552787,0.4689241,-0.1441601,-0.780966,-0.3014902,1 +0,user133,0.168977,-0.9143327,0.8287198,0.07711063,0.6047992,0.5858926,-0.4987482,-0.4565554,-0.01374036,-0.666869,0 +0,user134,-0.5111745,-0.799374,0.2526394,-0.05635604,0.5965373,0.9796861,-0.8491298,0.3808649,0.9858636,0.9370521,0 +0,user135,-0.6952204,0.5991224,-0.7536149,-0.6385821,0.5364901,0.4350213,-0.1608932,0.8694472,0.84676,0.7127907,0 +0,user136,0.464297,-0.5323518,-0.7690132,0.7470159,0.07257811,-0.4075764,0.8751801,-0.7068298,-0.7145852,-0.4447333,0 +0,user137,-0.7490502,0.8479845,-0.390502,0.8199486,-0.3131448,0.06103697,-0.1606799,0.6241155,0.04619075,0.67857,0 +0,user138,0.5869408,-0.6960938,-0.3836689,0.6539263,-0.4476113,0.7438941,-0.4426079,0.8889667,-0.2606803,-0.7435766,0 +0,user139,-0.6717961,0.003978481,-0.2756485,0.962742,-0.6726394,-0.2086609,-0.5011916,0.9785734,0.2349077,0.8898071,0 +0,user140,0.4901428,0.4456167,0.7586401,0.7146651,-0.8536516,0.9761814,0.03070578,-0.9500171,-0.8160588,-0.8992656,0 +0,user141,-0.8509404,0.0438719,0.4143568,0.1681492,0.8780285,-0.4540094,-0.361519,-0.3269308,-0.5576298,-0.1799913,1 +0,user142,0.9002081,0.6997817,-0.1713761,0.3196061,-0.3139505,0.2368109,0.3321229,0.6163798,-0.7691553,0.3619861,0 +0,user143,-0.8949415,0.006573282,-0.7490681,0.3587438,-0.7586816,-0.942112,0.07676678,-0.7637243,-0.216946,-0.6728915,0 +0,user144,0.04312676,-0.9868878,0.04239553,-0.7629943,-0.05055762,0.7965643,-0.5618147,-0.2697978,0.7176297,0.647426,0 +0,user145,-0.6475787,-0.2158337,-0.3115586,-0.9319335,0.9127603,0.5792705,0.9625001,0.399742,0.1705078,0.5184708,1 +0,user146,-0.5198753,-0.3182798,0.8651271,0.3823733,0.5501797,0.2560236,-0.6706153,0.9359339,-0.435263,0.9879988,0 +0,user147,0.5955961,0.1357302,-0.4226377,0.2055252,0.1107989,-0.1937313,-0.7885216,0.1796093,-0.8872377,-0.9753504,0 +0,user148,0.4653088,0.9684843,-0.2819501,0.697178,0.8735414,0.3935323,0.200368,-0.3201347,-0.5189485,-0.8723287,0 +0,user149,0.3131273,0.5508213,-0.4432431,-0.6137751,-0.9661993,-0.6636339,0.6644925,0.09058857,-0.1568992,0.46603,0 +0,user150,0.9348103,0.4962992,0.3210737,0.6208562,-0.6445873,0.3900077,-0.287158,-0.3398966,-0.9968123,0.6605306,0 +0,user151,0.2789167,-0.8230028,0.2461722,-0.6955007,0.1637624,-0.5463973,0.4607657,-0.5192686,0.030634,-0.3288532,1 +0,user152,0.5194697,0.2788765,-0.7681732,0.7019181,-0.8683426,-0.07508724,-0.2435062,0.6050012,0.6565981,-0.2460975,0 +0,user153,-0.813039,0.961912,0.6134202,-0.1945933,0.1572293,-0.554945,0.3286597,-0.6887092,-0.4262864,0.01349937,1 +0,user154,-0.7301952,-0.7067077,0.6926261,-0.1358887,-0.8071549,-0.01435335,0.7482296,-0.8265785,0.9373007,-0.53442,0 +0,user155,0.04042014,0.8936532,-0.6587151,0.1281124,0.993814,-0.1928672,0.6816944,-0.7995728,-0.5182535,0.9336764,0 +0,user156,-0.8435357,0.2651222,0.6251338,-0.939432,-0.649467,0.1110875,0.5633888,-0.9966276,0.225306,-0.6278075,0 +0,user157,-0.8601872,-0.1260381,-0.6554863,0.4418511,-0.2830251,0.8020703,-0.4383789,-0.07059398,-0.6551703,0.09772471,0 +0,user158,-0.5906898,-0.8159188,0.4387773,0.9638568,0.1580228,-0.1740822,-0.06335546,-0.882259,0.9806051,0.2868966,0 +0,user159,-0.07715905,-0.285757,0.3719661,-0.3796987,0.2083315,0.7914529,0.8770165,-0.5444259,-0.3427933,0.2088604,0 +0,user160,0.6393546,-0.1598104,0.5927462,0.1688697,-0.124952,0.6136377,0.8720473,-0.9801987,0.8238889,-0.8417058,0 +0,user161,0.1466373,0.04790686,-0.8599357,-0.7596071,0.8894416,-0.7007233,0.2996319,0.8207086,-0.8562052,-0.5490306,0 +0,user162,0.5111617,0.9666359,0.1384321,-0.4663022,0.60051,0.3024496,-0.7335887,0.4189537,0.7882627,-0.4408617,0 +0,user163,-0.8961224,0.02930326,0.3994005,0.7424822,-0.1341561,-0.541641,0.3409715,-0.1243589,-0.9570771,-0.143196,1 +0,user164,-0.6843857,0.1335741,0.9687841,0.3175036,0.4942408,0.8851692,0.8008837,-0.6358467,0.1300545,-0.2158995,1 +0,user165,0.9999871,-0.8327381,-0.6089286,0.4773417,0.1970474,0.2821357,-0.5827185,-0.2001813,0.7741263,-0.5038095,0 +0,user166,-0.5913428,-0.3715744,0.6457856,-0.8960999,-0.597666,0.8933804,-0.8199218,-0.2549117,0.8896829,-0.4304053,0 +0,user167,0.7799113,0.6012223,-0.8002292,0.06451944,-0.4331811,-0.5224072,0.6760638,-0.3426766,0.4154693,0.3393672,0 +0,user168,-0.749063,-0.9847536,0.0005694446,0.2972904,0.8839025,-0.6568273,0.2566015,-0.5760658,-0.1796829,-0.8252395,0 +0,user169,0.995598,-0.06766816,-0.7378834,0.7578264,-0.04527726,0.6372745,-0.2625297,-0.365945,-0.3709974,-0.1739819,0 +0,user170,-0.8918848,-0.3947992,-0.07587765,0.0272614,-0.1058204,0.2689319,-0.8251277,-0.3641032,-0.3496231,0.2291743,0 +0,user171,0.7410797,0.4608632,-0.2407904,0.01195549,-0.9697491,-0.680646,-0.7126927,-0.5260829,0.004258221,-0.7245051,0 +0,user172,-0.8553424,0.9762037,0.6764734,-0.0740244,-0.1672488,-0.816735,0.3759514,0.3071242,0.07137284,0.6460268,1 +0,user173,-0.9916768,-0.6950175,0.7527463,-0.6531325,0.5802291,-0.4942572,0.5069952,-0.7477234,-0.1187784,-0.4088396,1 +0,user174,0.8461383,-0.5325636,0.01014152,-0.6293007,-0.7284308,-0.6227579,0.3640741,-0.2898072,0.7873122,-0.3973966,0 +0,user175,0.1877844,0.9893159,-0.2811311,0.1629814,0.7821936,0.9798294,0.8141367,-0.9626737,-0.2109975,0.2934528,0 +0,user176,-0.6392555,0.08914874,-0.5588123,-0.585066,0.4929894,-0.9149867,0.4694953,0.6520186,-0.9482707,-0.8903688,1 +0,user177,-0.673737,0.1491567,-0.1247314,0.7530725,0.821749,0.6332656,0.6934588,-0.3538733,-0.6479508,-0.4093977,1 +0,user178,-0.2166195,0.1250462,0.2962313,-0.6314934,-0.1070075,-0.2139019,-0.974385,0.2169357,-0.09823514,0.3181024,0 +0,user179,0.8260534,0.05763308,0.1592376,-0.8878881,0.3665308,0.4785456,-0.3301368,-0.6681161,-0.4672191,-0.7626975,0 +0,user180,0.6393903,-0.300022,0.4320256,-0.8607025,0.8555496,0.9696318,0.3579512,0.7367153,0.19515,-0.9433678,0 +0,user181,-0.2818093,-0.3786546,-0.382695,0.9893627,0.2484052,-0.8238942,-0.261543,0.8770391,-0.09504742,-0.02136707,1 +0,user182,0.10497,0.2346303,-0.5945902,-0.5833887,-0.4697067,0.9321483,-0.8693711,-0.1873847,0.5634149,-0.09155073,0 +0,user183,0.15886,0.9788545,0.6638524,0.8412155,0.9872071,-0.1054555,-0.885555,0.3417165,-0.1482519,-0.1894653,0 +0,user184,-0.09484831,-0.4167426,-0.7692748,-0.2052306,-0.5943654,-0.3788392,-0.9328832,-0.8116701,0.4786662,0.9921323,0 +0,user185,0.3747749,0.5279225,-0.9019641,0.2807226,-0.2768616,-0.08220505,0.8788585,-0.01396323,0.5007156,0.3740293,0 +0,user186,-0.8007199,0.8725077,-0.9948628,-0.03067212,0.9810211,0.7016774,0.7961394,0.5421437,0.3334946,-0.2557888,0 +0,user187,0.06161598,0.8483796,0.855859,-0.1446626,-0.2438324,0.7322483,0.6305056,-0.8082976,-0.2960278,-0.6356752,0 +0,user188,0.5145877,-0.5981156,-0.5574504,-0.2774263,0.4401132,-0.2801347,-0.5595204,0.9154428,0.8455452,-0.528246,0 +0,user189,-0.3914097,-0.9434111,0.4439145,-0.06681536,0.1390439,-0.4724048,-0.2672161,0.6598847,0.3140997,-0.9688922,0 +0,user190,0.9844569,-0.4373774,0.2278251,0.4756388,0.9644991,0.5237012,0.507522,-0.3527235,0.3611789,0.5731852,0 +0,user191,0.1539422,0.242074,-0.9647041,0.8914434,-0.6848388,-0.666497,-0.6874731,0.9352441,0.6694341,-0.3699518,0 +0,user192,0.7552276,0.1044957,0.5839788,0.1735776,0.02848551,-0.1731282,-0.9675842,0.4805933,0.4578946,-0.5179227,0 +0,user193,0.4956186,-0.4707415,-0.6337428,-0.9906634,0.5650091,-0.1738491,0.7739333,-0.9337698,0.1494415,-0.8676765,0 +0,user194,0.2578199,-0.7286227,0.4346964,0.6339256,0.1810052,-0.208138,0.6534983,-0.1891148,0.712357,0.4868522,1 +0,user195,-0.9291582,-0.7619301,0.5527629,-0.5089189,-0.4772737,-0.287959,0.8332996,0.8447466,-0.412051,0.2661777,1 +0,user196,0.4956057,-0.3034796,-0.2426714,0.4866783,-0.2379435,-0.8917134,-0.8087852,-0.1339511,-0.07643216,-0.371486,0 +0,user197,0.6664771,-0.1001971,0.08048197,0.7378256,0.5833392,-0.3147576,0.8335766,0.5559735,0.6020399,-0.9435531,1 +0,user198,0.8507531,0.8392922,0.7525338,0.5556006,0.08954524,0.1896339,0.5093634,-0.49793,-0.9965817,-0.3944551,0 +0,user199,0.7465427,-0.2882332,0.7578981,-0.2160313,-0.354041,-0.5485408,0.4478163,0.2899831,0.7438849,-0.1967256,1 +0,user200,0.6620751,0.8321347,0.3425986,0.495652,-0.4619381,-0.6774831,-0.4289531,-0.8099715,-0.7689575,-0.1175349,1 +0,user201,0.9588683,-0.555507,-0.3233439,-0.417138,0.9837248,-0.5414343,0.6842357,0.1379668,-0.3462048,0.8347192,1 +0,user202,0.4876224,-0.8273701,-0.4828923,0.7959242,-0.3237901,-0.2291867,0.7351236,0.7639002,-0.2518569,0.07876933,1 +0,user203,0.8067327,0.8083385,0.01907202,-0.5783724,0.3708131,-0.4942181,0.9469983,0.4971527,0.3024154,-0.4715082,1 +0,user204,0.9671915,-0.2505246,-0.5705976,-0.07027055,0.5639539,-0.03569146,0.1912309,0.3902434,0.5350167,-0.5741204,1 +0,user205,0.3337607,-0.3599337,0.5272492,-0.8333766,-0.05222085,0.1480553,0.09919767,-0.525907,-0.4645447,0.6813727,0 +0,user206,-0.005482962,0.7976544,0.737941,0.584609,0.1530067,-0.5143887,0.761135,0.534479,-0.9085821,0.8219446,1 +0,user207,-0.672064,0.8386242,-0.1294099,0.3446634,0.05694328,0.04932185,-0.3392738,0.04226201,0.5867461,-0.4644892,1 +0,user208,0.6600237,0.789223,-0.5974822,0.919696,-0.2304719,-0.218679,-0.2073436,0.1202198,-0.1124954,-0.728025,0 +0,user209,0.7778975,-0.07729942,0.03417222,0.9531155,-0.9540008,0.2717095,0.78675,-0.2485853,-0.006817258,0.140047,0 +0,user210,-0.8460106,-0.1037427,-0.9701724,0.4567754,-0.5765259,-0.4721325,0.3305894,0.3741459,-0.8804731,-0.2271867,1 +0,user211,0.299414,-0.510799,0.8345434,-0.9410066,-0.3749222,-0.2490472,-0.8493923,-0.1430649,-0.9173455,-0.6713928,0 +0,user212,-0.5039118,0.544046,0.6514772,0.9424783,0.2944044,0.4478153,-0.474793,-0.3715462,0.8981353,-0.8813201,1 +0,user213,0.2589594,-0.8691125,-0.5647626,0.8733866,-0.04623264,-0.5399842,0.4612183,-0.8132388,0.6829418,0.6812626,0 +0,user214,-0.541726,-0.5319445,0.4983958,0.9002089,-0.3877152,0.6454973,-0.7349473,-0.8013484,-0.06559734,0.139142,0 +0,user215,0.4012399,-0.8726966,0.8822024,-0.2627523,0.700039,-0.9310239,-0.4076762,-0.1832163,0.3768015,-0.8891878,1 +0,user216,-0.3662657,0.6588101,-0.4667267,0.1541092,0.6769058,0.3778107,0.3400767,0.172798,0.1836574,0.05529189,1 +0,user217,-0.3424459,-0.6594369,0.503533,-0.1304632,-0.406694,0.3471746,-0.9388079,0.7407953,-0.7321027,0.8833532,1 +0,user218,-0.5371441,0.975683,0.7380614,0.5925851,-0.5437934,0.8012244,-0.7771707,0.008486058,-0.9192263,-0.524863,1 +0,user219,-0.8516781,-0.9393055,-0.02417706,0.8766829,0.117019,-0.902324,0.7805563,0.08824075,0.02920264,0.5270459,1 +0,user220,0.2661444,-0.602848,-0.05255248,0.8027215,0.7323499,0.8747698,-0.206024,0.40068,0.581997,0.914461,1 +0,user221,-0.5526872,-0.4616944,-0.0341135,0.06822388,-0.5792943,0.3249256,0.7303514,0.6557625,0.4419525,-0.9516778,0 +0,user222,0.3022642,0.3027685,0.01111881,0.7681263,0.4321802,-0.568821,-0.9069168,0.02348481,-0.3013632,-0.8429059,1 +0,user223,0.02137194,0.5016477,-0.4685737,-0.02370098,-0.2391646,-0.2983584,-0.1736082,-0.1187267,0.03989159,-0.6034618,0 +0,user224,0.9429314,0.06756409,0.3321437,0.07756044,0.9857149,-0.8489236,0.5042847,0.7219927,-0.4086059,-0.8193543,1 +0,user225,-0.439916,0.5741457,-0.5541848,0.4020519,-0.3868146,0.223041,0.7465815,0.83437,-0.5890062,0.6439463,1 +0,user226,0.09221376,0.7397176,-0.9158107,0.4673801,0.2835617,0.4136826,-0.3403086,-0.2739801,0.6278406,0.6627159,0 +0,user227,0.4385372,0.7640844,-0.9105277,-0.4357613,-0.2522286,-0.740637,0.6954994,-0.4119584,0.5149619,-0.1908403,1 +0,user228,-0.7734389,-0.5260514,0.5262972,0.1398776,-0.8034754,0.9082834,0.5801581,0.3903435,-0.9869663,0.7003933,1 +0,user229,-0.05703314,0.5790098,0.836723,0.02298071,-0.626893,-0.3966835,-0.8309452,0.2280899,0.6312589,-0.7317392,1 +0,user230,0.1850798,-0.5241488,0.8473704,0.3482074,0.3937304,-0.2891778,0.1433157,0.8780247,0.2588468,0.6124341,0 +0,user231,0.8886362,-0.6939166,-0.1311042,-0.3644704,-0.2654135,-0.7691997,-0.8487949,0.580372,-0.7559238,-0.4171417,0 +0,user232,-0.09816488,-0.9764973,-0.4866208,0.6058427,-0.6431682,0.06188223,0.8532905,-0.6339433,-0.714946,-0.89702,1 +0,user233,-0.3272977,-0.3515189,-0.6355219,0.1441316,-0.9300597,0.4816355,-0.1215607,0.6419248,-0.9930101,-0.3087966,0 +0,user234,0.6953689,-0.8855782,0.8879678,0.05715721,-0.8946004,-0.2634177,-0.9017967,0.07752467,0.5464916,0.1113502,1 +0,user235,-0.1309734,-0.2270218,-0.05721842,-0.4644279,0.9207857,-0.9738092,0.04452135,0.7563001,0.8200708,-0.4711403,1 +0,user236,-0.9935371,0.2885475,0.8917272,0.310755,0.01771943,-0.3703091,0.977637,-0.8839821,-0.4575547,-0.6274238,1 +0,user237,-0.3101141,0.9120763,0.6259088,-0.3582338,0.2584063,0.2221936,0.8593383,-0.3879963,0.6379095,-0.06670522,1 +0,user238,0.1969626,-0.3883976,0.8133717,0.8802355,-0.02227106,0.07551262,0.7052475,-0.2014379,0.4068168,0.06437045,0 +0,user239,0.6664866,0.07777046,-0.7057549,0.230451,0.7872476,0.4110119,-0.2297066,0.2362376,0.4299498,-0.3554488,0 +0,user240,-0.5322166,-0.1652232,-0.339919,-0.4051183,0.3044055,-0.5060969,0.6460883,0.3634183,-0.3689078,-0.9266582,1 +0,user241,0.350952,0.5078596,0.8431993,0.3370109,0.401203,0.6033801,0.03583689,-0.827292,0.5263438,0.8371838,0 +0,user242,-0.03409944,0.5669714,-0.8712115,0.2894444,-0.5876747,-0.8380354,-0.07909895,-0.9068273,0.5126044,-0.02684158,0 +0,user243,-0.03612835,-0.6211772,-0.6884418,-0.46264,-0.40119,0.9417184,-0.8287047,0.9918721,-0.4707725,-0.8079783,1 +0,user244,-0.3900886,0.6387471,-0.7215633,0.2103975,-0.6450296,-0.9366041,-0.5029448,-0.6405309,0.2092856,0.5184464,0 +0,user245,0.4241745,-0.9649731,0.6271842,0.1896534,0.02461015,0.8074619,0.1859537,-0.7081757,-0.552993,-0.8876996,0 +0,user246,-0.6348884,-0.4938738,-0.8062394,0.2746076,-0.701151,-0.9893056,-0.2363809,-0.1913442,0.906029,-0.6971661,0 +0,user247,0.2436456,0.2975572,-0.18829,-0.6354933,-0.9681238,0.4412066,0.8371319,0.5322671,-0.607057,-0.4262617,0 +0,user248,-0.9182714,-0.62441,0.1307172,-0.9408098,0.6179161,0.1546365,0.2471458,-0.9673804,-0.2850957,0.9956535,0 +0,user249,-0.1720325,-0.5181908,0.9318219,-0.1328073,-0.2449444,0.8119188,-0.01355152,0.8171418,0.9868027,-0.2220291,0 +0,user250,0.3919676,0.3582517,0.787533,-0.7588103,0.1488952,0.5388826,0.6176882,-0.3794922,0.4221456,-0.8992159,0 +0,user251,0.347873,-0.227258,-0.9218353,0.8619116,0.350266,0.02940629,-0.9588782,0.4332996,-0.7030986,0.9101145,0 +0,user252,0.2752803,0.0201148,-0.1022916,0.9354166,0.1757613,0.1368444,-0.2832001,0.4729044,0.4287552,-0.1737069,0 +0,user253,-0.3057683,-0.3389799,-0.2013482,-0.990684,-0.4189246,0.9700616,0.7107714,0.6439926,-0.8792176,-0.7421217,0 +0,user254,-0.6307551,-0.7256103,-0.3904089,-0.1617893,-0.8888986,0.7310479,-0.1324864,-0.6854271,0.3367929,-0.6933473,0 +0,user255,0.2182117,-0.9123211,-0.7701479,0.01297704,0.1614761,0.2879209,-0.7789155,0.1948971,-0.9798507,0.006938749,0 +0,user256,0.2543158,-0.7648341,0.244467,0.411368,0.1942608,0.1931026,0.4573529,0.4783627,-0.4682239,0.9018246,0 +0,user257,0.4614587,-0.9858927,-0.3062197,-0.6944092,0.3946631,0.1447305,0.527205,0.04059283,-0.03536645,0.9693687,1 +0,user258,-0.3432511,0.8517633,-0.6806756,0.5772158,0.9092475,0.5472839,0.9165839,0.7829386,0.5351112,0.8160984,0 +0,user259,0.4808769,-0.2908855,-0.2292358,-0.4487545,0.3907854,0.101386,0.03751108,-0.1312938,-0.4551902,0.6022179,0 +0,user260,-0.5955745,0.5931171,-0.4694966,0.3285715,0.76777,0.748047,0.6962598,-0.7313173,-0.4041076,-0.7623705,1 +0,user261,0.8418287,-0.6723855,-0.8333052,-0.07457679,0.3029778,-0.7418939,0.05989966,0.6609633,-0.2060419,0.4285325,0 +0,user262,0.3695131,0.01519786,0.63966,0.1867751,-0.8746282,0.3321864,0.1887161,-0.5509218,-0.2111139,-0.8149238,0 +0,user263,0.3062607,0.6166199,0.04388255,-0.06558581,-0.8753982,-0.1900708,0.5495502,-0.3652606,-0.1190536,-0.6593905,1 +0,user264,-0.485469,-0.02390434,-0.4688271,-0.9304452,0.3729181,0.7397416,0.938339,0.3028881,-0.199052,-0.8802641,0 +0,user265,0.06488195,0.1296197,0.5276278,-0.7560677,-0.7692286,-0.9312314,0.2869195,0.5266029,-0.6646224,0.2964264,1 +0,user266,-0.8247127,-0.6104019,0.9866641,0.4699863,-0.9546125,-0.16388,-0.4059284,-0.6089606,-0.2989828,-0.1305308,0 +0,user267,-0.4790061,-0.7353569,-0.5770999,0.3803099,-0.6093625,-0.6305675,0.9159759,0.418906,0.3433933,-0.5076879,0 +0,user268,0.7547679,0.04169594,0.1535365,-0.1143015,0.4891777,0.2909622,0.1462578,-0.8613935,0.9732871,-0.7702788,0 +0,user269,0.3722499,0.001200411,0.8000358,0.3502218,0.0231164,0.9116326,-0.7006809,0.1896015,-0.892166,0.9338396,1 +0,user270,-0.8125195,0.3424136,-0.2828548,-0.3892391,-0.8221149,0.7804443,-0.3137307,-0.3448564,-0.2266569,0.1368633,0 +0,user271,-0.7774487,0.8764728,0.8136175,0.4805802,-0.2064168,0.7848653,-0.2076539,0.5020249,-0.3956207,-0.6969371,1 +0,user272,-0.2767982,-0.49094,0.6432351,-0.3127673,-0.5756806,0.5150127,0.335156,0.3623095,0.6341778,0.7710234,0 +0,user273,0.153381,-0.09061499,-0.1540664,0.9002053,-0.4097896,0.9424089,0.6071704,-0.2516837,-0.7140525,-0.8899783,0 +0,user274,0.1864229,-0.7447044,-0.8748243,-0.9820598,0.3923932,0.7265836,-0.03635858,0.493897,0.1336068,-0.5049154,0 +0,user275,0.3331132,-0.8521929,0.9216718,0.8976303,-0.2207101,0.5784086,0.8322111,0.7217786,-0.1565366,0.2894698,0 +0,user276,-0.4224444,-0.05558811,-0.5268821,0.08985868,0.6148206,0.7498708,-0.2068759,0.04014062,-0.2670455,-0.7776779,1 +0,user277,0.5515345,-0.2385783,-0.6810638,0.2925478,0.6912422,0.7372781,0.7272606,-0.6974473,0.03963584,-0.2020815,0 +0,user278,-0.4232412,0.4453643,-0.2666181,-0.737863,-0.188834,0.01961527,0.669343,0.2540457,0.2364064,0.863208,0 +0,user279,-0.3407158,0.3200019,0.6038351,0.1490489,0.2327367,-0.09549266,-0.9597301,0.07276024,0.4478589,-0.7820244,0 +0,user280,-0.620498,0.2432309,-0.7492418,-0.8402595,-0.5537023,0.5491969,-0.286291,-0.8803054,0.0264385,0.5758894,0 +0,user281,0.9687264,-0.196384,-0.4790851,-0.4966733,0.9600612,-0.4415021,0.2870313,0.8745535,-0.341448,0.9639922,0 +0,user282,-0.9928429,-0.9072561,0.6819998,0.01096051,-0.4169973,0.9339136,-0.9186084,-0.4939401,0.7447602,-0.8719098,0 +0,user283,0.6547823,-0.7366543,0.1484666,-0.9048429,0.622059,-0.3139587,0.4305089,0.5925989,-0.5448063,-0.5978176,1 +0,user284,-0.3370418,0.4646362,0.3195667,-0.4873573,-0.4588634,-0.4714405,-0.002197332,0.5185462,-0.2206656,-0.7781296,0 +0,user285,-0.6235979,-0.6328664,-0.7084091,0.8491712,-0.305896,0.6649615,-0.05109482,-0.1793672,0.08155316,-0.5652571,0 +0,user286,-0.127006,-0.6489754,0.3783187,0.1081342,-0.2164649,0.9739622,0.6515934,-0.212504,-0.524657,0.4091212,0 +0,user287,0.9172739,0.6998021,-0.4359663,0.9240107,0.7353974,0.7216621,-0.5448444,-0.003091185,0.3111105,-0.8763049,0 +0,user288,0.8378608,-0.618759,-0.01462877,-0.845238,-0.9112329,-0.190308,-0.5238899,0.8612256,-0.9538133,-0.5958884,1 +0,user289,0.5297429,-0.797212,0.6976432,-0.3146501,-0.3072175,0.5212461,0.5681774,-0.4295654,-0.9895458,0.2252196,0 +0,user290,0.3981508,-0.5910835,0.3347978,-0.5247438,0.1261827,-0.1769518,0.4926667,0.865615,0.8559203,0.725913,0 +0,user291,-0.7577137,0.9743581,0.5158746,0.4833335,0.8565372,-0.442261,-0.8276301,-0.8700916,-0.3579209,-0.3582589,1 +0,user292,0.3715716,-0.4695975,0.864338,0.6107732,0.9957604,0.7793522,-0.371923,-0.7686021,-0.1955877,-0.3462479,1 +0,user293,-0.2323361,0.4241144,-0.02554222,0.6620313,0.2515545,-0.8447655,-0.3186172,-0.6853068,-0.3551936,0.9109892,1 +0,user294,0.548547,0.590978,-0.4402428,-0.5822523,0.981139,0.3676682,0.7219201,-0.2353522,0.5230256,-0.01764941,0 +0,user295,0.8861025,0.5064982,-0.6044891,0.680328,0.3686785,0.5190938,-0.433584,0.534286,0.6053603,-0.226512,0 +0,user296,0.8325458,-0.4462659,-0.4979145,0.9059637,0.482326,-0.7759969,0.9683023,0.841296,-0.01981596,0.2074156,1 +0,user297,0.7238342,0.980576,-0.4535787,0.887734,-0.9734735,-0.7962117,-0.6840083,0.1556872,-0.7759572,0.8518198,1 +0,user298,-0.5929036,0.7711413,-0.181589,0.06063783,0.7593161,0.8885263,-0.5176081,-0.04680796,-0.05124643,0.2658001,0 +0,user299,0.5873137,0.59543,0.6556221,-0.2083378,-0.02849635,0.5149654,0.1145601,0.9799026,-0.04652886,0.4371368,1 +0,user300,0.0960841,-0.01822357,-0.6535429,0.2379559,0.04964285,-0.8845791,-0.3846892,-0.6547113,-0.6681232,0.7856594,0 +0,user301,-0.4054231,0.1135549,0.5355562,0.6713987,0.9372011,0.6689706,0.1686612,0.6083357,0.7220967,-0.5973366,1 +0,user302,0.8098649,0.4719028,0.4692396,-0.7277576,0.7650869,0.2998307,0.9069061,0.4819274,0.5578505,0.7401997,0 +0,user303,0.8192859,0.4908364,0.9896922,0.9251886,0.4739623,0.6304336,0.9504667,0.7075982,0.9660546,0.5566828,0 +0,user304,0.7479579,-0.9770601,-0.6185102,0.571604,-0.4725884,0.6113796,-0.2241684,-0.643348,-0.9919558,-0.4873149,0 +0,user305,-0.003712147,0.7271984,0.5944152,-0.7098174,0.1574801,0.02641429,-0.1294524,-0.02417561,-0.3085427,-0.7647157,0 +0,user306,0.1523991,0.6386436,0.911364,0.8228189,-0.7467478,0.2088423,0.7826779,0.4293768,-0.190482,-0.1538474,1 +0,user307,-0.6744865,-0.03264824,-0.1453923,-0.3385373,-0.8577679,0.3612504,0.5689556,0.3967926,-0.2590013,-0.2649928,0 +0,user308,-0.4521777,-0.5113799,0.9133514,0.5827304,-0.1512778,-0.2363076,-0.4021919,0.2783771,0.7310931,0.03320281,1 +0,user309,0.7291579,0.08400794,-0.3552541,-0.9150441,0.06441817,-0.7715425,0.4520209,-0.3165775,-0.9540756,-0.2906393,1 +0,user310,-0.01520236,-0.7126464,-0.5415573,0.8105116,0.3749688,-0.7342422,0.6092255,-0.5304472,-0.8111424,-0.04701714,0 +0,user311,-0.0726757,0.731851,-0.8358904,0.7424709,0.2950199,-0.6871107,0.3115172,0.3980717,-0.2424684,-0.3909078,1 +0,user312,0.6978843,0.887624,0.1656608,-0.4117174,0.02447935,-0.2130446,-0.2609478,-0.442024,-0.2955236,-0.3266471,0 +0,user313,-0.008045207,-0.6199025,-0.8595575,-0.1785279,0.9579715,-0.8003286,0.6906171,-0.02438731,0.9336178,0.08107301,0 +0,user314,-0.4178934,0.9951968,0.3125762,0.837628,-0.08292108,-0.001069356,-0.257974,-0.009329362,0.2127253,0.01127461,1 +0,user315,-0.6391575,0.3522601,-0.5147725,0.1009253,0.5656159,0.3155149,0.7368548,-0.9234779,0.4838107,-0.1047767,0 +0,user316,0.3683569,-0.2527689,-0.5679666,-0.3293567,-0.3479245,0.8646329,-0.3604777,0.7962455,0.01517091,0.5158159,0 +0,user317,0.4551006,-0.6537786,-0.3091051,-0.05423784,0.700614,-0.02710716,-0.6063805,0.7781666,0.6880683,-0.5796042,0 +0,user318,-0.7218836,0.0520622,0.04926113,0.024936,0.3010133,0.03717702,-0.8079895,0.07343096,-0.2050787,0.01891837,0 +0,user319,0.2062177,0.1284721,0.4174046,-0.1745947,-0.2591574,-0.3256751,0.1156324,0.6574711,0.06135762,0.9199274,0 +0,user320,-0.01515656,-0.4509907,-0.6114619,0.6311121,-0.6066034,-0.5058611,0.9617968,-0.6513988,0.6985226,0.6456154,0 +0,user321,0.6762672,0.4609787,-0.615941,0.5001922,-0.572804,0.8602252,0.6846772,-0.06095404,-0.3491584,-0.2551687,0 +0,user322,0.448504,0.1028302,-0.06672074,-0.6912612,-0.4026202,0.2320639,0.2880023,0.7873795,0.7034367,-0.4383315,1 +0,user323,-0.643585,0.07941183,-0.7471239,0.2418853,-0.6108431,-0.7265089,-0.4101262,-0.4200008,-0.4970651,-0.7006325,0 +0,user324,-0.5560689,-0.1149069,0.3585167,0.1622235,0.6787506,-0.9845403,-0.63394,0.2537391,0.295648,-0.3441795,1 +0,user325,-0.002949035,-0.3061919,0.4930364,-0.2735135,-0.4214812,-0.4002678,0.009922423,-0.4479728,0.2264623,0.5440191,0 +0,user326,-0.7574824,-0.41409,-0.3516131,-0.07778678,0.7578355,0.7925849,0.1562898,-0.8857148,-0.8917049,0.07285549,1 +0,user327,-0.7235231,0.4388272,0.8606023,0.06818719,0.1610765,-0.7605372,-0.6656377,0.09503518,-0.724168,0.8632361,1 +0,user328,-0.2791148,-0.3256158,-0.9605423,-0.3857795,-0.3949547,-0.1964796,0.3259141,0.7077144,0.4505051,0.3958389,0 +0,user329,-0.350386,-0.6429487,0.4667979,0.982851,0.5171515,0.6811112,0.6386817,0.06747723,0.0570487,-0.6613444,1 +0,user330,0.8637906,0.03425727,0.5162243,0.8598494,-0.8674198,0.7544282,0.4489224,0.07493776,0.2293032,0.3003729,0 +0,user331,0.8169693,0.6561606,-0.6140852,0.8521764,0.6546881,-0.08105868,0.9412249,-0.9469969,0.7823819,0.1814983,0 +0,user332,0.2441909,0.4706061,0.002354051,0.6542497,0.4543527,0.3500818,-0.1926571,-0.3241871,-0.2208546,-0.2586809,1 +0,user333,0.6736555,-0.4938399,-0.01453611,-0.8679082,0.8976671,0.05425886,0.3558285,-0.4431348,-0.2128464,0.04057262,0 +0,user334,0.6362552,0.146997,-0.6243929,0.777365,0.1286504,-0.4506251,0.8916916,0.7606013,0.7484365,-0.2618188,0 +0,user335,-0.00785116,0.493546,0.3838439,0.2258538,0.9817642,-0.03853861,0.5831745,0.03246486,-0.2128104,0.2540042,0 +0,user336,-0.3300566,-0.7666415,-0.4201209,-0.5777257,0.05514712,-0.9193268,-0.7736239,0.5326896,0.4786109,0.275857,0 +0,user337,-0.2113457,-0.2143594,-0.7130289,0.6001839,0.3819026,0.7582172,0.6743695,0.1899781,-0.4420455,0.5843338,0 +0,user338,0.3176623,-0.5391023,-0.7615485,0.8873164,-0.8760037,-0.6772882,0.1521301,-0.5707425,0.5281883,0.9890114,0 +0,user339,0.2177657,-0.2780214,-0.5067695,-0.9949953,0.9038694,-0.1556345,-0.1758158,-0.1889333,0.2097041,-0.6909402,0 +0,user340,-0.4821878,0.8696485,-0.06828298,0.6851398,-0.5536793,0.9866747,0.1263904,0.8734005,-0.3961211,-0.7063055,1 +0,user341,-0.69754,-0.2517486,-0.3031057,0.697828,0.4989651,-0.4115304,-0.2386444,-0.1011897,0.7170458,-0.05800575,1 +0,user342,-0.85491,-0.5461704,-0.3426599,0.7474756,0.1988893,0.1572548,-0.8642987,-0.7908616,0.9672357,-0.08184806,1 +0,user343,-0.7843034,0.7572725,-0.9026222,-0.7265776,0.4708001,-0.2263698,0.8654426,-0.5686235,0.3083553,-0.03295269,0 +0,user344,0.2944148,0.1283489,-0.1626632,-0.4806999,0.4569366,-0.2118591,-0.5480273,0.874423,0.6506636,-0.9769327,1 +0,user345,-0.2728034,-0.5509736,0.9699163,0.5851036,-0.8840318,-0.8438145,-0.1222726,0.1998091,0.179961,0.9294265,1 +0,user346,-0.423461,0.1095327,-0.4173947,0.3743477,0.03641602,-0.9108549,0.6022975,-0.4921013,-0.2078339,0.8622706,1 +0,user347,-0.3372284,0.8755801,0.2693702,0.1899433,-0.8909879,-0.3472262,0.09149503,0.6706685,-0.3341655,0.5388831,1 +0,user348,-0.8177028,-0.2047522,-0.3391888,-0.4691342,0.8165822,0.1290783,0.2713469,-0.02202431,-0.1319706,-0.6501777,1 +0,user349,-0.1453446,-0.8384052,0.6318664,-0.6007163,-0.6625707,0.1263221,0.7943079,0.5813296,0.5870873,-0.118811,1 +0,user350,0.8689893,0.00405216,-0.3132251,-0.9846514,-0.1501453,0.3270988,-0.7928725,0.3281396,0.7271921,0.4588106,0 +0,user351,0.1671406,0.3442571,0.04934929,-0.8380221,-0.7900212,0.6232172,0.2331437,0.3265769,-0.433448,0.9954377,0 +0,user352,-0.4690774,0.6225736,-0.9840746,0.8994759,-0.2353747,-0.01345273,0.4789851,-0.4796244,-0.7620711,0.6260203,0 +0,user353,0.3174933,-0.8931177,0.6200541,-0.6759127,0.4472345,-0.4408373,0.4951298,0.1155191,0.4306288,-0.9795209,1 +0,user354,0.5235556,-0.576331,0.3022254,0.4038631,-0.4008643,0.8967083,0.8230175,0.9065761,0.06948682,-0.7051948,1 +0,user355,-0.02514627,-0.4923333,0.3744421,0.06169945,-0.5566241,0.002006967,0.8450451,0.7741147,0.5335769,-0.7181592,1 +0,user356,-0.6854558,-0.1993095,0.1130906,0.05057383,-0.9742466,0.1588949,-0.4949478,0.6675463,-0.3429089,0.5644982,0 +0,user357,0.7660732,0.00957894,0.9506123,-0.6739237,-0.6430288,0.6892931,-0.02069264,-0.9791387,0.177782,0.3676607,0 +0,user358,0.2513306,0.946494,0.2350444,-0.8701134,0.6044524,0.2414698,-0.8205926,-0.1308501,0.8094089,-0.8549231,0 +0,user359,0.03542944,0.4750746,0.1525483,0.6647943,-0.3692014,0.9624153,0.8309663,0.3752608,-0.8924038,-0.03966293,1 +0,user360,-0.5843128,0.3666302,0.4174102,-0.6910726,0.8741227,0.3704043,-0.3820109,0.08833853,-0.7651693,0.7063163,1 +0,user361,0.1151212,-0.01924876,-0.2487313,0.989736,0.7370326,-0.004101983,0.6283297,0.9440877,0.0387121,0.4454498,1 +0,user362,-0.1476012,0.1312352,0.5384631,0.5169707,-0.7145132,-0.1186433,0.7721912,0.4282639,0.8899781,-0.8581646,1 +0,user363,0.6598781,-0.1627637,-0.5802358,0.9631771,0.3284754,-0.2795139,0.425332,0.7641514,0.01397605,-0.5523647,1 +0,user364,-0.2112232,0.4869113,0.7367326,-0.8781722,0.6346997,-0.9498431,-0.01584178,-0.4990471,0.8258657,-0.5139776,1 +0,user365,-0.511346,-0.7217678,0.9140702,0.2943357,0.4141372,0.4307316,0.6638828,0.1888652,0.6384146,-0.1199835,0 +0,user366,-0.3479731,-0.6692177,0.8036081,0.1890309,0.3102396,0.6819475,0.008506465,-0.2033837,0.8011656,0.7016395,0 +0,user367,0.4587201,0.7202698,-0.6833883,-0.4558979,-0.3101532,-0.86917,0.2105343,-0.9663575,0.3044766,0.7618794,0 +0,user368,0.2773083,0.06387282,-0.7989587,-0.1054805,-0.2039602,0.1889488,0.3382523,-0.6211567,-0.8036308,-0.5356497,0 +0,user369,0.9696893,-0.20832,-0.9579404,0.0763473,0.4342359,-0.9953407,-0.8393634,0.2258737,0.3293539,0.6906509,0 +0,user370,-0.3235141,-0.5577516,-0.1901578,-0.4508932,-0.4062838,-0.02480443,-0.9652815,-0.1552908,-0.4858193,-0.9290608,0 +0,user371,0.7951205,-0.06647864,0.1327583,-0.4203407,0.2423605,0.1756235,-0.5353573,-0.7477562,-0.1997519,-0.2419552,0 +0,user372,-0.7278508,0.5399314,-0.2610461,-0.2258247,-0.066799,-0.4068712,-0.07800779,-0.875316,0.04639968,-0.3673548,0 +0,user373,-0.1784241,-0.103922,0.4671824,-0.7034176,0.7926055,-0.8675496,-0.8295802,0.05384759,-0.5185836,-0.01090889,1 +0,user374,-0.9891829,-0.3092061,0.2301361,-0.1469183,-0.2868394,0.9492537,-0.6699146,-0.3163796,-0.8913966,0.7250921,0 +0,user375,0.566564,-0.3317196,0.5762907,0.2934754,-0.6098624,0.3812698,0.3739649,0.999107,-0.3029367,-0.3442876,1 +0,user376,0.5487725,0.3451044,0.4370987,0.881686,0.9085737,-0.7113641,0.04814724,-0.7463433,0.6613774,-0.08148234,0 +0,user377,-0.4126439,0.8003265,0.8127414,-0.7725705,0.7495766,-0.9616012,0.9323828,0.191519,-0.0992305,0.5873627,1 +0,user378,-0.7706644,-0.4561396,-0.1543391,-0.5165813,-0.5008504,-0.9659564,-0.53454,0.6697755,0.3628977,-0.8054044,1 +0,user379,0.7310696,-0.8596478,-0.9020901,-0.5874483,0.7251559,0.4177142,-0.6805059,0.2316323,-0.4705932,0.26834,0 +0,user380,0.4420116,0.9619214,0.4446078,-0.3732868,-0.9129941,0.1647208,0.7266907,-0.2271513,-0.5121432,-0.5314483,1 +0,user381,-0.9016751,0.5479126,0.5324358,-0.5012328,0.3490043,0.3611424,-0.3274126,-0.002084902,0.09008984,0.6534062,1 +0,user382,-0.1017898,0.4846093,0.1472592,-0.4254704,0.9351347,0.04093146,0.5526378,-0.4417907,0.09595879,0.2637777,1 +0,user383,0.9729342,0.584495,0.4605332,-0.4738109,-0.1483687,-0.8487319,0.2056758,0.2932243,-0.2742143,-0.905428,1 +0,user384,0.4158182,0.6547949,0.1524899,-0.1771454,-0.2037611,0.9203051,-0.8322828,-0.8865658,-0.4792813,0.6738853,0 +0,user385,-0.5782342,0.9082783,-0.5505155,0.9783927,-0.4657295,-0.06236026,0.3756553,-0.5352146,-0.8345544,0.5585829,0 +0,user386,-0.05221205,-0.9078383,-0.1650247,0.5878886,0.2950072,0.1532751,0.05072086,0.06733898,-0.7406374,-0.6235872,0 +0,user387,0.7303624,-0.5445146,-0.7344195,0.8734284,-0.1780078,0.07920001,-0.3272306,0.7809805,0.1778098,0.2383835,0 +0,user388,-0.812161,-0.08214281,-0.5999032,-0.6955309,-0.1087583,-0.3730671,-0.6450373,-0.5143533,0.3432276,-0.07375648,0 +0,user389,-0.8008814,-0.9613443,-0.9299803,0.7177752,-0.1005404,-0.6052551,0.2301282,0.9364889,-0.9312284,-0.4785103,1 +0,user390,-0.2342081,0.93056,0.4181288,0.5382227,0.4527909,0.04161535,-0.4962643,0.1562413,0.2854061,-0.8012795,1 +0,user391,-0.3964738,-0.7155126,0.817507,-0.3866036,-0.2346356,0.9973372,-0.02704824,0.5739852,0.5780582,-0.3674402,0 +0,user392,0.3142398,0.0194069,-0.1787116,0.7075112,-0.3635078,0.3906429,-0.1415421,0.8805766,0.1074837,0.9669396,0 +0,user393,0.6181906,0.06179519,-0.04340809,0.05519344,0.7382776,0.922972,-0.7240731,-0.4154948,0.1753842,-0.6594441,1 +0,user394,-0.7365957,0.1217237,-0.7627288,-0.4234265,-0.9061602,-0.2821767,-0.6017163,0.3381366,-0.4079657,0.08019516,0 +0,user395,-0.8969834,-0.4936818,-0.441979,0.829339,-0.7288081,0.4407998,0.8426162,-0.6184706,-0.0666506,-0.547038,0 +0,user396,-0.8931554,0.3400274,-0.1293379,-0.6504709,0.1524148,0.3537036,0.9398097,0.7733704,-0.1862012,0.2205725,0 +0,user397,-0.08456878,0.452506,-0.9591207,0.7656044,0.4040793,-0.6002293,0.4067902,-0.8652471,-0.6068001,-0.2181653,1 +0,user398,0.5617367,-0.773412,-0.1253672,-0.6265589,-0.03896128,0.5716298,0.05315045,-0.5848281,-0.762174,-0.7851586,0 +0,user399,0.3841529,-0.5960998,0.07170338,0.2440487,0.9484546,-0.4573476,0.278062,-0.8477863,0.01016798,0.6849228,1 +0,user400,-0.1148795,-0.755814,-0.9170611,-0.1580483,-0.1616848,-0.59557,0.5674268,0.3606266,0.7225538,-0.5275144,1 +0,user401,-0.7617774,-0.3311636,0.684475,-0.07745214,0.5547549,-0.4531746,0.08786895,0.259881,-0.2479933,-0.7142194,1 +0,user402,0.1792734,0.3374216,-0.7955383,0.823708,0.1908151,0.7182759,0.7427047,-0.5955424,0.8104161,-0.5570325,0 +0,user403,0.1572697,0.7841174,-0.1781072,0.616127,0.7715162,-0.002441139,-0.510581,0.4853107,-0.2310465,0.1051308,1 +0,user404,0.05979844,0.5649143,0.1516574,0.2191302,0.3473603,-0.3207242,0.2582888,-0.6862714,0.2334231,0.2748717,0 +0,user405,0.1900905,-0.9717845,0.4345978,-0.3232103,0.9039757,0.6675296,-0.9272099,0.08807795,0.9190195,-0.8319404,0 +0,user406,-0.2761663,-0.5476022,-0.6018166,-0.09039765,-0.8383462,-0.6211714,0.863384,0.4844177,0.4660167,0.7608432,1 +0,user407,-0.3914291,-0.08998127,-0.411244,0.1008162,0.255934,-0.03208828,-0.693564,-0.4326147,-0.1051994,-0.8066107,0 +0,user408,0.7774467,0.828542,0.2473392,-0.0957808,0.6535522,0.7059284,-0.9948271,-0.720403,-0.180211,0.7554223,1 +0,user409,-0.04683069,-0.003741779,0.2438443,0.393021,-0.3391966,-0.5871277,-0.6711561,0.1541932,-0.1710856,0.9554387,0 +0,user410,-0.6603595,0.05037089,-0.3133341,0.513368,-0.01891003,-0.614374,-0.3740699,0.7990176,0.4242074,0.4617293,1 +0,user411,0.2194582,0.7904634,-0.3080529,0.5309324,0.7405582,-0.1293508,0.7318636,0.05244565,0.3076458,-0.776026,1 +0,user412,0.05149423,-0.4558292,-0.2237199,0.8917883,-0.9901922,0.7740147,0.001431359,-0.8478917,0.9190043,0.6088449,0 +0,user413,0.2378508,-0.4650198,0.8339251,-0.9121024,-0.0837753,0.4265574,-0.8214321,-0.6427731,-0.4798338,-0.274493,0 +0,user414,0.1923925,0.3749583,-0.8475197,-0.9428785,-0.4078106,0.02191734,-0.06246059,-0.6543301,-0.9665684,-0.6814539,0 +0,user415,-0.5326876,-0.8010343,0.92877,-0.2853572,-0.1939534,0.6943198,0.1691485,-0.7344576,-0.560277,0.2827302,1 +0,user416,0.6596166,-0.5567416,-0.7165904,-0.9337097,0.4504952,-0.6358028,0.5542233,-0.1779877,-0.3143882,-0.7159101,0 +0,user417,-0.8598196,0.46712,-0.01254439,0.6450101,0.8871966,-0.8248076,0.9882603,0.4130089,-0.7072058,-0.3050411,1 +0,user418,-0.8023252,-0.3455489,-0.8056495,-0.4119287,0.6280389,-0.2264802,0.8419179,-0.953477,0.6175328,-0.4788864,0 +0,user419,0.8474556,0.3611156,-0.3164936,-0.6292406,-0.6582631,-0.008869962,0.9091859,0.307659,-0.9711606,0.2103334,1 +0,user420,-0.660701,0.5057757,0.05747531,0.3627854,-0.2133438,-0.4300627,0.2183885,0.3494978,-0.6384342,0.2164486,1 +0,user421,-0.03653329,-0.4149889,0.6124793,-0.873706,0.08082977,0.8151351,-0.6543464,0.2027643,-0.09706112,-0.2801658,0 +0,user422,-0.5490182,0.645603,-0.4989866,-0.01584421,0.1071013,-0.01153279,-0.1178623,-0.1183559,0.6068976,0.8428932,1 +0,user423,0.6535388,-0.4748174,0.8787637,0.07029657,0.4231485,0.9605802,-0.9231536,0.2300743,0.4690495,0.1833882,0 +0,user424,-0.4183427,0.6468063,-0.4309288,0.1814874,-0.1808926,0.7381071,-0.3784195,0.7872695,-0.9216769,0.06039008,1 +0,user425,-0.2856139,-0.2326733,-0.2617154,0.5607293,0.200941,0.7062905,0.2804215,-0.7802192,-0.8010681,-0.07691165,0 +0,user426,0.7565554,0.0315008,-0.5632152,-0.1003644,0.6943404,0.40138,0.9194626,0.6116037,-0.5976012,0.6363502,0 +0,user427,-0.3114981,-0.01316632,0.4397333,0.5310166,0.9715222,0.09181076,-0.4386099,0.5606399,-0.1078781,-0.7190375,1 +0,user428,0.6298173,-0.7801673,-0.2208361,0.3263337,-0.3949796,-0.8939388,-0.3127883,-0.6454664,-0.4078682,0.704923,0 +0,user429,0.3182921,0.2580888,0.3114175,0.2730766,-0.3446209,-0.02699016,-0.02738698,-0.9732244,-0.3597751,0.8511916,0 +0,user430,-0.9273452,0.3907339,-0.4885633,-0.2249348,0.9199768,0.6344632,0.8394521,0.7128537,0.9022898,0.9658853,1 +0,user431,-0.4850622,-0.5359813,-0.1378973,-0.8317146,0.4433356,-0.4895088,-0.7453615,0.7151603,-0.6853145,-0.8225914,0 +0,user432,0.5565147,0.9269252,-0.004107452,-0.8043755,-0.789866,0.5198352,-0.939518,0.2866566,0.3922316,-0.8630279,0 +0,user433,0.2519282,-0.2718445,-0.2841016,-0.4012268,0.1107919,0.3527391,0.5821569,-0.8826887,0.7127059,-0.5911472,0 +0,user434,0.6722075,-0.7518638,0.6839955,0.7844124,0.2148518,0.5080501,-0.2559425,0.2004709,0.083639,0.2825394,0 +0,user435,-0.3836869,0.4918395,-0.8524501,0.4147547,0.5574944,-0.800889,0.3187707,0.6003853,-0.3743453,0.4118438,1 +0,user436,-0.5579813,-0.243629,-0.8495038,0.2755629,0.01476753,0.02026872,0.6549469,0.2053892,0.6317254,-0.4230875,0 +0,user437,-0.6039588,-0.299466,-0.9178211,-0.3059852,0.3765056,0.8868788,-0.3925586,-0.3151114,-0.4503443,0.04338256,0 +0,user438,0.224884,-0.5981418,-0.2636941,-0.4844291,-0.1865716,0.1670228,0.6252068,-0.8322294,0.5204553,0.6052331,0 +0,user439,-0.7805346,-0.4150871,0.3978354,-0.8202179,-0.3316802,-0.2738029,0.6601198,0.4849862,-0.5484856,-0.6676652,1 +0,user440,0.3492105,0.6967922,0.3260233,-0.9129642,-0.9626909,-0.700249,-0.06371463,0.8390817,0.3785701,-0.001178696,0 +0,user441,0.5645245,0.4522291,0.4229718,-0.9710611,0.7945184,0.5526487,-0.7488631,0.9667882,-0.05533731,0.06696247,1 +0,user442,0.4389236,-0.6246237,-0.9102175,0.7107145,-0.5911221,0.5968463,0.3919835,-0.4625681,0.7591602,-0.4436911,0 +0,user443,-0.5992953,-0.759037,-0.8976966,0.978824,-0.9528832,-0.9262343,0.9377167,0.99119,0.2975744,-0.3923338,0 +0,user444,-0.1976247,0.9872093,0.2568969,-0.8831635,-0.2892569,-0.02079386,-0.5702952,-0.6759849,0.4648289,0.7924695,0 +0,user445,-0.3686839,0.7503346,-0.7577372,0.7678361,0.001067368,-0.3812364,-0.6704771,-0.1168982,0.7925918,-0.1251451,0 +0,user446,-0.1319829,-0.5600713,-0.9689266,-0.3065331,-0.1468365,0.7680854,0.1068653,-0.7432676,0.7372974,0.8903964,0 +0,user447,-0.5380081,-0.5695322,0.5403065,-0.8168732,-0.8387617,0.3434033,0.9839281,0.1460274,-0.8495593,-0.9234407,1 +0,user448,-0.2285035,0.2174546,0.2297184,0.4128462,-0.111736,-0.2060439,-0.6822169,-0.7038894,-0.914614,0.5698138,0 +0,user449,0.06569198,0.09437979,-0.7745761,0.2815381,-0.5187977,-0.4583948,-0.0512168,-0.6967446,0.3548302,-0.58849,0 +0,user450,-0.6905525,0.7915834,-0.7761871,-0.4461138,-0.4970249,-0.6654667,0.893114,-0.5463136,-0.82072,0.2868927,0 +0,user451,0.1107955,-0.2767697,-0.7128063,-0.2243685,0.6749202,0.3638934,0.5361716,0.6456084,-0.5530482,-0.2137376,0 +0,user452,-0.9708413,0.6793909,0.8379032,0.4078321,0.5620321,-0.6432597,0.2944368,0.5060197,-0.7422309,0.1313442,1 +0,user453,-0.2395707,0.4371864,-0.2751738,0.538042,0.6100764,0.3230006,-0.2247483,0.3353305,0.7861776,0.1297859,0 +0,user454,-0.2356657,0.2484129,-0.8340425,0.8459281,0.09806869,0.3244736,0.613018,-0.1243173,0.9160012,0.9696506,0 +0,user455,-0.389184,0.3261971,-0.5930257,-0.4106804,-0.6188605,-0.9051525,0.9160172,0.2932892,-0.6639078,-0.8082658,1 +0,user456,0.4748155,-0.7954869,0.4631108,0.09877129,-0.1889826,0.02929103,-0.9443268,0.5551113,0.9851095,-0.9471257,0 +0,user457,-0.4791103,-0.7200863,-0.3972578,-0.2544363,-0.2075909,-0.2741464,0.5324806,-0.5127136,-0.6815999,0.6060008,0 +0,user458,0.2993179,-0.6869692,0.8467076,-0.8796639,-0.6473382,0.1866583,-0.5225926,-0.1460709,0.228214,-0.5273032,1 +0,user459,0.1046328,-0.5756542,-0.7577254,-0.574895,0.4160378,0.1353523,-0.2571151,0.9096449,-0.4227587,0.7577973,0 +0,user460,0.8391818,0.5380025,0.9141598,-0.9813597,0.4477882,0.6988635,-0.4949064,-0.485938,-0.04137503,0.4571924,1 +0,user461,0.3719727,0.7037647,-0.6418557,-0.1045987,-0.7273614,-0.1788786,-0.6831405,-0.4332172,0.1305039,-0.5614179,0 +0,user462,0.6195706,-0.1116355,0.1043774,-0.4066095,-0.1406266,0.6458435,-0.002476681,0.6248052,-0.1080732,0.9352059,0 +0,user463,0.3956964,0.4649276,-0.08994767,-0.7857352,0.6579222,0.2186987,-0.4344244,0.8007187,-0.6491434,0.5941645,1 +0,user464,-0.3760991,-0.5680798,0.07404262,0.4941745,0.3834304,-0.8261395,0.8990163,-0.3159059,-0.1567902,-0.1525651,1 +0,user465,0.2917781,0.1365007,-0.2116271,-0.6221971,-0.9257748,0.1538936,0.7415808,-0.1747239,0.9755658,0.2177453,0 +0,user466,-0.9879905,-0.04323285,0.05760224,0.6290195,0.2154166,0.4178097,0.8843463,0.401104,-0.02348866,0.006008328,0 +0,user467,0.06591967,0.1882912,0.2245388,-0.2302625,-0.601802,0.1941293,0.5539633,0.8894833,-0.5250648,0.4243474,0 +0,user468,0.6878193,0.8370347,-0.1294482,0.07181765,0.4507308,0.04077237,-0.6509778,0.5101647,-0.4747785,-0.7388721,0 +0,user469,0.2368936,0.3586254,0.7939082,-0.8554096,-0.971155,-0.4151675,0.5095531,0.5688745,-0.5030334,-0.3887585,1 +0,user470,0.2853851,0.7732041,-0.3776258,-0.05048042,0.06651776,0.9203263,0.2140831,0.3744695,-0.07355034,0.7566822,0 +0,user471,0.03702975,0.5338268,-0.8034249,0.1588534,0.4880399,0.3405234,0.2853076,0.3492464,0.9037916,0.2599492,1 +0,user472,-0.1985819,-0.1891455,0.21688,-0.8264707,0.8233634,-0.8625188,0.76069,0.5356627,0.4416293,0.6782039,1 +0,user473,-0.2756913,-0.8514196,-0.2878433,-0.3397659,0.4753957,0.5171726,-0.3939334,0.9119014,-0.3143901,-0.6870089,0 +0,user474,0.4377345,0.7747898,-0.7011215,0.1376775,0.5351567,0.4142891,0.2230243,0.3404364,0.2013661,0.8676154,0 +0,user475,0.6037934,-0.2019362,-0.5262231,-0.7096342,-0.4658936,0.1166874,-0.8096052,0.8596779,-0.0935418,0.4706734,1 +0,user476,0.3556247,0.898915,-0.04558048,-0.5719298,-0.5235369,-0.8640637,-0.06441055,-0.2049969,-0.5217983,0.187846,0 +0,user477,-0.6942484,-0.7852815,-0.6700481,0.8311443,-0.6116798,0.1823745,-0.6701104,0.5971688,-0.06133652,0.7580118,0 +0,user478,-0.9342147,0.2285316,-0.9859166,-0.5265074,-0.3046553,-0.5399093,-0.8256771,0.005705276,0.05689885,0.5472328,1 +0,user479,-0.8728788,0.1163696,-0.8158621,0.8409164,0.364727,-0.07010765,0.2533726,0.09111375,-0.4364123,-0.2423402,0 +0,user480,0.3714436,0.3090983,-0.4446242,0.1126825,-0.1304775,0.7239797,0.2786728,0.9004242,-0.7065063,-0.8304782,1 +0,user481,-0.6247672,0.020115,-0.7621038,0.02737881,0.1983198,-0.205376,-0.9325631,0.4593916,0.2361789,-0.1658745,1 +0,user482,0.2379167,0.8395999,-0.5286683,-0.3834521,0.03964725,-0.7062143,-0.2104558,-0.2632779,0.01053952,0.5439222,1 +0,user483,0.4006023,-0.01151081,-0.606721,-0.4794854,-0.5684454,-0.9192799,-0.4268904,0.4064439,-0.4487372,0.3008659,0 +0,user484,0.1356622,-0.5426986,-0.03727754,-0.4345792,-0.1916038,-0.8823754,-0.1573113,-0.2052779,0.02235645,0.9639114,1 +0,user485,-0.997749,0.08801279,-0.3627109,-0.537524,-0.8622841,0.6182593,-0.5974378,0.6124048,-0.07345924,0.5135728,0 +0,user486,-0.9885817,-0.6853137,-0.1997467,0.1098342,-0.1873059,-0.8244324,-0.5108732,-0.3002669,-0.112645,0.4926002,0 +0,user487,-0.3895224,-0.3381854,-0.5741667,0.6641921,0.6194136,0.1469156,-0.1016381,-0.6501666,0.007465935,-0.9832143,0 +0,user488,-0.4768594,0.3679265,0.2400314,0.2080397,-0.06987497,-0.655887,0.9350428,-0.9003087,0.2449409,0.1195736,1 +0,user489,0.3107362,-0.3722828,-0.3530391,0.2301703,0.1653559,0.3622258,-0.0334658,0.5536622,-0.884431,0.9652969,0 +0,user490,0.7151104,0.08616038,-0.3318921,-0.9107029,0.0354514,-0.7177321,0.6412467,-0.7405217,0.5847072,0.774583,0 +0,user491,-0.6376776,-0.09407108,0.1541911,0.22668,-0.6220867,-0.9570236,-0.5598636,-0.3862467,-0.7964342,-0.423234,1 +0,user492,-0.3172911,-0.6685181,0.005105189,-0.8744284,0.4379944,-0.8166528,0.2833937,-0.879555,0.2460729,-0.596121,1 +0,user493,0.334681,0.9745249,0.7724853,-0.3173125,0.8948248,0.9281114,-0.36123,0.8842835,-0.523366,0.7097889,1 +0,user494,0.7580189,-0.6291434,-0.9357565,0.4409448,-0.9641645,0.2616752,0.005711988,-0.585528,-0.4455776,-0.8290695,0 +0,user495,0.3066099,-0.2365979,-0.9208522,0.6197462,-0.1785751,-0.6427922,0.18241,-0.1954609,-0.9107173,0.2513139,0 +0,user496,-0.3735409,0.1110256,-0.4391418,0.06049041,0.96905,0.08200498,-0.6196492,-0.2904404,-0.5478002,-0.07246578,0 +0,user497,0.7700284,0.3276237,0.1218457,0.06996424,0.2512521,-0.3205151,-0.1099417,0.8155759,0.5309338,0.1769388,1 +0,user498,-0.6274704,0.9516933,0.3036866,-0.6105163,0.2196229,0.551337,-0.2636267,-0.3059776,-0.435782,-0.3243387,0 +0,user499,-0.6857216,-0.05193972,0.43141,-0.8676919,0.4197808,-0.8772227,-0.2706269,-0.7802758,-0.0225787,0.1886621,0 +0,user500,0.00692197,-0.3137509,-0.08424611,0.2145547,0.2800971,0.2643174,-0.6003886,0.3844505,-0.9720996,0.7881803,0 +0,user501,0.6579146,0.7248974,0.9260608,0.3390032,-0.7138594,0.4716633,0.9504564,-0.9315081,0.4906676,-0.5676565,0 +0,user502,0.3513081,-0.5181129,0.6279851,0.2911615,-0.09217927,0.4633007,-0.9853193,0.5689706,-0.1187871,-0.5513887,0 +0,user503,0.8083401,0.4971036,-0.8673661,0.388084,0.1034604,0.4017987,-0.8396986,-0.07988677,0.4695297,0.4663842,0 +0,user504,-0.6177767,0.8734778,-0.3617824,0.9992373,0.7615363,-0.01116401,-0.4434771,0.9803932,-0.8237225,-0.2546654,0 +0,user505,-0.2109574,-0.743323,0.9268636,-0.571161,-0.5570226,-0.1224102,0.237705,-0.09059297,-0.917421,-0.6837733,1 +0,user506,0.4121335,-0.7048326,-0.3935892,0.6784498,0.6375669,-0.481514,-0.6493038,-0.2202089,-0.6240121,-0.06294238,1 +0,user507,0.737848,0.7723928,0.5926371,-0.5726925,-0.7620006,0.1247723,0.4921124,-0.2246037,-0.3455208,0.9331806,1 +0,user508,0.09479419,-0.5286045,-0.7431845,-0.7400167,-0.1687024,-0.9400357,0.5675946,-0.4934241,0.0212425,-0.9257616,1 +0,user509,0.4779188,0.523699,-0.3795058,-0.8480575,-0.6670885,-0.02142329,-0.4749809,0.7854964,0.4328868,-0.5157096,0 +0,user510,0.8649692,-0.1112376,0.776775,-0.7317762,0.6027264,-0.9453354,-0.254515,0.8665101,0.2180669,-0.3091596,0 +0,user511,-0.5337622,0.7804938,-0.1878087,0.3726658,0.7008201,0.783944,-0.1537326,-0.5929999,0.3147362,-0.7562398,0 +0,user512,0.8531516,-0.456186,-0.1416096,0.1793213,0.5312313,0.7732007,-0.4075439,0.244888,-0.3309344,0.3184158,0 +0,user513,0.1028859,-0.2716377,-0.7518933,-0.1152282,-0.3576263,-0.6515496,0.5350292,-0.3967678,-0.7713935,-0.7652374,1 +0,user514,0.86684,-0.231017,0.2054702,0.8931804,-0.8676253,0.8646641,0.419377,0.813444,0.865999,0.5446261,0 +0,user515,-0.01118624,0.001115464,0.8211128,0.744742,-0.6603725,0.8908253,0.4351448,-0.9603898,0.6914221,0.2823272,0 +0,user516,0.1051368,0.8163751,-0.1146042,0.3472478,-0.2199104,0.9667097,0.9375914,-0.7843629,0.1551472,0.7483354,0 +0,user517,0.8782583,0.08366933,-0.9942765,0.00301456,-0.05493117,-0.9597684,0.9085038,-0.4868228,-0.246646,0.03722631,0 +0,user518,0.5992914,0.66293,-0.7530539,0.4089341,0.9590411,0.0377409,-0.6664934,-0.6105564,-0.301112,0.2991129,1 +0,user519,0.6282775,0.1843016,-0.8745729,-0.4447125,0.7102147,-0.6891773,0.8726342,-0.6846717,-0.5999119,-0.132091,1 +0,user520,0.1889945,0.7113865,-0.3473156,-0.7668151,-0.8895753,0.4024574,-0.124962,-0.9331606,-0.131077,0.00252323,0 +0,user521,0.3144018,-0.2509096,-0.08494601,0.4982312,-0.005507462,0.3200088,0.9747534,-0.3510781,-0.7164048,0.07369587,0 +0,user522,0.9905999,-0.9097695,0.2796182,0.7819675,-0.9118721,-0.6462009,-0.6872294,-0.07091837,-0.3963461,0.444675,0 +0,user523,0.8717035,-0.9571316,0.6577896,-0.6412435,0.5484192,0.5858046,-0.8415683,-0.8127156,-0.8850041,0.4064022,0 +0,user524,-0.3509172,-0.2763847,-0.3124607,-0.8190813,-0.1106827,0.2481201,-0.3864766,-0.4667946,-0.2397708,-0.2165152,0 +0,user525,0.7486188,-0.5389129,0.3438617,0.2229122,-0.8760366,0.6154743,0.3184826,0.3435536,0.1580763,0.6156054,0 +0,user526,0.1783134,-0.1937295,0.7369374,0.9785027,-0.630156,0.9430124,0.3408417,-0.008176544,-0.7957214,-0.3422839,1 +0,user527,0.2755419,0.8346409,0.2483975,0.2414091,-0.1416327,-0.6698749,-0.006125746,0.2427649,0.212429,0.711019,1 +0,user528,0.5186472,0.7887108,-0.5342926,-0.7071235,0.3752155,-0.7050408,-0.7914591,0.1591296,-0.3109899,-0.2074558,0 +0,user529,0.5508429,-0.2420362,0.04062403,-0.6320136,0.5894669,0.4943494,-0.9227851,0.6858458,-0.2315034,0.3333774,0 +0,user530,0.5898203,-0.2172988,-0.3201925,0.3737172,-0.7218518,-0.5470975,0.7232473,0.4624892,-0.8101497,-0.1003189,1 +0,user531,-0.4744308,-0.5250402,0.3814613,0.5074311,-0.3446874,0.5592766,-0.3918477,-0.4564199,-0.2830895,-0.4192755,0 +0,user532,0.2087575,-0.5171388,-0.03331514,0.7069896,0.8756075,-0.03398725,-0.9723287,0.7543377,-0.7408358,0.7657209,0 +0,user533,-0.05887158,0.2645883,-0.6922074,-0.3351213,0.1859689,0.9162032,0.737928,0.03145979,0.07106324,0.3482924,0 +0,user534,-0.6660908,0.9720634,0.5140952,-0.1044849,0.758773,-0.03892476,-0.2315463,0.4636933,-0.8135598,-0.9528913,1 +0,user535,0.5909808,-0.643661,0.6049024,0.7062269,0.6371439,0.9548487,-0.4158057,0.7347309,-0.5645583,-0.4889446,0 +0,user536,0.730171,0.5212653,-0.7653439,0.09371765,0.6289463,-0.206207,-0.02436701,0.9408668,0.1536423,0.664519,1 +0,user537,0.7460427,-0.7327691,-0.879494,-0.4260351,0.3963399,0.4795613,0.1191499,-0.7565156,-0.4375719,-0.0158337,0 +0,user538,0.3288288,-0.8712683,0.1975395,-0.8664656,0.8751433,0.07962102,-0.9236933,-0.4898728,0.08992097,-0.555764,0 +0,user539,-0.1750348,0.9926608,-0.5085284,0.353701,-0.5397561,-0.1462427,-0.4567724,-0.5525573,-0.8251152,0.7387575,0 +0,user540,0.2239614,0.7909299,-0.2589998,-0.2740926,0.7292514,-0.541862,0.6441691,-0.9710192,0.9953149,0.4684567,1 +0,user541,0.193798,0.01749417,-0.02568551,-0.5982417,0.4778697,0.1342857,-0.1782084,-0.6233627,-0.6920121,0.1350764,0 +0,user542,0.291203,0.7731546,0.3036628,-0.2736333,-0.838936,-0.3622988,0.389495,-0.1455572,0.489621,0.9825177,1 +0,user543,0.07711303,-0.6652561,0.5993905,0.9052287,0.2604828,-0.7686613,-0.7633748,0.2738688,-0.3356195,-0.2131275,0 +0,user544,-0.7033161,0.7458565,0.2224211,0.28653,-0.8797566,0.482736,-0.6431792,-0.0201305,-0.4634056,0.369839,0 +0,user545,0.158043,-0.4578624,-0.490867,-0.3804529,-0.7065613,-0.4976347,-0.191128,-0.3321132,0.35562,0.5271438,0 +0,user546,-0.9340732,0.3358594,0.4205034,0.6499707,0.6001103,-0.877836,0.6717699,0.3134789,-0.6441974,-0.9308002,1 +0,user547,0.4018207,0.5622317,-0.8921831,-0.3662222,-0.09966699,0.4494457,-0.7055878,0.1955066,0.6917416,0.1181744,0 +0,user548,0.03630134,0.6258069,-0.4851434,0.6225617,0.2385075,-0.4574031,-0.2826242,0.181064,-0.891026,-0.4356299,0 +0,user549,0.6652182,-0.001210577,0.6674495,0.05890482,0.5591514,0.1599049,-0.9947235,0.7029225,0.05469066,0.3683127,1 +0,user550,0.03009816,-0.2534668,-0.766756,0.1890653,-0.3894523,0.7602684,-0.8329535,0.5108349,-0.9081703,0.9860834,0 +0,user551,-0.7747041,0.3371934,0.167541,0.8557465,0.3489323,0.9450543,0.5924138,0.2479034,-0.02210303,0.5668934,1 +0,user552,-0.02038,0.7478798,-0.4174965,-0.442864,-0.4463561,-0.5200863,0.9800299,-0.6481556,0.3382859,-0.5579915,1 +0,user553,0.02069805,-0.1632363,0.5128623,-0.02896727,-0.3013244,-0.8859325,-0.5201829,-0.5600835,-0.3045164,0.4307584,0 +0,user554,-0.9030006,0.3800618,-0.1746694,-0.7854969,-0.1026486,0.5308589,0.7508455,0.4351878,0.09289289,-0.02670442,0 +0,user555,0.6287028,-0.5285049,0.2700428,-0.2619452,0.4429613,0.7280338,-0.4064467,-0.1149503,-0.9014849,0.2254933,0 +0,user556,-0.2306832,0.2978508,-0.143276,-0.8060551,-0.177361,0.7295418,0.7982997,0.7834701,0.8535599,0.04636379,1 +0,user557,0.2753127,-0.8136677,-0.437732,-0.8069942,0.2671954,0.4738713,0.09168718,-0.5729888,0.2971715,0.6310117,0 +0,user558,-0.09575523,-0.6938639,-0.4815598,0.9794639,-0.6986714,-0.9418411,0.5874276,-0.8721853,0.3109441,-0.06348771,0 +0,user559,-0.712036,0.08656155,0.3224314,-0.5131786,-0.8021455,-0.975499,-0.9931593,-0.05740029,-0.45743,0.838908,1 +0,user560,-0.1738443,-0.05570395,0.6028921,-0.4390079,-0.1433377,-0.03177926,0.1689021,-0.887143,-0.9343319,-0.03561095,1 +0,user561,-0.5059349,0.08883727,0.1982477,0.3531811,-0.4205232,-0.4889386,0.3106749,0.5903038,0.5007944,0.8361934,1 +0,user562,-0.1864669,0.5615214,-0.2961073,0.9942525,-0.1468329,0.5837776,-0.385007,0.4861798,0.2594804,-0.5803675,0 +0,user563,-0.9650868,0.4271572,-0.4304231,-0.7320183,-0.2677301,0.9342335,0.1965734,0.8671947,-0.6751677,-0.2698901,1 +0,user564,0.4351935,-0.6465744,0.5060402,-0.9819402,0.7654457,-0.5727354,0.0486029,-0.3782364,-0.4281424,0.1844857,0 +0,user565,0.1474424,0.5335848,-0.7820121,-0.1102324,-0.3880598,-0.4551472,0.3834467,-0.05012695,0.4459206,-0.5332588,0 +0,user566,0.625894,0.7834962,-0.8255207,0.9742086,-0.6305863,0.8890822,0.7807677,0.6019256,-0.2397259,0.2411653,0 +0,user567,0.1653645,0.8746909,0.7406964,0.1117774,0.394392,0.2210576,-0.9757641,-0.4373696,0.7254999,-0.1509952,0 +0,user568,-0.106515,0.8008157,-0.6615061,0.4637325,-0.9917199,-0.9755859,-0.4974033,0.1933574,-0.9916512,0.4509075,0 +0,user569,-0.04527715,0.9122279,0.3720188,-0.8922569,-0.755443,-0.03129675,0.8570743,-0.8879472,0.8501951,0.6854013,0 +0,user570,0.9903297,0.8673517,-0.7678321,-0.5345216,0.8546359,-0.9251852,-0.4325365,0.01007311,0.9003847,-0.4122377,0 +0,user571,-0.8825535,0.5917456,0.07949406,-0.8103601,0.7375315,-0.5174479,-0.8532342,0.2223382,-0.9963363,-0.08063585,1 +0,user572,-0.8514791,-0.0702779,-0.6536667,-0.4904987,0.7224267,-0.8970111,-0.321134,-0.5113099,-0.841817,-0.1795223,1 +0,user573,0.2815327,0.6405063,0.5358308,0.1918451,-0.9843001,-0.287484,0.9569585,0.8645159,0.3900056,-0.42972,1 +0,user574,0.1945595,0.9264895,-0.3211154,-0.9051314,-0.001985753,-0.2861092,-0.6166091,-0.5037931,-0.3319558,0.7062367,0 +0,user575,-0.5547953,-0.3244214,0.5687545,0.7960313,0.8426701,0.5857249,0.0356868,0.4685596,-0.3052227,-0.8096833,1 +0,user576,-0.5604243,-0.8173562,-0.9550362,0.8113922,-0.6908614,0.2148813,-0.2341695,-0.4675972,-0.2543744,-0.9025762,0 +0,user577,0.2604863,0.2623489,-0.900612,0.7448393,-0.4018755,-0.1639452,-0.9448392,0.8096859,0.02384685,0.7754364,0 +0,user578,0.8470254,-0.7621897,0.6765714,-0.5701909,-0.2569969,0.03517064,0.330099,-0.3359338,-0.6134811,0.3084911,0 +0,user579,0.475877,0.8084507,-0.4401796,0.4339539,0.5476462,0.7574782,0.4832063,0.7134668,-0.1454005,-0.3382061,0 +0,user580,-0.0742955,-0.7388617,0.7668375,-0.1962559,-0.8427241,0.9959597,-0.9395626,0.5126084,-0.9214625,0.1437491,0 +0,user581,-0.1228764,-0.01565649,0.9098154,0.6188744,0.3535507,-0.204561,0.4971455,-0.825099,-0.5216514,0.2945745,0 +0,user582,0.7011729,0.1456441,0.7273614,0.2897005,-0.1034216,0.7025325,0.07562007,-0.03862985,0.8324965,-0.7713127,0 +0,user583,0.9053245,-0.9909818,-0.6506591,0.3608801,-0.2890801,-0.5241266,-0.9595327,0.8644527,0.4168234,0.5857576,0 +0,user584,0.8978216,0.8211072,0.4226776,-0.4100929,-0.9477737,-0.09049345,0.9769626,-0.3851824,0.1738322,-0.2746671,0 +0,user585,0.7981723,-0.4742941,-0.447308,0.5042035,0.7939298,0.2333915,-0.1735344,-0.6034421,-0.07461061,0.2019829,0 +0,user586,0.5340273,-0.5194867,0.6193837,-0.9010651,-0.8461189,-0.7960928,-0.3659794,-0.2504975,0.5153385,-0.188749,0 +0,user587,-0.3328615,0.118958,-0.7205984,-0.2161479,-0.1251346,-0.3609516,0.7752624,-0.6017123,0.02739207,0.7716967,1 +0,user588,0.07348499,-0.2879619,0.11496,0.6972093,0.06112527,-0.2927372,0.9181528,-0.1764309,-0.7774391,-0.1670054,0 +0,user589,-0.5617279,-0.2133507,-0.8621761,-0.9216012,-0.5447903,-0.7379339,-0.7785518,-0.1226828,-0.1737174,0.7477632,0 +0,user590,-0.04489754,-0.7944804,0.601833,0.2706735,0.0727199,-0.3364507,0.782103,0.3408874,0.569962,0.6106047,0 +0,user591,0.8996407,0.6563342,-0.2821479,-0.7417986,0.9177876,0.6754836,0.08705488,-0.06357382,-0.711771,0.7973836,0 +0,user592,-0.06766281,0.8754866,0.3360716,0.4315799,0.03468648,-0.2268725,0.5321231,-0.532379,-0.672923,0.5839566,1 +0,user593,0.7686356,0.767041,-0.6942742,0.264926,0.925887,-0.7526731,-0.6029039,-0.1729328,-0.1705576,-0.9697628,0 +0,user594,0.9345539,0.0834914,0.287429,-0.4738169,-0.3499425,0.609717,-0.7163717,-0.1963791,-0.3869386,-0.4725065,0 +0,user595,-0.6324693,-0.7710878,-0.1578882,0.4496397,-0.1998679,0.2003921,-0.419274,0.08938458,-0.1010654,-0.2315577,1 +0,user596,-0.08392203,0.3006258,-0.4762863,-0.8453064,-0.4621728,-0.2078203,0.7805428,0.7769402,-0.7246369,-0.5030216,1 +0,user597,0.5604479,-0.1330124,0.4619084,-0.4996082,0.01947123,0.4987993,-0.935604,-0.5944535,0.3733355,0.7686588,0 +0,user598,0.5328952,-0.8963969,-0.4171918,-0.4385829,-0.8054759,-0.5785504,-0.3950381,0.652015,-0.3755655,0.6174471,1 +0,user599,0.809563,0.1014414,-0.1377924,0.6184261,-0.4538927,-0.1834062,-0.7168605,-0.02970236,-0.7162882,0.9478858,0 +0,user600,-0.4848293,-0.2207845,-0.1660728,-0.3918652,0.2640282,-0.5324975,0.9214703,-0.4824007,0.2235305,0.4540602,1 +0,user601,0.5232249,0.9709548,-0.1850239,0.02689552,-0.95084,-0.5037356,0.1724254,-0.3379119,-0.4751808,-0.7947906,0 +0,user602,0.9270095,-0.306813,0.9417016,0.8080661,-0.7163612,0.2991459,-0.5700947,-0.8073642,-0.7126245,-0.13275,0 +0,user603,-0.3363084,0.7089376,0.1802605,0.1176361,-0.01354509,-0.4295086,-0.3996637,0.006289371,0.3817135,-0.7254621,1 +0,user604,-0.1952425,0.6114611,-0.6491931,-0.7812594,-0.9351401,0.2087805,0.1293839,-0.473396,0.9148248,-0.2245107,0 +0,user605,0.121569,-0.3803235,-0.3794138,0.9029347,0.281653,-0.9869633,-0.1867038,-0.3111572,-0.04458031,-0.4265133,1 +0,user606,0.1088963,-0.6154838,-0.250985,-0.08633254,-0.170875,-0.8437837,0.6360231,-0.525151,-0.9235092,-0.5351454,1 +0,user607,0.2443332,0.7941049,-0.6042293,-0.9698671,-0.6260014,-0.5763382,0.8952143,0.05900677,-0.3395497,-0.1270869,0 +0,user608,-0.6179447,0.8820254,-0.2800258,0.6477739,0.8797775,-0.1509085,-0.1315429,-0.5014714,0.9792665,-0.6510769,0 +0,user609,-0.04407824,-0.3776735,-0.5744137,0.3434766,0.5721281,0.191387,-0.03387785,0.1389151,-0.5369902,0.7733457,1 +0,user610,-0.2797898,0.6025556,-0.0444089,0.4640868,0.9216448,-0.81886,0.3784206,-0.2275265,0.5150499,0.534707,1 +0,user611,0.3077598,-0.8568362,-0.5131883,-0.548482,-0.9629465,-0.1549488,-0.07110554,-0.988863,-0.942196,0.4926722,0 +0,user612,0.8330454,0.60667,-0.6645983,-0.03764905,-0.0743212,0.986826,-0.5367324,0.3138162,-0.05864161,0.06792024,0 +0,user613,-0.5786169,-0.2518003,-0.3170475,-0.2462127,-0.1817768,0.8836726,-0.5459594,0.7338437,0.3475464,0.7633944,0 +0,user614,0.2130843,-0.8478181,-0.1638474,0.8123982,-0.2520267,0.3209246,-0.03063824,0.8755897,0.4746274,0.07842985,0 +0,user615,0.730867,0.4277772,0.7580794,0.552258,-0.02209486,-0.1036674,-0.5597698,0.9286337,-0.8848094,0.7932531,1 +0,user616,-0.7804446,0.2739056,0.2356445,-0.7420092,-0.387847,0.1170641,0.2805062,-0.8695984,-0.7270642,-0.03462277,0 +0,user617,-0.2528883,-0.3673048,-0.5444637,0.911333,-0.09814556,0.5248318,0.6033824,-0.3749078,-0.0100341,0.8896808,0 +0,user618,-0.6019945,-0.4532647,-0.962519,-0.6638899,0.8527705,0.5353809,-0.7845074,-0.6730786,0.1425826,0.5649498,0 +0,user619,0.2930404,0.9859437,-0.6493955,0.9552001,0.6732783,0.8243269,0.198659,-0.04602927,-0.5045033,0.7983718,0 +0,user620,0.1853838,0.4193446,-0.4066398,0.9897318,0.3570642,0.7868979,0.8248306,0.5024094,0.8162485,0.637444,1 +0,user621,0.3531079,-0.2477451,0.639314,0.6067836,-0.0745096,-0.8010697,0.9975956,0.6678088,-0.2874553,0.1755545,1 +0,user622,0.192681,0.6422779,0.06845663,-0.7865985,0.5910659,0.4998105,-0.7142861,0.8903969,-0.2162743,0.5957554,0 +0,user623,-0.882279,0.2948312,0.9294318,0.4213117,-0.6082494,-0.4399746,0.3569537,0.9700304,-0.8566746,0.2214007,1 +0,user624,0.1217435,-0.4807041,0.9450398,-0.1282905,-0.1486226,-0.5537428,-0.6053083,-0.505124,0.5419871,0.2057917,1 +0,user625,0.1272349,-0.2742307,-0.6441143,-0.2604154,-0.7588766,0.1095275,-0.4306578,-0.3059822,0.3967871,-0.8767511,0 +0,user626,-0.5147483,0.5237434,-0.2284564,-0.1290486,0.1918828,0.7604175,0.9376797,0.05941494,0.04226005,0.989843,1 +0,user627,-0.9621785,0.8199216,-0.5312466,0.02640313,0.3892047,0.2384369,-0.8247655,-0.7281838,0.8173502,0.7027701,0 +0,user628,-0.3123172,0.5927569,0.8177941,0.2399764,0.2605946,-0.3916732,-0.3662618,0.09956428,-0.2298774,0.8919078,0 +0,user629,-0.9818531,0.6273465,0.3543518,0.4323685,0.3864069,-0.8181329,-0.4573584,-0.2885701,0.6666946,0.6072901,1 +0,user713,0.1782372,-0.243706,0.2075103,0.314981,0.8013957,0.7619735,-0.9150296,0.05681682,-0.4566527,0.1100775,0 +0,user714,-0.8838954,-0.9808943,-0.501159,0.3114904,0.005327349,-0.79276,-0.7054062,-0.5605854,-0.0580323,0.5966227,1 +0,user715,0.7791064,0.1522858,0.6051627,0.6125937,-0.04104304,0.7534318,-0.007461783,0.1342199,-0.1468817,-0.09077787,0 +0,user716,0.8910033,0.3405064,-0.1641957,0.01062322,0.5619107,0.3495819,-0.6637216,-0.760742,0.4345769,0.3232831,0 +0,user717,-0.8879556,0.4476763,0.0313106,-0.2113295,0.6484082,-0.8003655,-0.003238693,-0.4696942,-0.8371201,0.6987225,0 +0,user718,0.8504393,-0.6829105,0.381874,-0.7825504,-0.07701794,0.2444008,-0.1370124,-0.3386238,-0.5969552,-0.5103012,0 +0,user719,-0.06189999,0.2069309,-0.8498423,0.1671135,0.3303859,-0.04835646,0.7787214,0.08749381,0.1731616,-0.7137249,1 +0,user720,-0.4860619,-0.3633456,-0.2455674,0.8443571,0.1262467,0.4881169,0.1431313,-0.1377137,0.5696728,-0.830232,1 +0,user721,0.1740224,-0.9428309,-0.4585803,-0.8455296,-0.1204843,0.2698119,0.2735228,-0.5776703,0.3014244,0.20384,0 +0,user722,-0.6623257,0.982914,-0.4950777,0.2168205,-0.506772,-0.2499746,-0.0450587,-0.6870457,0.4257959,0.6063434,1 +0,user723,0.1691703,-0.4925969,-0.0008321083,0.1233797,0.3741549,-0.8722821,0.1920776,-0.3225302,0.2634086,-0.3395849,0 +0,user724,-0.04729067,-0.255503,0.735521,0.2717672,0.3531806,-0.09450546,-0.2206477,-0.9657558,0.5419101,-0.6023863,1 +0,user725,0.631098,0.2667571,-0.9305742,0.619008,-0.5139959,0.422859,0.5800049,0.2891575,-0.6160065,0.09598756,0 +0,user726,0.8521351,-0.3255275,0.03817929,0.07863361,-0.3889164,0.4726143,-0.9681203,0.410606,0.6325246,0.6885697,0 +0,user727,0.0530451,-0.5400171,-0.2038403,-0.4649127,0.8550546,0.7977693,-0.463979,-0.8794276,-0.5001315,0.6915258,0 +0,user728,0.1645003,-0.7317775,0.3638032,0.05168073,-0.1908591,-0.1429436,-0.5016368,0.9162255,-0.8459071,0.3431965,1 +0,user729,0.4587954,0.3720893,-0.2352843,-0.9340512,-0.6623636,-0.1099806,0.3641129,0.1852873,-0.9791219,-0.6543757,0 +0,user730,-0.5495289,0.6690532,-0.6956673,0.8238456,-0.2746595,0.7491734,0.3350967,0.7796164,0.627157,0.1709366,1 +0,user731,-0.4716562,0.9911776,0.3039597,-0.7770237,0.3370639,0.3631913,-0.5503187,0.153287,0.5028829,0.8088721,1 +0,user732,0.2671697,-0.2834946,-0.2079998,-0.9110708,0.9341948,-0.4037621,0.7588099,-0.3763653,0.7515927,-0.9281054,1 +0,user733,0.07635055,0.4594605,0.2666231,-0.6390668,0.8763689,0.7536308,-0.06573033,0.2485931,-0.161357,-0.3054475,0 +0,user734,-0.9666988,0.04204518,0.7613921,0.305119,0.8658432,0.4329776,0.5520449,-0.2680556,-0.2877977,0.4886235,1 +0,user735,0.8630361,-0.03286324,-0.7799324,0.6026967,-0.1818066,0.652626,-0.2851024,-0.6410937,-0.7944038,0.8003685,0 +0,user736,-0.3049081,-0.4743726,0.02501562,-0.6684074,-0.8538505,-0.2081084,0.5544975,-0.9214644,-0.009448048,0.1889997,0 +0,user737,-0.5885716,-0.2245521,0.2905493,0.2241205,0.009772244,0.4418018,-0.6224194,-0.8823563,0.4114362,0.141454,0 +0,user738,0.04525732,-0.4644188,-0.0148945,-0.1252284,0.1550405,-0.5585359,-0.2993351,-0.1000502,-0.7615563,0.5424637,1 +0,user739,-0.1980293,-0.01024619,-0.3204255,0.4733241,0.350597,-0.2589615,0.8569682,0.749111,0.05430009,0.3741579,0 +0,user740,-0.3856633,0.8199138,0.6815247,0.1639831,0.9952973,-0.1178857,-0.2210792,-0.09376735,0.5029054,-0.5082559,1 +0,user741,0.3598075,-0.5338684,0.4788376,-0.9159929,-0.9880014,-0.1301352,0.7877829,0.3807053,0.6956563,0.1992788,0 +0,user742,0.2902679,0.5207749,0.3678449,0.8597381,0.1583966,0.01353503,-0.2816377,-0.9330926,0.1438863,0.5348564,1 +0,user743,-0.1133223,-0.2100853,0.0750165,-0.7360907,0.7221431,0.07069248,0.8073574,0.6475294,0.3844275,0.06224633,1 +0,user744,-0.4619553,0.2224256,-0.3136521,0.3989881,0.8133943,-0.3681617,0.8727533,-0.5624779,-0.7609964,-0.6906438,1 +0,user745,0.4063725,0.5398806,0.8666859,0.1712285,-0.836276,0.220775,0.01295604,-0.493678,-0.914146,0.1314791,0 +0,user746,-0.3342159,0.9422005,-0.3198208,0.876503,-0.3188999,-0.1758757,-0.2001044,-0.2182506,-0.7624542,0.9714685,0 +0,user747,-0.570952,-0.437068,0.5221522,-0.5903887,0.375305,0.9814202,-0.7909684,-0.3232199,0.6735805,0.6326394,0 +0,user748,0.5184168,-0.01244309,-0.1020035,0.959899,0.8121322,0.4204095,-0.9902827,0.03662775,-0.7512662,-0.1697983,1 +0,user749,-0.4837767,-0.74071,-0.9379467,-0.9060474,0.6040821,-0.9314749,0.6628832,0.4431255,-0.3594094,-0.5388327,1 +0,user750,0.367148,0.7698629,0.6723099,0.5767248,-0.2943091,-0.06693627,0.9877531,0.7642739,-0.153258,0.9189144,1 +0,user751,-0.967645,0.6242113,0.652429,0.8042561,-0.06162116,-0.09147367,0.1528486,0.898914,0.8184067,-3.026007e-05,1 +0,user752,0.6902458,-0.6835408,-0.3965271,-0.751577,-0.5164022,0.3383371,-0.06359408,0.8654552,0.942015,0.6650073,0 +0,user753,0.7048223,0.7527769,-0.8227678,-0.2064547,0.1989189,0.6830891,-0.05730561,-0.9227718,-0.7274621,0.5252578,0 +0,user754,0.2015253,-0.8683855,-0.3484031,-0.07236417,-0.6874662,0.03624426,-0.6550738,-0.4236162,0.08181525,0.6603849,0 +0,user755,-0.3570449,0.06095612,-0.6610061,0.5201902,0.8367784,-0.7561684,0.7157582,0.8996994,0.4839251,-0.9373791,1 +0,user756,0.3359203,0.01953394,-0.753342,-0.5874468,0.684923,0.1059481,-0.4773007,0.3663857,-0.3434687,-0.3787547,0 +0,user757,0.05366037,-0.193913,0.6897762,-0.9937306,-0.07638269,-0.4911414,-0.6231941,0.9869898,-0.2856601,0.3489546,1 +0,user758,0.6960002,0.5209391,0.1351536,-0.9447225,0.691833,-0.9583991,-0.7482208,-0.9797282,0.9837936,0.7541467,0 +0,user759,-0.4995794,0.2877565,0.6104612,0.464234,-0.5059361,0.9630045,0.0210625,0.2826111,-0.1893758,0.9644418,0 +0,user760,-0.4875442,-0.8218237,-0.5455081,-0.9277818,0.2612537,0.398878,0.7409188,0.1722771,-0.264782,0.6945789,0 +0,user761,-0.8535287,0.1899923,0.4394864,0.879123,-0.5828265,0.7907743,0.5868759,0.7998882,0.6109506,-0.07491668,0 +0,user762,0.02876436,0.278934,-0.08557907,0.6872103,0.8311278,0.3261958,0.4707438,-0.5641019,-0.6864929,0.773314,1 +0,user763,0.7796254,-0.1053183,0.246492,-0.8388526,0.1954485,0.9951159,0.4997287,0.7959118,-0.5131893,0.7664735,0 +0,user764,0.2228219,-0.3505472,-0.2938906,-0.7599437,-0.7064576,0.5444051,-0.4788544,0.04848128,-0.5504064,0.6196358,0 +0,user765,0.06206552,-0.6790208,-0.324187,-0.007670674,0.696971,-0.2408266,0.02278869,0.1678425,0.02570945,0.2619375,1 +0,user766,0.6426616,0.8618185,0.4665596,0.7638441,-0.9863581,0.6477419,-0.7853737,-0.8451819,-0.3075931,0.566842,0 +0,user767,0.9179138,0.1750802,0.7311251,-0.4283511,-0.5603081,-0.6637033,-0.9243569,0.1270169,0.4401456,-0.1913645,0 +0,user768,0.4734939,0.09642708,0.9663623,-0.7835502,-0.2932568,-0.7990248,0.4003693,0.2854863,-0.5628543,-0.5966086,1 +0,user769,-0.3120811,-0.6026004,-0.5483349,-0.3613842,0.1686825,-0.910794,-0.0847088,0.0547679,-0.06914945,0.1093057,1 +0,user770,-0.2801155,-0.835166,-0.5893004,-0.955027,0.7902889,0.07733524,0.9326112,-0.1238721,-0.5055543,-0.8172066,0 +0,user771,-0.9121694,-0.08365916,0.647887,0.380433,-0.2979595,0.08308946,-0.8207099,-0.8082811,0.9400511,-0.1048645,0 +0,user772,-0.9522736,-0.1364688,0.9305027,-0.2773772,0.180681,-0.04092915,-0.2969259,-0.5645268,-0.3734932,-0.6914156,1 +0,user773,-0.9898476,0.6856089,0.7785444,0.9047111,-0.05131441,-0.9091297,-0.3490265,-0.05696468,0.6383319,0.7176499,0 +0,user774,-0.02549167,0.7062556,-0.2770965,0.6443423,-0.5758164,-0.8462181,0.9866475,0.8392483,0.3244786,0.9573819,1 +0,user775,-0.4142289,-0.9140432,-0.3831494,-0.8783891,-0.005924718,0.5909091,-0.4241726,-0.1270047,-0.1344896,-0.3820594,0 +0,user776,0.4165249,0.2254895,0.6452303,0.07593961,0.1124096,0.3116453,0.6639296,0.4493573,0.7241859,-0.150871,1 +0,user777,0.6402924,0.6484561,0.4030827,0.5208453,0.1052837,-0.02209376,-0.213457,-0.3790023,0.5620244,0.9288503,0 +0,user778,0.0148191,-0.3511112,-0.8609971,-0.4687778,-0.6306197,0.5723293,-0.215141,0.5497753,-0.4609091,-0.74942,0 +0,user779,-0.06505825,-0.7869536,-0.4567733,0.03583858,-0.07545826,-0.2679452,0.6736469,-0.514015,0.9729197,0.6793307,0 +0,user780,-0.8434842,0.9077461,0.465136,0.6147979,-0.2906342,0.04643135,-0.5505738,-0.9358767,-0.797385,-0.6099824,1 +0,user781,-0.6180329,-0.5812483,0.8113128,-0.892053,0.07507116,-0.494607,-0.2273879,0.3140492,0.3858329,-0.8305056,1 +0,user782,-0.0327033,0.8372577,-0.8043442,-0.1599053,0.8629206,0.6405811,-0.1735045,-0.615101,0.7913264,-0.3206996,0 +0,user783,0.8467615,-0.7757948,-0.9313911,0.8632209,0.1929636,-0.6152316,0.3858321,0.9295785,-0.85537,-0.9449752,0 +0,user784,-0.9132106,-0.8284714,0.988545,-0.09850768,-0.7260099,-0.8115178,0.7153065,0.3912774,0.6583708,0.6947522,1 +0,user785,-0.831178,0.9688722,-0.1527473,0.7677305,-0.8245457,-0.3231747,0.1714218,-0.03871718,-0.1268584,-0.6603147,1 +0,user786,-0.5102834,0.2851614,-0.5923972,0.383411,0.02974192,-0.3714,0.1015903,0.8292779,0.6285551,-0.8823542,0 +0,user787,0.4227097,0.1910625,-0.764797,0.3140456,0.958913,0.2944303,-0.7619942,-0.242337,-0.6850979,-0.6840024,0 +0,user788,0.2224824,-0.2250408,-0.4629711,0.774,0.09907164,0.1856839,0.5482277,-0.05172742,0.5874815,0.6886399,0 +0,user789,-0.8142832,-0.1938996,0.5427565,0.4386885,-0.2784251,-0.3297991,0.3533695,0.8495497,0.6123487,0.8717925,0 +0,user790,0.9231303,-0.5211811,0.8456642,-0.2217205,-0.5470231,0.2574348,0.2590683,-0.9597258,0.1255263,-0.7195606,0 +0,user791,0.7349381,-0.04686449,-0.008479285,0.8462182,-0.6396746,-0.4154381,0.2891465,-0.8794504,-0.6773005,0.3832187,1 +0,user792,-0.6678119,0.9960927,-0.01775718,0.3178115,0.1387484,-0.5390248,-0.05975459,0.6494379,0.2232993,-0.2031242,1 +0,user793,-0.04810529,0.757753,-0.2399149,-0.5345102,-0.7158953,-0.4163694,-0.2701879,-0.5238277,0.4390334,-0.9462466,0 +0,user794,0.5145636,0.8478172,-0.7619873,-0.9926345,0.5557739,-0.4203222,-0.2111249,0.9164614,-0.1904898,0.1496922,0 +0,user795,0.55501,-0.3544545,0.6883523,0.5578678,0.4322907,-0.9946197,0.461391,-0.3020808,0.6728929,-0.5834884,1 +0,user796,-0.9860398,-0.9212678,0.4358982,0.4578192,0.9810756,0.342804,0.7526008,0.6440148,-0.5352572,0.3156909,1 +0,user797,0.1572251,0.7096357,0.7045723,0.7712096,0.5694158,-0.7725803,0.00350146,-0.9287205,0.501917,-0.2834658,1 +0,user798,0.4729238,0.8206258,0.4194773,-0.8704833,0.8719826,-0.658323,0.5370341,0.8249361,0.1130385,0.2251471,1 +0,user799,0.4874541,0.1751593,0.4022605,0.674269,-0.3121812,0.5437792,0.15297,-0.07049891,-0.09811149,0.7190823,0 +0,user800,0.845144,-0.8929647,-0.8437625,-0.5901746,-0.2619017,-0.6833742,0.9187927,0.1260474,-0.5672324,0.8258398,0 +0,user801,-0.8071918,0.9854598,0.8301769,-0.8255102,0.6622716,0.4190123,0.4696453,-0.2989359,0.6074842,0.4079405,1 +0,user802,0.5752848,-0.9084999,0.05014744,0.05470199,0.3898594,-0.3731313,0.3322601,0.12122,-0.1580604,-0.3857822,1 +0,user803,0.8928704,-0.02943354,-0.9132598,0.1324482,0.9187793,0.2756966,-0.3781333,0.5615206,0.05927441,-0.8655758,0 +0,user804,-0.7970394,0.6710687,0.6087213,-0.9207992,-0.3890428,0.5098826,-0.8793812,0.6440994,0.2458161,0.1255904,1 +0,user805,-0.4502069,0.7977557,0.7730509,-0.3009557,0.814043,-0.2193494,0.3189076,-0.03953166,-0.8335818,-0.4284003,1 +0,user806,-0.5213584,0.05652328,-0.2964092,0.2540592,-0.08714545,-0.1333943,0.1976941,-0.5654841,0.9247848,-0.2476351,0 +0,user807,0.6194855,-0.1034418,0.2539516,0.1551405,0.7233667,-0.1784722,0.7845484,0.09345667,-0.02999802,0.9747194,0 +0,user1033,-0.146891,0.5838859,0.08294423,-0.09394704,-0.9333581,-0.8352637,0.8839263,-0.8691067,-0.9854051,-0.6773,0 +0,user1034,-0.7932195,0.1458002,-0.9355195,-0.110186,0.3635793,0.2590424,-0.7082051,0.5072927,0.7389611,-0.500203,0 +0,user1035,0.2008109,0.2687709,0.9292257,-0.055815,0.3442201,0.2033448,0.533234,-0.4919755,0.9282551,-0.7802012,0 +0,user1036,0.9353882,0.7190607,-0.1396716,0.2715474,-0.3612369,-0.7573155,-0.9000194,0.3910798,0.9692459,-0.9373726,0 +0,user1037,-0.7815332,0.2217718,0.001470417,-0.6667491,-0.2863088,0.4931202,-0.2719762,-0.995224,-0.6997377,-0.1031965,0 +0,user1038,0.7381793,-0.7842273,-0.1180828,-0.4714603,-0.1466879,-0.8072901,0.8589702,-0.02905488,-0.618805,-0.906545,1 +0,user1039,-0.6813512,-0.9853979,0.01338358,-0.4166553,-0.07876576,0.1525718,0.7234594,-0.196626,0.7900173,0.5342445,1 +0,user1040,0.6052074,0.9504697,-0.7308718,-0.3990123,-0.1443725,0.1914427,0.4351608,0.4851562,-0.8006305,0.7068255,1 +0,user1041,-0.373626,-0.156324,0.1935768,-0.8249629,0.5865924,-0.531826,-0.6976883,-0.5790015,-0.4068776,-0.8945963,1 +0,user1042,-0.2086316,0.2377687,-0.5967477,0.06539749,-0.6399878,0.6528327,0.3089996,-0.795509,-0.684089,0.9176774,0 +0,user1043,-0.08861922,-0.0659448,-0.5823891,0.551176,0.1996511,0.3837014,0.8954804,0.1263079,0.7299812,0.8935591,1 +0,user1044,0.06468379,-0.5970231,-0.6494278,0.2331116,-0.6415168,0.4921578,-0.6880892,0.9446749,-0.8555999,-0.9382523,0 +0,user1045,0.4900165,-0.3897079,-0.9060469,-0.3150085,0.401514,0.1400776,-0.5209005,-0.9801353,0.7158504,0.717202,0 +0,user1046,0.9400842,-0.9484608,0.7577457,0.790651,-0.5113654,0.7525824,0.7850383,0.831856,-0.05824643,-0.1877736,0 +0,user1047,0.9175069,0.8832299,-0.8933726,0.05741681,0.3676337,0.1984893,0.2017949,-0.1584783,0.4793884,-0.6649096,0 +0,user1048,-0.8229434,0.9764363,-0.07833977,0.7486078,0.4806182,-0.9766376,0.8099211,0.04955569,0.8133668,0.4461165,1 +0,user1049,-0.9589833,-0.994581,-0.7635468,0.6267067,0.5791568,-0.4710404,-0.6780936,-0.2825208,0.4202216,0.8716666,0 +0,user1050,-0.8938533,-0.8783477,0.7198965,-0.9208625,-0.5118774,0.3231431,0.9454617,0.1458013,-0.3292917,-0.00212238,0 +0,user1051,0.7957662,-0.7573129,-0.1774771,-0.5842162,-0.2936475,0.9369703,-0.2775263,0.1278978,0.2962957,-0.5256108,0 +0,user1052,0.7990419,-0.3404941,0.2552517,0.8483529,0.0559496,0.1518345,-0.8963433,0.5066884,-0.2238036,-0.78913,0 +0,user1053,-0.920566,-0.7374714,0.428492,0.7654849,0.5234689,0.6487007,-0.3820667,-0.454389,-0.6248789,-0.2241617,1 +0,user1054,-0.1617122,-0.5755215,0.1768133,-0.8750985,-0.9141294,-0.3010301,0.203395,0.761155,0.05730702,-0.6371678,0 +0,user1055,-0.6323983,0.8527212,-0.1249954,0.2710883,-0.3077137,-0.2599628,0.1432429,0.7009895,-0.006236679,-0.7283292,1 +0,user1056,-0.5602933,-0.1198975,-0.5146378,0.9256347,-0.457135,0.4834975,0.8453167,0.8998655,0.4719988,-0.9400519,0 +0,user1057,0.5567812,0.3407757,-0.7302123,0.7101169,-0.5904454,0.3617416,0.1621005,0.83175,0.3749711,-0.989505,0 +0,user1058,0.2455826,-0.5167613,0.6472486,-0.829795,-0.5831916,-0.02371737,-0.7293701,-0.03121919,-0.2710733,-0.196635,1 +0,user1059,-0.8527243,0.4315983,-0.2853168,0.6560542,-0.9522608,0.50141,0.1762956,0.199125,0.8064149,0.1058043,0 +0,user1060,-0.07702219,-0.8785026,-0.3764497,-0.1181316,-0.7226138,0.6449991,0.986234,-0.244034,-0.5052318,0.1144119,0 +0,user1061,-0.1820439,-0.3957821,-0.6947262,0.758711,-0.04187942,0.1983423,0.3522064,0.1616763,-0.6148051,0.1683684,0 +0,user1062,-0.08012697,0.2851802,0.2618062,0.9160759,0.5384089,-0.7420067,0.2306846,0.3914268,-0.4654274,0.4947966,1 +0,user1063,0.2036538,0.9884526,0.5966614,-0.4618734,-0.6219747,0.1746372,0.3278292,-0.828357,0.6775304,0.3080294,0 +0,user1064,0.6710651,-0.8118961,0.3882181,-0.3352361,0.02476252,0.3630786,0.2361327,0.2925696,-0.6002102,0.4910684,0 +0,user1065,0.1266535,-0.5690195,0.3262867,-0.1941101,-0.09801178,0.5170356,0.5224795,-0.1012805,-0.7264663,0.9945936,0 +0,user1066,-0.5955353,0.2572234,0.5258872,0.4823116,0.7222454,-0.622018,-0.1389368,-0.3203326,0.6057855,0.5278283,1 +0,user1067,0.6064534,0.9071646,-0.7514536,0.9363113,0.6635256,0.605763,0.3361134,-0.3163505,-0.6309643,0.5536958,0 +0,user1068,0.3451203,0.6527523,-0.6722429,0.1391408,0.6156794,0.01015581,-0.7494967,-0.09650441,-0.426204,-0.1086029,0 +0,user1069,-0.857356,0.4729962,-0.5921956,-0.9891487,-0.4244425,-0.4293081,-0.2799666,0.6506125,0.9869805,0.6212833,0 +0,user1070,0.9251022,0.9217667,0.26193,-0.480344,-0.4152401,-0.2416652,0.05957278,0.4870235,-0.840947,0.08794029,1 +0,user1071,-0.04967225,0.603222,-0.4031147,0.7401285,-0.5286931,-0.7984014,0.6856641,-0.6113482,-0.2268345,-0.4017774,0 +0,user1072,-0.230982,-0.6833278,0.6013812,-0.8141117,-0.8378501,0.03886584,0.02234509,-0.928389,-0.4198971,0.726687,0 +0,user1073,-0.2835294,0.1595354,0.6651823,0.5850535,-0.05522795,-0.5888325,-0.6314276,0.6915145,-0.525036,0.005617664,1 +0,user1074,0.8617085,-0.4627228,0.01449618,0.2913046,0.670958,0.5852999,0.5811445,0.5149597,-0.4968534,-0.5082183,1 +0,user1075,0.8337018,-0.2803509,0.9519534,0.419,-0.4793669,-0.4689764,0.3342558,-0.9837141,-0.2754971,0.7884347,0 +0,user1076,-0.7935129,0.7698275,0.7591355,-0.729955,-0.6537139,0.5512451,-0.1523281,0.7113792,-0.8091856,-0.2771803,1 +0,user1077,0.8017927,-0.4111836,-0.2277581,0.08195551,-0.8404074,0.3378823,0.3661827,0.3468157,0.4449002,0.3040081,0 +0,user1078,0.7512087,-0.3971209,-0.9414192,-0.5235832,0.8882669,0.729513,-0.4639493,-0.1421924,-0.7961087,-0.8764748,0 +0,user1079,-0.6164562,0.7462637,-0.3192043,-0.9813472,0.8269043,0.5746075,-0.342407,-0.2390652,-0.9958188,-0.8310638,1 +0,user1080,0.8428094,-0.4057646,0.008695064,-0.2913378,0.7387493,0.8668418,0.6880892,-0.9357051,-0.1348782,0.1756747,1 +0,user1254,0.2912882,-0.1849557,0.2388303,-0.4462187,0.9139444,-0.5111146,0.5679703,-0.3274799,0.8005682,-0.2219899,1 +0,user1255,-0.6917171,-0.9945851,0.9196139,-0.3805327,-0.2632819,0.188228,0.5786624,0.1885548,0.4635907,0.6520012,1 +0,user1256,-0.8197406,0.5978655,-0.6488857,0.9920002,-0.5916532,-0.2546032,-0.1197947,0.1472524,-0.03320263,-0.05491735,1 +0,user1257,0.5689781,-0.7627801,0.9727248,-0.770497,-0.5928521,-0.3070041,-0.7077939,0.7637644,0.573144,-0.6361306,1 +0,user1258,0.7188828,-0.04036782,0.2856193,-0.3265308,-0.2173847,0.1228931,0.9102324,0.6236144,-0.1851158,0.05267707,1 +0,user1259,-0.3546105,0.5947769,-0.3252384,-0.9551976,-0.007053915,-0.6434371,0.4030307,-0.7819499,0.6726857,-0.9553167,1 +0,user1260,-0.2367949,0.3166965,-0.262732,-0.5997704,-0.7096466,0.9689579,-0.3838929,-0.4471032,0.2113785,-0.904758,0 +0,user1261,0.734643,0.7296537,0.2576913,-0.153586,-0.4447029,-0.4726025,-0.9241273,0.5392392,-0.5474574,-0.6045099,1 +0,user1262,-0.7164072,-0.5371613,-0.9177355,-0.8411634,-0.08155357,0.3904679,-0.7407258,0.5430956,0.5506009,0.06269496,0 +0,user1263,0.7092961,-0.1042349,0.1894423,0.1435993,-0.6020449,0.9697487,-0.7393579,-0.5759738,0.7597218,-0.3600047,0 +0,user1264,-0.2198993,0.7672421,0.9774717,-0.7518654,0.2040007,-0.689951,0.9678358,-0.4424845,0.4192718,0.6477671,0 +0,user1265,0.8654324,-0.6096019,0.9787138,0.1990142,-0.9841562,0.6290803,0.4479532,0.7330141,0.217629,-0.6305446,0 +0,user1266,0.9629065,0.6616245,-0.9507782,0.7968348,0.9751901,-0.5188243,-0.1169308,0.1413846,-0.05072973,-0.08732025,0 +0,user1267,0.1145829,-0.2436397,0.9310116,-0.5622638,0.9813593,0.2134399,-0.1875724,0.8393106,-0.5953484,-0.7783885,1 +0,user1268,0.4172312,-0.1440662,0.7267726,0.5473013,-0.1326261,0.2235346,0.488876,0.7540542,-0.9895928,-0.7338155,1 +0,user1269,0.8139287,0.3913481,-0.921297,-0.9003562,-0.9400167,0.9036236,0.3144873,-0.4231147,-0.6645438,-0.5254588,0 +0,user1270,-0.7716017,-0.441663,0.6499566,0.5641078,0.07259588,-0.7915385,-0.7156606,-0.6775673,-0.1242274,-0.7806005,1 +0,user1271,-0.9999062,0.9170173,-0.07459185,-0.1520057,0.2118208,0.6137921,0.4496521,0.4964097,-0.00957416,-0.7055052,0 +0,user1272,-0.6901467,0.3033117,-0.7998142,0.1652228,-0.6482228,0.4925164,0.7323772,0.7096529,0.0708182,0.5917491,0 +0,user1273,-0.3844736,0.02419597,0.1050626,-0.246893,-0.1325591,0.07317353,-0.4342582,0.4088758,-0.07040727,0.6364656,1 +0,user1274,-0.9366935,0.2534785,-0.3614565,-0.9775473,0.3785077,-0.9794096,-0.2817362,0.8242781,0.6820859,0.5625535,1 +0,user1275,-0.4658336,0.9983236,-0.5995453,-0.5504306,0.9841563,-0.666225,0.03876019,0.8663083,0.04178016,0.7552048,1 +0,user1276,-0.9299221,0.1057449,0.5067275,-0.6025266,0.7225124,-0.3420625,-0.4735626,0.3002754,0.6734282,-0.3732181,0 +0,user1277,0.2950954,0.08627363,-0.802821,-0.5648465,0.02735582,0.1546818,-0.4925654,-0.1363766,0.03827959,0.2023101,0 +0,user1278,-0.04226175,0.5946725,0.7265338,-0.5221857,0.1593842,-0.7473872,-0.2038172,0.3636829,0.7546333,0.8302987,0 +0,user1279,0.4263999,-0.4493937,-0.225657,-0.7344547,0.7525674,0.09480759,0.2557555,-0.6269992,-0.3318489,-0.673573,0 +0,user1280,0.174394,-0.03949112,0.1716447,-0.2508034,0.573037,0.3179976,-0.5813781,-0.3007151,-0.6889752,-0.7341827,1 +0,user1281,-0.4657312,-0.7548003,-0.238866,0.9923358,-0.3303809,-0.9812506,-0.0423409,-0.1534678,0.3705112,0.3276855,1 +0,user1282,-0.1305201,-0.3878312,-0.9992456,-0.7653294,0.5526131,0.4682147,-0.5313304,-0.5083953,-0.2923718,-0.7705609,1 +0,user1283,-0.3719137,0.6498416,-0.9601284,-0.04642786,-0.984535,0.2747187,-0.07330032,0.1611003,0.4329383,-0.4115258,0 +0,user1284,0.1462437,-0.05644232,-0.1736625,0.4589698,-0.0316698,-0.5461968,-0.2733164,-0.9961002,-0.7416065,-0.6512291,0 +0,user1285,-0.8392319,0.4272132,0.2395847,-0.2115481,0.4665575,0.9571002,-0.9633601,0.1641249,-0.4918036,0.007449168,1 +0,user1286,-0.06363076,0.6552564,0.9594855,0.5730395,-0.2478169,-0.5370533,-0.4946379,-0.6503449,-0.1034711,-0.7595246,0 +0,user1287,0.3265031,-0.4585768,0.1774518,0.45097,0.376677,0.1992,0.6068889,0.1511522,0.2251908,0.2938535,0 +0,user1288,0.7297462,0.664433,0.2123094,0.01795488,0.8737054,-0.3499039,-0.671154,-0.07211074,-0.9186596,0.3713185,0 +0,user1289,-0.344748,-0.3851114,0.2451048,-0.7534913,0.5347984,0.5858398,-0.5844055,0.9732695,0.7114131,0.2931525,1 +0,user1290,0.9718926,-0.8638,0.8522134,0.4957725,-0.6303769,0.5557629,0.009919549,0.3692023,-0.1021234,0.3385368,1 +0,user1291,-0.5070487,-0.01887041,0.9495774,0.4181845,-0.8359412,-0.3809461,-0.05504687,0.4807861,0.2927189,0.4665605,1 +0,user1292,-0.610105,-0.6554577,-0.4972039,0.09292272,-0.9099045,-0.8867627,-0.5085328,0.5125087,-0.8360443,0.6886426,1 +0,user1293,-0.7445146,-0.4009613,0.9344779,0.6546091,0.2880696,-0.0537692,0.2691938,-0.08770206,-0.5515225,-0.5987683,1 +0,user1294,-0.7977526,0.8768947,0.1390197,-0.4382163,-0.4379861,-0.4111973,0.2055952,0.9048123,0.05244064,-0.8934442,1 +0,user1295,0.1699958,-0.8882156,-0.5197322,0.3410573,0.2940962,-0.5767137,-0.540697,-0.9299757,0.5832275,0.3364097,0 +0,user1296,-0.8790822,-0.01056319,0.9131916,-0.1463767,0.3039134,-0.4246889,-0.282853,-0.354688,0.6661065,-0.2293129,1 +0,user1297,-0.8348461,0.5385192,0.1882415,-0.6413815,-0.462796,0.06997837,-0.9113356,0.04619689,-0.9982891,0.01923557,1 +0,user1298,-0.7154213,-0.1318553,-0.5887205,0.7787935,0.2754555,0.6367262,0.2717306,0.9093349,0.9878791,0.5580212,1 +0,user1299,0.538149,0.8453706,0.6399642,-0.5990755,-0.8287127,0.7988457,-0.7939771,-0.6006338,0.6765137,0.03687165,0 +0,user1300,0.9790826,-0.07013274,0.2669445,-0.5417377,-0.4028127,-0.02639804,0.4031517,0.6230822,-0.6628329,0.4937768,0 +0,user1301,-0.487023,0.4264817,-0.938764,0.3429014,-0.6519486,0.8451876,0.5560701,-0.7682324,-0.1363484,0.7774206,0 +0,user1302,0.5382427,0.7623879,-0.4346276,0.2489189,0.3831082,0.4126378,0.655675,0.8957759,-0.3330605,0.3313665,0 +0,user1303,-0.7110641,-0.766821,0.4671303,0.623485,-0.05103548,-0.5338816,0.1355289,0.3327351,0.4079853,0.08552591,1 +0,user1304,0.1285035,-0.5493223,0.1662986,-0.9039916,0.2154923,-0.08163886,-0.8781881,0.6406435,0.7932444,0.4138862,0 +0,user1305,0.6015492,0.01586638,0.2039159,0.2713716,-0.2383841,0.4332281,-0.6260612,0.7200539,-0.6509746,-0.10608,0 +0,user1306,-0.1768976,-0.7684975,0.867585,-0.9269455,-0.0668792,-0.2001066,-0.8257109,0.1990434,-0.5502345,-0.1592693,0 +0,user1307,0.1985814,0.5564226,-0.3269739,-0.5065182,-0.06199527,0.5762987,-0.3517507,-0.05908116,0.4666725,-0.9593319,0 +0,user1308,-0.1033554,-0.89786,0.4010949,0.706525,0.7889717,-0.41209,-0.1186266,-0.4163227,0.387305,-0.9037699,1 +0,user1309,0.7808406,0.826175,0.5941188,-0.4491312,-0.907495,0.05250621,-0.02952803,-0.4372738,-0.7956012,-0.3289706,1 +0,user1310,-0.3750188,-0.8929711,0.4473691,-0.2409728,-0.3094279,-0.3288937,0.9040049,0.3139196,-0.8651764,-0.6329049,0 +0,user1311,-0.9289614,0.0626489,-0.4272605,-0.5442784,0.3620087,0.9059076,0.2999953,0.2829622,0.6983298,-0.6379525,0 +0,user1312,-0.6848906,-0.9286253,-0.6447472,-0.4567954,-0.2378758,0.0712556,0.9281311,0.4092585,0.57491,0.9987149,0 +0,user1313,0.4944611,-0.2808022,0.4481235,-0.006302198,-0.7568147,-0.860679,-0.6273255,0.8055244,-0.1575482,-0.4034658,1 +0,user1314,-0.3008751,-0.2875095,-0.3873889,0.4092938,0.3774737,0.1806262,-0.7733051,-0.5559375,0.131268,-0.04947833,0 +0,user1315,0.4613531,0.01493243,0.1815903,-0.9978256,0.7304544,0.5250588,-0.3451854,0.4131582,0.8333035,-0.6525143,0 +0,user1316,0.6552292,-0.8535891,-0.3122918,0.7821497,0.7097428,-0.9035788,-0.5906856,-0.03035078,0.3506482,0.6039833,0 +0,user1317,0.6354942,-0.6322531,-0.4279034,-0.01766676,-0.8703432,0.6435729,-0.2679429,-0.2062824,-0.9722031,0.1909971,0 +0,user1318,-0.2121438,0.5563556,-0.6409579,0.4531444,0.1071314,-0.2757412,-0.7382965,-0.4356895,0.05849433,0.6413392,0 +0,user1319,0.3849754,0.810844,0.9000176,-0.1998954,0.5834482,-0.2534827,-0.2618396,0.8975385,0.4319886,-0.02469812,1 +0,user1320,-0.7092538,-0.01736448,0.8172014,0.2288419,0.6644552,0.2294127,0.1476516,-0.2330129,0.73921,-0.5158504,1 +0,user1321,-0.2402511,0.6925557,-0.7887445,-0.05108309,0.4767545,-0.7199783,0.2716231,0.9335128,0.9563709,-0.02012398,1 +0,user1322,0.8779267,-0.2080264,0.849595,-0.781711,0.747507,0.3655712,0.6831136,0.3783245,-0.2752925,-0.5581376,1 +0,user1323,-0.3193588,0.3271778,-0.6800025,-0.6782353,0.7545508,0.3426499,0.6391188,-0.7205042,0.9031658,-0.8272078,0 +0,user1324,0.01523426,-0.7084056,-0.8542667,-0.396474,-0.2351759,0.2262525,-0.4591831,-0.1541893,-0.5951516,0.3811077,0 +0,user1325,-0.9198259,-0.3311318,-0.01138526,-0.2199272,-0.6904791,0.9543739,-0.1112912,0.2831368,0.7771481,-0.4515818,0 +0,user1326,0.850637,0.4389622,-0.1997347,0.662822,0.04864696,0.7659362,-0.9015782,-0.6504799,0.4863933,0.5092018,0 +0,user1327,0.136152,0.2810312,-0.941075,0.4571493,-0.9312625,0.8015636,0.2579638,0.4911227,-0.9290452,-0.8482051,0 +0,user1328,-0.7546719,-0.7926126,-0.8231438,0.1386913,-0.1532751,0.02435228,-0.0226268,-0.6706663,0.778859,0.5676538,0 +0,user1329,-0.8647843,-0.6928931,0.2115448,0.4416155,-0.6758975,0.4026624,0.3701524,-0.741145,0.4742724,0.06722298,0 +0,user1330,-0.325699,0.1264018,0.6988892,0.8580738,-0.7599752,0.6004093,0.4639867,0.890489,0.7474685,0.1886665,0 +0,user1331,-0.7755893,0.1372547,0.4438007,0.5969536,0.4439122,0.9979542,-0.6194751,0.9524158,-0.8839739,0.0614306,0 +0,user1332,-0.3518073,0.7335886,0.2727808,-0.2154831,-0.3278462,0.24785,-0.07377753,-0.5093774,-0.662076,-0.1553564,0 +0,user1333,-0.7874563,-0.1112103,-0.7357384,0.1069927,0.623133,0.01304703,0.1196618,0.7862648,-0.585592,-0.479967,1 +0,user1334,-0.4866534,0.3704336,-0.08906896,0.2204386,-0.6071233,-0.5359274,0.5160539,0.2851509,0.5240114,-0.8530435,0 +0,user1335,0.7766962,-0.8157338,-0.5609207,-0.1194747,0.8876461,-0.8337889,0.04803437,-0.8687339,-0.8688316,-0.7414702,1 +0,user1336,0.8140929,0.9046561,0.4681775,-0.6216358,-0.6152512,-0.5537248,0.4936006,0.5063188,-0.2365666,0.413953,0 +0,user1337,0.336449,0.6019362,-0.221484,0.2934931,0.3259975,0.263966,0.690343,-0.5158057,0.9737769,-0.01231281,0 +0,user1338,-0.02472245,0.7406888,0.1121054,0.3740071,-0.1743491,0.7425098,0.6962837,0.07218492,0.5978409,-0.7008021,0 +0,user1339,-0.2892625,-0.9932039,-0.1307276,-0.9151108,-0.8262795,0.03418511,-0.625026,-0.910004,-0.8492616,0.5101831,0 +0,user1340,0.1172896,0.4281112,-0.6273651,0.8443618,0.4185025,-0.6835278,-0.339185,0.04692052,-0.8218243,0.6587166,0 +0,user1341,0.6002588,0.8477178,-0.4405255,-0.8669657,0.516223,-0.5863839,0.6002886,-0.6138955,0.7326645,-0.3337069,0 +0,user1342,-0.2182239,0.06944501,0.4420119,-0.4593891,0.5357292,-0.05990734,0.6749692,0.3729582,0.8490681,0.8722306,1 +0,user1343,0.4323989,0.499486,-0.2721124,-0.6124336,-0.8193733,0.3877278,-0.4110539,-0.543821,0.7530857,0.6574315,0 +0,user1344,0.09471993,-0.4330845,-0.992402,0.1267321,0.7594083,-0.4470629,0.972963,-0.8083711,-0.4248837,0.2628272,0 +0,user1345,0.480901,0.7819355,-0.945377,0.9499046,-0.08679706,-0.8792811,0.9016642,0.8170207,-0.01966387,-0.1772478,0 +0,user1346,-0.106248,-0.4855816,0.9094779,-0.6102592,0.9110811,-0.08721335,0.2437607,0.8693372,0.5863892,-0.9950828,1 +0,user1347,-0.2500509,-0.2866735,-0.3046938,-0.09111818,0.4691511,-0.3506417,-0.6177226,0.1612781,0.9257645,-0.1331894,0 +0,user1348,0.1163952,-0.8503176,-0.3732804,-0.06776214,0.04285976,0.7642918,-0.3662788,-0.3892617,0.008133045,-0.9862507,0 +0,user1349,0.6816082,-0.929226,-0.73148,0.8428852,0.01821257,0.6370455,0.5054642,-0.5663523,-0.3551165,0.6462565,0 +0,user1350,-0.8650755,-0.4758296,-0.4046762,0.7089864,0.05259934,0.3958756,0.1204379,0.05881659,0.3577531,0.8421125,0 +0,user1351,0.4071414,0.1323179,-0.556079,-0.8389202,-0.292685,-0.006295564,0.7813728,0.3777254,-0.2526569,-0.5021011,0 +0,user1352,-0.5586429,0.7633297,-0.5202246,-0.2081979,-0.5050329,0.9170672,-0.2229127,-0.6328395,-0.3987456,-0.3738675,0 +0,user1353,-0.9871488,0.316144,-0.5550812,0.9272754,-0.1998937,-0.2385532,-0.1964486,-0.5628589,-0.9175395,-0.7160251,1 +0,user1354,-0.9122174,-0.5405043,-0.2360815,-0.5171555,-0.5381342,-0.6636456,0.4204916,0.6572212,-0.3494911,-0.3293089,1 +0,user1355,0.4565913,-0.9450759,-0.3744912,0.395328,0.2597912,0.1433197,0.3179041,0.2129712,0.006102794,-0.9927598,1 +0,user1356,-0.9069747,0.9850122,0.4335336,-0.2926518,0.1096272,-0.2841793,0.6922602,0.7202779,0.8596086,-0.1676069,1 +0,user1357,0.9384195,0.8984579,0.5641838,-0.8543336,0.5105127,-0.8977094,0.5189134,-0.9932587,-0.8630978,-0.8201071,1 +0,user1358,-0.4072566,0.3359553,-0.3155663,-0.1475227,0.3285287,-0.05511668,-0.424132,-0.2959061,0.07705761,-0.8409649,0 +0,user1359,-0.6616466,-0.8076004,0.6103898,0.8460395,0.9563521,0.740173,-0.3303666,-0.9503884,0.6384676,-0.5999531,0 +0,user1360,-0.9263648,-0.7944352,-0.2242715,0.587282,0.8346152,0.504953,-0.1109342,-0.7344038,0.6111745,0.2471159,1 +0,user1361,0.2670443,-0.5376429,-0.6166771,-0.2894489,0.5685535,-0.4547074,-0.9601453,-0.4054171,-0.1754739,0.3477016,0 +0,user1362,-0.4372359,0.3296543,0.0541905,0.4429931,0.4002643,0.7381272,0.05015839,-0.9979725,0.7544937,0.4614775,0 +0,user1363,-0.2781721,0.9391534,-0.9514907,-0.6282011,-0.493231,-0.247197,0.8152883,-0.2437811,0.9490986,-0.9082405,0 +0,user1364,0.479588,0.3511468,-0.3524155,0.8175438,0.1916865,0.5583396,0.1595165,-0.6191523,0.2389341,0.8677346,0 +0,user1365,0.07611075,-0.2999121,0.9651215,-0.3365683,0.793141,-0.7978002,-0.4337877,0.2871784,0.2785052,0.608434,1 +0,user1366,-0.5014759,-0.8765804,-0.5124113,0.2523242,-0.6055849,-0.08098587,-0.1366773,-0.1125151,-0.9197331,-0.6497106,0 +0,user1367,0.2936809,0.2558029,-0.884238,-0.804092,0.5764353,-0.9953852,-0.346883,0.8871664,-0.9976325,0.2816876,1 +0,user1368,-0.5874403,-0.6979759,-0.2563624,0.9569247,0.1191384,0.4661658,-0.7434447,0.7713726,0.252282,-0.4038788,1 +0,user1369,0.4738016,0.8641085,0.5996941,-0.3736687,0.220066,-0.3384761,-0.4403936,0.9596698,0.6781078,-0.3505127,1 +0,user1370,-0.9955816,0.262599,-0.0149656,-0.7192028,0.7501559,0.03879987,0.02809102,0.9771625,-0.8468941,-0.2081293,1 +0,user1371,0.5298493,0.7301353,0.1162724,0.8012865,-0.462359,0.7826381,-0.08262974,-0.1817068,0.4304577,-0.7451622,0 +0,user1372,0.07406042,0.7118263,-0.8408314,-0.2406344,-0.263711,0.07514003,-0.8401051,-0.6542256,0.4107724,0.3157804,0 +0,user1373,-0.2138055,-0.667956,-0.5729537,-0.178592,0.2858851,0.9788925,-0.2969397,0.3501207,-0.997826,-0.3358988,0 +0,user1374,-0.03775174,0.2296213,0.8441601,-0.8111471,-0.2817323,0.1703659,0.5063163,0.2744722,0.1835434,0.9122693,0 +0,user1375,-0.8312197,-0.7212582,-0.8332334,0.8860978,-0.5043028,0.6280771,-0.867142,-0.4625967,0.9858887,-0.4213924,0 +0,user1376,-0.7329045,-0.8860205,-0.5183306,-0.2286873,-0.800912,-0.9003886,-0.3952756,0.1671414,-0.01748986,0.4868535,0 +0,user1377,0.8560003,0.7440397,0.753638,-0.4214063,-0.3706511,-0.9168475,-0.249923,0.1438094,-0.2300673,0.9171865,1 +0,user1378,-0.08127053,-0.007931728,-0.1379272,-0.2050204,0.9648483,-0.7225646,-0.4848646,0.6986814,0.9116532,0.4454182,0 +0,user1379,0.3834907,-0.7363381,0.1083889,0.7035505,0.2419478,0.8639032,0.2384457,0.7778796,0.9906432,0.5006028,0 +0,user1380,0.5376085,0.8148137,-0.9778421,-0.5785212,0.6475614,0.720198,-0.7444588,0.5774571,0.4148162,0.563443,0 +0,user1381,0.05365398,0.5162387,0.4573966,-0.496034,0.01744768,0.673311,0.6355732,-0.242502,0.2694063,0.2875306,0 +0,user1382,-0.209368,0.3959798,0.5523099,0.8646303,0.9492628,-0.1423924,0.01981852,0.155605,-0.2620137,0.9985018,1 +0,user1383,0.9789656,0.5781434,-0.4980667,0.2132809,-0.8574714,0.6372652,0.03262852,0.9446175,-0.9839294,-0.8104245,0 +0,user1384,0.06650519,-0.1676173,0.9023154,-0.5687586,0.817554,-0.5652423,-0.5608753,0.1946391,0.3518668,0.5715055,1 +0,user1385,-0.1215854,0.8554755,-0.6837716,-0.6525252,-0.5888715,0.193962,-0.5596899,-0.1871738,0.3884951,-0.3308071,1 +0,user1386,0.435557,0.6330674,0.1274421,-0.391391,0.4023198,-0.2194151,-0.6494673,0.1575887,0.02217343,-0.8031843,1 +0,user1387,0.1595305,-0.1826051,0.335849,0.1385896,-0.07281877,0.1505784,-0.8686151,-0.08508297,0.2114754,-0.5961014,0 +0,user1388,-0.1831659,0.7539334,0.8804121,-0.5068588,0.9216412,0.2962526,0.9592235,-0.1804325,0.5253973,-0.1509142,0 +0,user1389,-0.9716997,-0.0309773,0.8118758,0.4610862,-0.2691515,0.7254682,-0.07359938,0.8616826,-0.900769,-0.6441492,1 +0,user1390,0.4978839,0.009794509,-0.05376122,-0.01537094,-0.1164667,-0.1092486,-0.1989816,-0.03547136,-0.1500569,-0.1960545,0 +0,user1391,-0.1095307,0.9594982,-0.3438593,-0.9195768,0.7562564,-0.1987944,-0.1517106,0.08516371,0.1365718,-0.9037982,1 +0,user1392,0.2953446,0.4313798,-0.8048013,-0.8283627,-0.700598,-0.7292392,-0.03374468,-0.5437345,-0.07624286,0.7035524,0 +0,user1393,-0.9393519,-0.6605512,-0.9995707,-0.5723779,-0.7162024,-0.3711214,0.8511768,-0.0334439,-0.3955632,-0.734577,0 +0,user1394,0.6122972,0.8986516,-0.29535,-0.547778,-0.7369746,0.5540086,-0.3364223,0.8413826,0.08567039,-0.8120387,0 +0,user1395,-0.2250673,-0.2174734,-0.1572167,0.9891811,0.4910885,0.8291004,-0.8742282,-0.1628869,-0.8373088,0.571287,0 +0,user1396,0.1367588,0.03953667,0.9655508,0.09105376,-0.9230615,-0.1689216,-0.582611,-0.7462655,0.882942,0.8738569,0 +0,user1397,-0.8891787,-0.9779288,0.1922386,0.7045462,-0.3425595,-0.5269773,0.5269003,-0.2711325,0.1659373,-0.4617493,1 +0,user1398,-0.9313864,-0.9616705,-0.04145471,-0.8149109,0.06752386,0.8337152,-0.2211112,-0.2757204,-0.8349413,-0.1470254,1 +0,user1399,0.5493185,0.3415608,-0.2908116,0.04797847,0.196077,-0.7027558,-0.3260557,-0.9748929,0.135224,-0.5300219,0 +0,user1400,0.5846229,0.8861797,-0.2080673,-0.6691225,0.8775065,0.1345466,-0.9134933,-0.3114627,-0.1559548,0.187738,1 +0,user1401,-0.9269679,0.3009285,0.9435797,-0.5341137,-0.1823203,-0.1274849,0.8069798,-0.2985579,-0.6818354,0.6448452,1 +0,user1402,0.07916786,0.0716961,0.8254608,-0.150735,0.733718,-0.9201177,0.5913146,-0.1565997,-0.4343183,-0.2751841,1 +0,user1403,-0.3413167,0.5980059,-0.04889869,0.09024313,-0.3862046,-0.7903133,-0.7535984,0.03431172,-0.7451825,-0.4964816,1 +0,user1404,-0.1407734,0.6329726,-0.629374,0.2872943,-0.8964352,-0.1485924,-0.4899599,-0.9484372,-0.6796614,-0.6910535,0 +0,user1405,-0.9585839,-0.6986826,0.6696209,0.0381179,-0.5480143,0.2502482,0.09763089,-0.8821276,0.7492252,-0.3629148,0 +0,user1406,-0.1725363,0.8767477,0.1178679,-0.02365909,0.1094927,0.8377638,-0.6207404,0.571715,-0.7592938,0.082126,0 +0,user1407,0.1263221,0.7469521,-0.1477046,-0.941393,-0.6973472,-0.04898097,0.1147645,0.2187041,0.3028488,0.7958,0 +0,user1408,0.8974164,-0.9546429,0.4232588,0.6167116,0.08133459,0.3334007,0.8477079,0.2616818,-0.4808422,-0.4457283,0 +0,user1409,0.7461931,-0.131184,0.9799408,0.7713205,0.07434103,-0.8848008,-0.1056051,0.2703964,-0.8476406,-0.4724558,0 +0,user1410,-0.4901872,-0.9893861,0.9606843,0.7621575,0.5446006,-0.1850778,-0.6467898,-0.003416221,0.2934919,0.2964028,0 +0,user1411,0.4350249,0.8601708,0.4454167,-0.9618096,-0.271104,0.05359868,-0.8967509,-0.1608611,0.933974,-0.8822853,0 +0,user1412,-0.2001529,-0.6149453,0.4373374,-0.7247135,-0.9082113,0.7885101,-0.4700318,-0.9721057,0.4217656,0.8150748,0 +0,user1413,0.3004448,0.4065937,0.5129942,0.6267878,0.4938634,0.6725298,0.3730288,-0.8478112,-0.9685218,0.2949045,0 +0,user1414,0.4139905,0.4383141,0.94735,0.2514713,-0.1285754,-0.3091361,0.1358776,-0.2162436,0.9500447,-0.6927098,0 +0,user1415,0.8663523,0.2174374,0.3396528,-0.2934722,0.9093427,-0.7767322,-0.03090713,0.2225334,-0.2263676,0.3865804,1 +0,user1416,-0.8211406,0.2620691,0.8292226,0.9742626,0.9049919,-0.1335082,0.8133389,-0.034985,0.4199733,0.9640974,1 +0,user1417,-0.1504525,0.07138155,0.07479213,0.8600802,-0.7262556,0.4714488,0.4864103,0.9413451,-0.0277819,-0.4958941,1 +0,user1418,0.02588282,-0.9651677,-0.3244982,0.8451174,-0.163476,0.3738463,0.1004778,-0.8625495,0.9851079,0.7904789,0 +0,user1419,-0.004306428,0.01600251,0.7096347,-0.5325962,0.8266332,-0.8372556,0.7725624,0.7845825,-0.05462937,-0.1868168,1 +0,user1420,-0.1221522,-0.9595958,-0.1133321,0.3211665,0.004592863,0.1969171,-0.5871891,0.8030277,0.07144914,-0.1400433,0 +0,user1421,-0.4762332,0.04462684,0.6217406,-0.1702535,0.7200573,-0.7354024,0.9014962,0.1019791,-0.164949,-0.4055756,1 +0,user1422,0.8861629,-0.02449931,-0.6342246,-0.4521731,0.5828896,-0.03605,-0.3791482,-0.1302538,-0.9180575,-0.09061501,0 +0,user1423,-0.8268076,0.471784,0.08186669,0.4928038,0.3039949,0.4676779,0.3790662,-0.7407068,0.9952063,-0.4364909,0 +0,user1424,-0.4155852,0.3840756,0.6221699,0.2573686,-0.9961451,-0.1065238,0.7526729,-0.9314648,0.4394878,-0.1401526,0 +0,user1425,0.4984601,-0.1258477,0.07042538,4.896568e-05,0.845915,-0.4820414,0.2844294,-0.2888713,0.1676129,0.09734629,1 +0,user1426,-0.05187488,-0.7456894,0.92465,0.4819849,-0.2049166,0.2967783,0.504838,0.09640636,-0.8421025,-0.8652039,1 +0,user1427,0.7211736,-0.5763877,0.5877207,-0.6515777,-0.9192066,0.7245546,-0.829938,-0.6777303,0.3224298,-0.2662957,0 +0,user1428,0.6092814,-0.1037765,-0.737336,-0.2954049,-0.4966445,-0.009018763,-0.1886703,0.4399962,-0.6664498,0.635597,0 +0,user1429,0.01673877,-0.7073599,-0.1168047,0.667074,0.8626072,0.1304935,-0.7162732,0.8206859,-0.6770437,-0.01222932,1 +0,user1430,0.2704922,0.765173,-0.7030909,0.3964008,0.2768704,-0.9782012,-0.1559937,-0.6526232,-0.5423462,0.2036824,0 +0,user1431,0.1939043,-0.2175968,0.05459672,0.03547264,-0.619138,-0.8744721,-0.1021636,-0.8714664,0.1775953,-0.176665,0 +0,user1432,0.08977085,0.5935686,-0.1732251,-0.8670397,-0.319713,-0.9969914,-0.9092933,-0.477872,-0.3588791,-0.3673841,1 +1,user1433,-0.65034,-0.1631309,-0.8776301,-0.7543342,0.01058835,-0.8983189,-0.5646791,0.190777,0.02333552,0.9284984,1 +1,user1434,0.8525876,-0.6195909,-0.994302,-0.8742842,-0.005342588,-0.6647855,0.144238,0.1628453,0.4324129,0.3268534,1 +1,user1435,0.9489975,0.2265412,0.197401,0.4202546,-0.2161482,-0.1455838,-0.3992532,-0.4263093,-0.03854049,-0.05843759,1 +1,user1436,-0.6089239,0.1381865,0.7919908,0.2837837,0.4625741,0.3519292,0.5329518,0.3086495,-0.2274393,-0.4344164,1 +1,user1437,-0.3199487,-0.7428432,0.123566,0.1020567,-0.8958499,-0.8270217,0.5234976,-0.2654397,0.673119,-0.5910206,1 +1,user1438,0.07531958,-0.02650677,-0.9503037,0.4788615,0.08650458,0.8054352,0.7155113,0.7923949,-0.7356917,-0.2626376,0 +1,user1439,-0.7115075,0.1835436,0.2152496,-0.09950475,-0.4560913,-0.3146701,0.3806596,-0.4296687,0.2917185,0.1198553,0 +1,user1440,-0.5737556,0.1259728,0.1035068,-0.1266228,0.1784911,-0.7118225,-0.5821075,-0.9950434,0.8254784,-0.06347644,1 +1,user1441,0.5851324,-0.01589285,-0.9896194,0.241019,-0.3688948,-0.3796425,-0.9312784,-0.2110213,0.5578002,-0.9662349,0 +1,user1442,0.7235174,0.04371438,-0.3393337,-0.06131437,0.2728047,0.7389286,0.4839088,0.4094702,0.2256926,0.23757,0 +1,user1443,0.2260915,0.5110276,-0.4591558,0.1486636,0.2702798,-0.9233124,-0.05213932,-0.967149,0.247244,-0.2484016,1 +1,user1444,-0.1144228,-0.6092992,0.5233749,-0.1321932,-0.8750315,-0.7071127,0.4417503,-0.05883255,0.5892784,0.3286697,1 +1,user1445,0.137508,-0.5179715,-0.3919836,-0.8098431,-0.8557707,-0.5702075,-0.3802136,-0.8067734,0.1757372,0.5448602,0 +1,user1446,0.09244381,-0.271535,0.880497,0.8551915,0.1796226,-0.7000446,0.9169535,0.2553844,-0.9791235,-0.8618212,1 +1,user1447,0.06443666,0.65277,0.3525975,-0.1579306,-0.9700396,0.1593791,0.2550892,0.9061824,0.009251766,0.2927671,0 +1,user1448,0.9870554,0.5534101,0.6828085,-0.9497628,-0.5820263,0.9012413,-0.8938033,-0.8654282,-0.8520447,-0.951034,0 +1,user1449,-0.8816734,-0.2367027,-0.4440012,0.7003089,-0.9838535,0.6738017,0.01743134,0.3928349,-0.9940156,0.9286577,0 +1,user1450,-0.9398698,-0.3312275,0.06223219,0.3094732,0.8565936,0.3221236,0.02765161,0.6907649,0.9546224,-0.8940497,1 +1,user1451,-0.1350968,0.5938143,-0.4305235,0.3714037,0.4225665,0.09815833,-0.4809924,0.9375995,0.2194045,-0.09107727,1 +1,user1452,-0.3579066,0.8079241,-0.8222606,-0.4699446,0.7362038,0.9383993,-0.08107248,-0.505186,-0.1589646,-0.4769179,0 +1,user1453,0.9462931,0.6442732,0.4280076,0.8573001,0.4394832,-0.7139265,0.6485034,-0.4394889,-0.9634351,0.01533532,1 +1,user1454,0.03809564,0.06559834,0.6513431,-0.1357925,-0.2734386,-0.4341638,0.8980738,-0.8031073,0.2146108,0.4724318,0 +1,user1455,0.2265082,0.1919997,0.7999093,0.787424,0.7400586,-0.1681245,-0.3283995,-0.4366508,-0.7194769,0.3829295,1 +1,user1456,0.4447532,-0.4815746,-0.501567,-0.1426509,0.2853981,-0.1959679,-0.06706719,0.2716398,0.2041777,-0.8873184,1 +1,user1457,0.9862208,0.319909,0.5759931,-0.6538076,0.5216448,0.8626145,0.4029118,0.2932991,0.3725083,0.607228,0 +1,user1458,-0.05231816,0.615612,0.38763,-0.8641537,0.820852,-0.44357,-0.1583376,-0.1143812,0.6029529,-0.8833662,1 +1,user1459,0.05403464,0.4146489,-0.238903,0.5619442,0.7887536,0.7950133,0.7442626,-0.2883639,0.5377279,0.7482786,0 +1,user1460,0.002959527,0.6125491,-0.5408116,-0.9867336,0.384252,-0.006891954,0.6866387,0.113985,0.6954645,-0.4050014,0 +1,user1461,-0.781826,0.3807851,0.6845391,0.5322471,0.09772241,-0.4217712,0.6856687,0.2329956,-0.9393933,0.3203163,1 +1,user1462,-0.7520611,-0.8029479,0.8156937,-0.4025831,-0.8303844,0.9205412,-0.357901,-0.1598304,-0.2846768,-0.4283864,1 +1,user1463,-0.9072696,0.2061177,0.2859633,-0.8537733,-0.935461,-0.003883358,0.7773453,0.636113,-0.6634146,0.2276146,1 +1,user1464,-0.432166,-0.7823458,0.806909,0.7779129,-0.8916892,-0.3200901,-0.8790104,-0.5762274,0.0839422,0.2488146,1 +1,user1465,-0.8994735,-0.4225388,0.8213917,-0.2768674,0.164273,-0.7442443,0.786337,-0.9969851,-0.8522639,0.8984669,1 +1,user1466,-0.9582722,-0.5673411,-0.5166357,0.5664813,-0.1516092,0.8505329,-0.6219079,-0.7901962,0.2980449,-0.830823,0 +1,user1467,-0.04108985,0.3558407,0.5988998,0.06169658,0.5708848,-0.9681609,0.6539414,0.7324221,0.8565029,0.8143982,1 +1,user1468,-0.2194222,-0.165382,-0.05504228,0.8251893,0.2684231,-0.571266,0.3098346,-0.2624248,0.8208551,-0.6925537,0 +1,user1469,0.1170474,0.4061521,-0.4669394,0.04534282,0.9348953,0.6559681,-0.9063966,-0.9978014,0.5623532,-0.09346066,0 +1,user1470,0.2474027,-0.4606156,-0.1858506,0.9621918,-0.8852065,-0.282831,0.034601,-0.6972466,0.1482214,-0.06574653,0 +1,user1471,0.2068222,0.9605908,-0.9515355,-0.3014335,-0.5530858,-0.2830885,0.727727,-0.2574682,0.6463335,0.2439698,0 +1,user1472,-0.2978202,-0.6097407,-0.4565588,-0.7136382,-0.4339995,-0.7236745,-0.837675,-0.2088227,0.1201534,-0.05969551,1 +1,user1473,-0.02907988,0.5830987,0.4748157,-0.09912254,0.3875982,-0.5439024,-0.4814902,0.7122236,-0.626086,-0.8281765,1 +1,user1474,-0.5670863,0.4716184,-0.4106914,0.8472301,0.717194,-0.206401,-0.3244123,-0.2246172,-0.1064224,0.9955682,1 +1,user1475,0.587757,-0.2190399,-0.9331839,0.1541687,-0.309031,-0.4307872,0.6040753,0.7323447,-0.2905681,-0.7310258,1 +1,user1476,-0.8915719,-0.9348728,-0.9171679,0.09103439,0.5318275,-0.1141099,0.1382961,0.9054502,0.5496513,0.7166836,1 +1,user1477,0.5253575,-0.7999166,-0.5301944,0.7024216,-0.1031834,0.09355448,-0.4074587,-0.9692328,-0.08554595,-0.866253,0 +1,user1478,-0.3478063,-0.56627,0.4194135,0.9962381,-0.2790705,0.728592,-0.1408355,0.6385272,0.7186836,0.5617413,0 +1,user1479,-0.9045165,0.6185373,0.7656406,0.1412716,0.9498012,-0.2128686,0.2444928,-0.9599781,0.6976066,0.7656497,1 +1,user1480,0.6436842,-0.03661932,0.02580444,0.4027305,-0.08703691,-0.2326438,0.6099726,0.4236021,-0.07956157,-0.9375953,1 +1,user1481,-0.2876761,0.1025025,-0.5183543,0.3057113,-0.4224769,0.05071551,0.8868161,0.3292921,0.673306,0.6676916,0 +1,user1482,-0.03961327,0.2123516,-0.6648829,-0.4873248,0.3723677,0.8852897,0.7635004,0.9776214,-0.08298891,-0.3254276,0 +1,user1483,-0.7142224,-0.2286952,0.2035439,0.9327859,-0.3508331,-0.2942445,-0.4710999,0.918416,0.7614738,-0.4145132,1 +1,user1484,-0.3413829,-0.2532243,0.9096533,0.1630114,-0.9829938,0.3367891,0.5353195,0.8898032,0.7098709,-0.3169731,1 +1,user1485,0.9984824,-0.7220501,0.9864602,0.3768827,-0.9010709,-0.5488741,0.6615742,-0.8254858,-0.8683782,-0.8529958,1 +1,user1486,0.5122858,0.9633046,0.003453195,0.7202099,-0.6107745,0.5376309,0.2005006,-0.5182348,-0.9580031,0.9684163,0 +1,user1487,-0.8966297,0.2652011,-0.5919137,-0.9796395,0.3024043,-0.8591788,-0.5317477,0.161443,-0.0859514,-0.2042915,1 +1,user1488,0.9847031,0.5978589,0.5624533,0.7230751,0.6205739,-0.6862596,0.06448606,0.4678132,0.5041301,0.7542322,1 +1,user1489,-0.5400324,0.5789166,-0.6089168,0.8560562,-0.7899224,-0.905939,-0.957837,0.367384,0.6449498,-0.9149499,1 +1,user1490,0.1574049,-0.32015,0.1691833,0.5823047,0.09115792,0.9358345,-0.7874852,0.873079,-0.5482235,-0.4560129,0 +1,user1491,-0.01233735,0.210408,-0.9783583,0.7363415,0.004825932,0.3068485,-0.2488753,-0.4182018,0.1995947,-0.6507692,0 +1,user1492,-0.3218584,-0.04029837,-0.9243777,0.3883032,0.3078,-0.3277102,0.7278318,-0.3996204,0.7055565,0.4053664,0 +1,user1493,0.4053439,-0.1230979,-0.01512301,-0.8202784,0.2607735,0.8563757,-0.1453862,-0.2867513,0.1670997,0.1156007,0 +1,user1494,0.08039303,-0.5834743,0.307605,0.8825682,0.06936493,-0.6970349,-0.4715299,-0.7820888,0.5361801,0.5768454,0 +1,user1495,0.2459756,0.1773558,0.8825314,0.1662161,0.4161107,0.3521997,0.8488214,0.02415222,-0.2105013,-0.345819,1 +1,user1496,0.5058704,0.4543633,-0.1937313,-0.09714575,-0.5749535,-0.8878686,-0.3590492,-0.2837364,0.3148358,0.01406757,0 +1,user1497,0.1221209,-0.1508155,0.7909692,0.4490495,0.9177557,-0.846502,-0.09343782,-0.572285,-0.165775,0.7460223,1 +1,user1498,-0.7951142,-0.4668034,0.4814312,-0.7720873,-0.01300446,0.3840388,0.5027627,-0.2434257,-0.3539984,-0.5314208,0 +1,user1499,-0.7135517,-0.7110187,0.7512264,-0.2719564,0.6934695,-0.4591346,0.9507854,0.4538388,0.1356909,0.3215138,1 +1,user1500,-0.7608317,-0.7446634,-0.6759702,-0.5056077,0.852651,0.8094661,0.0001656166,-0.5700864,-0.6034218,-0.3474383,1 +1,user1501,0.4522885,0.07258093,-0.7044194,-0.8098955,0.101789,-0.8987922,-0.4626363,0.0593277,0.7942231,0.4028326,0 +1,user1502,0.4932705,-0.7504279,0.7996909,0.4266101,-0.8596163,0.2577769,0.6785124,-0.8036294,-0.2179756,-0.4345163,0 +1,user1503,-0.05865193,-0.3544041,-0.132529,-0.2192458,-0.5813485,-0.9142084,0.1624906,0.2210909,0.5167317,0.5928662,0 +1,user1504,-0.5767914,-0.3443203,0.7703963,0.090982,-0.5106128,-0.4426946,0.05587349,-0.2284487,-0.8318629,0.5746561,1 +1,user1505,0.9261842,0.7211905,-0.6110005,0.2738402,0.8575777,-0.9486241,-0.6458999,-0.02824653,0.675602,-0.4389481,0 +1,user1506,-0.4708949,0.426556,-0.06571289,0.9349229,0.1096206,-0.3449956,-0.2334341,-0.04656437,-0.7738365,0.8618403,1 +1,user1507,-0.4683633,-0.2791931,0.8532284,-0.8179836,-0.9787853,0.4431955,-0.8058304,-0.3229986,0.7177884,0.2913397,0 +1,user1508,0.4515418,0.9212739,-0.1411949,-0.02373817,-0.2456057,0.1449304,-0.05335859,0.002520695,-0.409944,-0.3052011,0 +1,user1509,0.1812988,0.860286,-0.6462993,0.9311609,0.83055,-0.6164036,0.6257304,-0.4080372,0.9448472,0.4235816,1 +1,user1510,-0.3728798,-0.6606558,0.618869,0.3232879,0.9710158,-0.7696731,0.4386625,-0.2829766,0.415395,0.05698937,1 +1,user1511,0.09522592,-0.1153455,0.8846096,-0.6210076,0.6673574,0.9122866,-0.443386,-0.5738772,0.5104944,-0.2427965,0 +1,user1512,0.8936227,-0.03721148,-0.1646536,0.2368722,-0.5919269,0.4343119,0.5125465,0.9212549,0.6181532,0.09127318,1 +1,user1513,0.5875069,0.5516958,0.9539861,0.8359632,0.3433835,-0.8843834,0.2021629,-0.3053552,-0.6675939,0.7315617,1 +1,user1514,0.3810035,0.6559594,0.08815346,-0.6882217,-0.6834757,-0.381958,0.08551416,-0.6554612,0.2719682,0.3426903,0 +1,user1515,-0.4477602,0.7095642,-0.2550003,-0.6001164,-0.5749207,-0.228899,0.04786597,0.811058,0.3280241,0.7743001,0 +1,user1516,0.5859893,0.8296457,0.9404463,0.2128459,0.4423126,-0.4332575,-0.1362629,-0.130841,-0.5359721,0.878566,0 +1,user1517,-0.1067107,0.6192639,-0.9083933,-0.9680119,-0.2942502,-0.844327,-0.7139852,-0.173696,0.3139652,0.3111066,0 +1,user1518,-0.3443899,-0.0252347,0.153086,-0.5797559,0.7274836,-0.08807788,0.5161183,-0.02749896,-0.7579273,-0.4299914,1 +1,user1519,0.5706924,0.4275046,0.5028996,-0.06407903,0.06288651,-0.1195171,0.9282232,-0.6630278,0.968158,0.6327981,0 +1,user1520,0.3532569,0.1981805,-0.5173101,0.8880443,-0.08417262,-0.7502661,-0.6718222,-0.806312,-0.04108498,0.3961567,0 +1,user1521,0.813015,0.6546153,-0.6777307,-0.9974512,-0.1813585,-0.1522434,0.7286331,-0.1544199,-0.3061509,0.1139957,0 +1,user1522,-0.441645,-0.3620874,0.5245412,-0.3277375,-0.9322876,-0.8126686,-0.3206521,-0.08122953,0.1677527,0.9820289,1 +1,user1523,-0.9686015,-0.8421179,-0.4416878,0.2763475,-0.7763727,-0.07797626,-0.9439904,-0.2059324,-0.3355285,-0.1984769,0 +1,user1524,0.2183589,-0.4684826,0.3071463,-0.8177296,-0.920585,-0.2958677,-0.4167531,0.5588288,0.8609488,-0.7704036,0 +1,user1525,0.6387481,0.05443825,-0.1678538,-0.4451692,0.1370774,-0.5097035,0.207818,0.1366817,-0.2960672,0.5588743,1 +1,user1526,0.2773741,0.3352379,-0.5591564,-0.5574364,0.6397381,-0.7257766,0.904831,0.8182198,0.4539703,0.455704,1 +1,user1527,-0.2757707,0.9858807,-0.886585,0.08512466,-0.4955385,-0.1837363,0.2241977,-0.7249076,0.1757846,0.2436639,1 +1,user1528,-0.2391311,0.9036228,-0.3768846,-0.9961197,0.05483306,-0.3562055,-0.8856199,0.5643967,0.5381578,0.3048966,1 +1,user1529,0.4822599,0.8684345,0.9222747,-0.3295236,-0.3732664,0.6582622,0.4075937,-0.4252059,-0.9000281,0.9242832,1 +1,user1530,0.01067759,-0.7251381,0.8646415,0.8131682,-0.802069,0.3571291,0.1749831,0.7289312,-0.6885245,-0.4348222,0 +1,user1531,3.719656e-05,-0.8410406,-0.05285477,-0.5017274,-0.09251591,-0.5467394,0.1145458,0.9943103,0.9347361,0.9574583,0 +1,user1532,-0.06545167,-0.05898455,-0.7821447,-0.1394191,0.7285226,0.75947,0.9449575,0.6341218,0.894195,0.3271159,1 +1,user1533,-0.4960519,-0.475566,0.6643324,0.2397783,-0.6616853,-0.385094,-0.1465045,0.9253018,0.0934999,0.1306614,1 +1,user1534,0.9413853,-0.1954447,0.8146163,0.2790268,0.3261356,-0.4609478,-0.7229636,0.2154012,0.4514677,0.5503245,0 +1,user1535,0.3577569,0.5966951,0.9882516,0.9515629,-0.7820902,-0.6832245,0.0008309451,-0.5943269,-0.9376679,-0.09822804,1 +1,user1536,-0.5698677,-0.7543755,-0.9466681,-0.4863815,-0.8041075,-0.333718,0.2075956,-0.1029447,-0.2308981,0.6917133,1 +1,user1537,-0.5295096,-0.7688887,-0.2510966,0.2139497,-0.5642438,0.1940566,0.04360228,-0.8311631,0.6776313,0.4121648,0 +1,user1538,0.8893936,-0.682498,0.8414799,-0.8664207,-0.7608755,0.759971,0.1950006,0.08267455,0.7801205,-0.8068883,0 +1,user1539,0.881674,-0.8331016,-0.08786294,0.4898804,-0.04971319,0.8112124,-0.845763,0.899576,0.3591579,-0.6134878,0 +1,user1540,0.6517892,-0.9086027,0.102604,0.1451106,-0.7336938,0.577653,-0.3306673,-0.2392003,0.6224784,-0.1642536,0 +1,user1541,-0.4834863,-0.3431538,0.4603489,0.4568672,-0.7898597,0.9902978,-0.366337,0.7996979,0.1955154,0.250101,0 +1,user1542,-0.02310005,0.05155291,-0.2032534,0.8688727,-0.3823558,0.723499,-0.289149,-0.6743013,-0.1303477,0.1437157,0 +1,user1543,0.5454119,0.05418581,0.9379504,-0.6180172,-0.3256207,0.0119649,-0.8181208,-0.3179454,0.2406316,0.9270196,0 +1,user1544,-0.8959794,-0.791458,0.414335,0.2928304,0.5535238,-0.8940856,0.8358259,-0.5056572,0.5279215,-0.01833721,1 +1,user1545,-0.6420966,-0.2924877,0.8849001,-0.819349,-0.06583146,-0.658459,0.7963652,-0.3297624,-0.8583795,-0.513594,1 +1,user1546,-0.9023484,-0.23625,-0.3170499,-0.2181336,0.09945859,0.7830659,0.2297451,-0.5068874,-0.4313443,0.7013196,0 +1,user1547,0.6900099,-0.9618123,0.3547813,-0.4943238,-0.004163585,-0.3273431,-0.300437,0.3635018,0.9919494,-0.1397712,0 +1,user1548,0.2511927,-0.6732238,0.9765068,-0.7873609,0.6399184,-0.5027861,-0.91762,0.4965415,0.4555857,0.7975126,0 +1,user1549,-0.2467383,0.7385153,0.8360361,0.2021105,-0.1730578,-0.305012,-0.2541366,0.4656137,-0.1892717,-0.7286718,0 +1,user1550,0.2607023,0.4656924,-0.1423192,0.4415972,-0.9412771,0.5531398,-0.3722138,0.700474,0.9601074,-0.5069731,0 +1,user1551,-0.3955504,0.5249567,-0.5408034,-0.8993166,-0.4442542,-0.2530521,-0.5894422,0.6902295,-0.5854993,0.1936693,1 +1,user1552,-0.4337233,0.3931306,-0.8416946,0.2046593,0.6455838,0.5427446,-0.5255035,-0.6888063,0.5045775,0.385324,0 +1,user1553,0.8190573,-0.8963951,-0.6177779,-0.8861403,-0.8735646,0.7404712,0.3071341,-0.3807555,0.1278601,-0.5249442,0 +1,user1554,-0.364152,0.6828388,0.01750884,0.3770309,-0.2206269,0.6689716,-0.5334326,-0.5157029,0.07897228,0.9951924,1 +1,user1555,0.7846356,0.924648,0.4654517,0.3869297,0.7249988,-0.7531231,0.05774342,0.8700225,0.3655263,0.6149203,1 +1,user1556,0.4578054,0.1580432,0.2143683,-0.3313095,0.2635127,-0.7692323,-0.4850479,0.7559262,0.8317929,-0.9660699,1 +1,user1557,0.9132221,0.01807672,0.4583524,0.8195945,-0.5808888,0.9431951,-0.6286017,-0.697483,-0.4670574,0.4508964,0 +1,user1558,-0.4911351,0.9105287,0.5788667,-0.5279456,-0.7705397,0.06314063,-0.7180589,-0.8548852,-0.4586891,-0.1414157,0 +1,user1559,-0.7813257,0.06166597,0.8374837,-0.3274292,-0.6816542,-0.1254378,-0.3706678,0.3203229,0.3699507,0.3388268,0 +1,user1560,0.395482,-0.1134888,0.3806271,-0.5099291,0.04584475,0.6014573,0.7789921,-0.1226889,-0.3670855,0.3751797,0 +1,user1561,0.5195425,-0.8146094,0.4435082,-0.7147774,-0.5726087,-0.5797302,0.4569242,0.874046,-0.1472136,0.423762,1 +1,user1562,0.2187115,0.2206254,-0.2153711,0.1708434,0.2258299,0.3278228,0.743878,0.3146333,0.3046868,0.2962851,0 +1,user1563,-0.6699696,0.8275267,0.5984824,0.3506518,-0.2256326,0.3609273,0.7239495,-0.4885671,-0.4728906,-0.2977045,0 +1,user1564,-0.9765094,-0.2901754,0.1078405,0.5250009,-0.234294,0.03517581,-0.6895803,0.7993478,0.9462863,-0.4455765,1 +1,user1565,0.1600968,-0.9748193,-0.4007548,-0.5501298,-0.4480345,0.866875,-0.9790857,-0.4699655,-0.2438455,-0.1533905,0 +1,user1566,0.6877873,0.4242218,0.5867339,0.3022147,-0.007722825,0.6777028,-0.2752195,-0.08289398,-0.4105585,0.6040675,0 +1,user1567,-0.5463771,-0.04455092,0.1611725,-0.9613806,-0.03840151,0.7014578,0.5180153,-0.3035969,-0.2846118,-0.7538632,0 +1,user1568,0.6305871,-0.743708,0.3481485,0.6638199,-0.01227832,0.06093155,0.06451663,-0.3011286,-0.5662142,-0.7412257,0 +1,user1569,0.5771808,0.7417238,0.4282139,0.435794,0.2314016,0.4376737,0.919781,0.9997806,-0.630438,0.7971792,1 +1,user1570,-0.6647031,0.1223475,-0.9266905,0.5284998,0.9118853,0.5126702,0.6722524,-0.404021,-0.9254539,-0.3673511,1 +1,user1571,0.2823763,-0.6523107,-0.5492474,-0.1910695,0.2540279,-0.3614155,0.7338493,0.4596711,-0.9437358,0.09452071,0 +1,user1572,-0.9063054,-0.60143,-0.1114372,-0.1073388,0.4415419,0.4279716,-0.4465559,0.7994785,0.5650774,0.0472802,0 +1,user1573,0.3121968,-0.8260996,-0.1299439,0.3973725,-0.4704705,0.2361692,-0.6168966,-0.07832221,-0.05580163,0.7763646,0 +1,user1574,-0.1722118,0.4018751,-0.611297,0.1909134,0.9284072,0.6505494,0.9157285,-0.8582744,0.2968958,0.02154027,1 +1,user1575,-0.8022848,-0.392888,-0.6971022,-0.8145084,-0.00493426,0.5338859,-0.61073,-0.7061787,0.09299893,-0.971057,0 +1,user1576,0.6701003,-0.1185874,-0.2450437,0.5780235,0.4636981,0.5777101,-0.8205314,0.5919153,0.08581892,-0.7372293,0 +1,user1577,-0.07456022,-0.8343749,0.07165307,0.9727798,0.02786578,0.4336153,0.1454736,-0.3651618,0.8655514,-0.2771401,0 +1,user1578,0.8877251,-0.3547002,0.6576791,-0.3088321,0.9909022,-0.7934572,0.08883305,0.6573231,0.08494835,-0.1108282,1 +1,user1579,-0.07870705,0.2081888,-0.268537,0.7906626,0.1036164,-0.9250759,-0.7381514,0.08845689,-0.4585954,-0.9397167,0 +1,user1580,0.6787015,0.9041404,-0.0923108,0.1748903,0.854808,-0.8713967,0.891337,-0.8995481,-0.3237202,-0.005811842,0 +1,user1581,0.1484274,-0.8890079,-0.4846401,-0.8672349,-0.9503749,0.7596826,0.7166193,0.3577971,0.04505579,0.3821987,0 +1,user1582,0.5257425,-0.2668545,0.1906596,0.8913459,0.6593622,-0.178128,-0.3275936,-0.2213136,-0.04409463,0.2539526,1 +1,user1583,-0.7550218,0.297271,0.06599462,-0.6204504,0.5003918,0.6713479,-0.6341665,-0.5883544,-0.8191428,-0.6204879,1 +1,user1584,-0.03251526,-0.7854029,-0.102418,-0.7533752,-0.8239396,0.5001538,0.02375337,0.9770416,-0.8270841,0.8572545,0 +1,user1585,-0.8384095,-0.5840157,-0.7918315,0.2683768,-0.5612647,-0.5091564,0.1389737,0.2629836,-0.9651223,0.249145,1 +1,user1586,-0.9703862,0.221919,-0.4685537,0.7664794,0.2253905,0.9182248,0.4235769,-0.7183319,0.5463835,0.9944324,0 +1,user1587,-0.5747099,0.3726403,-0.8880498,-0.08468467,0.4395732,0.7309215,0.5387055,0.7329678,-0.9952912,0.8911846,1 +1,user1588,-0.9251873,0.434061,0.6665209,0.08797136,-0.1421536,-0.5659614,0.5103721,0.5655005,-0.4321798,-0.2999585,1 +1,user1589,-0.4615213,0.1324477,-0.889687,-0.7614663,0.4548508,-0.01863461,0.705518,-0.573217,-0.9123055,-0.1469833,1 +1,user1590,-0.3560356,-0.5656938,0.9494339,0.5878861,0.757919,-0.3945163,-0.8319623,0.05329072,0.3746595,0.2300114,1 +1,user1591,0.4702947,-0.6794278,0.04714799,0.5780423,0.9036912,-0.9645041,0.2893642,-0.5571884,0.2007347,-0.9247789,1 +1,user1592,-0.9419787,0.3178383,0.5538212,-0.4762437,0.8822421,0.4016352,0.1624423,-0.699171,-0.05951911,-0.7232213,1 +1,user1593,0.8626759,0.6549316,-0.2659372,-0.2412705,-0.01625114,0.9333065,0.9119157,-0.632076,-0.3206537,-0.4737035,0 +1,user1594,0.8003251,-0.8519011,-0.3543696,-0.07130596,-0.3219414,0.3964232,0.01331368,-0.04575546,0.7278441,-0.2224833,0 +1,user1595,-0.9184881,-0.9723371,-0.3383382,-0.9512427,-0.3520519,-0.563189,0.472862,-0.8998232,-0.1132328,-0.1687978,0 +1,user1596,0.02277272,0.6801123,0.333308,0.2085998,0.5357144,0.8001814,0.93283,-0.1020415,0.4355008,0.372906,0 +1,user1597,0.4881123,0.5723207,-0.7676357,-0.7690913,0.6703357,0.074126,0.7380941,0.8713506,-0.6827144,-0.6184159,1 +1,user1598,-0.4648653,-0.01688806,0.8228342,-0.9126233,0.6095466,-0.8617312,-0.009122667,-0.2034202,0.6021554,0.07733896,1 +1,user1599,-0.3466401,0.9364043,-0.3185434,-0.1275803,-0.476564,-0.138887,-0.002653375,0.5968299,0.8692865,0.6316804,0 +1,user1600,0.06529315,0.3140445,0.6605782,0.6667027,-0.09826261,-0.4882003,0.6578752,0.8711311,-0.3131524,-0.8212367,1 +1,user1601,-0.1295684,-0.8945406,0.8961437,0.6158765,0.5214319,0.650939,-0.3368703,0.3925589,0.6767015,0.7099879,1 +1,user1602,0.9357362,-0.7159064,0.1322091,0.6813502,0.7774639,0.4996975,-0.2688041,0.05650093,0.9255507,-0.2737989,0 +1,user1603,0.1589877,0.7126146,-0.450859,-0.4406361,-0.6567207,0.9397713,-0.7886807,0.6706096,-0.748075,0.2260435,0 +1,user1604,-0.8173715,-0.7206402,-0.2338001,0.01324897,-0.9490386,-0.1128919,0.04623312,-0.6857633,-0.3791002,0.4863525,1 +1,user1605,-0.2364757,0.6859686,0.5209121,-0.1277364,0.7058711,0.150247,-0.3530756,0.1982266,0.2224465,0.7477413,1 +1,user1606,0.3567029,-0.6802734,-0.1479612,-0.2551445,0.3383451,0.4736572,-0.3994107,0.9644309,0.3449239,0.2549865,1 +1,user1607,0.8527287,0.1607724,0.5211561,-0.4087276,0.5146595,-0.5351818,0.2257017,0.906152,0.7067188,0.7491232,0 +1,user1608,0.6889641,0.8515937,-0.4074349,-0.1549566,-0.2662631,-0.4161377,0.792398,0.8330648,0.08799794,-0.5293987,0 +1,user1609,0.2444281,-0.03497363,-0.4902821,0.4360234,0.3292472,0.6802,0.6894224,0.621754,-0.5701277,-0.8558417,1 +1,user1610,-0.2259783,-0.6310388,-0.7473809,-0.618065,-0.3817241,-0.4602577,0.4875503,-0.005391084,-0.7518766,0.8094065,1 +1,user1611,0.3676656,0.7557341,0.5002543,-0.9800663,-0.4114551,-0.2875344,0.6837349,0.9335167,0.7642777,0.4647894,0 +1,user1612,-0.6071445,0.07601851,0.02507779,0.5687885,0.3788723,0.4398826,0.4060416,-0.02044888,0.4749281,0.5263569,1 +1,user1613,-0.7002358,0.1021067,0.4432788,-0.7267191,-0.7223619,0.3616143,-0.8400433,0.7732953,0.2040288,0.06335914,1 +1,user1614,0.6126437,0.05300508,-0.433751,-0.6005167,-0.9110634,-0.6161866,-0.9504316,-0.6548377,0.9451349,0.8443015,0 +1,user1615,0.3603402,0.2906156,0.9226597,0.8154133,0.5549327,-0.05996359,-0.570205,-0.0434073,0.647844,0.3836114,0 +1,user1616,-0.5386453,0.5180909,0.6514472,0.5416578,-0.2836266,0.8524579,0.2989304,0.03627892,0.2389064,-0.6874958,1 +1,user1617,0.6422575,-0.7250759,0.09769529,-0.8340374,0.3143272,-0.6979618,0.4731453,-0.3731695,0.4915185,0.838734,0 +1,user1618,0.7856303,-0.3367441,-0.96539,-0.2692714,-0.005494076,-0.3290421,0.9685004,-0.3104395,0.6525528,0.274796,0 +1,user1619,-0.4638326,-0.04784808,0.3179681,-0.3703709,0.5742199,-0.7135035,-0.1906975,-0.3982206,0.8067266,0.01254565,1 +1,user1620,-0.8192637,0.4073718,0.2080083,-0.5955036,-0.230822,0.2834036,0.1786633,0.05361345,0.5792129,-0.3082493,1 +1,user1621,-0.5704052,0.0975621,0.9840439,-0.6813853,-0.2475751,0.2764416,-0.8634619,0.7428512,0.02721235,-0.4951926,1 +1,user1622,-0.9935379,0.2727241,-0.6348839,-0.7923286,0.4779111,-0.6780075,-0.9013333,0.04459105,0.00746129,0.08776679,1 +1,user1623,-0.7612425,-0.2747899,-0.2381704,-0.07174729,-0.3485799,-0.3149613,-0.6588944,0.3544424,-0.4803062,-0.03147059,1 +1,user1624,-0.7077293,-0.2475063,-0.2818933,0.07734428,0.7361738,0.2097481,-0.9515462,-0.8892248,0.7065586,0.03110392,0 +1,user1625,0.8067871,0.420823,0.01074647,0.1363654,-0.8440304,0.7184157,0.1119804,0.9988356,-0.2646946,0.8652834,0 +1,user1626,-0.6797306,-0.2471271,0.4234913,-0.02299004,0.2993682,0.1218497,0.8139676,0.4546192,0.406461,0.7997316,1 +1,user1627,0.3150434,-0.567394,-0.9485853,-0.7140559,0.2718881,0.009929533,0.9812838,0.008733675,0.1420594,-0.5959901,1 +1,user1628,0.2948995,-0.006856249,0.2431108,0.3672742,0.8263054,-0.2074583,-0.1499255,0.8701862,0.052591,-0.7531324,0 +1,user1629,-0.1445959,0.7359849,0.2463255,0.06438665,-0.09108527,0.2601185,-0.1951551,-0.748801,0.008616445,-0.1229294,1 +1,user1630,0.9684033,-0.6309897,-0.2671287,0.1583638,0.7953242,0.8710425,-0.02136962,-0.3944365,0.01134599,-0.9643097,0 +1,user1631,-0.6398074,-0.6928117,-0.09631101,0.03397688,-0.2719573,0.3043414,-0.4920503,0.7413173,0.7394386,-0.5743691,1 +1,user1632,0.7258357,0.8414442,0.1424693,-0.3197369,-0.5696534,-0.08894257,0.4679746,0.6437579,-0.3146821,-0.4129416,0 +1,user1633,0.9041394,-0.3468961,0.8650804,-0.160286,0.5727881,0.3707401,0.7098263,0.6620645,-0.06310329,-0.2381086,0 +1,user1634,0.5191803,-0.9801972,0.45283,0.5933408,0.07132207,0.2441127,-0.280731,0.4119269,0.9913636,0.6516744,0 +1,user1635,0.9084642,-0.879196,0.9086692,0.6935121,-0.518692,0.7981655,-0.4857923,0.9579946,0.3062178,-0.926589,0 +1,user1636,-0.3323362,-0.6609275,0.3859924,0.7119775,0.2786592,-0.479013,-0.6432493,-0.139709,-0.8406568,-0.4903673,1 +1,user1637,-0.1241167,-0.6604706,-0.6951312,-0.6618037,-0.5903329,-0.2822301,0.3198583,0.3763579,0.3362875,-0.09333912,0 +1,user1638,0.7611929,0.2815764,0.4298253,-0.7152155,0.9959675,-0.7370162,0.7399095,0.8641466,0.01293651,0.8225342,1 +1,user1639,-0.6433722,-0.8093338,0.9785576,-0.4429791,-0.9876039,0.1048493,-0.8508514,-0.3066442,0.2473412,-0.01976603,0 +1,user1640,-0.8796886,0.3045558,-0.1854133,0.7742197,0.7389144,-0.6020301,0.009280651,-0.001888101,0.7661598,0.05081914,1 +1,user1641,-0.4647854,0.6505376,0.6824444,-0.3332805,-0.3857566,-0.1972739,0.2274598,-0.1412444,0.2610599,0.6319407,1 +1,user1642,0.7242934,0.9464003,0.4788119,-0.4230454,-0.399059,0.8173149,0.8328836,-0.3731275,0.01161886,-0.5549766,0 +1,user1643,-0.4868332,-0.6194257,0.8396645,0.3430082,0.1177867,0.8378526,-0.5846777,0.977663,0.2410879,-0.422824,0 +1,user1644,-0.1650212,-0.2473557,0.1257232,-0.05999952,-0.1081185,-0.8356596,0.3874165,-0.3679491,-0.5349113,-0.3047002,0 +1,user1645,0.3369372,-0.0005946462,-0.9549391,-0.02356214,-0.3101224,-0.7988717,0.882452,-0.0279651,-0.04324621,-0.7106751,0 +1,user1646,0.8735071,0.6711899,0.7623242,0.1584215,-0.3272806,-0.222111,-0.1548827,-0.06574428,-0.1110682,0.9607875,0 +1,user1647,0.2963335,-0.7292648,-0.2228296,-0.5183417,0.6082549,-0.9832017,-0.313653,0.6683298,0.7039951,0.00780399,1 +1,user1648,-0.02080531,0.2743295,0.1427562,0.1424005,-0.9957952,-0.4968335,0.3555973,0.5988654,-0.5517278,-0.8719411,1 +1,user1649,0.6591374,-0.6655543,0.7969342,0.8891501,0.6672253,0.4488469,-0.1863823,0.6238162,-0.4585153,0.2355835,0 +1,user1650,0.8325009,0.2228871,-0.9048615,0.1112874,0.1824748,-0.6967052,0.4956495,-0.7298907,0.5107217,-0.9796504,0 +1,user1651,0.159931,-0.3182988,-0.6492355,0.5468969,-0.2266173,0.78657,-0.4657394,-0.3475212,-0.9725148,-0.1801904,0 +1,user1652,-0.9112678,0.4320078,0.7809781,-0.7922352,-0.5803498,-0.2747115,-0.0498442,0.3666674,0.568697,0.7403909,1 +1,user1653,0.838963,-0.5043888,-0.5397454,0.3189588,-0.3396142,-0.3747127,0.5943162,0.3147003,-0.481817,0.1081164,0 +1,user1654,0.3986885,0.4069113,0.1125941,-0.5248504,0.4248028,-0.5283913,-0.1246338,-0.9930788,-0.452821,0.788339,0 +1,user1655,-0.6189971,-0.8154984,-0.5009152,0.2851091,-0.844176,0.9350366,-0.00139044,0.4774426,0.2752557,-0.2285052,0 +1,user1656,0.6457501,0.9164343,0.4710011,-0.5446758,-0.1836446,-0.656297,-0.2937034,0.3135359,0.2534884,-0.02660013,1 +1,user1657,0.7189579,-0.8402158,-0.4639146,0.4521595,-0.2758291,0.5934585,-0.3106663,0.4615404,0.95364,0.5880706,0 +1,user1658,0.6960463,-0.3828924,-0.4495005,0.5710532,0.4277121,-0.05503387,-0.02010668,-0.5138237,-0.5826849,0.1755048,0 +1,user1659,-0.05935041,-0.09042196,-0.2858881,0.8225984,-0.3573392,0.1362447,0.5563711,0.1837221,-0.6939206,0.2202675,1 +1,user1660,-0.425638,0.8957691,0.7824109,-0.4834538,0.6330857,-0.1464231,0.4941786,0.7127395,-0.03774354,-0.5348589,0 +1,user1661,0.6644496,-0.01388209,0.2833707,-0.2705831,0.2230363,-0.1839913,0.9585237,0.09173984,0.4286611,0.2111951,1 +1,user1662,0.3008422,0.2167663,0.6178009,-0.1434248,0.3707035,-0.5594139,-0.9356792,-0.07496066,-0.954482,0.6458983,0 +1,user1663,-0.6998023,0.7372133,-0.07511978,0.1968093,-0.9365677,0.7646344,-0.03784678,0.3564974,0.6475744,0.05219955,0 +1,user1664,0.5685891,0.6392218,0.1484511,0.5691309,-0.2041756,-0.8132513,0.66835,-0.2461957,-0.6344422,0.9730865,1 +1,user1665,-0.1799775,0.2365691,0.07063086,-0.550084,-0.5579744,0.6846988,-0.2164102,-0.6630337,-0.9631185,0.2975727,0 +1,user1666,-0.7913381,0.8580173,-0.1664506,-0.1096786,-0.4552597,0.5627999,0.4763609,0.314492,-0.04620787,0.1256105,1 +1,user1667,-0.7637472,0.9782943,-0.4655564,0.2811084,-0.9255165,-0.2922642,-0.9748993,0.6140953,-0.475099,-0.5172808,1 +1,user1668,0.6959058,0.5760986,0.3754997,-0.2118876,-0.1483072,-0.5975313,-0.8965519,0.7133241,0.373169,-0.7957664,0 +1,user1669,0.9698548,0.1395937,-0.7366253,0.1751059,-0.4592922,0.8257837,0.2162704,0.1786387,0.9667286,-0.05185529,0 +1,user1670,-0.4071193,-0.8310395,-0.4869989,0.8381293,-0.9131204,0.8125851,-0.8257507,-0.6925488,0.7722422,0.4629531,0 +1,user1671,0.8162172,-0.1193457,-0.8099136,-0.4376679,-0.4093929,-0.1995614,0.1127287,-0.288564,0.1393288,0.2550527,0 +1,user1672,-0.4949306,-0.2098687,0.9458191,0.8418255,0.1549512,-0.3714902,-0.5562698,-0.9626058,0.2277885,-0.4199146,0 +1,user1673,-0.6828259,-0.8846393,0.9918131,-0.5849161,-0.3121794,0.6299,-0.9928671,-0.06567628,-0.216139,0.9079765,0 +1,user1674,-0.670616,0.2612286,-0.9702491,0.9053403,0.7083938,-0.3617088,0.528051,-0.3109009,-0.6195833,0.8322288,0 +1,user1675,0.3400481,0.5427756,0.07154226,-0.218174,-0.9531673,-0.2071498,0.8311467,-0.3305549,0.6928772,0.2753852,0 +1,user1676,0.6541112,0.1147661,-0.9631261,0.3915218,0.3776981,0.8310283,0.8895849,0.9063586,0.7406148,-0.8026986,0 +1,user1677,-0.7971089,-0.06758149,0.7920751,0.06376177,-0.6188868,0.4161802,-0.6268317,0.6233548,0.2693485,0.7930162,0 +1,user1678,-0.3636184,0.8135108,0.8487127,0.2634842,0.6550876,-0.1903515,-0.4825063,-0.662225,0.3968723,-0.7168108,0 +1,user1679,-0.3666941,-0.6109044,0.1796301,-0.4660777,0.3819029,-0.6658053,0.2451821,0.505224,-0.8111129,-0.6746397,1 +1,user1680,0.8620285,0.2668642,0.5890093,-0.04708813,-0.9516615,-0.1349729,0.1867859,0.247171,0.8108332,0.02859972,0 +1,user1681,-0.5311175,0.03639787,0.9438512,-0.6252284,-0.1624376,0.1129433,-0.9868568,-0.3921158,-0.09240603,-0.6964612,0 +1,user1682,0.7932369,0.0707968,0.5303946,-0.9191808,-0.8447144,-0.8792352,0.7794427,-0.8422972,-0.7836278,0.1451699,1 +1,user1683,0.9507606,-0.3011279,0.3699874,0.1606767,-0.5320112,0.5903156,-0.8630583,-0.3861616,0.3795302,-0.2310094,0 +1,user1684,-0.6921545,0.5320091,-0.5958942,0.6937304,0.4979482,0.7382306,0.6074594,0.9225846,0.425777,0.4116553,0 +1,user1685,0.1919254,-0.5222919,-0.3570113,-0.4440313,0.5800884,-0.4076265,-0.3451911,-0.835376,-0.2364488,-0.06649118,0 +1,user1686,-0.6682365,-0.1166264,0.8690722,-0.5542142,-0.3761872,0.5253522,0.1355513,-0.9087189,-0.3452142,0.5404855,0 +1,user1687,0.9535957,0.4484434,0.8751069,-0.8509454,-0.6856964,-0.9180664,-0.686244,0.2361205,-0.3207347,-0.6149449,1 +1,user1688,-0.08911677,-0.3625077,0.1790741,-0.9918717,-0.6957406,-0.8141681,0.3441426,0.6261645,-0.2828088,-0.4784206,1 +1,user1689,-0.9721902,0.5004812,-0.5804283,-0.983161,-0.9484751,-0.5296817,-0.8845554,-0.4225426,0.07210093,-0.2840097,0 +1,user1690,-0.1057547,-0.6419785,-0.4107812,0.9716529,-0.04303559,0.2181783,0.870127,-0.5801575,-0.01465529,0.6053226,0 +1,user1691,0.4852452,-0.4667386,-0.03851496,-0.4753255,0.937345,0.03940887,-0.1616788,0.3389039,0.6794477,-0.01327951,1 +1,user1692,0.6922595,-0.5134009,0.7029424,-0.2537441,0.2745611,0.286327,-0.9260317,0.6691972,-0.499238,0.9271854,0 +1,user1693,-0.8049125,0.5747878,-0.7929803,-0.1717718,-0.6723321,0.6587644,0.9344478,0.3448819,0.03086267,0.2512209,1 +1,user1694,0.7854429,-0.7295252,0.8863653,0.7214838,-0.9992227,-0.1959568,0.8004744,-0.3045987,0.327022,-0.96108,0 +1,user1695,0.2608485,-0.8741791,-0.1486065,-0.6846131,-0.9296145,0.4730757,0.7423183,-0.5769985,-0.1336802,0.9002718,0 +1,user1696,0.01511,-0.1886431,0.2776505,0.2781442,-0.2303064,0.3434631,-0.2819624,0.6818481,0.0677442,-0.4512064,0 +1,user1697,0.9941048,-0.8715079,-0.2800854,-0.3881948,-0.4544824,-0.6331569,0.2768354,-0.9901067,-0.7191858,0.1645306,1 +1,user1698,0.4971014,-0.8958848,0.3858371,0.5964953,-0.855131,-0.8191886,0.767419,-0.9629032,0.3912208,-0.617009,0 +1,user1699,-0.2889842,-0.6125445,-0.3468498,-0.9337434,0.6213863,0.7459318,-0.1785143,0.3951723,-0.5590868,-0.2469728,0 +1,user1700,0.9639595,0.2680858,-0.0167107,0.7869112,0.08622536,-0.8073732,-0.5068942,0.188532,-0.7524572,-0.8873247,1 +1,user1701,-0.910018,-0.7269244,0.8988382,0.4346246,-0.7682513,0.9933965,0.9416683,-0.655452,0.163463,0.8459441,0 +1,user1702,-0.472767,0.2681098,-0.1567634,-0.3714113,-0.7880066,-0.4536296,0.9342144,-0.8933917,0.5802421,-0.99192,1 +1,user1703,-0.5309711,-0.9417829,-0.07089163,0.6287366,-0.7588235,-0.1788635,-0.06316401,0.2259262,0.4753313,-0.3072393,0 +1,user1704,-0.5928439,-0.6115636,0.8906513,0.8497086,-0.08043078,0.6232965,0.9488012,0.2788717,0.947324,0.7539207,0 +1,user1705,-0.143383,-0.4706616,-0.1270125,-0.466071,0.9203872,0.1846616,0.4622654,-0.2042926,0.9606588,0.8403088,0 +1,user1706,0.809077,0.6009927,-0.9993494,-0.5894374,-0.7119908,0.6139867,-0.2320173,0.8953714,0.1682085,0.9681459,0 +1,user1707,-0.9387327,0.5032025,0.9275252,0.2412303,-0.7027326,0.4543248,0.8383861,0.1852303,0.6879388,0.9512221,1 +1,user1708,0.05950809,0.4617569,-0.3349374,0.5976908,-0.6984996,-0.3991583,0.8354337,-0.5809378,0.2300073,0.633325,0 +1,user1709,-0.5545413,0.4145034,0.8493633,0.6740468,0.9430968,-0.5763648,0.2854764,-0.7668537,-0.4349192,-0.7486649,1 +1,user1710,-0.3054267,0.892298,0.1071553,0.7751526,0.6791703,0.7885195,0.08356827,-0.3095457,0.8768259,-0.7234176,1 +1,user1711,-0.07846345,-0.2713788,-0.745928,-0.4493974,-0.650161,0.4658688,0.02221964,0.6662332,0.04084045,-0.3380753,1 +1,user1712,-0.08565877,-0.5490987,0.7932145,-0.9511816,-0.2193409,0.5365785,0.2986197,-0.1589694,0.4726748,-0.4451261,1 +1,user1713,-0.5121899,-0.03690516,-0.36245,0.8559718,0.8344559,0.9092843,-0.136989,-0.1518429,-0.9068019,0.4217523,1 +1,user1714,-0.1277028,0.4274932,0.6240594,0.7112793,-0.1821723,0.0561844,0.1591614,-0.7199284,-0.5796294,0.4309154,1 +1,user1715,0.2221868,0.9829104,-0.8026797,0.7425488,-0.7213927,0.2748092,-0.09392094,-0.2363849,-0.1015483,0.9665292,0 +1,user1716,0.6797355,0.4408029,0.2805387,-0.5880595,0.4145443,-0.4983422,0.5178199,0.01278117,-0.1432507,-0.6447389,0 +1,user1717,0.2040607,-0.6891331,0.4931316,-0.8429348,0.4416405,-0.4184634,-0.7052873,-0.6286473,0.07515647,-0.02859916,1 +1,user1718,0.1757824,0.4313538,-0.9275727,0.8916034,-0.4070891,0.3567428,0.219835,0.9997356,0.5777171,-0.6484157,1 +1,user1719,-0.4093813,-0.9217047,-0.5403872,-0.5799312,0.7188037,-0.3125103,-0.1380376,-0.3610544,0.5739405,-0.1231595,1 +1,user1720,0.2318706,0.8113481,0.9127033,-0.8260958,0.4931653,0.05185487,-0.5898427,-0.05118998,-0.8527426,0.6873911,0 +1,user1721,-0.9299723,0.7893753,-0.3383539,0.8632563,0.5498754,-0.4250789,0.08996207,-0.5804219,-0.4369382,0.9569069,1 +1,user1722,-0.9241361,-0.3884433,0.4210978,-0.05525674,0.6561487,0.7268986,0.7002836,0.9778496,0.2533882,0.863561,1 +1,user1723,-0.07586998,-0.7020528,0.6156457,-0.07983989,-0.2322735,-0.6618182,-0.5158744,-0.3819928,-0.3519806,0.6145765,0 +1,user1724,-0.7348849,0.364163,-0.1313343,-0.3085155,0.8775433,-0.7663146,0.0244099,0.76446,0.5939244,0.2081278,1 +1,user1725,0.8613068,-0.1179685,0.3074631,-0.3337729,0.656926,-0.4690582,0.5007581,-0.3267491,-0.4195897,0.902481,1 +1,user1726,-0.8150215,-0.5762319,-0.5329608,0.235547,-0.161888,0.8112575,-0.773556,0.0410087,0.5143392,0.5148483,0 +1,user1727,0.2802251,-0.8244801,-0.8536838,0.9696288,-0.3527631,0.5771485,0.7424475,0.4463081,-0.3383314,0.7569214,1 +1,user1728,0.8554116,0.01052354,-0.9726223,0.2780323,-0.7975564,-0.1022151,-0.2224066,-0.3168557,-0.1387756,0.06701157,0 +1,user1729,0.6820799,-0.4721167,0.8528763,-0.1679577,-0.01701898,0.9920689,0.993863,0.07810553,-0.09443999,0.8978393,0 +1,user1730,0.991241,-0.4370246,-0.2005336,-0.9641147,-0.7313768,0.3230803,-0.4360668,-0.1585196,0.1025819,-0.4900513,0 +1,user1731,0.8193711,-0.7213907,0.01066701,0.06494342,0.288669,0.09041167,0.2706992,0.8716763,0.1087673,0.1796868,1 +1,user1732,0.7720619,-0.1990411,0.7517146,-0.7333331,0.2147297,0.9854655,0.9355313,0.4226535,-0.930977,0.7437834,0 +1,user1733,-0.481526,0.8310852,0.642703,-0.335526,-0.5193834,0.8694507,-0.5018524,-0.05191125,-0.3171761,-0.4819714,0 +1,user1734,-0.7116,-0.6631735,0.9397754,-0.3063199,0.5298455,0.9115482,-0.7924648,0.0976025,-0.4159014,0.8724475,0 +1,user1735,-0.820782,0.1893953,0.6423658,-0.8836245,-0.8657011,0.608762,0.8843326,-0.2984748,-0.983653,0.4977041,1 +1,user1736,0.375091,-0.6395764,-0.4843095,0.198403,-0.5989962,0.05411224,0.9604131,0.7437961,-0.3565173,-0.6416626,0 +1,user1737,-0.902523,0.9378191,0.940426,0.1042427,0.8178547,0.5255349,-0.02448209,-0.007026135,0.7523071,0.8405934,1 +1,user1738,-0.7595147,-0.3074022,0.5698911,0.3576058,-0.5684337,0.06308678,0.7227187,0.8867555,0.7042858,0.4489262,1 +1,user1739,-0.5654009,0.8221805,0.1807532,-0.2039062,-0.2974957,0.654954,0.7958468,-0.8371417,0.87349,0.9916624,0 +1,user1740,-0.4570643,0.3523225,0.7897893,-0.2217105,0.7609515,0.9491701,-0.7390057,0.2261202,-0.6826121,-0.9080716,0 +1,user1741,-0.06494141,-0.4151042,-0.3229536,0.1327584,-0.8892635,-0.1483937,-0.1937131,-0.4227901,0.5811117,0.7255086,0 +1,user1742,0.3561356,-0.4491983,0.4348252,0.3466964,0.05234323,0.1208228,-0.1819336,0.8290915,-0.08566958,-0.3464129,1 +1,user1743,0.457277,0.8032238,0.5830038,-0.172892,-0.4583894,0.4857486,0.559614,-0.9328492,0.7900627,-0.3531977,0 +1,user1744,0.4228687,0.5479907,0.3145964,-0.01126979,0.9451924,-0.2391094,0.6692979,0.425367,0.6743098,0.1472609,0 +1,user1745,-0.7715672,0.978295,0.05888459,0.05797579,0.8701709,-0.8229928,0.9772278,-0.8908369,0.334701,-0.9154975,1 +1,user1746,-0.3205363,0.7861342,0.7803242,-0.4303432,-0.179782,-0.2394422,-0.5343069,-0.1692341,-0.3114856,-0.3866685,0 +1,user1747,0.1026042,-0.01120639,-0.404865,0.4006707,0.3597368,0.2625483,0.1871178,-0.5618518,-0.4689409,0.502522,0 +1,user1748,0.4324935,-0.7108382,-0.4479838,0.215041,0.3118114,-0.2414562,-0.7280595,-0.5194842,-0.5901425,0.05590329,0 +1,user1749,0.8552461,0.2174881,0.8527514,-0.5387398,0.4131289,-0.8826995,0.6855281,-0.1694985,-0.7337685,-0.03508426,1 +1,user1750,0.6932229,0.06708888,0.05474779,0.8207395,0.07854045,0.950038,-0.9509198,0.0770938,-0.8950003,-0.6206375,0 +1,user1751,-0.3356359,-0.8994901,-0.5352805,0.3889451,-0.1950233,0.8103987,-0.3179022,0.4293258,-0.4428851,-0.2567056,0 +1,user1752,0.9252738,0.006863344,-0.4856025,-0.6754835,-0.03699574,-0.3077784,-0.2245099,0.2500796,-0.1707068,-0.07817738,0 +1,user1753,0.7690869,0.6786456,-0.5241544,-0.2345172,-0.2653108,0.6769366,0.7493639,0.05494339,0.3583879,-0.7570765,0 +1,user1754,0.5884941,-0.6015429,-0.9196348,-0.6908948,0.5727032,-0.8514195,0.1662234,-0.952667,0.2051343,-0.6421291,1 +1,user1755,-0.809611,-0.6289736,0.3830632,0.01600108,-0.1594524,-0.07409298,0.7999,0.01453959,-0.5767824,-0.8700496,0 +1,user1756,0.6303937,-0.439323,0.7833087,0.4317098,-0.6083848,-0.7921216,0.2501219,0.7281943,0.9387982,-0.8545955,1 +1,user1757,0.7734727,-0.1777748,-0.4525956,0.5446522,-0.5891848,0.959838,0.3926674,0.08834169,-0.2805265,0.8727191,0 +1,user1758,0.4706141,-0.4534537,0.5293795,-0.01437017,0.4877844,-0.4969445,0.5423476,-0.5391523,0.08488628,0.8868718,1 +1,user1759,0.4858052,0.5712006,0.8106864,-0.2902579,-0.4059411,0.1056633,-0.9722847,-0.5886614,-0.1999774,0.2124161,0 +1,user1760,0.4555526,0.3501084,-0.5997192,-0.6233055,0.3937962,0.9519069,0.3865304,-0.8335528,0.6250335,0.7705584,0 +1,user1761,0.4618551,0.1095217,-0.6711541,0.02151517,0.7564076,0.8261358,-0.8937192,0.3023281,-0.8125318,-0.6031795,0 +1,user1762,0.3051763,0.8498099,-0.1786466,0.7746855,0.8827278,-0.803925,0.2984145,-0.7169852,0.9087899,-0.6078971,0 +1,user1763,0.2276145,-0.8489327,-0.8480047,-0.3566386,-0.3914741,0.9373724,0.3220617,0.5891007,0.6940565,0.5143418,0 +1,user1764,0.9803291,-0.05939307,0.9715489,0.6859892,-0.7629758,0.6955865,-0.3955716,-0.7495831,-0.1297079,-0.08515086,0 +1,user1765,0.5935763,-0.8133636,-0.2388712,-0.5316344,0.4125733,-0.8923768,0.5059497,0.3806173,-0.5071115,-0.7354496,1 +1,user1766,0.4068325,0.3404626,0.7943612,-0.2402632,-0.2571752,0.5461344,0.2063943,-0.709374,0.7104035,0.01204593,1 +1,user1767,0.3554201,0.3010305,-0.5127605,-0.1156077,-0.3619719,-0.2503012,-0.4351585,0.994213,0.5137748,0.2731865,1 +1,user1768,0.6910533,-0.8755445,-0.2984452,0.5726083,0.230428,0.6331581,-0.5185324,-0.6264088,-0.7548044,-0.8948562,0 +1,user1769,0.6473178,-0.9669396,0.3642522,-0.8826573,0.1743911,-0.3907789,-0.07088706,-0.8226185,0.4146893,-0.5390279,0 +1,user1770,0.7900192,0.1232111,0.6679927,0.6804861,0.3405324,-0.5953473,-0.6393117,-0.8429287,0.3872648,0.2648489,0 +1,user1771,-0.766011,0.476778,-0.5086559,-0.6491022,-0.008620463,0.5823282,-0.2575381,0.5997114,-0.4374165,-0.8029278,1 +1,user1772,-0.4176236,-0.3820438,-0.9587014,0.2501011,0.2851276,0.4608274,0.7353999,-0.2454086,-0.004199062,-0.8135192,0 +1,user1773,0.1461548,0.6740128,0.1028179,0.02718252,-0.6071244,0.5254756,0.1787547,0.9861628,-0.6984048,0.918436,0 +1,user1774,0.691266,0.2800019,-0.925652,0.1780057,0.5329902,0.06807676,-0.6979241,0.6668622,-0.6473538,-0.1561255,0 +1,user1775,-0.9947548,-0.8340531,0.355895,-0.7611687,0.23032,-0.778282,0.4046978,-0.8200416,-0.3298893,0.3337417,1 +1,user1776,0.3745877,0.6523078,-0.8382976,-0.9148417,-0.7369535,0.7024828,0.1559825,-0.9046741,0.6362962,-0.9970615,0 +1,user1777,-0.6292703,0.0661361,0.8546721,0.7476625,-0.6467919,0.8286345,-0.232231,-0.502372,0.0411606,0.4572059,0 +1,user1778,0.1078494,0.1547405,0.95103,0.639502,-0.4099432,0.4842663,-0.4081844,-0.3818935,0.2011698,-0.1637363,0 +1,user1779,-0.1929188,0.9414696,-0.2862813,0.3001993,0.5748579,-0.5389734,0.4279229,-0.4241583,-0.9538463,0.05884176,1 +1,user1780,-0.7740241,-0.7163758,0.7074236,-0.7910773,0.766337,0.9459351,-0.5467029,0.3281295,0.3073921,-0.5778783,1 +1,user1781,-0.1989277,-0.7781706,0.005777793,0.4602415,0.6685972,0.4343043,-0.3591042,0.6952003,0.3061695,0.2156262,1 +1,user1782,0.4714453,-0.9580205,0.1784381,-0.3108556,-0.6201653,-0.7285748,-0.8899793,-0.9948325,-0.3967313,0.8021362,0 +1,user1783,-0.8487503,0.2904875,-0.7781789,-0.4665607,-0.2706587,-0.3618433,0.2287872,-0.4217909,-0.8633147,0.3439443,1 +1,user1784,-0.4298408,0.900475,0.4816234,-0.7742757,-0.5967136,0.1112409,-0.6097403,-0.2498563,-0.3354426,0.4585497,1 +1,user1785,0.05993942,-0.5595635,0.2588033,-0.001750372,0.9525379,-0.5799943,0.2762442,-0.9474995,0.808403,-0.839993,0 +1,user1786,-0.6583613,0.6615139,0.6048843,0.5494404,0.5698889,0.5640637,0.02868724,0.5927487,-0.4400971,0.4738947,1 +1,user1787,-0.7994472,-0.538848,0.2649321,0.6574341,-0.2050984,0.3191194,0.6403816,-0.521662,-0.3966444,0.6039542,0 +1,user1788,-0.1665879,0.2626617,0.8062078,-0.4570982,-0.6366469,-0.6201563,-0.3310884,0.1408422,-0.4721235,-0.9672739,1 +1,user1789,0.8122528,-0.7919398,0.1342638,-0.4649298,0.05767328,-0.9328808,-0.4289652,-0.9464036,0.6447892,0.3607666,1 +1,user1790,0.6863581,-0.9676474,0.07561845,-0.6328238,0.3889605,-0.5752173,0.6680969,-0.1103234,0.4033782,-0.1836298,0 +1,user1791,-0.7110354,-0.3872299,-0.7935114,-0.08040371,0.7571493,-0.6682494,-0.944558,0.3072894,-0.84709,0.8032845,0 +1,user1792,0.2741079,0.317582,0.4631097,0.5565854,-0.1859191,0.8932551,-0.3226844,0.3559246,0.8322574,0.7575871,0 +1,user1793,-0.008465652,0.8821625,0.8969718,-0.8581383,0.2716883,-0.3791423,-0.03348857,0.1726914,0.312168,0.2084731,0 +1,user1794,0.5165791,-0.2361626,-0.6415161,0.5629577,-0.6343248,-0.730877,0.3775037,-0.1036099,0.8469665,0.3176264,1 +1,user1795,0.254437,-0.7418111,0.4346586,0.2425746,0.05110511,0.5888416,0.281744,0.6063415,-0.2974505,-0.3275638,0 +1,user1796,-0.4148894,-0.9312011,-0.3418994,-0.3897728,-0.3157383,-0.2715192,-0.5275389,-0.4466913,0.8050565,0.4730235,1 +1,user1797,-0.07658839,-0.8956999,-0.8471549,-0.6773055,0.1085,0.8152573,-0.416102,0.1870161,0.55737,-0.6703277,0 +1,user1798,-0.3901429,0.5592194,0.9218981,-0.8730332,0.6891332,-0.6614597,0.8465855,0.6005545,-0.7836757,0.9456227,1 +1,user1996,-0.505139,-0.3891592,0.04546911,0.6894496,0.7771339,0.6418929,-0.6984262,0.7529061,-0.3714026,0.9208234,1 +1,user1997,0.03927932,0.4410501,-0.6940883,-0.1558275,0.595243,-0.9032923,-0.869496,-0.1353484,0.2213518,-0.3515713,1 +1,user1998,-0.6145329,-0.2541279,-0.9422715,-0.3106021,0.3633087,0.8836607,-0.6953307,0.2106634,0.8698667,-0.3689291,0 +1,user1999,0.001413106,0.184933,0.9999353,-0.8484532,-0.8041993,-0.001085618,-0.1772451,0.671221,0.2985496,0.5027685,0 diff --git a/psi/psi/demo/data/bob.csv b/psi/psi/demo/data/bob.csv new file mode 100644 index 00000000..e665f2d2 --- /dev/null +++ b/psi/psi/demo/data/bob.csv @@ -0,0 +1,1561 @@ +idx,id,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20 +1,user1,0.1037222,0.3286688,0.8073164,0.2146366,-0.9908792,0.5692856,0.0561529,-0.4129725,0.9291588,-0.1073308 +1,user2,0.07885405,-0.7050547,-0.6051254,-0.2247453,-0.1127391,-0.117463,0.9373149,0.4381533,0.2713191,-0.9808069 +1,user3,-0.558026,-0.6808125,0.5465339,-0.7311879,0.8973241,0.8622853,0.5557214,-0.8297518,0.07622435,-0.1101171 +1,user4,0.1137507,0.8425722,0.6952187,-0.680933,0.2948163,-0.9311873,0.2623415,0.07261752,-0.5700419,0.2504987 +1,user5,0.4530572,-0.7036529,-0.8890432,0.4490287,-0.459556,0.6020563,-0.3515757,-0.8605247,0.3286168,-0.01624326 +1,user6,0.2473782,-0.05653108,-0.8381591,-0.1972202,-0.4179593,0.9628136,-0.3599197,-0.2347297,0.7615432,0.9878466 +1,user7,-0.2124835,0.6211248,-0.4198574,0.6894154,-0.4992381,-0.5617928,-0.7694952,0.2370069,0.8995586,0.4569834 +1,user8,-0.7565525,0.8737258,-0.7366072,-0.1153731,0.4620923,0.4892273,0.4357672,-0.06626883,0.03582206,-0.9450782 +1,user9,-0.5303183,-0.7163486,0.06634315,0.06568135,0.8859513,0.1623057,0.9712025,-0.3752247,0.1340124,-0.8976452 +1,user10,-0.6268767,-0.6249271,0.8926257,-0.4762651,0.4462711,0.411621,0.144974,-0.718891,0.1295648,0.4107351 +1,user11,-0.5934344,0.999977,0.2567963,-0.9609649,-0.4984772,-0.6335839,-0.3863574,-0.66624,-0.3650303,-0.3888592 +1,user12,0.7470386,-0.4897706,-0.1885526,0.421613,-0.9152771,0.03895415,0.6091573,0.4484669,-0.7973595,0.9794329 +1,user13,-0.1385716,-0.360139,0.2124682,0.7334109,0.2290166,0.308546,0.4104837,0.8298407,0.7164711,-0.9582136 +1,user14,0.1367789,0.5179967,0.5091836,-0.9677715,-0.01766043,0.6417012,-0.7742099,0.02182418,0.8584791,0.2887811 +1,user15,0.2719186,-0.9954501,0.8909051,0.2267425,0.8542413,-0.2707861,-0.3408062,0.03400175,-0.1931723,0.7177876 +1,user16,-0.2858972,-0.9540337,0.4776035,-0.4759653,0.05027175,0.277112,0.3027969,0.3586828,0.2953446,0.04825809 +1,user17,-0.3792467,-0.5122228,0.4131949,-0.9080799,0.7715957,-0.2782625,-0.3094955,-0.08621523,0.9045332,-0.08336186 +1,user18,-0.4105659,-0.2346241,-0.8583543,0.7988975,-0.994524,0.1397084,-0.6801556,0.6818956,-0.1012165,-0.04093658 +1,user19,-0.2035044,0.9968768,-0.4936887,0.9747568,-0.8769255,-0.6265327,-0.2004481,-0.7853359,0.5538439,-0.6483564 +1,user20,0.7186799,0.5243351,0.6733948,-0.9729245,-0.09978013,-0.652304,-0.08019365,0.1467457,0.8249891,-0.5172263 +1,user21,-0.3023376,0.8065632,-0.1736501,-0.5382653,-0.005302239,0.8071427,-0.9532377,-0.7649963,-0.8375962,0.2738303 +1,user22,-0.4601818,-0.4534552,0.9068978,-0.5951641,-0.1881026,0.4130904,0.4216905,-0.1630338,-0.154566,0.5435413 +1,user23,0.2889728,0.2956655,-0.6867983,-0.9823263,0.6313105,-0.7312484,0.1297511,0.5794849,0.9530751,0.7695588 +1,user24,-0.6453376,-0.3881143,-0.1129227,0.3099239,0.4202549,0.7984404,-0.2229038,-0.8316156,-0.2400682,0.5292447 +1,user25,0.2374107,-0.01789421,0.5471715,0.7413738,0.949355,-0.2670915,0.5187057,0.5185319,0.281679,-0.8962244 +1,user26,-0.5525259,-0.6466292,0.2821148,-0.5053412,-0.7078854,0.9612136,-0.06456404,-0.7664245,-0.3992978,0.2882044 +1,user27,-0.811461,-0.0313306,-0.9084027,-0.5286905,-0.0862235,-0.4239115,0.9863737,-0.1525009,-0.6677636,0.6831061 +1,user28,-0.1845853,0.04703893,-0.07870023,-0.1505996,-0.01938269,0.8664421,0.5855091,-0.3476806,0.8955946,-0.36144 +1,user29,-0.862191,0.2694384,0.9656141,-0.02063609,0.4208954,0.6364922,0.6549044,0.8613258,-0.8616028,-0.0732074 +1,user30,0.5184194,0.9993252,-0.1155855,-0.9214355,-0.5757127,-0.613495,0.2180769,0.2989015,0.9758553,0.9207602 +1,user31,0.4307006,-0.2034433,0.4333719,0.9288804,-0.09296998,0.2101197,0.1125071,0.7524346,0.07652296,-0.4427824 +1,user32,0.2415311,-0.4018928,0.7729304,-0.8059995,0.4300162,0.2057778,-0.2889427,-0.5516467,-0.9324441,0.8194618 +1,user33,-0.4027265,-0.7057295,0.279289,-0.1461808,0.3115481,0.269042,0.1553918,-0.2629452,0.2471744,0.9399533 +1,user34,0.8726747,0.1157442,-0.02009422,-0.8023075,-0.1956459,0.07240506,-0.3317716,0.9226827,-0.8472527,0.4471005 +1,user35,-0.6447181,-0.5593206,0.4681491,-0.4869325,-0.2751675,0.2745905,0.9733988,0.5209709,-0.502486,0.06996049 +1,user36,-0.9496693,-0.4093824,0.3902458,-0.6971521,0.8519921,-0.1289017,0.8038162,-0.1234699,-0.4242088,-0.07628992 +1,user37,0.1200528,-0.9407869,0.1417467,0.0004722788,0.3863948,0.03521863,0.3083088,-0.3120469,0.9142905,0.4349471 +1,user38,0.1427984,-0.9381957,-0.9517083,-0.7975171,0.2255944,0.7127977,-0.7960963,-0.2420222,-0.6029274,-0.4730561 +1,user39,-0.7062218,-0.5356567,0.6536386,0.1874749,0.3140844,-0.6396744,0.2395834,0.8102612,0.6116132,-0.02136814 +1,user40,0.5897345,-0.6571355,-0.7919102,-0.9338464,0.2723461,-0.8024757,0.2795113,0.3127283,0.04830297,0.5373019 +1,user41,0.5159216,-0.5631228,0.9409175,-0.2737821,-0.3281346,0.1244187,0.3488777,0.03908683,0.5266374,0.937679 +1,user42,-0.2996562,-0.5356796,-0.08956508,0.22651,0.8156072,-0.2732582,0.853226,-0.8559788,-0.7534171,0.5897727 +1,user43,0.3367731,-0.1469061,0.01953725,0.4877667,0.357069,0.2364784,-0.1113314,-0.2388048,0.2509435,0.5167347 +1,user44,-0.62265,0.07673824,0.1533856,-0.5403712,0.900882,-0.5670353,-0.2406386,-0.1310725,0.2431085,0.9794655 +1,user45,0.8371227,0.9823171,-0.5803815,0.2587384,-0.2020532,-0.631557,-0.9209839,0.1658454,-0.894938,-0.1214462 +1,user46,-0.3913083,-0.1423562,-0.0895576,-0.2854908,0.2113103,0.9656923,0.5478623,0.795197,-0.9422288,0.2345223 +1,user47,0.09145283,0.1227045,-0.3690108,-0.01633657,-0.04884623,0.7100767,-0.9378417,-0.7723896,-0.461547,0.02772356 +1,user48,-0.542124,-0.5299057,0.8328134,0.3506585,-0.4304576,0.09018051,-0.2304794,-0.9203698,-0.9904048,0.7951919 +1,user49,0.1981258,0.6230197,0.05208815,-0.4865933,0.2167863,0.1054007,0.8677067,0.4770926,-0.04344533,-0.8064143 +1,user50,0.8879484,0.1195813,0.1373005,-0.04157974,0.0742283,-0.916456,-0.1382898,-0.5577256,-0.9077031,0.3793671 +1,user51,-0.8234441,0.9944295,0.5062081,0.377734,0.4697623,0.4378765,0.6893269,0.2263759,0.8345842,-0.7220344 +1,user52,0.8957882,0.4295829,0.878438,-0.02485868,-0.788516,-0.08745659,0.914469,0.7120963,0.1189585,0.467416 +1,user53,-0.5722334,0.6661261,0.04419833,0.3632562,0.8861257,0.4966344,-0.7165993,0.2792406,-0.06226913,-0.07709159 +1,user54,0.4655288,0.290095,0.8194099,0.3954077,0.1010728,0.7066281,-0.1809219,-0.1941392,0.7876593,-0.9524757 +1,user55,-0.7495494,-0.9585315,-0.2344847,-0.7149348,0.631739,-0.2890161,-0.3084348,0.8804808,0.8788903,-0.003339247 +1,user56,0.6651774,-0.3517681,-0.4086301,0.10463,0.8354807,-0.7704571,0.8021064,-0.2022275,-0.7805901,0.02668404 +1,user57,0.9130029,0.6434658,0.1015247,0.8900665,0.3931874,0.6678417,0.7545141,0.03943628,-0.6116385,0.3357287 +1,user58,-0.5610105,0.01013794,-0.1428874,-0.2436253,-0.4544845,0.2870724,-0.3220611,-0.2720201,-0.7888733,-0.3202331 +1,user59,-0.519408,0.6952708,0.5126696,0.9540304,-0.1839019,-0.904015,0.3876156,0.4500919,-0.8849955,0.665244 +1,user60,-0.9491882,-0.08709583,0.06713877,-0.1305696,-0.1859172,0.3043339,0.4094185,-0.09923787,-0.4732413,-0.7374787 +1,user61,0.957409,0.00946311,0.7415271,-0.1650608,-0.03019728,0.6735774,0.8960158,-0.9731186,-0.813018,-0.3994729 +1,user62,0.9112926,-0.5081725,-0.05395848,0.8829108,0.7231281,0.3061048,-0.4998773,0.2025265,0.1915274,-0.7775383 +1,user63,0.292343,0.5110114,-0.1599308,0.06343097,-0.755901,-0.4898882,-0.8795242,0.3491155,-0.4056853,-0.9180169 +1,user64,-0.4453175,0.3037336,0.02081614,0.6887584,-0.7186491,-0.05738063,0.05140767,-0.2360639,0.4341564,-0.4595196 +1,user65,0.7839673,0.6075717,0.9259473,-0.9193968,-0.4725179,-0.6214902,0.1683511,0.1252093,0.3442748,0.6695621 +1,user66,0.6476248,0.9516908,-0.6917817,0.5764985,-0.03106849,0.7847023,-0.9061253,-0.1299137,0.09182867,0.1519435 +1,user67,-0.3949869,0.8943512,-0.5889381,0.9916064,-0.8666571,0.8137177,-0.1447762,0.6404662,-0.9900524,0.4641905 +1,user68,-0.09597985,0.6667848,0.06769397,0.08107549,0.9138769,0.4137285,-0.5233402,0.8131623,0.2585653,0.1045092 +1,user69,-0.2095768,-0.9865049,-0.6434899,0.7789814,-0.8054741,0.4975,-0.7022217,0.6280641,0.4889013,0.6788875 +1,user70,-0.1012087,-0.6413055,-0.9352994,0.1790812,0.4474273,-0.8259567,-0.9051928,0.4507274,0.6215608,-0.5571777 +1,user71,-0.5062454,-0.9903507,0.2757838,0.1472291,0.186223,0.6112528,0.7561711,0.1258907,-0.6931318,-0.3581889 +1,user72,-0.6936552,-0.5496277,-0.7025725,-0.4948007,-0.1336086,-0.3780813,0.6466561,-0.3328491,0.01553865,0.6165665 +1,user73,0.5991351,-0.1769851,-0.02486452,-0.5944088,0.2630345,-0.09921495,0.9480332,0.5947487,0.8681437,-0.967405 +1,user74,0.8305277,-0.1372567,-0.704679,-0.3650042,-0.456708,-0.1522688,-0.3551603,0.8870859,0.5578118,-0.8414542 +1,user75,-0.3163051,0.5271106,0.4508132,-0.03517193,-0.2327266,0.05488337,-0.5939826,0.5360785,-0.7413529,0.596032 +1,user76,0.4362578,-0.194668,0.3947539,0.6643296,-0.9390187,0.2692281,-0.9729507,-0.2394059,0.9732057,-0.08885123 +1,user77,-0.5607806,0.7203871,0.2057634,0.349505,0.7546022,-0.1865764,-0.807298,0.6822829,0.6155829,0.3930681 +1,user78,0.7751477,-0.3501849,-0.9181976,0.9484915,0.7184272,-0.2350399,-0.5318243,0.7636888,-0.2028999,-0.3762445 +1,user79,0.8941339,0.2754263,0.2275673,0.01498816,-0.3694763,-0.6405914,-0.2034301,-0.1597758,0.9828009,-0.2936593 +1,user80,0.6373452,0.3434068,-0.7421484,0.8629116,-0.02861149,0.9188243,-0.9395913,0.1593754,-0.4278624,0.5866539 +1,user81,0.6630961,0.7693964,0.2191029,-0.09308824,-0.2073446,-0.1514959,0.329886,-0.7940368,-0.110603,-0.9968773 +1,user82,-0.9293102,0.2698558,-0.2662245,-0.6072778,-0.899714,0.7972851,-0.5141031,-0.9333999,0.8173851,-0.01569376 +1,user83,0.5331333,-0.2270103,-0.8637104,-0.161947,0.1828725,-0.1686323,0.9748776,-0.1285282,0.6910961,0.0540699 +1,user84,-0.9091373,0.4355225,-0.7366988,-0.7298321,-0.3212189,-0.6548615,0.6132867,0.4852038,0.8271279,-0.0739689 +1,user85,0.5362186,-0.4400493,-0.4468147,0.7881299,0.2013589,0.5039132,0.304975,-0.1275391,0.6050444,0.03183057 +1,user86,0.7835839,-0.1855417,-0.09819507,0.1231182,-0.1853885,0.5423516,-0.3335572,-0.2480475,0.5699864,-0.9492693 +1,user87,0.7560401,-0.9162456,-0.1453289,0.3747979,-0.4857381,-0.4253186,0.4153931,-0.7170237,-0.9534622,0.9527151 +1,user88,0.4492214,-0.7965835,0.65471,0.6781965,-0.4054537,0.1717549,0.059489,0.9118972,0.993406,-0.6324407 +1,user89,-0.7774266,0.8245962,0.7589175,0.8794929,0.360127,-0.1705761,0.3443818,0.4799324,0.7811131,-0.2695025 +1,user90,-0.7633679,0.7790253,-0.6326593,0.3288283,0.33036,-0.3293336,-0.1969913,0.7330683,-0.8384578,0.6179592 +1,user91,0.5000333,0.1163207,-0.2781512,-0.4523731,0.4086291,-0.5239112,-0.5310925,-0.1873407,-0.4798353,-0.3699194 +1,user92,-0.8200176,-0.1659407,0.5004446,-0.2855679,-0.6700703,-0.4969987,0.2403976,0.5068137,0.9680951,0.3310246 +1,user93,-0.8520753,-0.7291472,0.3133823,0.211739,0.05348803,0.9767712,0.3031313,-0.0644052,0.3530697,0.8404208 +1,user94,-0.2076238,-0.3726679,0.561918,0.6110579,0.6527282,-0.01379941,-0.4106167,-0.8382252,0.1144793,-0.2879364 +1,user95,-0.2653352,-0.8622071,-0.4787393,-0.5968095,-0.3887195,0.4456207,-0.7081947,-0.7292501,0.4022515,0.871505 +1,user96,0.9318921,0.8784245,0.2393296,0.2923422,0.5809702,-0.6447189,-0.5285176,-0.9391959,-0.3026555,0.509983 +1,user97,-0.5599989,-0.4209771,0.8701363,0.1875564,-0.3783403,-0.2290971,-0.316742,0.03186112,-0.793692,0.8640072 +1,user98,0.3396779,-0.9678559,-0.06767731,-0.6052031,-0.2553765,0.2593384,0.1470291,0.9112161,0.4121991,0.3356955 +1,user99,-0.1640878,0.5452093,-0.6929765,-0.6265823,0.4948471,0.7690095,-0.05185771,0.8739664,0.9559097,-0.3855078 +1,user100,0.2304243,-0.407482,-0.7733536,-0.03346216,-0.1838144,-0.7315971,-0.01896371,-0.3400748,0.6952093,0.5428946 +1,user101,-0.7615307,-0.6091614,-0.002976751,0.5738781,-0.8079492,0.4333817,0.2418363,0.3619435,0.03375995,0.7785178 +1,user102,0.3296668,0.5548587,0.5828073,0.5206469,-0.3189299,0.3802623,-0.2956866,-0.0001429454,-0.737222,0.2563033 +1,user103,0.5367691,0.04289036,-0.4759261,0.4717371,0.6825769,-0.1096784,-0.3723077,0.3270762,-0.2892521,0.1594612 +1,user104,0.8376044,0.2138534,0.9721587,0.9794693,0.4550853,-0.6658333,0.1898696,-0.04330783,-0.09809632,0.8111128 +1,user105,0.1601946,-0.582398,0.8781284,-0.8443574,0.2243621,-0.7720065,0.3491531,-0.1130571,0.8205897,0.4148491 +1,user106,-0.779536,-0.4299991,0.9748871,-0.5634348,-0.5501497,0.9452049,0.03370979,-0.1368454,-0.03060498,-0.2445069 +1,user107,0.2738622,-0.9808146,0.3669127,0.6437989,0.5160666,0.6033948,0.2169189,0.7172862,-0.1248906,-0.2777384 +1,user108,0.599414,-0.8620109,0.08389182,0.5051476,-0.02103566,0.04141705,0.5418551,-0.4307742,0.4361727,-0.1920828 +1,user109,0.9956117,0.219816,-0.9433105,-0.6149433,-0.8317226,-0.289835,0.5018855,-0.3731566,0.7664951,0.3792487 +1,user110,0.1679961,0.2946117,-0.40552,-0.3412129,-0.8534097,0.9628033,-0.9865112,-0.4424895,-0.1420898,0.4286023 +1,user111,0.2367591,0.4813959,0.3417434,0.3680592,0.9503529,-0.03975866,0.6022638,0.7286012,-0.9916897,-0.6054289 +1,user112,0.6587078,-0.01078756,0.2757924,0.2919685,-0.0390671,0.5586691,-0.1682285,-0.1671933,-0.3441079,0.3823714 +1,user113,0.2386858,-0.4355325,0.3282555,0.0515093,-0.7531237,0.7600884,-0.5006143,-0.3758894,-0.3247047,-0.5870915 +1,user114,-0.2301076,-0.7456144,0.478033,-0.7938878,0.1332254,0.791609,0.5771414,-0.399927,0.6994064,0.448641 +1,user115,0.7495705,-0.575265,0.5390936,0.5621364,0.639714,0.9038075,-0.5549419,-0.6819895,-0.51698,-0.6915975 +1,user116,-0.2250956,0.1244182,0.8814408,-0.1603608,0.4482352,0.2640016,0.8043606,0.4965716,-0.7196603,0.4447391 +1,user117,-0.4465237,0.06884388,-0.6201621,0.3292304,0.9478369,0.3339606,-0.7564157,0.3520255,0.2693927,0.4993716 +1,user118,0.5056106,-0.4915106,-0.6062353,-0.06306571,-0.8460241,-0.5215111,0.8604513,-0.3990131,-0.4704423,-0.7388824 +1,user119,-0.7758742,0.3278347,0.5361508,-0.4821643,-0.9572186,-0.5642435,-0.1361504,0.4084688,-0.7262543,0.8122983 +1,user120,-0.2239503,-0.1065599,-0.8612445,0.2087233,0.3079639,-0.8366155,0.5879661,-0.1680421,0.05050582,-0.7701309 +1,user121,0.7422427,-0.7124853,-0.2388945,-0.7342374,0.4843359,0.1491553,-0.3365401,-0.6659449,-0.3089,0.8790768 +1,user122,0.7241591,-0.5558446,-0.7420004,0.0654626,0.4514106,-0.08815472,0.3327571,-0.7788719,-0.2060896,-0.5576211 +1,user123,-0.04396796,0.7274994,0.6392001,0.9231554,0.6378936,-0.3336142,-0.1716363,-0.6612284,0.01860094,0.5608937 +1,user124,0.8901674,-0.4416325,-0.9255123,0.4775016,-0.4621761,0.1259266,0.9665913,0.2696499,-0.9558303,0.7194976 +1,user125,-0.4834647,0.07148746,0.8199176,-0.3234795,0.1041387,0.8980459,0.9221404,-0.6170971,0.9083897,0.1544425 +1,user126,0.6906968,0.8652923,-0.8395392,-0.6736541,-0.7508259,-0.8879935,0.120169,-0.3904785,-0.5791475,0.4323987 +1,user127,0.8220595,-0.563208,0.3138173,-0.2301561,-0.8812059,0.4812076,-0.5619263,0.330454,-0.2584859,0.2294806 +1,user128,-0.04346358,0.6505104,0.6900539,0.8640769,0.7257984,-0.3310513,-0.3946016,0.414764,-0.8853023,0.01844967 +1,user129,0.0303748,0.8974364,0.09278352,-0.2788572,-0.006202394,0.3713449,-0.7328019,-0.4792624,0.8330516,-0.2319058 +1,user130,-0.3420283,0.9820014,0.6208408,0.1432616,0.6136412,0.2502171,0.386216,0.2044204,-0.3025761,0.8439727 +1,user131,-0.8130393,-0.7569716,0.9167003,-0.1693853,-0.458016,-0.0626484,0.5864346,-0.9253108,0.809907,-0.4386557 +1,user132,0.2688441,-0.711725,-0.9101932,-0.704979,0.1858484,-0.1952735,0.5090344,0.8826811,-0.1331884,-0.453388 +1,user133,0.9876385,0.5368601,0.2036482,-0.3360916,-0.7052887,-0.3695206,-0.9094706,-0.7957226,-0.03979814,0.100276 +1,user134,0.7237299,0.2859188,-0.5592258,-0.6976481,-0.7754391,0.8276732,-0.785873,0.4017654,-0.4793451,0.7208055 +1,user135,0.1064484,0.5021284,-0.9380345,-0.7255097,-0.3590663,0.1388932,-0.301096,-0.1606267,0.7687152,-0.6422751 +1,user136,0.1478331,0.954462,0.08177654,-0.1804489,0.5190734,-0.1415271,0.4396825,0.09122037,-0.2192084,-0.4848749 +1,user137,0.9441938,0.8559197,-0.5843386,-0.2610829,-0.3255888,0.7728781,0.2478368,-0.73508,0.4900499,-0.5237014 +1,user138,-0.6196894,0.5213138,0.4288782,0.9182892,-0.8429997,-0.257712,0.9158229,-0.4433405,-0.3561754,0.07998646 +1,user139,-0.252753,-0.9075489,-0.8343316,-0.6753013,-0.5019623,0.89989,-0.01846241,0.6604462,-0.7830357,0.3230422 +1,user140,0.9398055,0.07573573,-0.5276491,0.1239738,-0.1573114,-0.5169569,-0.2502777,-0.1082365,0.256545,0.8555473 +1,user141,0.5483067,-0.1840744,-0.9766418,-0.4229236,-0.6964094,-0.2949087,0.9293117,0.11417,0.5017348,-0.4914113 +1,user142,0.9840061,0.573847,0.5074118,0.6927579,-0.5516094,-0.1398687,-0.4161986,0.3890474,-0.7747255,0.7176133 +1,user143,0.5985133,-0.9350518,0.7481433,-0.5840577,0.8036215,-0.9582879,0.5814938,0.7245701,0.9124372,0.2379186 +1,user144,-0.2130075,0.3803931,0.3516136,0.6285857,-0.4495331,-0.5348203,-0.5713027,0.7382806,-0.8229699,-0.07850277 +1,user145,-0.2461014,0.8282326,-0.01455521,0.8988701,0.581616,-0.3482596,-0.8390572,0.9891204,0.9246809,0.1662542 +1,user146,0.3480838,-0.5103169,0.2872369,0.9780787,0.4433356,0.9455197,-0.9734481,-0.9574193,-0.6045429,0.5463211 +1,user147,0.5618969,-0.4951887,0.2330544,-0.5317751,0.9987021,0.7291813,-0.7669421,0.2348522,-0.5426302,-0.6337637 +1,user148,0.3073749,-0.1029235,0.3652827,0.2281004,0.5294529,0.985701,-0.5954729,0.341146,0.1940736,-0.3343742 +1,user149,-0.1463056,-0.001827455,0.6810016,-0.08498705,0.5973115,-0.5759914,0.8870032,-0.3564325,-0.07498511,0.8074387 +1,user150,0.7860228,0.832646,-0.2307948,-0.01393944,-0.9585165,-0.8350623,0.09690754,-0.3566791,-0.2688845,-0.8214654 +1,user151,-0.9165755,0.7905166,0.5040382,-0.5631763,-0.1625832,-0.8509145,0.9924931,-0.8268962,-0.7554205,-0.104505 +1,user152,-0.4040629,0.2856872,-0.5578929,0.1807755,0.08164739,0.5731639,-0.4495369,-0.02237736,0.6161149,0.6865155 +1,user153,0.5101819,-0.7231986,0.0272048,-0.9484768,0.4928941,0.07678301,-0.5703354,-0.135551,0.5250259,-0.3790865 +1,user154,0.03945657,0.518016,0.1432383,-0.6400209,-0.5246896,-0.1845286,-0.1791432,-0.4881245,0.2631804,-0.5436113 +1,user155,-0.5138954,0.8440547,-0.4834052,-0.3417229,0.6194713,-0.3009095,-0.4829456,-0.7527274,0.6602846,0.4060131 +1,user156,-0.9732828,0.3482889,-0.1528776,-0.2719563,-0.4029672,-0.02517112,-0.648195,0.2473519,0.4334157,0.7753561 +1,user157,-0.2698466,0.3833083,0.3036991,-0.313675,-0.2755155,-0.07252212,0.9410257,0.121397,0.6840329,0.8887874 +1,user158,-0.691836,-0.7191533,0.8304121,0.428121,0.7382654,-0.8197019,-0.04487186,0.5777266,-0.5982013,-0.3645064 +1,user159,-0.0167464,-0.001200759,-0.4628237,-0.4078795,-0.6771688,0.6437776,-0.0427966,-0.3378841,0.5481134,-0.2061943 +1,user160,0.7605282,0.2807447,-0.6035174,0.4074679,0.7182821,-0.7011772,-0.7917762,0.6421346,0.5170845,-0.3431184 +1,user161,-0.03386426,-0.7371519,0.4512529,-0.4286174,0.3519066,0.4305153,-0.6586558,-0.2178531,0.09922261,-0.5205336 +1,user162,0.1702143,0.2418276,-0.5461234,0.4227353,-0.1351849,-0.4188708,-0.456362,-0.2631948,0.3580204,0.35515 +1,user163,0.02937227,0.5690197,-0.5137106,0.7024888,-0.0958695,0.1035493,0.7172583,0.5248157,-0.616104,0.2034937 +1,user164,-0.04622574,0.7997082,-0.3450989,0.235291,0.6466179,-0.9390053,-0.5681264,-0.01357564,-0.9405755,0.5797424 +1,user165,-0.1060558,-0.4722536,-0.1053492,0.7250872,0.089376,-0.5911976,-0.242235,-0.8614294,0.8786753,0.07595551 +1,user166,-0.8641793,0.07114807,-0.4517451,0.9769791,0.5450642,-0.7575575,-0.5838378,-0.6358111,-0.8473887,0.5612185 +1,user167,-0.8983927,0.7541702,0.7366776,-0.9451579,0.1656913,-0.08053236,0.8715561,-0.9223553,-0.1597839,-0.9051326 +1,user168,-0.161862,-0.6163339,0.3103122,-0.5359958,0.7637872,-0.8183195,-0.9943982,-0.5965094,0.3687252,0.5522541 +1,user169,-0.4838687,-0.4075381,0.9771331,0.8952684,0.7020645,-0.01526946,-0.6680149,-0.07915155,-0.2035641,-0.358795 +1,user170,-0.1511456,0.8466213,0.902346,-0.6204592,0.663729,-0.1806424,-0.1469063,0.7380909,0.05718033,0.4179097 +1,user171,-0.2220564,0.4594018,0.7826631,0.587978,-0.3935242,-0.3352765,-0.2446759,0.2952541,-0.3747298,0.4078014 +1,user172,-0.935562,0.4083875,-0.9995087,-0.5276553,-0.9943449,0.6898219,-0.7387032,-0.9649816,-0.7018293,0.1497937 +1,user173,-0.1671395,0.4204683,0.4097578,-0.9277013,-0.8878804,0.679489,0.4368951,0.1271383,0.2824549,0.1355229 +1,user174,-0.6235431,0.52435,0.5308063,-0.9960797,-0.5899027,-0.2935643,-0.6631821,0.01982421,-0.4622926,-0.35428 +1,user175,-0.1485695,-0.2112194,0.3521049,-0.8990696,-0.443878,-0.8449984,-0.3100059,0.7732991,-0.5247992,-0.9287091 +1,user176,0.5867591,0.248701,-0.6047974,0.9711687,0.6937356,-0.6687707,0.5978378,0.1162588,0.2071358,-0.6982228 +1,user177,0.7245407,-0.9859669,-0.1819568,0.981999,0.8534329,-0.3480447,-0.6366302,0.06240486,-0.06683549,-0.8079589 +1,user178,-0.5866726,0.2935919,-0.4148407,-0.4308447,-0.4451759,0.8841829,-0.07694797,0.008151241,-0.06742938,-0.5624728 +1,user179,-0.1058661,-0.8542225,0.7604853,0.1992692,0.2231885,-0.6830697,-0.9976351,-0.5425953,-0.5987906,-0.03259697 +1,user180,-0.4217649,0.01220568,-0.5009552,-0.1029881,0.4507444,0.07596393,-0.7496271,0.7059724,0.8581794,0.9994798 +1,user181,-0.8006498,0.1262379,0.3543645,0.5552158,-0.4036924,-0.9508794,-0.9800404,0.6514722,0.6636862,-0.3839381 +1,user182,-0.02244152,0.9362941,0.2645235,0.6360929,-0.9393947,-0.5339841,0.994858,-0.3694914,-0.3542111,0.862898 +1,user183,0.1741722,-0.7021071,-0.05884809,-0.9222125,-0.4676082,-0.3508721,-0.1991639,-0.316405,0.4742943,0.6859952 +1,user184,0.7095321,0.4030393,-0.6184307,0.606739,-0.9107983,0.1259036,-0.5503758,-0.4840788,0.1887121,0.2369754 +1,user185,-0.982985,0.4543101,-0.5922382,0.996072,-0.4640843,0.2814872,-0.1842852,0.142384,0.9089693,-0.6807133 +1,user186,0.6602768,-0.8580524,0.4577467,-0.2639354,-0.8481369,0.3482183,0.3178905,-0.06913243,0.1345789,0.09200829 +1,user187,0.7362492,-0.2486719,0.2286917,-0.6652174,-0.3137656,-0.8992675,-0.1985707,0.7632731,-0.3778722,0.0123315 +1,user188,-0.2528315,-0.1623815,0.7114609,-0.317603,0.2604001,-0.7910349,-0.2432595,-0.736219,0.5930022,-0.7919259 +1,user189,0.9684409,-0.5772056,0.2881588,-0.8358144,0.8901285,0.5285165,-0.7269814,-0.4914059,0.5363776,0.7275019 +1,user190,-0.2804972,0.7501274,0.765868,-0.07309681,0.009065629,0.7445101,0.7586327,-0.574611,-0.8297589,0.8061372 +1,user191,-0.4923033,-0.8816368,-0.8920565,-0.9101351,-0.02131779,-0.4922121,-0.03503562,0.9059156,0.1100867,-0.1350443 +1,user192,-0.06542341,-0.3143575,-0.2605883,-0.2644318,0.242035,-0.0409683,-0.3856373,0.2907411,-0.3643998,-0.7930317 +1,user193,0.8897172,-0.008044981,-0.7802554,-0.6503615,0.8738808,-0.6743607,-0.6977293,0.1621942,0.5282615,0.1612873 +1,user194,0.537069,0.6873828,-0.4057671,0.7923537,0.8828127,0.6113371,-0.3177774,0.4307313,0.4939827,-0.9315506 +1,user195,0.8883508,-0.5146493,0.3943128,0.9708592,-0.1113471,0.0200264,0.04623633,-0.7228346,-0.3049753,0.7867107 +1,user216,0.1471397,-0.7083068,-0.7289327,0.2801435,0.3436379,-0.1659966,0.6185568,-0.4334046,-0.9620207,0.8863756 +1,user217,0.9134246,-0.6468013,0.4541637,0.8234912,0.7320492,-0.9874531,-0.1097513,-0.5978265,0.3388566,-0.1874573 +1,user218,0.1633484,-0.877642,-0.9779981,-0.6962309,0.6182864,0.8432732,-0.424274,0.4629039,0.6234979,-0.2051464 +1,user219,0.8943081,0.1293117,0.9825282,0.9625405,-0.3959619,0.04296853,-0.6247027,-0.1696235,0.6309815,-0.9055503 +1,user220,0.8818655,-0.2240069,-0.2576775,0.9876767,0.6221776,0.5410633,0.1632673,-0.08923242,-0.1247658,-0.4599554 +1,user221,0.8828513,0.8724854,0.7878699,0.2306723,-0.3726479,0.5877834,-0.6656414,0.8882929,0.793739,-0.3990092 +1,user222,-0.5979952,0.2476749,-0.9095283,-0.9475946,0.5827203,0.5507564,0.3402617,-0.2637079,-0.2589318,-0.0405946 +1,user223,-0.1835579,0.4616355,0.4817342,-0.2767551,-0.1357873,-0.499905,0.77763,-0.7984914,0.5108344,-0.2529871 +1,user224,0.7725684,-0.1355596,-0.9923855,0.5803107,-0.4987672,0.9134227,-0.3633707,0.05048715,0.3220005,0.7622781 +1,user225,0.9390738,-0.06494231,-0.3152953,0.8447591,0.465533,0.1620935,-0.9775157,-0.8329766,-0.7649491,0.02785479 +1,user226,-0.2952071,0.9469862,-0.123953,-0.3058959,0.7528656,0.5201214,-0.1761336,-0.5213259,-0.7941409,-0.4662765 +1,user227,-0.4437702,-0.6158582,0.1220099,0.6550363,0.4644896,-0.3521356,0.6966651,-0.6487481,-0.2710628,0.9995209 +1,user228,0.6119634,0.6935886,0.8271925,0.6140919,-0.1065901,0.01587322,0.1208692,0.9619436,0.8816448,-0.3424773 +1,user229,-0.3052489,-0.813493,-0.9929625,-0.2801946,0.8072097,0.4596155,0.7416588,-0.1665158,0.7410998,-0.5846984 +1,user230,-0.8219709,-0.7124907,0.5467175,-0.8062338,-0.8084665,0.5639866,-0.2376974,-0.9444927,0.5045991,0.7890178 +1,user231,0.8009844,0.0445814,-0.05318653,0.2786932,-0.9766486,0.8543834,-0.4487608,-0.3222877,-0.6753254,-0.07160441 +1,user232,0.5335637,-0.7273508,0.04037394,0.1250474,0.5252829,-0.7815329,0.5125449,-0.07361476,-0.6664791,0.7147893 +1,user233,-0.422228,0.6502787,0.7540883,-0.6795259,0.5250533,0.1448323,-0.4167357,0.05501676,-0.09446877,-0.01368392 +1,user234,-0.9455566,0.8039617,-0.9330743,0.4156392,0.158948,-0.6172845,-0.757094,0.4284994,0.06587509,-0.6509378 +1,user235,0.2052368,0.7792597,0.4834681,0.6025881,0.3554758,-0.3431922,-0.2796739,-0.8535754,-0.7916032,-0.8502 +1,user236,0.3539718,-0.462602,0.4922653,-0.5488977,0.2686703,0.4321137,-0.2589563,0.07435041,-0.1558293,-0.1706657 +1,user237,0.1593329,0.3521226,-0.4608573,0.6535155,-0.1493333,0.06604923,-0.3754331,0.9525856,-0.7177237,0.8410197 +1,user238,-0.536331,-0.4654288,-0.6782351,-0.9487024,-0.1205958,0.4263778,0.5259451,-0.5172773,0.2904086,-0.1134122 +1,user239,0.8547123,0.4385505,-0.9515144,-0.4362705,-0.1342797,-0.6286496,0.262193,-0.8439111,0.7159747,-0.1356063 +1,user240,-0.3224502,-0.8061247,0.5965191,-0.5394529,0.09720943,0.6335658,0.9292798,0.4848231,-0.5687518,0.7705045 +1,user241,-0.3837648,0.4356601,-0.07945304,0.6992762,0.6265212,-0.4871219,-0.666071,0.2764256,-0.2263702,-0.4092213 +1,user242,-0.06631216,0.3519087,0.1037507,0.5733686,0.9135146,-0.6134489,-0.9662848,-0.0562002,-0.5540419,-0.1010672 +1,user243,-0.604883,-0.8381341,-0.9917399,-0.1772054,0.9400598,-0.7497971,0.2539523,-0.3314673,-0.7560937,-0.6839488 +1,user244,0.7463598,-0.7269568,-0.2161475,-0.01665226,-0.5657566,-0.9346057,0.136771,-0.2993631,-0.09736019,-0.8421325 +1,user245,0.1868357,0.5631598,0.1001677,-0.3392048,0.4937007,0.05087966,0.6060735,-0.5848943,-0.3497643,-0.3805328 +1,user246,0.8222162,0.5328957,-0.1984297,-0.208219,-0.1278883,0.9927437,0.02824897,-0.6318365,0.2452764,-0.9014267 +1,user247,-0.1065005,-0.4352635,0.05491981,-0.7365088,0.7778813,-0.1006023,-0.2446723,0.2672324,-0.05938085,-0.9557569 +1,user248,0.1002603,0.9163585,-0.4456685,-0.5157137,0.2257498,0.06342655,-0.5036778,-0.1827209,0.9890923,0.4320099 +1,user249,-0.01443538,0.6552537,-0.1764278,0.09555011,-0.5096018,0.8360169,0.6039749,0.8310674,-0.1312257,-0.1065731 +1,user250,-0.2121924,0.6940482,0.03744801,-0.7739683,-0.6180806,0.9423663,0.130625,-0.9023912,-0.4283993,-0.8613072 +1,user251,-0.01787419,-0.3076484,0.296654,-0.5280369,-0.1520725,-0.3955101,0.6595895,0.7280467,-0.1356735,0.9720544 +1,user252,-0.1315841,0.527739,-0.3885578,-0.6737776,0.1177502,0.4238003,0.9383336,0.7193604,-0.3374866,0.4944177 +1,user253,0.1898124,-0.05827698,0.1279197,-0.7215629,0.9646397,0.4931226,-0.5291133,-0.1660991,0.3126688,0.09809818 +1,user254,0.7985679,-0.8460129,-0.2216118,0.195208,0.7121401,0.1045849,0.4372195,0.9295553,-0.6248392,-0.2809327 +1,user255,-0.3590157,-0.6078206,-0.3809433,0.9065331,0.618983,0.337223,-0.4250371,-0.2301525,0.9845139,0.2566959 +1,user256,0.1288862,0.8767807,0.8126244,-0.8768038,0.4301726,-0.3447838,-0.506629,0.0009242422,0.5477197,-0.874047 +1,user257,-0.4966392,-0.8990267,0.6544353,0.889312,0.4650057,-0.3752936,-0.7389141,-0.5917706,-0.4189801,0.2527908 +1,user258,0.1972141,-0.2236788,0.7410666,0.5615694,0.0834726,0.9850874,-0.728372,0.1210995,-0.2865489,0.2562168 +1,user259,-0.2591503,0.5703693,0.6398169,0.7372881,-0.6764175,0.6710894,0.6142402,-0.03713216,0.4293645,-0.2165243 +1,user260,0.198112,-0.7125196,0.6614728,-0.3908826,0.2722154,-0.9156782,-0.9972553,0.2417136,-0.6778803,0.6680924 +1,user261,0.3752432,0.06383053,0.2877841,0.7553356,0.2750061,0.549074,0.03393061,0.1766068,-0.7819498,0.04523459 +1,user262,-0.4581659,-0.3850493,-0.4133696,0.0159813,-0.6530661,0.5254728,-0.8345205,0.6405801,0.7540391,0.7118713 +1,user263,-0.2683243,-0.4398704,-0.2981533,0.7341648,-0.2025016,-0.697211,0.5152896,-0.8319012,-0.3443594,0.3828817 +1,user264,0.9530152,-0.2858908,0.04187237,-0.9241903,-0.1999406,-0.3060937,0.6171949,-0.7683765,0.1235814,-0.9684493 +1,user265,-0.4037225,-0.5810876,-0.3464439,-0.5683795,0.505882,0.9081884,-0.5916146,0.06907957,-0.1800858,-0.9390665 +1,user266,0.9369125,-0.6606107,-0.8146852,0.336753,-0.8470259,-0.04040324,-0.7643843,-0.6854765,-0.1359626,0.5326816 +1,user267,0.306987,0.2515073,-0.4658623,-0.473088,-0.9312703,-0.87398,-0.6417614,0.3059739,0.9677521,-0.139115 +1,user268,0.7556104,0.771035,0.1926988,-0.914864,-0.6434514,-0.02576237,0.03295232,0.02166522,0.1021905,0.9019532 +1,user269,-0.5994185,-0.1260396,-0.4929203,0.3880506,0.03237833,-0.6140255,0.7615608,-0.2027538,-0.845554,-0.5807305 +1,user270,0.1616993,-0.3099422,-0.4173767,0.09064151,-0.06555003,-0.5026296,0.6204316,0.4620628,0.6837268,0.7252787 +1,user271,-0.5668398,0.9649102,-0.210782,-0.4543169,0.4537581,-0.3921966,-0.0377679,-0.4935117,0.5334387,0.6724577 +1,user272,0.01681664,-0.6903794,0.4276267,0.08732679,-0.3411005,-0.1011474,-0.9045102,-0.9263282,-0.07192422,0.01004813 +1,user273,-0.9046129,-0.9580334,0.686374,-0.3359899,-0.1520354,-0.1160784,0.6541468,-0.5941374,-0.8703151,-0.3757884 +1,user274,-0.1717228,-0.8732239,-0.202522,0.3684777,0.3938178,-0.1419936,-0.7838156,0.175021,0.777345,0.9885088 +1,user275,-0.2368236,-0.4173362,-0.7885208,-0.9293255,0.09314292,-0.03575305,0.2322608,-0.2256913,0.8307156,0.1679157 +1,user276,0.2822228,0.6051264,-0.2134583,0.3248053,-0.6583348,0.9348012,0.2602203,-0.1790317,-0.2200794,0.2436788 +1,user277,-0.3495066,0.6596718,0.5990483,-0.8397413,-0.7340704,-0.14925,0.2444333,0.5431845,0.0226214,-0.9129179 +1,user278,0.6566759,0.1474003,0.266399,-0.6658342,-0.1289757,0.8636447,0.9875885,-0.9584589,-0.2286653,0.2121588 +1,user279,-0.6175169,0.5214849,0.3408732,0.8090917,0.5674151,-0.001772231,0.7565425,0.6382475,-0.2309871,-0.3243114 +1,user280,0.636058,0.3149255,-0.5773794,0.2558089,-0.2436723,-0.313233,-0.1515917,0.374252,0.8913957,-0.01949097 +1,user281,-0.5555165,-0.1585516,-0.696153,-0.4398025,0.2529437,0.8060109,0.1182135,-0.8608501,0.3429354,0.3508516 +1,user282,0.3646089,-0.7861635,-0.3624729,-0.7189453,-0.5846575,0.6027177,0.416132,0.3662942,0.6333394,-0.352257 +1,user283,-0.4955261,-0.1573355,0.03406272,0.5820312,0.874078,-0.8894327,-0.2132582,0.09361235,-0.4460909,-0.5250732 +1,user284,0.634296,0.7831715,0.4317668,-0.1613654,0.2175833,0.2991336,0.5891002,-0.02694925,-0.3443958,-0.5510503 +1,user285,0.1631768,-0.6321764,0.4159154,0.4762627,-0.8725174,-0.2926974,-0.1466485,0.2958495,-0.9914998,0.3668103 +1,user286,0.1454583,0.2348439,0.6531194,0.4885643,0.493061,0.4477903,0.3617047,0.8634599,-0.4615771,0.7316226 +1,user287,-0.2368178,0.6599522,0.2443912,-0.03816925,-0.3522441,0.9543498,-0.9175287,0.973975,-0.7966761,-0.4250973 +1,user288,0.6665376,-0.5312031,0.07035063,0.3655747,0.5924883,0.3320089,0.1144374,0.7040789,-0.4104798,-0.3803989 +1,user289,-0.6573276,-0.9888349,0.3941859,0.05013377,-0.4234664,0.4328776,0.6333327,-0.01544067,0.251874,-0.01216058 +1,user290,0.5040319,0.2303215,-0.1157919,-0.3008811,-0.02866153,0.6254392,0.6967115,-0.06315716,0.6326884,0.3583784 +1,user291,-0.1353504,-0.2437227,-0.2681766,0.9746922,-0.1352963,0.4163308,0.117182,-0.05420749,-0.08836012,-0.7123065 +1,user292,0.7179156,0.07499563,-0.3180299,-0.1945306,0.8515397,-0.0180484,-0.3327367,-0.8388339,0.4699242,-0.966926 +1,user293,-0.954134,0.8452722,0.4708385,0.7151002,0.3182724,0.150912,0.862191,-0.422577,0.3867274,0.07024965 +1,user294,0.5963253,0.3164069,0.4336701,0.708857,0.662202,0.7191198,-0.3675284,0.1138913,0.5672805,0.6705752 +1,user295,0.6709308,0.7891049,0.7238424,-0.1187209,-0.3484009,0.6758579,-0.7155418,-0.6072104,-0.4064943,-0.9353753 +1,user296,-0.3578565,-0.7358154,-0.8756054,-0.8532794,-0.1758456,0.05910041,-0.7294236,0.6465026,-0.7933584,0.1311831 +1,user297,0.5332377,0.6557962,0.6189849,0.04560995,0.8151762,-0.3212835,-0.1319127,0.4284148,-0.5686821,0.2032568 +1,user298,-0.02208213,0.04061213,-0.7420199,0.4081911,-0.2796712,0.8018778,-0.3573032,0.6987635,-0.4387422,-0.0744903 +1,user299,-0.6022461,-0.9647805,0.3170934,-0.7681434,0.180703,-0.966662,0.3035287,-0.3318322,0.3088321,0.03313629 +1,user300,0.9338192,-0.4702434,-0.8739353,-0.5663395,-0.1524455,0.06469107,-0.3703519,-0.774339,-0.414236,0.6225263 +1,user301,-0.8603828,0.73067,-0.1593966,-0.5011674,0.6547788,-0.7007517,-0.7368716,0.1608264,-0.7550154,-0.3492116 +1,user302,-0.1690859,-0.9998702,-0.8936886,-0.2224603,-0.3655389,-0.3588585,-0.7342392,0.1746561,-0.1577291,-0.294406 +1,user303,-0.04936415,-0.1606228,0.5536914,0.5209873,0.506454,0.9635437,-0.2748621,-0.7006672,0.5138397,-0.3674256 +1,user304,-0.7649957,0.7726365,-0.4730227,0.1628428,-0.4972567,0.1831698,0.9172753,0.566689,-0.6253305,0.275 +1,user305,0.6591913,-0.8730942,-0.09621061,-0.8539825,-0.9717211,0.4991478,-0.5180548,-0.6503229,-0.3803842,-0.3058972 +1,user306,0.7138123,0.422041,0.7651706,0.5916618,-0.4004031,-0.07220934,0.9573987,0.07364147,0.3445553,0.8004901 +1,user307,0.5172271,0.3777629,0.3135191,-0.5123519,-0.1555914,0.117971,0.1774956,-0.6123427,0.1545902,-0.4813213 +1,user308,-0.6903153,0.7865776,-0.4971623,-0.6937238,-0.7057915,-0.6501021,0.7263785,0.8928616,0.6422372,-0.2188151 +1,user309,0.3704882,-0.4305587,0.03156959,0.9258276,0.4706212,-0.2085647,0.9449872,0.1151825,-0.8841099,0.01264888 +1,user310,0.8997102,-0.1007522,-0.3456078,-0.7032602,-0.5881764,-0.8838012,-0.06596194,-0.9740952,0.9236031,0.1943674 +1,user311,0.9457428,0.1015031,-0.07454172,0.5620851,0.05053625,0.03666483,-0.4252132,0.2671136,0.533633,0.761694 +1,user312,0.8149717,0.4108897,0.3354166,-0.5139749,-0.2764352,-0.4025537,0.06320073,0.2543324,0.4588255,-0.6364996 +1,user313,0.2643191,0.1130843,0.2919194,-0.4222055,-0.1728339,0.7189165,-0.64983,0.392199,0.5569425,0.8421104 +1,user314,-0.5497833,0.9441676,0.959521,0.1441163,-0.07538579,0.1472321,0.3615286,-0.639274,-0.912458,-0.7633793 +1,user315,0.4492677,0.1940612,-0.2328166,0.3246597,0.9411482,0.8965798,-0.347699,-0.7726168,-0.8855703,-0.1875498 +1,user316,-0.5725041,0.4809079,-0.2921653,-0.9459428,-0.04535126,-0.5737809,0.2035215,-0.3119515,0.5654427,0.2089207 +1,user317,0.5956749,0.1790115,0.6126404,-0.3673194,-0.5823248,-0.4049776,-0.2767666,-0.7758142,-0.374035,0.9682434 +1,user318,-0.7875501,-0.1459866,-0.9884254,-0.7135096,-0.4110959,0.8509296,-0.2652278,-0.7986418,-0.6822465,0.3873529 +1,user319,-0.9059664,0.9497048,0.7781854,0.4196319,-0.452863,0.758228,-0.6820411,-0.6078726,-0.8450371,0.8285218 +1,user320,0.9383473,0.1901766,0.006826298,0.6828144,-0.00579123,-0.9721,-0.6434339,0.2087452,0.877839,-0.04391719 +1,user321,0.7164818,-0.9156652,-0.1042173,-0.01439073,0.5602426,0.4763688,-0.5685163,0.138201,0.9504419,-0.2542687 +1,user322,-0.04131684,-0.2940179,-0.4899912,0.3943241,0.4118407,0.1745588,0.4351409,0.3379199,0.06660277,-0.8837847 +1,user323,0.6562629,-0.7348278,0.6887964,-0.5117162,-0.1542515,0.009851592,0.02382941,0.3699113,0.3477632,-0.01084317 +1,user324,0.7623477,0.929607,-0.6333788,-0.2992906,-0.121485,-0.3727192,-0.7063253,0.715624,0.3371693,0.815981 +1,user325,-0.4449916,-0.977611,0.9436788,0.1031811,0.07404276,-0.1063215,-0.9323874,-0.5481888,-0.3661167,0.7867905 +1,user326,0.3271937,-0.9457229,0.4126388,0.3695629,0.4973476,-0.3142905,0.3082876,0.7627009,0.9412689,0.05378151 +1,user327,-0.5955088,-0.8062084,-0.5089842,-0.1525699,0.7026693,0.6863812,-0.4357489,0.3621265,0.543811,-0.05283593 +1,user328,-0.9117539,0.6781852,0.5626638,-0.851209,-0.1107811,0.5723951,-0.06430012,0.880226,0.06520119,-0.009952656 +1,user329,-0.6948884,0.0948892,0.6706189,-0.222246,-0.7823236,-0.5124127,0.9509844,0.4614644,-0.4974733,0.9792912 +1,user330,-0.197755,-0.7709889,0.8081092,0.07928669,-0.1166277,0.7197192,0.8677798,-0.9697057,-0.1473569,0.9803004 +1,user331,-0.9779347,-0.7920582,0.6887284,-0.4175485,0.7367734,-0.3629139,0.565348,-0.894113,0.6509652,-0.3874264 +1,user332,-0.5552712,-0.1744409,-0.4887777,0.2765867,0.8724552,-0.2131644,-0.7858872,-0.3777092,-0.2524886,-0.3699204 +1,user333,0.6331591,-0.7708592,0.9144206,0.8568264,0.5178334,-0.6391393,-0.8664593,0.2049504,0.6949139,-0.3141057 +1,user334,-0.02729881,0.04731904,0.2424198,-0.8965612,0.2432275,-0.3993701,-0.7095141,-0.5947802,0.1648049,0.245148 +1,user335,-0.320267,-0.4018043,0.03819959,-0.5605705,-0.6248015,0.9700054,-0.8686119,-0.8110202,0.1221809,0.9050796 +1,user368,0.1657366,0.5500806,-0.2608598,-0.697412,-0.8416898,0.5143077,-0.3432561,-0.265158,0.5685397,-0.7059084 +1,user369,-0.857993,-0.6284652,0.4986881,-0.3904891,0.2553885,0.04469147,0.2954651,-0.5097561,-0.9802318,-0.9350299 +1,user370,-0.2531986,0.662322,0.7685418,0.4999292,-0.5038714,-0.394663,-0.1087741,-0.2695697,-0.833465,-0.6621592 +1,user371,0.2227383,-0.4111181,-0.2216998,0.9235163,0.4717558,0.8341636,-0.1503844,0.3288858,-0.80621,-0.6476214 +1,user372,-0.7613227,0.2467411,-0.495201,0.8333282,-0.1131808,-0.7511333,0.5383868,0.09278584,-0.7798577,0.6830959 +1,user373,-0.7054206,-0.09355063,-0.9849522,0.3711344,0.3869857,0.8519082,0.8078771,0.445033,0.656935,-0.7392832 +1,user374,0.09471163,0.038573,-0.8471232,-0.9695304,0.5087662,-0.2485343,-0.8943119,0.177262,-0.7221342,-0.2258339 +1,user375,0.5996667,0.2350317,-0.1971708,0.63494,0.345416,0.1719585,-0.8685215,0.08752671,0.9774589,0.143332 +1,user376,-0.707426,-0.9052557,-0.7789252,-0.6135442,0.202457,-0.7542885,-0.9139431,-0.4796383,0.234877,-0.5797864 +1,user377,-0.5840473,-0.3176747,-0.7053633,0.4620826,0.4869248,0.5653476,-0.9859384,0.2530214,-0.5236288,-0.9915963 +1,user378,0.388152,-0.2957699,0.8086942,0.490609,-0.2413385,-0.4787307,0.9280917,0.770316,0.3002183,0.8124888 +1,user379,0.8862435,-0.5379492,-0.9602579,-0.9655421,0.4356036,0.2345371,0.08747009,0.8198763,0.438784,-0.4520462 +1,user380,0.9496436,0.1800909,-0.5520287,0.1801859,-0.9460126,-0.7698409,-0.3427927,0.530139,-0.007369828,-0.3700057 +1,user381,0.2706709,-0.8768667,-0.4072555,-0.2340901,-0.2809559,0.6288081,-0.9573362,-0.1547672,-0.2220595,-0.6898325 +1,user382,0.4182604,-0.9804662,-0.1347642,0.3652743,-0.3370412,-0.7487372,-0.5545507,-0.671864,0.52053,0.6317768 +1,user383,0.1998163,0.7621914,0.4970886,0.8838986,-0.8187073,-0.6286606,0.7318367,-0.05454247,0.459331,0.9973161 +1,user384,-0.8881271,-0.7519813,0.8868036,0.4355349,-0.9087326,0.9109057,0.5923768,0.2580694,0.3222655,-0.07593855 +1,user385,-0.3934599,0.8421891,0.3795257,0.1843746,-0.2639374,-0.72216,-0.172742,-0.7936931,-0.04996081,0.7047566 +1,user386,-0.7876632,-0.7261011,-0.08717295,0.2883207,0.1871129,0.1398005,0.1001408,-0.9235999,0.2632012,0.1806189 +1,user387,0.5080834,-0.6047069,0.1245416,0.2083409,-0.4624666,0.08668187,0.2097023,-0.8772828,-0.4995262,0.324746 +1,user388,0.1220136,0.7191214,0.3064545,-0.6269623,-0.693486,-0.009873318,-0.4826458,0.8471788,-0.6791827,-0.1684821 +1,user389,0.6293484,-0.02060203,-0.1804186,0.5401729,0.8956024,0.5946427,0.03269608,-0.4305309,-0.3891176,0.3110858 +1,user390,-0.007460026,-0.7792472,0.9249432,0.1299379,0.8730184,0.8348531,0.7627278,-0.1324089,-0.2561167,-0.2845222 +1,user391,0.9425987,-0.3090571,-0.09599786,-0.6605451,-0.9053583,-0.8099993,-0.8415651,-0.05048502,-0.8058779,0.9375705 +1,user392,0.848605,0.9139081,-0.4655551,-0.1286883,0.4874642,0.7692042,-0.1669689,0.0928325,-0.1887934,0.4218531 +1,user393,-0.5009381,-0.7458458,-0.5859267,0.6339865,-0.05472321,0.2201105,0.8811012,0.718352,-0.3617421,-0.2812167 +1,user394,0.2079126,-0.5116765,0.01277203,0.5824588,0.7552246,-0.8232897,-0.9863717,-0.325858,-0.1850617,0.6737026 +1,user395,0.7010208,0.07755911,-0.836271,-0.940723,-0.4028405,-0.6953736,-0.2330931,-0.1788538,-0.2935552,-0.7814853 +1,user396,-0.02171497,0.3348747,-0.8543768,-0.7585262,0.2607627,0.2059977,-0.7100396,-0.0256673,0.6974374,0.9672368 +1,user410,-0.9340145,0.7228233,-0.3443703,-0.4579365,0.774142,-0.3624492,0.1888985,-0.3802112,0.4133438,0.9618984 +1,user411,-0.1729322,-0.6251736,-0.4414515,0.1403162,0.9405072,0.1014414,-0.426723,-0.001517062,0.2066343,-0.973729 +1,user412,0.7921334,-0.4354291,0.3674967,0.5991903,-0.2436647,-0.2509806,-0.06370415,0.873854,-0.1465364,-0.6710314 +1,user413,0.4842458,0.7423571,0.5208655,0.9073378,-0.5628991,-0.1111864,0.6343478,-0.05207519,-0.06612623,0.5936752 +1,user414,-0.9731158,-0.8629822,-0.944363,0.02421482,-0.8782002,0.4727808,-0.6948863,0.9439405,-0.3340346,-0.9764128 +1,user415,0.9040064,-0.1874104,0.2543003,0.03472517,-0.1523974,-0.3400748,-0.4713273,0.1319234,-0.8242709,0.2530301 +1,user416,-0.909214,0.5845461,-0.09960878,0.09171235,0.1731635,0.1666536,-0.5383942,0.1542317,0.883913,0.2984318 +1,user417,-0.7607791,-0.5890833,-0.03153593,-0.6874645,0.3089127,-0.3874188,0.4052545,-0.9796595,0.9291666,0.2042061 +1,user418,0.4120897,0.2078828,-0.6211581,-0.7569339,0.385136,0.746607,0.738375,0.2546406,-0.323797,-0.422224 +1,user419,0.2127996,0.3036675,-0.7931543,0.4647501,0.4796775,-0.8432198,-0.02103995,0.001410491,-0.7952698,-0.8700502 +1,user420,0.8685693,0.3903147,0.7880454,0.8527084,0.2045151,-0.7927761,-0.5620494,-0.4101903,-0.459951,-0.4847081 +1,user421,-0.5953703,0.4286355,-0.6962149,0.373004,0.2581544,0.5814601,0.5011028,-0.8777684,0.4200862,0.2932539 +1,user422,0.1553983,0.9946104,0.1108479,0.804205,0.5743192,-0.6532191,0.1373949,0.9509255,-0.6011476,-0.9324798 +1,user423,0.7171743,0.3042228,-0.6775097,-0.2759799,-0.3080207,0.9764281,0.2709818,0.6826422,0.3512556,0.9371451 +1,user424,-0.09630838,0.6827898,-0.2821416,0.006990445,-0.7965688,-0.1984294,0.3822039,0.8405836,-0.9416559,-0.9879629 +1,user425,-0.6366891,-0.5170661,-0.8763801,0.3866639,0.3295438,-0.4765088,0.1510232,-0.3749325,0.2137907,0.7412229 +1,user426,0.4181952,-0.6182181,-0.5137806,-0.2167029,0.2891388,-0.7189455,-0.9621114,-0.4962116,-0.9422996,-0.8443403 +1,user427,0.8819766,0.01766449,-0.1365184,0.2484643,0.4641939,-0.9924317,0.6721644,-0.1850837,0.7557815,0.9792739 +1,user428,0.5162703,0.3668337,-0.7166386,-0.3484439,0.1205499,-0.3430835,0.151233,0.2128162,0.7717261,0.05613727 +1,user429,-0.7360177,-0.02096127,0.09744253,-0.9666167,-0.4578939,-0.01888843,0.354157,-0.3921241,-0.02608686,-0.4491669 +1,user430,-0.9740017,0.9026198,0.7482451,0.7925262,-0.1167332,-0.2721263,-0.3811313,-0.475909,0.02175865,-0.7593977 +1,user431,-0.1887632,-0.3777317,0.9417911,-0.4740408,-0.8330556,0.8350333,-0.553092,-0.7091911,-0.6505704,-0.5639782 +1,user432,0.8565708,0.2386175,0.4772075,-0.2166013,-0.7087982,-0.7134944,0.5616514,0.4423937,-0.9433391,0.2838473 +1,user433,0.3927582,0.376457,0.4113087,-0.7398957,0.7740955,0.2823426,-0.5848114,0.5621515,-0.5184742,-0.1456906 +1,user434,-0.6551194,0.1244441,-0.8949803,-0.7663094,-0.8998419,0.2620168,0.2809697,-0.5384125,0.1472755,0.4990022 +1,user435,0.7437387,-0.5953544,0.8720204,-0.09545149,0.4272833,0.4438079,0.5770229,0.7219445,-0.2036563,-0.7224217 +1,user436,0.8542297,0.8888672,0.2272491,-0.241848,-0.8263096,-0.4117227,-0.6828033,0.7774741,-0.7808412,-0.7578176 +1,user437,0.4781911,-0.1383485,0.07107762,0.576362,0.3787877,0.8609588,-0.75349,0.7198927,0.9225803,0.7053146 +1,user438,0.9234807,0.6654181,-0.512092,0.4121541,0.7658218,-0.1531784,0.6784513,-0.4781429,-0.2290966,0.691523 +1,user439,-0.2683462,0.08360268,0.3378263,-0.2817177,-0.9397898,0.4595595,-0.7667336,0.245818,-0.5668371,0.6384592 +1,user440,0.9996537,0.3030891,0.8458297,-0.5903576,0.4160789,-0.01882993,0.140142,-0.251486,0.9981034,0.7241158 +1,user441,0.9894661,0.3882414,0.1435377,0.9542176,0.5399638,0.4843724,-0.1326502,0.1416459,-0.8157528,0.6534214 +1,user442,0.5587217,0.4584291,0.8963748,0.8585985,-0.9992827,-0.4389991,-0.1934567,-0.755699,0.6397972,0.6647303 +1,user443,0.7917871,0.86766,0.2133264,-0.9911673,-0.8275859,0.7301895,-0.9235621,-0.377632,-0.148433,-0.9469155 +1,user444,0.473712,0.1305984,-0.3355968,0.8615553,0.9770647,-0.626814,-0.4983024,-0.9104293,0.118121,0.2470966 +1,user445,0.5856058,0.5954469,0.9520118,-0.1171867,-0.8774828,-0.9662184,0.111657,-0.8117586,-0.6942374,0.6883174 +1,user446,0.6957934,-0.3197504,-0.5323733,0.04355786,0.02001674,-0.6098853,-0.3948894,0.7542914,0.02729609,0.3061146 +1,user447,0.5644979,-0.2848554,0.5647944,-0.04673231,0.1502282,0.5398395,-0.03669654,0.2438025,0.002033937,-0.4544715 +1,user448,0.8248268,-0.9936363,-0.07952414,0.1953488,0.4314299,-0.3536371,-0.4830884,-0.791418,-0.7650708,-0.1074765 +1,user449,0.1078832,0.8881324,-0.1535315,0.2866239,-0.5948472,-0.8632783,-0.6565144,0.008931987,0.703499,0.8838906 +1,user450,-0.2227025,-0.9811879,0.7716401,-0.5819822,-0.3700943,0.6966198,0.9422635,-0.7547871,0.2067642,-0.3245218 +1,user451,0.6933961,0.3966784,-0.2914787,0.04805722,-0.364055,-0.1464132,-0.04513782,-0.2016084,-0.2250219,0.4078155 +1,user452,0.5125129,0.3167679,0.1502536,-0.3403721,0.6633072,0.7181818,0.8445883,0.1311636,0.1235853,0.1771445 +1,user453,0.9326958,-0.9865776,-0.117512,-0.7777772,-0.7957751,-0.9565993,0.07965842,-0.8038616,0.6056165,-0.2570015 +1,user454,0.4105704,-0.2990988,0.03101161,0.7720773,0.3279243,-0.169985,-0.7741561,-0.5189662,-0.8737663,0.3449605 +1,user455,-0.5837955,-0.0004423461,0.8681121,0.6666183,0.8667383,-0.4802476,0.2267923,-0.02825276,0.1819294,0.1891816 +1,user456,-0.7039933,-0.5036437,0.006107871,0.6088867,0.5337687,-0.4331081,-0.7693184,-0.1787941,-0.1805928,-0.5157787 +1,user457,-0.1712344,0.08268312,0.517231,-0.4446255,-0.3829369,0.1110695,-0.7362675,-0.0151778,-0.8160659,0.5006203 +1,user458,-0.7018189,-0.9827779,-0.2684063,-0.08491737,0.3309322,-0.4726793,-0.1010434,0.7866635,-0.06228909,0.1684556 +1,user459,0.812277,0.86319,0.2894693,-0.7395573,-0.3456814,0.2238084,0.3819147,-0.9659779,-0.4088667,0.5403586 +1,user460,0.09274783,-0.9382782,-0.3853265,-0.4112422,0.1591692,-0.9078189,0.6178896,0.5926981,0.1578472,-0.9485466 +1,user461,-0.6758206,0.9198419,-0.5201612,-0.2923912,-0.785801,0.2551943,0.5178253,-0.6892454,0.9594696,0.4090579 +1,user462,-0.3764862,-0.5145417,0.2312604,-0.2135981,-0.178737,0.05884165,0.8288226,-0.675169,-0.05943706,0.9763804 +1,user463,-0.05068135,0.3003393,-0.908119,0.3721565,0.450371,-0.6213133,0.179541,0.03509173,0.2145082,0.3353007 +1,user464,0.7169376,0.2962989,0.8911476,-0.03228694,0.9882944,-0.462463,0.9330139,0.8729061,-0.5590046,-0.7366327 +1,user465,-0.03160555,0.6099024,0.3362801,0.02009249,-0.07857897,-0.6791416,0.1097924,-0.2135815,-0.9121616,0.4753827 +1,user466,-0.3069426,0.704985,0.9639014,-0.723295,-0.1223457,0.8224945,-0.2434361,-0.2429638,-0.9891482,0.612879 +1,user467,0.5711672,0.1851661,0.1183967,0.7258651,-0.8380151,0.1258143,-0.7497894,0.6503801,-0.3398459,-0.4944503 +1,user468,-0.5534144,-0.528446,-0.5926423,-0.4035455,-0.6997913,-0.8181828,0.3563024,-0.4936888,-0.9895813,0.1806973 +1,user469,-0.3834619,0.3704031,-0.5481907,0.6888591,-0.3565239,-0.3306839,-0.5649848,0.2788933,-0.2182448,0.304402 +1,user470,-0.6971789,-0.7312312,-0.543777,-0.5558527,-0.777805,-0.4146262,-0.516523,-0.1038018,0.09331703,-0.8559911 +1,user471,-0.5537607,0.7746431,-0.7468126,0.006096829,0.7162876,0.1629872,-0.5035556,0.2548252,-0.9914779,-0.09518686 +1,user472,-0.3939958,-0.2413556,0.595347,0.6430767,-0.8165601,-0.8463115,0.302365,-0.5794608,-0.03399755,-0.0421766 +1,user473,0.8615427,0.7271979,-0.6474022,-0.6972542,-0.7770876,0.1463747,0.2900203,0.1404991,-0.2668857,0.8087392 +1,user474,-0.7619737,0.642303,0.4665138,0.01492951,0.8887017,-0.1068233,-0.4271177,0.8771932,-0.1399109,-0.04210237 +1,user475,-0.9202838,0.8892429,-0.7402498,0.504632,-0.8394954,-0.4731256,0.8040627,-0.4898901,-0.9158766,-0.79508 +1,user476,0.4471486,0.3226448,-0.6953904,0.1855591,-0.6545704,0.1801563,-0.5983227,0.3287405,0.03887686,0.4970566 +1,user477,0.9338198,-0.6774473,0.9341405,-0.9415126,-0.09128153,0.2832914,0.1779929,0.6314847,0.8873851,-0.7359878 +1,user478,0.6442141,-0.3956125,0.8245446,-0.5421003,0.3107328,-0.9332861,-0.2326339,0.7539124,0.08615736,-0.2495515 +1,user479,0.2719753,0.3290085,0.2250854,-0.619092,0.7768595,0.8265192,-0.0814111,0.5373225,0.273806,-0.6104199 +1,user480,0.04170296,-0.789315,-0.219391,0.3451113,0.3138712,0.4200131,0.5214784,-0.3595834,0.5908842,-0.8520972 +1,user481,-0.5784884,-0.3768005,0.5961847,-0.1240825,0.9406385,0.7633337,-0.2903703,0.9991253,-0.7070785,0.4259267 +1,user482,-0.03462863,-0.2743132,0.9336067,0.4289652,-0.5871956,-0.319894,0.8734511,-0.6642859,-0.9512158,0.7973956 +1,user483,-0.4457842,0.5274529,0.9308626,-0.9952608,-0.0228216,0.1381949,0.3660668,0.7715803,-0.2855305,0.3250473 +1,user484,-0.6457926,-0.363378,-0.5213273,0.09814028,-0.8551366,0.8067344,0.7892881,-0.8047363,0.8985381,-0.8310748 +1,user485,-0.6240582,0.426588,-0.0353817,0.2010425,0.7407287,0.510121,-0.900705,-0.1832521,-0.8249821,0.1423561 +1,user486,-0.02957966,-0.4729894,0.7989747,0.6713575,-0.1560833,0.6579473,-0.407141,-0.2566725,0.8963989,-0.4857711 +1,user487,-0.3497859,0.1329783,0.4847806,-0.2929731,0.6786321,-0.6263737,-0.9800303,0.01646961,-0.2820547,-0.3468535 +1,user488,0.2047073,-0.4907289,-0.5181507,0.756417,-0.6422082,-0.3788095,-0.6369724,0.8015701,-0.641048,-0.3570236 +1,user489,0.2686015,-0.4557673,-0.4694316,-0.4135598,-0.8251511,-0.814732,0.4918156,-0.4700089,-0.1658902,0.6826845 +1,user490,-0.5375089,-0.003831678,-0.2257501,-0.03253032,-0.6670493,0.5974347,0.4018844,0.05049172,0.3090786,-0.8064949 +1,user491,-0.7025448,-0.429007,0.09652277,-0.6548252,0.516961,-0.2866285,0.9809171,0.3942682,0.5167993,-0.3055702 +1,user492,0.5927809,-0.5359253,0.01040725,0.2940489,-0.6109521,0.4404623,0.00964092,-0.1592544,-0.2064207,0.09174239 +1,user493,0.08600488,0.4816267,-0.9944898,0.7538716,0.1542137,-0.3437237,0.230707,0.3753228,-0.7503585,-0.8301145 +1,user494,0.2467738,0.8713323,0.1884038,0.7173313,-0.03266801,0.09205821,0.1604581,-0.5706401,-0.2686926,-0.9702695 +1,user495,0.3097185,0.7603736,-0.0984452,-0.738238,-0.6226576,0.9779993,-0.05734518,-0.2863483,0.2345747,0.3551096 +1,user496,-0.9456007,0.0915291,0.3417903,-0.2260359,-0.9243652,-0.02286526,-0.6595006,-0.8382587,-0.66252,0.6452682 +1,user497,0.9398312,0.5763173,0.1523052,0.9940363,0.8449863,-0.08544725,0.917022,0.1863962,-0.2578408,0.6426095 +1,user498,-0.1191143,-0.05446028,-0.9800485,0.987627,-0.4606728,0.1038135,0.1928654,-0.6359682,0.8947288,0.8606594 +1,user499,-0.4990151,0.5630831,0.749148,0.3704185,-0.6241565,0.1589519,0.6968017,-0.3319475,-0.6521013,-0.1740345 +1,user500,-0.4436307,-0.05327962,0.6041145,0.6828954,-0.5115376,0.5838689,-0.6479628,-0.5347105,0.5239145,-0.05298845 +1,user501,0.1837068,0.2143085,-0.5238255,-0.5682256,-0.2384777,0.6891873,0.6763424,0.26023,-0.01195418,-0.9953317 +1,user502,-0.05277581,0.3377261,-0.9976646,-0.6234846,-0.9078689,-0.6780609,-0.8067539,0.9228777,-0.6435792,0.7307787 +1,user503,0.1623735,0.7053648,0.1994616,0.3259721,-0.3280977,0.7375573,0.6544023,-0.1141713,-0.5100831,0.9048349 +1,user504,0.04524952,-0.05849356,-0.1712277,-0.2654798,-0.01556532,-0.164438,-0.03363732,-0.5992709,0.7211601,0.8134075 +1,user505,0.1852505,-0.01997081,0.4688492,0.3914449,0.9808328,0.2151159,-0.2338716,0.800071,0.2165099,-0.3113237 +1,user506,0.2420896,0.5946077,0.4592118,-0.1693959,-0.1675932,-0.7355682,0.458465,0.3959386,-0.4259597,-0.890245 +1,user507,-0.5076019,-0.7358487,0.1333818,0.9200793,0.3298643,-0.9842817,0.36804,0.7294696,-0.239963,0.3104641 +1,user508,0.1190703,0.3025818,0.4029897,0.4499323,-0.1104487,-0.5015927,0.9441213,0.4315556,0.103895,-0.04731152 +1,user509,-0.1136963,-0.8010048,0.2837564,0.2885038,-0.8568604,-0.6688543,-0.7741689,0.149851,0.6601977,-0.1397965 +1,user510,0.7643734,0.5931597,-0.6415328,-0.6990127,0.1067238,0.8422375,-0.7133711,0.2667922,-0.966157,0.7000443 +1,user511,-0.8392267,0.5132669,-0.8164013,-0.2049565,-0.7965775,0.9184204,0.4655997,-0.9280277,-0.3052208,0.1005913 +1,user512,0.3078153,-0.1778053,-0.1200589,-0.8355787,-0.9162219,-0.9055206,-0.06453923,0.1489763,0.9531192,-0.7138698 +1,user513,-0.2702552,-0.6811534,-0.7079261,0.7299524,0.5195282,-0.4776565,-0.83992,0.6025063,-0.9173728,0.4974399 +1,user514,-0.2850109,0.0407198,-0.8855387,-0.2002173,0.1806009,0.0566153,-0.1683335,0.8435526,0.4092487,-0.5743614 +1,user515,0.6620227,0.4588167,0.3586138,0.2625615,-0.7713584,0.9012138,-0.2752512,0.34424,0.8516573,-0.5449446 +1,user516,0.1056865,0.7454346,0.2566922,-0.06900507,0.2602569,-0.9675355,-0.740625,-0.5807458,-0.7423549,-0.360204 +1,user517,0.6854094,0.5677304,0.913436,-0.5288597,-0.9754823,-0.2854374,0.4245255,-0.4131199,0.3056475,-0.06013245 +1,user518,-0.6877632,-0.408205,-0.1566056,0.9695885,0.9072737,-0.7251599,-0.2552814,-0.6392904,-0.4303974,0.108202 +1,user519,-0.6896061,-0.7452943,0.7385415,-0.3125881,0.6180487,-0.346345,-0.3775974,-0.7791757,-0.3834029,0.2827724 +1,user520,-0.04598907,-0.8880369,-0.5559955,0.05758042,-0.8006334,-0.1001694,-0.0836589,0.1168711,-0.8602427,-0.377448 +1,user521,-0.2252722,0.5879633,0.6176442,-0.06294183,-0.7597756,0.8722748,-0.8533971,0.4112014,0.8786812,0.3017071 +1,user522,-0.392151,-0.1743013,-0.1649357,0.03258666,0.1350097,0.3670265,-0.3966803,0.6150925,-0.8666036,0.9772022 +1,user523,-0.4532082,-0.4239622,0.4544117,-0.6483706,-0.4115855,-0.6597071,0.925982,0.9576167,-0.06666341,0.7142944 +1,user524,0.8607327,0.06958999,0.6231545,-0.3090702,0.3944382,-0.4714489,0.3773099,-0.2134759,-0.8716773,0.4715926 +1,user525,0.8546228,-0.3029689,-0.9765319,-0.2500821,-0.8976583,-0.5409153,0.7637778,-0.9555475,-0.1352961,-0.9930673 +1,user526,0.8565103,-0.6635886,-0.6440335,-0.3866087,-0.03424312,-0.6817078,-0.1313632,-0.3287316,-0.8320887,0.06940408 +1,user527,0.915132,-0.8388809,-0.03505526,0.4648939,0.4700729,0.5056859,0.7178093,-0.05173459,-0.5341973,0.1168608 +1,user528,0.794454,-0.7266516,0.1757733,-0.2560458,0.947328,0.3736374,0.6807999,0.2308486,0.6068631,0.6495423 +1,user529,-0.262604,0.2819511,-0.624082,-0.3989816,0.5050841,0.4221057,-0.9384978,0.03530026,-0.9373599,-0.06993656 +1,user530,-0.583883,0.7242022,-0.2859073,-0.1646876,0.8459164,-0.3353622,0.414611,0.6163179,-0.1862986,0.9428263 +1,user531,-0.6491767,0.2200687,-0.2201122,-0.5731504,-0.5642097,-0.0424937,-0.9671629,0.6961381,0.1307776,-0.4034462 +1,user532,0.9211028,-0.5037404,-0.1479075,0.03279276,-0.7333936,0.111293,0.7378446,-0.7044698,0.05068589,-0.06526826 +1,user533,0.3633412,0.06192831,-0.2835718,0.2118278,0.9380475,-0.01342308,0.6078572,0.5391957,0.1701222,0.673605 +1,user534,0.5131968,-0.07456644,0.9793494,0.7528216,0.1076926,-0.3049364,0.6872394,-0.4180333,0.6206945,-0.4986112 +1,user535,-0.03364768,0.437766,0.6808648,0.7673129,0.2510411,0.946855,-0.2957927,-0.3037407,-0.228154,-0.2518608 +1,user536,-0.4514083,-0.9580425,-0.8147226,-0.3967273,0.9188803,-0.7983072,-0.6260144,0.3392666,-0.613368,-0.6377187 +1,user537,-0.2447136,-0.4799587,0.4385612,-0.4165743,0.9400994,-0.04050462,0.1457043,0.9779053,-0.8052652,-0.3888562 +1,user538,0.4587504,0.7019173,-0.1857534,0.6873922,-0.4190947,0.9625733,-0.9277527,-0.574271,0.5318829,-0.9413966 +1,user539,0.667662,0.3445393,0.5882671,-0.9467951,-0.1915684,-0.2998999,-0.6818931,-0.2291777,0.490527,0.3149697 +1,user540,0.6415901,-0.2809636,-0.2776824,0.8719295,-0.9167609,0.2906411,0.3715355,0.1277563,0.8549325,0.4713473 +1,user541,0.2231238,0.295077,0.1727138,0.9883795,0.6876291,0.8048108,-0.6411238,0.6925211,0.5657259,0.7586476 +1,user542,0.8284353,-0.1421938,0.7718657,-0.1517515,0.01185413,-0.3814795,0.7837066,-0.1572054,-0.8146938,-0.584439 +1,user543,-0.05059462,0.5412311,0.6022587,-0.9636493,-0.8329828,0.3851205,-0.6930038,-0.7232674,0.8080518,0.7574775 +1,user544,0.9528686,0.6139236,0.4647877,0.7183319,0.2071573,-0.6728457,-0.4810438,0.2950274,0.6483531,0.2560875 +1,user545,-0.4565756,0.898526,0.8863271,0.6480312,-0.8075449,0.6751358,-0.3846269,-0.3136528,0.5945549,-0.1588003 +1,user546,-0.3885719,4.782481e-05,-0.03912744,0.2989123,-0.6043412,0.2863343,0.03174507,0.6209726,0.6597091,-0.7874671 +1,user547,0.05855516,0.3593582,-0.2785201,-0.3506731,-0.5325858,-0.6403812,-0.2216688,0.7142816,0.9059982,0.8958835 +1,user548,-0.7711662,0.4662564,0.7997631,-0.8808285,-0.7830273,-0.6103016,-0.9601015,0.2732272,-0.0997976,0.7810672 +1,user549,-0.07633515,0.5918428,0.8042669,0.2685007,-0.6970675,0.5611744,0.7764636,0.9816822,-0.7706883,0.3207349 +1,user550,0.368949,0.614064,-0.5399786,0.3367388,-0.9145371,0.01327376,0.4007338,0.935106,-0.4774046,0.178656 +1,user551,0.1828447,0.5782195,-0.7562324,0.1767519,-0.5836606,0.289529,-0.04376037,-0.6099016,0.03995967,-0.5963807 +1,user552,0.6983927,0.1798062,0.4219112,-0.7944411,-0.4568431,0.4334492,0.9230666,0.3928836,-0.8920071,-0.3775581 +1,user553,0.976798,-0.5602373,0.2950857,-0.6306746,0.2204726,-0.6196998,-0.9959465,0.5501985,-0.3440082,0.1558582 +1,user554,0.7296366,-0.8457427,0.6981793,0.5283812,0.004753882,0.6298219,-0.1177784,-0.6522849,0.9732963,-0.8820863 +1,user555,0.5591254,-0.7506038,0.04506564,-0.1035113,0.9375951,0.9620003,0.3003765,-0.8205923,-0.7636844,-0.9059655 +1,user556,0.8314209,0.1367938,0.3185538,0.1192433,0.3228142,-0.1606151,0.7678313,0.5946509,0.5206957,0.1627909 +1,user557,0.5861469,-0.5093314,-0.9458542,-0.8582274,0.9705108,0.9481141,0.7508585,0.01898355,-0.8587925,0.1873178 +1,user558,0.4742574,-0.5894848,-0.9899896,-0.6386175,0.407668,0.4676862,0.01818581,0.1276731,-0.2978818,0.2108953 +1,user559,0.6258749,0.4101421,-0.5056729,0.8631975,0.2701422,-0.7869776,0.4486312,-0.1745004,0.1275588,-0.1876668 +1,user560,-0.6764571,0.7726197,-0.5699362,-0.257209,0.4755949,0.3702198,0.8123607,-0.9457162,-0.7961524,-0.8826188 +1,user561,0.8903744,-0.8652826,-0.2758969,0.1966949,0.2535844,-0.867676,-0.5672032,-0.256009,0.5158196,0.1537217 +1,user562,0.9766982,-0.3697891,0.2742149,-0.7099529,0.7059325,0.1705287,0.4814683,-0.4783624,-0.7416637,0.4088871 +1,user563,-0.7553543,-0.7311207,0.2821563,0.7755837,0.7422013,-0.5184872,0.5502054,-0.6501859,0.2545335,0.05211293 +1,user564,0.2537155,0.1966457,0.4405313,-0.5914773,0.1916319,0.1189009,-0.959346,-0.7168133,-0.3140582,-0.1726734 +1,user565,0.4898949,0.5556444,0.2535643,-0.9571313,-0.1863749,0.8655923,0.1687077,0.1036044,0.8790308,0.9102759 +1,user566,0.210998,0.7066453,-0.03697896,0.5428967,-0.006757666,-0.5716322,-0.7455873,0.0460734,-0.9736205,0.8002522 +1,user567,0.8023072,0.2386032,0.6258086,0.01179536,0.1105122,0.3205937,-0.5853604,0.6224533,0.0725738,0.1896079 +1,user568,-0.7548187,-0.9243143,-0.3078745,-0.3737056,-0.2462755,-0.1749123,-0.685588,0.08150972,-0.9262344,-0.4785804 +1,user569,-0.3302516,0.4085626,0.7772676,0.2302889,0.5741477,-0.6090589,-0.67334,0.4718024,0.5582624,0.8588555 +1,user570,0.4699692,-0.4168574,0.2140757,0.06500027,0.9189438,-0.9793062,-0.2672535,-0.6067244,-0.4368991,-0.4954224 +1,user571,0.8867714,-0.2052779,0.4144431,-0.5017762,-0.1630364,-0.8842712,0.6859475,-0.790734,0.9286982,0.9927669 +1,user572,0.8928723,-0.2963603,-0.05001859,0.2186684,0.2617768,-0.8042481,-0.3144638,0.1643235,0.1239883,0.6175032 +1,user573,0.2984045,0.4409488,-0.01405855,0.9132487,-0.06920209,-0.3607857,-0.4835469,0.2360702,-0.2515929,-0.07986134 +1,user574,-0.1638233,-0.6640468,0.01670183,-0.4654255,0.003980791,0.5008493,0.9929437,-0.5140014,0.73675,0.7502444 +1,user575,0.8457409,-0.6824367,-0.5852309,-0.06299967,-0.5310659,-0.4770937,0.2044924,-0.540649,-0.2276586,-0.1264093 +1,user576,0.8418289,0.3394748,-0.1277315,0.5612799,0.123253,-0.6856499,0.1318261,0.9224174,-0.657038,0.7613383 +1,user577,0.4476048,0.336001,0.9775744,0.8334868,0.3996396,-0.2128164,0.02468876,-0.8930288,0.3964591,0.9627773 +1,user578,-0.09570395,0.6769215,0.1362491,0.5863272,-0.06365177,-0.1174749,0.9828237,-0.8263674,-0.3216603,-0.2305258 +1,user579,-0.9293373,-0.1942688,-0.3279684,0.6804514,0.3402257,-0.2959515,0.1717247,0.1956446,0.2431644,0.5424055 +1,user580,-0.6287303,-0.07215613,0.7818413,0.1019875,0.702572,-0.651642,-0.1988476,-0.9113466,0.6257708,0.2835121 +1,user581,-0.7267549,0.2909855,0.5962705,-0.07693406,0.02181111,0.8957988,0.3835575,-0.8912614,0.2009351,0.9481302 +1,user582,0.2535074,-0.6160493,-0.08420079,-0.1427968,0.7565651,0.9935775,-0.8720357,0.585743,-0.7168759,0.9460248 +1,user583,-0.9303377,-0.89235,0.2037525,0.3075464,-0.7542711,0.7818072,-0.275781,0.481537,0.7337636,0.905954 +1,user584,-0.7499569,0.7307482,-0.1086438,0.2923914,-0.7577163,-0.7239009,0.387611,0.658937,0.8569269,0.1039884 +1,user585,-0.01685598,-0.461792,-0.3860215,-0.6144155,-0.2386811,0.6233994,0.01018594,0.9334581,-0.7435797,-0.9360615 +1,user586,0.6287877,-0.6429538,-0.7511818,-0.7959649,-0.816676,0.7438075,-0.9754045,0.6609447,0.9700792,0.9999886 +1,user587,-0.918536,-0.1324581,-0.79009,-0.5883653,0.5650979,0.115484,0.1554423,0.253588,0.3776226,-0.7332207 +1,user588,-0.4307091,0.0288766,-0.3318757,-0.472643,-0.2681703,0.5715134,-0.2389556,-0.04755835,-0.6023722,0.2512562 +1,user589,0.1030451,-0.2324386,-0.7411714,-0.4345823,0.5909919,0.2114937,0.04278128,-0.2113822,-0.3278026,0.2108839 +1,user590,0.7073388,-0.7223159,-0.295763,-0.7251678,-0.1647599,0.3285064,-0.3959265,-0.9209125,-0.4948186,0.07911255 +1,user591,-0.1071662,-0.1985037,0.09818808,0.270148,-0.7925754,-0.05826678,-0.4265948,0.00672546,-0.3985246,0.3686374 +1,user592,-0.006580492,-0.09772115,-0.01706832,0.7621126,-0.1554237,0.3438177,0.4755781,0.5326088,-0.811983,-0.6353944 +1,user593,0.684037,-0.09210508,0.9784519,-0.4351207,-0.4588274,-0.5009649,-0.9144582,-0.3992748,-0.2364823,-0.5120004 +1,user594,0.1374795,0.07037557,-0.6196556,0.04573174,0.9496258,0.423246,-0.8763895,0.3565395,0.8560089,-0.5792497 +1,user595,-0.752865,-0.9010754,-0.576537,-0.8293647,-0.9637918,-0.5372814,0.5162321,0.8157955,-0.1260412,0.1919322 +1,user596,0.1739319,-0.5364607,0.2320162,-0.392252,0.3547977,-0.6353726,0.2542495,0.7043296,-0.3574515,-0.6017245 +1,user597,-0.6515224,-0.2229791,0.3433654,-0.4113716,-0.05713183,0.8516138,-0.6219768,-0.5973871,0.8823883,-0.7789975 +1,user598,-0.9505577,0.3375278,-0.9507284,0.1824307,0.1467204,0.7833123,0.9308717,0.4382488,0.9465326,-0.6184599 +1,user599,0.4191132,-0.460775,0.9241417,0.2340423,-0.8914778,0.189715,0.5686615,-0.2141607,-0.2836858,-0.0803049 +1,user600,0.01822603,-0.8144165,0.120633,0.8189173,-0.4829842,-0.7574451,-0.2953168,0.8744153,0.4406507,-0.920142 +1,user601,0.5194115,0.9206704,0.2633473,-0.7525691,0.06566413,0.8040061,-0.3363819,0.8315245,-0.4903666,-0.1138823 +1,user602,0.3058846,0.3339471,0.3385848,0.7322661,-0.05451418,0.3054438,0.2546089,-0.004894681,-0.3549876,-0.08753802 +1,user603,-0.08890169,-0.1107768,-0.9293856,0.03758571,0.7787926,-0.5616931,0.3902194,0.03873884,-0.4353609,0.6973612 +1,user604,-0.182184,0.3616192,-0.7507112,-0.8393204,0.996462,-0.5567796,0.1800712,0.06759469,0.2580405,0.8062564 +1,user605,-0.8579387,0.6699003,-0.6447134,-0.7331593,0.9494666,-0.1937069,0.2475526,0.4811039,-0.6182377,-0.3372937 +1,user606,-0.2431608,0.2067865,-0.5146164,0.974586,-0.7522733,-0.03878686,-0.4052882,0.4980898,0.3369805,-0.4290481 +1,user607,-0.3403551,-0.298906,0.1215573,0.7219596,0.119715,-0.2424295,-0.6881027,-0.009987926,0.6010025,0.5675947 +1,user608,0.5896661,0.005901324,-0.667139,-0.8996726,0.3491062,0.5934767,-0.7277586,0.5880751,0.7782214,-0.3745164 +1,user609,0.6611353,-0.1162919,0.6216326,0.5609132,0.1840749,0.8437382,-0.4224645,0.6717224,-0.9846798,0.3404261 +1,user610,-0.2696924,0.5068252,0.7935889,0.4024109,-0.5400593,0.461619,0.483622,-0.8143433,-0.1558331,0.1100002 +1,user611,0.9609358,0.9337452,-0.8852977,0.202315,0.05167817,0.9418347,0.07339379,0.6767285,0.4039921,0.9089957 +1,user612,0.9343803,-0.8253064,0.2179031,-0.5160208,-0.794114,0.739537,0.961093,0.780461,0.2162553,0.2885562 +1,user613,0.983815,0.890776,-0.2906119,-0.7403858,-0.7834942,0.4551965,0.6115863,0.7713997,0.127291,0.056025 +1,user614,-0.9694018,-0.9586048,0.3184548,-0.4901386,0.2974071,0.7236419,0.7976127,0.1582655,0.1377558,0.8149498 +1,user615,-0.8155766,0.9054417,-0.8907407,0.7763705,-0.5518303,-0.9843639,0.348704,0.439398,0.0731822,-0.6074554 +1,user616,-0.03304098,-0.5710161,0.3233666,-0.3548014,-0.02217531,0.07859585,-0.3782278,0.7048578,0.3837113,0.1199635 +1,user617,0.6593859,-0.6015586,0.567273,-0.2861035,0.480731,0.4674494,0.8222082,-0.1807898,0.107835,0.8149383 +1,user618,-0.7341126,-0.2270163,-0.6808307,-0.8119948,-0.9867324,0.1311201,-0.4958536,-0.307014,-0.5491952,-0.3406761 +1,user619,0.5362499,0.4578605,0.9914909,0.1725557,0.7096544,-0.3498907,0.3828167,-0.3427006,0.7813391,-0.6287803 +1,user620,-0.237569,0.1660028,0.8261016,0.2793142,0.07172298,-0.3210568,-0.1350105,0.607828,0.7800324,0.02582227 +1,user621,0.9732262,0.05066775,0.02340634,-0.5371626,-0.1514923,-0.5403735,0.1082199,-0.2279265,-0.04401384,0.7384365 +1,user622,-0.5709162,-0.7406432,0.08967898,-0.5572963,0.917079,0.5918425,0.9562218,0.6640249,-0.6171855,0.7398571 +1,user623,0.7558505,-0.9317183,-0.1909668,0.04142676,0.9162993,-0.9772391,-0.6594324,0.1404368,0.9680495,0.3904279 +1,user624,0.6572632,0.9585627,0.001858245,0.02771672,0.3896803,-0.04133845,0.1937617,0.3727987,0.7195039,-0.7735639 +1,user625,0.5665633,0.3297324,0.4700233,0.4884354,0.8667048,0.01508853,-0.9201677,0.02056439,-0.7611766,-0.8393926 +1,user626,-0.9970144,-0.8327937,0.2324962,0.2120621,0.9525075,-0.5145205,0.8567997,-0.04376773,-0.1579918,-0.4176399 +1,user627,-0.1688049,-0.577898,-0.7661256,0.6354647,-0.255522,0.3232889,-0.5519888,0.0771283,-0.6379476,-0.3752885 +1,user628,0.9150409,-0.8932467,-0.1866112,-0.9229362,-0.190427,-0.1332977,-0.5421445,0.4231773,-0.8787882,-0.6183901 +1,user629,-0.9475722,0.5047341,0.2817678,-0.6055073,0.09922783,-0.7312083,0.7876714,-0.6055189,-0.2114592,-0.03609982 +1,user630,-0.7496917,-0.03867302,-0.8419839,-0.130493,-0.1469998,-0.486996,-0.9833273,0.8629676,0.0783666,0.5444066 +1,user631,-0.06673307,-0.7076632,0.9340218,0.8959811,0.3265888,0.1092573,0.1625387,0.2975926,0.5618625,-0.5385321 +1,user632,0.5718393,0.4254045,-0.4548849,-0.3580764,-0.835108,-0.9272022,-0.5487105,-0.7739944,0.2981743,0.8500179 +1,user633,0.5561929,-0.7047259,0.4966009,-0.3982268,0.798486,0.8184478,0.2712816,-0.1419271,0.723379,-0.5431314 +1,user634,0.8443652,0.1815601,-0.9953638,-0.06643315,0.1053814,0.5475642,-0.4472419,-0.6636685,-0.8734985,-0.8411709 +1,user635,-0.6103447,-0.2129764,-0.2055961,-0.1973967,-0.838646,-0.4839818,0.6313607,0.2936003,-0.4437852,0.6562743 +1,user636,0.6982542,0.9651743,0.8518875,-0.1313862,0.7479527,-0.3752591,-0.4811657,-0.6608232,-0.8948587,0.119575 +1,user637,-0.3987956,-0.6116534,-0.5099802,-0.09184711,0.3531081,-0.4912227,0.1474699,0.8344213,0.463482,-0.2702191 +1,user638,0.04930018,0.4881176,0.9159612,-0.4754371,0.281069,0.2735887,0.943258,-0.7163877,-0.8427826,0.223869 +1,user639,0.2879203,-0.02892433,-0.8152515,-0.03105872,0.09705881,-0.7817824,-0.2089243,0.9272519,0.8833627,0.7450586 +1,user640,-0.7376603,0.2720547,-0.8883476,-0.5309339,-0.462817,-0.6474845,0.7250054,0.5061437,0.4788022,-0.929793 +1,user641,0.7796077,-0.005057122,0.7095501,0.9269738,0.7410097,-0.2647924,0.42688,-0.530731,0.001384274,-0.6661308 +1,user642,0.2488562,-0.09517914,-0.7005492,-0.8287438,-0.851263,-0.8399477,0.8644694,0.6039804,0.2873548,0.6540543 +1,user643,-0.80328,0.4467482,0.3295556,-0.04695474,-0.256931,-0.9079475,0.6860984,0.2866046,-0.3049425,0.3587632 +1,user644,0.7634227,-0.1142812,-0.5810618,-0.8134121,0.9575154,-0.8095959,0.03846632,-0.7593313,-0.8713248,0.3898942 +1,user645,0.2794543,-0.05378392,0.6179056,-0.3188824,0.446144,0.8836942,0.6620822,-0.237754,-0.5748895,0.469004 +1,user646,-0.6188566,0.35219,0.4388149,-0.2705842,0.1912387,-0.8923113,0.03480243,-0.2739974,0.7682397,0.7513078 +1,user647,-0.2696182,0.3147028,0.7423048,-0.1682134,-0.06465987,0.2689999,0.6602385,0.9455265,0.5123865,-0.4901423 +1,user648,-0.06115977,0.3446575,0.1851786,0.3950141,-0.07312491,0.3511436,0.4842904,0.5814561,0.5329455,0.2839424 +1,user649,-0.3529692,-0.8748263,0.7579842,-0.08257901,0.2045063,0.2388088,0.5389488,0.4189886,-0.7809555,-0.5893683 +1,user650,-0.7333683,-0.2274367,0.7337957,-0.9956577,-0.3550055,0.9191092,0.0430552,-0.3971741,0.2937256,-0.1189227 +1,user651,0.7012712,-0.4893397,0.0112802,-0.3256717,0.9985981,-0.9699132,-0.6507201,0.1892841,0.3129779,-0.6902354 +1,user652,-0.379743,0.1758414,-0.2186094,0.3802584,-0.9469861,0.6984352,-0.3528313,-0.8089379,0.1750307,-0.8509318 +1,user653,-0.3042845,0.03192012,-0.1765253,-0.5529541,-0.4379265,0.5109518,-0.0007229731,-0.7331492,0.6765401,-0.3790656 +1,user654,0.4571218,-0.421058,0.8203134,0.7157551,0.9148974,-0.9471524,-0.3101525,-0.6702791,0.2810274,0.7001925 +1,user655,-0.7224798,0.1344041,0.7832488,-0.5920249,0.4426942,-0.3429032,0.8409304,0.5638608,-0.1054655,-0.6244957 +1,user656,-0.7377212,-0.6383475,-0.706502,0.9354814,-0.5712217,-0.4739597,0.07910938,0.2874151,0.9153636,-0.2184582 +1,user657,0.4601073,-0.2538518,0.05280965,-0.07218286,0.8674048,-0.4616729,-0.4533529,0.2859531,-0.8769644,-0.7174474 +1,user658,0.1087153,0.5565061,-0.9828768,-0.9565602,-0.8128278,0.9803857,-0.7110584,-0.3590109,0.256587,0.0002158261 +1,user659,-0.8226803,-0.5315942,0.1068868,-0.9874548,0.2383512,0.3927426,0.5369649,-0.2894075,-0.9634247,0.1631517 +1,user660,0.5125352,-0.7491177,-0.6654225,0.3223099,-0.03336733,-0.1928812,-0.6656815,0.6804343,-0.08842352,0.2464528 +1,user661,0.3590237,-0.482167,-0.8248607,-0.08705312,0.04017242,-0.5066103,-0.6943858,-0.4960433,-0.6650465,-0.4553775 +1,user662,0.1105866,-0.2392574,0.04090857,0.9085263,-0.43506,-0.4980001,-0.3004964,-0.9918149,0.5984378,0.6246196 +1,user663,0.08437449,0.6762867,-0.1203074,0.9642335,0.1315246,-0.1200834,-0.214392,0.9064398,-0.7902492,0.09647066 +1,user664,-0.08478347,-0.1868929,0.6717402,0.5147201,-0.1613415,-0.6881625,0.5768959,0.3620296,-0.9416675,0.001491094 +1,user665,-0.04504814,0.9423027,0.04554478,-0.1579068,0.6703214,-0.9504359,0.2522617,-0.6554835,0.7249394,0.7834487 +1,user666,0.4740298,-0.5366896,0.6740965,-0.2331632,0.2928786,0.3959348,-0.5830313,0.2000401,-0.2340344,-0.247255 +1,user667,-0.3865293,-0.2217185,0.5236277,-0.6166661,-0.4133889,-0.06342164,-0.9042699,0.7012065,-0.8365262,-0.878934 +1,user668,0.5561563,-0.6693507,0.5355646,0.7502461,0.02342951,-0.4416586,-0.6002685,-0.8210622,0.1884214,-0.4867704 +1,user669,-0.47667,0.951428,0.5900576,0.2913997,-0.4260524,-0.3304765,-0.6397732,0.4836525,-0.07681704,0.9766139 +1,user670,0.9013911,0.7493571,0.7083761,0.3522752,0.6836699,0.1547959,-0.1131942,0.6284584,-0.9531636,0.8661246 +1,user671,0.818496,0.6027039,0.647217,-0.7806878,0.5606125,-0.08914308,-0.8752631,0.6850814,-0.3327764,-0.4165634 +1,user672,-0.6970623,-0.0536291,0.2996077,0.2183734,-0.6850427,0.4047311,0.7871068,0.9529215,0.9245672,-0.6895169 +1,user673,0.1502472,-0.345822,-0.9921731,0.5235314,0.8324069,0.3148482,-0.2487248,0.2324388,0.3341912,0.5201789 +1,user674,-0.984784,0.04945214,-0.02322747,0.1723574,-0.6963185,0.002909469,0.8108353,-0.02831396,0.3622812,0.9421998 +1,user675,-0.9336395,0.8320897,0.7185459,0.4049614,-0.7275273,0.5951352,-0.1744269,-0.8064098,-0.9467575,0.7003774 +1,user676,-0.5702984,0.6003941,0.6257325,-0.7953509,0.2785509,0.1985424,-0.5866426,0.9946848,0.7593018,-0.01081709 +1,user677,-0.6036406,-0.5983579,-0.5844126,0.9017732,0.4949202,0.1105981,-0.1543622,0.6976886,0.1305209,0.6935077 +1,user878,0.1980927,0.3409747,0.9082253,-0.3508397,0.05578354,-0.509267,0.7539599,-0.7354123,-0.4335982,0.3048674 +1,user879,-0.6370935,0.1641512,-0.07861016,-0.6798376,0.2891205,-0.2941439,-0.8167592,0.6694944,-0.06723434,-0.006153317 +1,user880,-0.7737482,0.644265,0.3752945,0.2794445,-0.9220366,-0.3586124,-0.6362113,0.4828829,0.06801904,-0.3120297 +1,user881,-0.007368653,0.02169892,-0.2445337,0.5271013,0.9402651,-0.5247804,-0.7344703,-0.07926876,0.01320416,-0.9487266 +1,user882,-0.00462238,-0.9056461,0.3073972,0.4637236,-0.3795766,0.8843186,0.3081892,0.06354898,-0.1725954,0.2866524 +1,user883,0.02841109,-0.6025055,0.4927004,-0.4338099,-0.6821628,0.07380243,-0.1570692,0.5732742,-0.4644923,0.1166572 +1,user884,0.1671515,0.3757951,-0.3095915,0.2592005,-0.936461,0.05144003,-0.8152834,0.6747073,-0.8787211,-0.9991112 +1,user885,0.2269982,-0.1129344,-0.6433316,0.5737446,0.8518686,0.7106367,0.3823273,-0.9539459,0.412136,-0.1430769 +1,user886,-0.9433826,-0.2821889,-0.08248557,0.0231611,0.7324883,-0.862836,0.5407627,0.1005185,0.4568113,0.2722024 +1,user887,0.008662837,-0.1380494,0.2382988,0.8766184,-0.2522317,-0.1249274,0.206003,-0.1841766,0.651804,0.7120225 +1,user888,-0.3013792,-0.2266765,0.8399846,-0.7073495,0.6363051,0.3068922,-0.6846382,-0.6974917,-0.0883087,-0.1331008 +1,user889,-0.8955614,-0.5593996,-0.9259849,-0.1444269,0.8175527,0.3713439,-0.6890552,-0.783958,-0.2584699,-0.1363947 +1,user890,-0.8063893,-0.6761728,-0.4712685,0.5905452,0.7664158,0.01689816,0.125337,-0.2326668,0.7831987,-0.6789631 +1,user891,-0.5653743,0.3491773,-0.07597715,-0.3656783,0.34586,0.6909759,0.4091759,-0.470919,-0.1965148,-0.189964 +1,user892,0.8717954,-0.2814455,-0.1931326,0.9788264,0.6116002,0.5709203,0.8465474,0.1081627,0.7684353,0.6103354 +1,user893,-0.5458085,0.1228753,-0.1996046,0.297974,0.1424553,-0.2320249,0.2148854,0.8471257,-0.1824313,-0.8843483 +1,user894,-0.6298972,0.183776,-0.7957555,0.6795359,0.1229969,0.2947345,-0.5905685,0.6482062,-0.7320749,0.3120417 +1,user895,-0.3587397,-0.2035315,-0.1653015,-0.492115,-0.2291164,-0.1341482,0.8979921,0.9445091,0.2242227,-0.9052834 +1,user896,-0.9678563,-0.2770846,-0.8154907,0.2294615,-0.2056157,-0.9101835,-0.3132585,0.5399203,-0.447794,0.05296674 +1,user897,0.9718778,-0.1769027,0.9914561,-0.4789236,0.3309166,-0.821227,0.2186828,0.03374999,-0.8520562,-0.1665632 +1,user898,0.7650821,0.7533554,0.05106261,-0.3898275,-0.7565527,-0.6271632,0.4988945,-0.5063241,0.1648233,0.5646368 +1,user899,0.989828,0.1822031,-0.4108627,-0.987132,-0.7659356,0.2893427,-0.5074875,-0.9536087,0.9213671,0.8050936 +1,user900,0.6170295,0.8919768,0.8070048,0.657957,-0.8442601,-0.00824485,0.3027575,0.05349078,-0.05764916,-0.4731579 +1,user901,-0.2779744,-0.8368444,0.6909163,-0.58988,-0.04252286,0.7115569,0.7279541,-0.565091,-0.1648335,-0.6011711 +1,user902,-0.09447022,-0.412372,0.3268872,-0.8571779,-0.564691,-0.4487702,0.3955645,0.2043683,-0.205416,0.6154786 +1,user903,-0.3299336,0.1314985,0.8248203,-0.5538059,0.5556078,0.3791414,0.6630896,-0.1994872,-0.7478653,0.8777083 +1,user904,0.7653947,-0.01303158,0.6742236,-0.4310145,0.3675505,0.2060394,-0.3858562,-0.3479709,-0.3013469,0.7285129 +1,user905,0.940028,-0.7881495,0.1813086,-0.2265256,0.8595463,-0.5013995,0.7993456,0.1632121,0.940795,0.5315525 +1,user906,0.3086453,0.7653136,0.0156352,0.2569913,0.9390053,-0.4733123,-0.4633678,-0.473898,-0.6445684,-0.2425863 +1,user907,0.3609269,0.4781414,-0.4548034,-0.8238947,0.9407965,-0.01588139,0.3294178,-0.4816057,0.1641733,0.8723378 +1,user908,0.7740062,-0.5749404,0.9418806,-0.925585,0.5915152,0.7601249,0.1495275,-0.1271021,-0.9208394,-0.1742064 +1,user909,-0.493262,0.1062883,-0.07613953,0.9061516,-0.005211159,0.01742074,-0.7094079,-0.2093104,-0.07816661,-0.937719 +1,user910,0.7238333,-0.3577073,0.4665864,-0.5037323,0.229917,0.6899748,0.5126586,-0.8121113,-0.9030611,-0.1338155 +1,user911,-0.999742,-0.9306754,0.3171751,0.3538595,0.6694786,-0.5984875,0.5133162,-0.6442191,0.1471796,0.513764 +1,user912,0.4993693,-0.8720128,0.6793268,0.4332528,-0.06494608,0.4926403,-0.4438782,0.7114209,0.9350376,-0.8864456 +1,user913,-0.280789,-0.2633534,-0.2260164,0.9599913,0.8503404,0.5742933,-0.1791522,0.2514377,-0.07565649,-0.8471631 +1,user914,0.02866908,-0.5331809,-0.1901244,0.9200496,0.9873159,0.4753149,-0.643753,0.9290551,0.6826873,-0.3695788 +1,user915,-0.3334792,0.5037824,-0.6302647,-0.3075467,-0.001407058,-0.4559196,-0.2591616,0.3861281,-0.9436835,-0.8855568 +1,user916,0.9462092,0.6237122,0.130652,0.5337359,0.702209,0.28493,-0.7968249,0.2974918,-0.6635205,0.009759984 +1,user917,0.08528653,0.1846303,0.72739,-0.05678928,0.7198042,0.6124789,0.8970097,0.02957355,0.1394986,0.9026237 +1,user918,0.6751836,-0.6342671,0.6080341,-0.4309283,0.7463612,0.4191529,0.9468414,-0.7980485,0.7081205,0.8264657 +1,user919,-0.35517,-0.6029643,-0.02936343,0.8263864,0.3385142,-0.4081778,-0.4814632,0.6000001,0.2481708,0.8766592 +1,user920,0.1897251,0.6252307,0.8014051,0.7987838,0.5373569,-0.01617719,-0.7920455,0.2456156,0.8810288,-0.233771 +1,user921,0.8687943,-0.3104398,-0.8632344,-0.8403831,0.512777,-0.5639489,0.07217832,-0.03071529,0.4913192,-0.8524974 +1,user922,0.07945561,0.746213,0.8946594,-0.5392919,-0.3156258,-0.7172019,0.9277127,-0.8709189,-0.948344,-0.3133049 +1,user923,0.06152054,-0.6562148,-0.3917276,0.7776102,0.1489571,-0.4452568,-0.9454981,-0.6462218,0.6494641,-0.6234356 +1,user924,-0.6770142,0.8124354,-0.062839,0.4575909,-0.3447677,0.2040262,-0.7129362,-0.1835896,-0.691112,-0.7368457 +1,user925,0.4495584,-0.07001098,-0.9010961,-0.859756,0.8073711,0.5775326,-0.6628558,0.7772873,-0.6804189,0.9987369 +1,user926,0.7027808,0.1402536,0.4429709,-0.7145047,0.9198408,0.420595,0.952494,-0.7017127,-0.1263132,-0.528719 +1,user927,-0.6448704,-0.4646492,0.1216703,-0.3129476,0.4496166,0.2938427,-0.02619475,-0.6436693,-0.1389061,0.3161211 +1,user928,0.4214362,0.7530864,-0.90964,-0.3386796,0.1382877,0.7563056,0.555827,-0.1889627,-0.5324751,-0.1678263 +1,user929,0.4678629,-0.106391,-0.5059664,-0.1043323,-0.8367119,0.7934318,0.4513885,-0.2080368,-0.9614898,-0.9640822 +1,user930,-0.6550425,0.717554,0.7108077,-0.3000796,0.683681,-0.4168146,0.4663177,-0.597278,-0.217539,0.1212146 +1,user931,0.03846565,0.6450632,0.8973648,-0.6807226,0.2940276,-0.2519392,-0.1414155,0.864528,0.4098757,0.3590158 +1,user932,-0.8101115,0.05676455,-0.8150501,0.3057877,0.1207652,0.5049887,0.1793425,0.2268723,-0.1263233,-0.5652532 +1,user933,0.2504873,-0.694818,0.0376949,-0.1572576,-0.8810101,0.1344152,-0.1381177,0.6070903,0.577045,-0.2633067 +1,user934,0.708532,-0.2234383,0.7221851,-0.2345285,-0.1503646,-0.8727978,-0.478326,-0.3349591,0.6620104,0.2367241 +1,user935,0.9552832,-0.956267,0.8591734,0.8747732,-0.5116843,-0.2889719,0.7934863,0.8789013,0.5723298,-0.8367404 +1,user936,0.1905153,-0.4829675,-0.7809965,0.6162169,0.9785362,0.6330157,-0.3387721,-0.2296977,0.5178399,-0.7317542 +1,user937,0.01717731,-0.4581247,-0.2621797,-0.9775372,-0.2113593,-0.3461101,0.05830628,0.1911429,-0.982558,0.9941378 +1,user938,0.31621,0.5218744,-0.59563,-0.9491215,-0.5708878,0.6951467,0.1229041,-0.6027043,-0.2634969,-0.9644026 +1,user939,-0.03547845,-0.05790789,-0.8391159,0.6906319,0.5700514,0.3931406,0.8107554,0.6432003,0.5970005,0.09403945 +1,user940,0.5239153,0.6481636,0.6616807,0.9286144,0.7834295,0.6713107,0.3488984,0.9818325,-0.06072464,-0.9435811 +1,user941,0.04004336,-0.8358329,0.8709564,-0.4528538,0.6590292,0.3851215,-0.3644373,-0.4148156,-0.166558,-0.09821813 +1,user942,-0.03522045,0.01141673,0.4780593,0.04449138,0.23953,0.7946531,0.3240716,0.9989811,-0.2558199,-0.3921966 +1,user943,0.02328458,0.7761508,0.3410075,0.3618672,-0.2815166,0.163951,0.9050202,0.6932534,-0.1256871,-0.8300267 +1,user944,0.7592543,-0.09918635,-0.35506,-0.4928626,0.5093696,-0.04058521,0.4564105,0.8366221,0.7577855,0.05461874 +1,user945,0.9934486,0.4782359,-0.7120652,-0.035459,0.2268459,0.269968,0.6803186,0.9280362,-0.5731326,0.2382247 +1,user946,0.6898053,0.2799332,0.7107428,-0.9456795,0.7170764,0.7080314,-0.3541414,0.07938152,-0.0693706,-0.7155836 +1,user947,0.7054635,-0.4754741,0.775592,-0.9591266,0.2115786,-0.7556552,0.6595855,0.1341139,-0.9057349,-0.9356213 +1,user948,0.07873516,-0.3371339,-0.9846752,0.9077517,-0.05334992,-0.1175531,0.5773283,-0.04239025,0.566366,0.1408483 +1,user949,0.3649889,0.6456661,0.318777,-0.3766078,0.4634376,0.1271843,-0.4073,0.2813331,-0.3612501,-0.8891179 +1,user950,-0.6497066,-0.07843845,-0.2537714,0.8672598,-0.4499072,-0.1638331,-0.8218777,-0.265886,0.3424359,0.9410379 +1,user951,-0.7315397,-0.7119032,0.8167299,0.7065355,-0.515993,0.8662697,0.7852828,-0.7967747,0.4473948,0.9070773 +1,user952,0.2337833,-0.6647738,0.4555426,-0.2169909,-0.02378539,0.5632354,0.6648783,-0.7493822,-0.8699308,-0.7416152 +1,user953,0.4297491,-0.3322255,-0.359112,-0.6720321,0.234467,0.118965,-0.894165,-0.1368049,0.3940919,-0.372267 +1,user954,0.3299808,-0.368118,-0.5749977,0.4841458,0.6329641,-0.5789871,0.8397847,-0.4429965,0.0968589,-0.7163583 +1,user955,0.5567691,-0.8523383,-0.6072964,-0.7594,0.6314469,-0.2327384,0.951942,0.06702819,-0.5610429,-0.4784609 +1,user956,-0.1206925,0.5977636,-0.2602081,-0.5317881,0.04183807,-0.3035023,-0.5570208,-0.3595176,0.713673,-0.3735301 +1,user957,0.03276168,0.7721356,0.8679733,0.7696411,0.5528049,0.8416079,0.7922787,-0.1447092,0.9705457,-0.2450772 +1,user958,0.9118987,-0.3169875,0.5143739,-0.07234765,0.08106351,-0.9388957,-0.07425271,0.4233589,0.3000511,0.8376601 +1,user959,-0.6992563,0.3508499,-0.1698481,0.1295323,-0.8198742,-0.5471967,0.9988062,0.4515196,-0.8188021,0.4586435 +1,user960,-0.4993754,-0.3342554,-0.6379932,-0.3346912,0.716093,0.6350397,0.2436672,0.6472541,-0.9909441,-0.2091594 +1,user961,-0.7431438,-0.5994335,0.2251816,0.6275727,-0.2352555,-0.3557103,-0.607935,0.8260809,-0.9174879,-0.04112525 +1,user962,0.3392093,-0.004086902,-0.2724833,0.4488096,0.4741534,0.200864,-0.1426094,0.3160477,0.5910736,-0.1823406 +1,user963,-0.3094869,0.7225092,-0.4530433,0.9710965,-0.1631418,0.1400285,-0.5769903,-0.1258737,-0.1172675,0.2255873 +1,user964,0.5073435,-0.2942515,-0.7371235,-0.5296848,-0.1162656,0.7787048,0.2539473,0.4331712,0.659557,0.695568 +1,user965,0.04774133,0.7724747,-0.5502982,-0.7857189,-0.6762112,0.3280662,0.3790647,0.9810886,0.2530839,-0.9456165 +1,user966,-0.3542037,0.7662421,-0.5938699,0.8458698,0.325174,0.8510565,-0.7835039,-0.2469723,-0.5449377,0.388847 +1,user967,-0.3021412,0.2227809,-0.51812,-0.913468,-0.1377294,0.4117205,0.9151752,-0.7965264,0.177397,0.9638138 +1,user968,-0.9350814,-0.68565,0.1875221,-0.7632561,0.1124294,0.9819561,-0.562629,0.1722314,0.2705259,-0.9514787 +1,user969,0.9620063,0.2881165,-0.1894999,0.8967482,0.7542861,0.5462033,0.3394002,0.1503233,0.1915654,0.4244444 +1,user970,0.6623804,-0.8351269,-0.3572359,0.7771639,-0.567678,-0.1951389,0.7259306,0.8466739,-0.2256025,0.05785325 +1,user971,0.5888339,0.9625136,-0.1507972,-0.8346417,-0.1041411,0.6532668,0.7862694,0.1540639,-0.7901987,-0.8950598 +1,user972,0.002049645,0.4522836,-0.3185436,-0.5561056,0.4133153,-0.06867528,0.9749629,0.7355077,-0.9749926,-0.6737737 +1,user973,-0.3728401,0.1762898,-0.8791766,-0.1783447,0.671852,-0.4004858,0.05000221,0.845655,0.5185776,0.6656567 +1,user974,-0.3878815,0.7386644,-0.8097896,0.5272254,0.6143424,-0.1827822,0.6912896,-0.1526827,0.08411417,-0.7250866 +1,user975,-0.238696,-0.6469028,0.3263965,-0.04896816,-0.07731512,0.8907395,0.4313733,0.5721298,0.7827929,0.380845 +1,user976,-0.3793914,-0.3454743,-0.5912418,0.7861963,-0.1013021,0.8694821,-0.2696792,0.7736912,0.945445,-0.09611863 +1,user977,-0.6980762,0.01859751,0.9009532,0.5815459,0.3314188,-0.4747509,-0.6628518,0.9266988,-0.9852564,-0.4406701 +1,user978,-0.5332325,-0.1223769,0.1019885,-0.008094805,-0.8657365,-0.8649157,0.09095884,-0.2937562,0.877058,0.4452237 +1,user979,0.6993437,0.3173918,-0.575917,0.693948,0.845348,-0.2480709,-0.6923508,-0.2686991,0.511811,-0.9552703 +1,user980,0.6669128,-0.3357364,0.2197302,-0.7950619,-0.2051436,0.6524335,-0.07015182,0.2080319,-0.3465065,-0.329788 +1,user981,-0.1829391,0.7991846,0.848217,-0.140835,-0.3156437,-0.02874879,0.2690812,0.4403578,0.2194939,0.3862616 +1,user982,0.967804,0.6054886,-0.7591871,0.4004836,-0.670645,-0.3818012,-0.907068,-0.06547376,-0.04079423,0.9518071 +1,user983,-0.09930393,-0.000510179,-0.3247272,-0.01205284,0.771071,0.2156689,-0.4052735,0.4586496,-0.2164373,-0.07140322 +1,user984,-0.75319,-0.5330408,-0.510895,0.1871329,0.9188232,-0.9097838,0.3749162,-0.6964471,-0.3864142,-0.9860054 +1,user985,0.2977849,-0.7626294,-0.3341848,-0.1153707,0.9623191,0.03921168,0.9327167,0.4915298,-0.9439353,-0.7645512 +1,user986,-0.5425348,0.1471515,0.0679764,0.2285471,0.4025179,0.9829305,-0.4533315,-0.4743222,0.2225198,0.4501359 +1,user987,0.1261174,-0.9352773,0.228897,0.6553448,-0.03933869,-0.2132861,0.8178954,-0.05596473,-0.6727412,-0.3595355 +1,user988,-0.6694534,-0.9904938,-0.4662115,-0.3457296,0.515124,-0.1191804,0.7249955,-0.6531794,-0.9733896,-0.009628466 +1,user989,-0.6306362,0.830164,-0.4176497,-0.8438005,-0.5164186,-0.9559652,0.4724158,0.9490368,-0.4774292,0.287796 +1,user990,0.4268611,0.4155727,-0.9409511,-0.215123,0.1407871,0.2395171,0.8167016,-0.6044451,-0.4915433,-0.900892 +1,user991,-0.1688288,-0.3247492,-0.1042047,0.3195792,0.231217,-0.4841407,-0.03133732,0.9940746,-0.9643337,0.7812121 +1,user992,-0.37378,-0.7692695,0.8075319,0.7837722,0.2483259,-0.3116755,0.8644808,0.7751177,-0.3949171,-0.7533293 +1,user993,-0.2339296,-0.5885143,-0.2134344,-0.7663134,-0.3850595,-0.5596188,-0.3259078,0.7116026,-0.9004697,-0.08323263 +1,user994,0.5216843,-0.60224,0.442752,0.2906758,-0.9319248,0.6558877,0.3916724,-0.131799,-0.0816012,0.006799466 +1,user995,-0.8664365,-0.06352104,-0.9295916,-0.7459126,-0.8679397,-0.5329707,0.1184281,0.208289,-0.7353601,0.9422387 +1,user996,0.8138117,-0.8160395,0.2362674,-0.5520322,-0.06127078,0.7684473,-0.9468431,0.6926912,0.3526142,-0.02884912 +1,user997,-0.8325195,-0.8359979,0.8488821,0.1365455,0.3932492,0.5069443,0.6081685,0.6212286,0.3734611,-0.6043535 +1,user998,-0.1685776,-0.8407401,-0.4477116,-0.6593806,-0.005669117,0.8787498,0.03360324,0.4117625,0.4420369,0.9060525 +1,user999,0.8787303,-0.5016895,-0.5762105,-0.3152883,-0.9488414,0.7504034,-0.5094722,-0.1350774,-0.3768599,0.01967221 +1,user1000,-0.8705132,0.4521186,-0.3406178,0.03329378,0.1475353,0.05314754,-0.05243128,-0.2284481,-0.4349734,0.8200909 +1,user1001,-0.5061972,-0.675867,0.1950526,-0.8822167,0.4266529,-0.3163891,-0.2404662,0.2584364,-0.7835656,-0.0360942 +1,user1002,0.4675643,-0.5391759,0.2729923,-0.1499301,-0.0529824,0.4036702,-0.7232028,-0.9810135,-0.1670586,0.1246124 +1,user1003,0.1315364,-0.09559778,0.3408386,0.4771882,-0.4391494,0.9844723,-0.07746841,-0.4929403,-0.409966,-0.8536829 +1,user1004,0.1209627,0.5004227,0.315876,-0.06056138,0.09850492,0.283125,0.809536,0.1040914,0.7350119,-0.3704375 +1,user1005,-0.9203172,-0.8005116,0.4632027,-0.6227046,-0.43864,-0.779112,0.9680869,-0.1336962,0.9170555,0.3995258 +1,user1006,0.8928404,0.2574995,-0.3327649,-0.57178,0.4835355,0.8752118,-0.6460951,-0.9208105,-0.6271731,0.5271622 +1,user1007,0.7415713,-0.8450516,0.7246342,-0.2743651,0.9972029,0.1526071,-0.4601432,-0.1222174,0.6804569,0.5334439 +1,user1008,-0.6183934,0.2180859,0.3641558,0.9588413,0.8927787,-0.2538629,-0.6947649,-0.2069974,0.9317991,0.9588557 +1,user1009,-0.6403921,-0.8648774,0.7692235,0.4201252,0.617799,-0.989704,0.4448638,-0.2145667,-0.7501151,-0.02761412 +1,user1010,0.440915,0.4723402,-0.8512828,-0.5804171,0.8425509,0.9045362,-0.152494,0.6090835,0.1922679,0.5781736 +1,user1011,-0.9514806,0.8823495,-0.416114,-0.8362207,-0.3123649,-0.6014294,0.2350833,-0.9989655,-0.4147074,-0.3709322 +1,user1012,0.1766688,0.9343072,0.6174406,-0.7207098,-0.6978447,-0.01845274,-0.2860551,-0.7742089,0.4693788,-0.6413525 +1,user1013,0.408719,0.07782887,-0.6104699,0.8200665,-0.8280942,-0.477265,-0.05956197,-0.4563902,-0.8485264,0.5299806 +1,user1014,-0.05078454,-0.1181607,0.2591588,0.1517265,-0.5412939,0.6142394,0.8298097,0.4596842,0.3688553,0.5576646 +1,user1015,0.4234787,-0.5987336,-0.8934544,0.4664231,-0.7790215,0.0717635,-0.9111389,-0.470656,-0.9170354,-0.6273579 +1,user1016,-0.2934961,0.3151995,0.05534535,-0.2953042,-0.8657751,0.5619467,-0.1268452,-0.9648605,-0.7924617,0.7654294 +1,user1017,0.4066806,-0.9710092,-0.6728648,-0.6197264,0.861224,0.5971699,-0.6235218,0.985362,-0.4086249,0.007800412 +1,user1018,-0.4504039,-0.5340109,0.3354426,0.1217678,0.1816398,0.8584774,0.9067565,0.4733793,-0.5897765,0.01310657 +1,user1019,0.03705047,0.3247057,0.5891338,0.3589662,0.6493489,-0.5572338,-0.4018498,-0.6180399,-0.7658513,-0.2441991 +1,user1020,0.7760445,0.8591548,-0.09051446,-0.4635269,-0.6551946,0.6412047,0.848894,0.9343988,0.1139459,-0.7044036 +1,user1021,0.9764572,0.8815618,0.3944914,0.9066448,-0.6775731,0.09799454,0.7234581,0.8689342,-0.08131984,0.1122146 +1,user1022,0.8682216,0.9999566,-0.5150709,-0.3214545,-0.1194341,-0.04137449,0.5668129,-0.6239652,-0.730185,-0.4629869 +1,user1023,-0.5977355,-0.9101147,-0.2829825,-0.6797547,0.5931312,-0.6704708,0.7133748,0.7095165,0.7190288,-0.4577329 +1,user1024,-0.2574724,-0.7069525,-0.818943,-0.8596685,-0.06263261,0.5383757,-0.6024496,0.5805368,0.01821042,-0.9710181 +1,user1025,0.3899059,-0.6022835,0.9276811,0.9692213,-0.05135888,-0.3854867,-0.04151464,0.2442357,0.1882138,0.5438125 +1,user1026,-0.4641719,0.02636428,-0.2125741,-0.4256672,0.7251915,-0.2034415,-0.1681972,-0.08219455,0.9836688,-0.5154941 +1,user1027,-0.4436607,-0.522992,0.4173244,-0.4117008,0.8760966,0.306823,-0.5492928,0.2732279,-0.6291754,0.0001328164 +1,user1028,0.5573864,-0.4382814,0.7765632,0.1057668,-0.6581097,-0.8785425,-0.4333461,-0.1345356,-0.4383251,0.939459 +1,user1029,0.3672504,0.1856242,0.3397144,-0.0850478,-0.2804776,-0.3246917,0.8654061,-0.670432,0.4257057,-0.6094416 +1,user1030,-0.5649304,-0.02468148,0.8411139,0.2730109,0.9272553,0.05722649,-0.0587649,-0.8618495,-0.006035297,-0.980195 +1,user1031,0.6868732,-0.9861627,-0.5640546,-0.8609394,0.4894256,0.1746051,0.5142226,0.6370163,0.1267015,0.7595498 +1,user1032,0.8610532,0.5097571,-0.465233,0.03273553,-0.8538247,0.3589191,-0.3750601,0.5880044,0.64214,0.3544642 +1,user1033,0.9026339,0.4361426,0.1141062,-0.8769192,-0.1257271,-0.5391033,0.2180323,-0.842863,0.8269061,0.1444174 +1,user1034,-0.1815904,-0.08176049,0.776784,0.6162488,-0.9497238,0.1590773,-0.5632458,-0.855924,0.7167354,0.905867 +1,user1035,-0.01798407,0.01017988,0.8506429,0.9721742,0.2446802,-0.3579559,-0.5655241,-0.3079042,0.377152,0.9840267 +1,user1036,0.9823166,0.635631,-0.4226911,-0.4996238,0.4356328,-0.3182153,0.1861192,0.0234408,0.7439616,-0.4560567 +1,user1037,-0.28875,-0.824261,-0.5559809,-0.9555312,0.5338117,0.03428912,-0.2093409,-0.7767345,-0.9104377,0.4330291 +1,user1038,-0.2764128,0.1651283,0.5752771,-0.3021909,0.2418831,0.7946513,-0.02566724,0.5698784,0.05760888,0.5174705 +1,user1039,-0.6360768,-0.1462831,0.9414647,-0.5407826,0.3284116,0.4279218,0.4913543,0.8164434,0.6757607,-0.497201 +1,user1040,0.07085787,-0.6891385,-0.7867574,0.464594,0.1516107,0.04458516,-0.7644771,0.008698773,-0.6605528,-0.594585 +1,user1041,-0.8354978,-0.3625315,0.7239944,0.117392,0.08443392,0.6991875,0.8218388,0.1789619,-0.7501233,0.09564411 +1,user1042,-0.5875574,-0.2639336,-0.4746493,-0.3770033,-0.9839533,0.8264924,-0.2735625,0.817478,-0.7389467,0.1318668 +1,user1043,-0.7524734,-0.7548312,0.8306832,0.7438842,0.4537659,-0.9738676,-0.05053222,0.2344899,0.808826,-0.2359375 +1,user1044,0.5732213,0.7152974,-0.8864755,-0.06254158,0.2563398,-0.7780775,-0.2377232,0.7225717,-0.5986496,-0.3743753 +1,user1045,0.3616581,0.6179057,0.7845095,0.7747232,-0.5252472,0.4407318,-0.4437528,0.2771621,0.6299086,-0.3104687 +1,user1046,0.6710053,-0.3535648,0.9372288,0.2103073,0.6747445,0.09789592,0.03832891,0.7638339,0.8917907,0.1367046 +1,user1047,-0.7202748,0.0304969,0.1688699,0.6421542,0.3905647,0.7838691,0.6354316,0.7577112,-0.3911113,-0.6089459 +1,user1048,-0.2316613,0.6468966,-0.8883553,-0.8450032,-0.6640233,0.03790175,-0.06727458,0.2625241,-0.7787164,0.6973318 +1,user1049,-0.7793985,0.1124243,0.2726714,-0.6679249,-0.1436157,-0.04362669,-0.05491455,0.2372131,-0.6979859,-0.8501888 +1,user1050,0.3167757,-0.6447974,-0.2419963,0.001120441,0.03991361,-0.7733647,-0.7664182,-0.8603287,-0.1569626,0.1468551 +1,user1051,-0.4556168,0.5060514,0.02113027,-0.3085301,-0.3192179,-0.3208935,-0.2183806,0.1969229,0.3352295,0.9929282 +1,user1052,-0.8029413,-0.006013954,-0.3328372,-0.7612801,0.1788112,-0.9456321,-0.3314564,0.1061473,0.2206943,0.2620257 +1,user1053,0.1849973,-0.6448408,0.2429328,0.6796659,0.9204795,0.1852609,0.8003948,-0.4842939,0.1128523,0.6838681 +1,user1054,-0.05335229,0.5959367,0.7381478,0.01171528,-0.7260867,0.008635703,-0.5050059,-0.09356061,0.05425837,-0.4648047 +1,user1055,-0.06041376,0.2870336,-0.1517801,-0.6209486,-0.8838214,0.5927436,0.06609393,-0.313316,-0.7610953,0.2910077 +1,user1056,-0.4250968,-0.2471243,0.1706139,0.6488872,-0.1308794,0.7997741,-0.2411199,0.7599418,-0.6989339,0.2276806 +1,user1057,0.4824758,-0.377699,-0.4744263,0.586048,0.9991048,0.8051942,0.326797,0.8242448,0.03792713,0.01970113 +1,user1058,0.4959255,0.7640416,-0.7344557,-0.03264943,0.9922752,-0.1004334,0.5168012,0.9599119,-0.3902707,-0.7088595 +1,user1059,-0.8677104,0.3145944,-0.05282285,-0.245346,0.2110109,0.9212317,0.325534,-0.3745938,-0.137259,0.1671396 +1,user1060,-0.1502738,0.8079252,0.8652881,-0.4989998,-0.2813728,-0.5194976,0.192203,-0.8461872,-0.5363672,0.4102595 +1,user1061,0.9309951,-0.2606399,-0.8933418,-0.7596385,0.9195305,0.9567931,-0.5419637,-0.9019376,0.603694,-0.6890545 +1,user1062,0.8191628,0.3284317,0.3831225,-0.1062855,-0.2995635,0.09583674,-0.1602434,-0.7375775,0.9894424,-0.07331058 +1,user1063,-0.2892206,0.3176823,-0.599945,0.5337358,-0.1351975,0.8394216,0.8171429,0.7418172,-0.8942271,-0.2352763 +1,user1064,0.833629,-0.8244973,0.2207645,-0.6365577,-0.2061967,-0.5823102,0.6760686,-0.7448006,0.4306001,0.455363 +1,user1065,-0.3624276,-0.7533288,0.1599065,-0.4900367,-0.2492873,-0.7450859,0.2765108,-0.5935016,0.7061779,-0.1674436 +1,user1066,0.6927954,-0.6721378,-0.749302,0.5059099,-0.8905173,-0.5185343,-0.7483812,-0.566087,0.4829248,-0.2512496 +1,user1067,0.8159456,0.8111337,0.7980734,-0.1361815,-0.7705638,0.09947447,-0.1378122,0.2786402,0.1745617,0.9993062 +1,user1068,0.3488224,-0.5775899,0.6039256,-0.4455679,-0.7154756,0.2892032,-0.9328301,-0.3702361,0.7957402,-0.7344145 +1,user1069,-0.5836174,0.4929905,0.8259751,-0.796281,0.3513657,-0.723883,0.2259516,-0.9962086,-0.4594663,-0.733779 +1,user1070,-0.8201311,-0.3351494,0.7395381,0.3230359,0.5578477,-0.4726037,-0.6464579,0.09508368,-0.1496777,-0.4978947 +1,user1071,-0.5803197,-0.2667283,0.8171682,-0.9809739,0.436135,-0.6662116,-0.6973072,0.6384627,-0.8648126,-0.3289995 +1,user1072,-0.4191152,-0.869541,0.5499695,0.3211109,-0.5642004,0.9753045,0.04779037,0.1827534,-0.2095895,0.3618651 +1,user1073,-0.4076885,0.400917,-0.7351112,0.9460326,0.5738944,-0.6461114,0.07997957,-0.08743836,0.1113756,0.633972 +1,user1074,-0.3327931,-0.02155957,0.6478514,0.7629103,-0.110099,-0.6400792,0.2521605,-0.1270474,0.9440135,0.435063 +1,user1075,-0.8458939,0.8457564,0.663494,-0.7414306,0.6921394,-0.8027731,0.8100672,-0.09467496,0.1917608,0.9874898 +1,user1076,0.9539696,0.01882273,-0.9506016,0.7207558,-0.9513528,0.7946204,0.6362268,-0.8102762,-0.2587158,-0.6764966 +1,user1077,-0.6617878,0.6248756,0.5850802,-0.02678242,-0.4353546,0.4578167,-0.7095106,-0.3632136,0.8358041,-0.4282324 +1,user1078,-0.5661687,-0.1237467,-0.1676361,0.9007236,0.08270407,0.9810961,0.4454988,-0.3369637,0.8006495,-0.6214561 +1,user1079,-0.2776917,-0.3342807,-0.8389569,0.8757527,-0.6153761,-0.1674778,-0.4310478,0.4522479,-0.03743219,-0.9791649 +1,user1080,-0.4411863,-0.2627001,-0.1422485,0.3052927,0.4210297,-0.58581,0.2355749,0.8739995,-0.8621817,-0.2784212 +1,user1081,0.7506069,0.2314559,0.5903676,-0.09815599,-0.8773823,-0.7922686,0.6790806,-0.1972924,-0.3563131,0.525399 +1,user1082,0.2666915,-0.8282293,0.1821733,-0.4327774,0.06540597,0.5116287,0.3505716,-0.3508292,-0.7022026,-0.9862367 +1,user1083,-0.2441276,0.7312859,0.5249144,0.5440126,-0.400159,-0.5314421,0.9041185,-0.0198532,0.3585126,0.9836045 +1,user1084,-0.06439577,0.5866151,-0.1666996,-0.4184901,-0.9569028,0.3929923,0.4794753,0.3184137,0.7565392,0.2092671 +1,user1085,-0.7866608,0.7677074,-0.0796789,0.5789379,0.3393193,-0.4797356,0.8455657,0.5556102,0.3520557,-0.4510414 +1,user1086,0.6954586,0.01831947,-0.6268658,0.9230639,-0.2839804,-0.9386986,-0.02978759,0.6668308,0.5974173,0.2746122 +1,user1087,0.5105074,-0.6605092,-0.9960857,-0.7696029,-0.08778216,0.1927664,-0.7616446,0.07835547,-0.9423947,-0.5630523 +1,user1088,0.6958149,-0.6099916,0.4458948,0.1649859,0.3384241,-0.6745414,0.1723627,0.379855,-0.6100171,0.5686597 +1,user1089,0.1913841,-0.217639,-0.3613215,-0.1095855,-0.2917052,-0.03913197,-0.5129864,0.6267428,-0.7928534,0.5657527 +1,user1090,0.642797,0.6540852,-0.04890852,-0.01494895,-0.8767712,0.113998,0.5638894,0.7037616,-0.07965372,0.6040873 +1,user1091,-0.4544589,-0.8020665,0.3111828,0.6659862,-0.9429487,-0.194039,-0.6354343,0.5336678,-0.1463843,-0.02108075 +1,user1092,0.1223792,0.5217211,-0.2546632,0.130776,-0.3721747,-0.08233889,-0.05495013,0.7248052,0.8108406,0.8766982 +1,user1093,0.4619598,-0.01748314,-0.665786,0.8787656,-0.1763347,-0.7901652,-0.596354,0.9661841,-0.09021128,-0.4692233 +1,user1094,0.2563206,0.5156158,0.7112378,0.1997219,-0.07814629,-0.3546174,-0.8182913,0.2754851,-0.04061149,0.743643 +1,user1095,-0.04399176,0.6972238,0.9661013,0.4942182,0.4216286,0.3353509,-0.3788815,0.9800046,0.2414407,0.3320612 +1,user1096,-0.9004679,0.229188,0.4941205,-0.6112711,0.5743779,-0.5352511,0.6801568,-0.6273175,-0.3840334,0.3633331 +1,user1097,-0.05088406,0.843478,0.9619358,-0.2943682,0.03133637,0.1268483,-0.5666725,0.7093981,-0.5576866,-0.5076066 +1,user1098,-0.2280461,0.5083575,0.7641746,-0.6419633,0.6510648,-0.5651746,0.4833063,0.2586449,-0.5839976,0.3313674 +1,user1099,0.4483545,0.6515981,0.09804611,-0.05683903,0.8589023,0.7539521,0.7473267,0.002446448,-0.5882932,0.6289187 +1,user1100,0.3654985,0.3364684,0.7879109,-0.09064917,-0.6172979,0.4029653,0.6592791,0.7131896,-0.01715294,-0.2413856 +1,user1101,-0.04817728,-0.8267919,0.5037127,0.6810726,0.2089125,-0.03777836,0.8368483,-0.6462714,0.2663247,0.8334727 +1,user1102,0.8680348,-0.6151302,-0.08478571,-0.03781293,0.2950373,-0.9122596,-0.9499805,-0.3590909,-0.4531058,-0.7000808 +1,user1103,0.9463833,0.4669275,0.3378804,-0.7695382,-0.1814983,0.3782697,-0.2929305,-0.1040571,0.7732575,-0.8795205 +1,user1104,0.5441342,0.5741251,0.7686015,0.6271052,-0.217193,0.3161103,-0.08317208,0.2662902,-0.6222996,0.4674447 +1,user1105,-0.4647583,0.3633103,-0.4369344,-0.2749026,-0.8150617,-0.5523388,0.30218,0.5138617,-0.5090923,0.7349822 +1,user1106,-0.8995106,0.3126839,0.001374381,-0.5109689,-0.4893589,0.5754966,-0.4828634,0.801268,-0.03498165,-0.8920307 +1,user1107,0.4981037,-0.4070522,0.8179999,0.347861,-0.1685458,0.1107307,-0.4469453,0.456014,0.1189845,0.7909481 +1,user1108,-0.1265461,-0.01181417,-0.8518542,0.698315,-0.2504163,0.9054779,0.5926695,-0.8493519,-0.6732882,-0.6932502 +1,user1109,-0.4656793,-0.8110628,0.8337382,-0.6102453,0.5933452,0.5565927,0.9626354,-0.5356958,-0.2343321,-0.5134867 +1,user1110,-0.779588,0.2586671,0.979043,0.2236137,0.2160781,0.9432529,0.1220069,-0.09173815,-0.9184476,0.8117832 +1,user1111,0.4322676,0.7254857,0.005897336,0.003607635,-0.8293865,-0.6803321,-0.1717556,-0.9753524,-0.5354699,0.02832861 +1,user1112,-0.7150724,0.4203931,0.4241058,0.2915987,0.7159628,0.7643241,0.641716,0.2670118,0.4093547,-0.9880877 +1,user1113,0.4871035,0.4304378,0.1612163,0.7908363,-0.718516,0.4548816,-0.5274215,0.5574326,-0.6206503,0.8255465 +1,user1114,-0.81186,0.4567716,-0.4691883,-0.4523798,-0.2295455,-0.2117742,-0.2676372,0.004794411,0.8230426,0.01193314 +1,user1115,0.2205318,0.007008188,-0.7425938,0.8731086,0.7590601,0.1573164,0.1211913,-0.4145745,0.1658939,0.2211794 +1,user1116,0.7004426,0.1981451,-0.9184626,0.3697741,0.6208033,0.975146,-0.6818557,0.1130428,0.7314054,-0.6254949 +1,user1117,0.8835986,-0.524909,-0.09605408,-0.5293159,0.4864741,-0.1504727,0.7025752,-0.3283748,0.4204599,-0.7134546 +1,user1118,-0.2689607,0.346499,-0.7386795,-0.8964943,-0.3287221,-0.6499173,0.3595467,0.6637809,0.2234992,0.6581271 +1,user1119,0.3962576,0.5881535,0.5274322,-0.46524,-0.04077264,-0.6993954,0.490507,-0.5071022,-0.8786117,0.9431648 +1,user1120,0.07498269,0.2574521,0.5426245,0.3610986,-0.8052311,0.8103953,-0.8104112,-0.701632,0.6276066,0.8522981 +1,user1121,-0.6261637,0.0005842182,0.212412,0.08855677,-0.2054933,0.4640808,-0.07656384,0.3675426,-0.8561545,0.2622144 +1,user1122,0.9417987,0.786087,-0.161385,-0.7992538,0.01627861,0.1065656,0.8550727,-0.9734343,-0.02499603,-0.07791594 +1,user1123,-0.8026381,-0.2208268,-0.7120388,-0.5081254,-0.1774058,-0.2719436,0.1346387,-0.9768268,0.4384472,0.7289963 +1,user1124,0.835796,0.9831011,0.546626,-0.03267764,0.6181719,0.6739155,0.3270822,0.3337267,0.05363423,0.7929912 +1,user1125,0.1981193,0.3017028,-0.4501471,0.4004681,0.9381323,0.7519482,-0.9632187,0.3020507,0.9343925,-0.3342729 +1,user1126,0.1533702,-0.5236029,-0.7459375,0.9860928,-0.7557772,-0.9365927,0.7557572,-0.9968221,-0.3201121,0.06105747 +1,user1127,0.9353282,0.2122891,0.04074655,0.3560513,0.1925498,-0.8613356,0.007239026,0.7064092,0.6696008,0.1563243 +1,user1128,-0.8527648,0.1451808,-0.4882113,-0.8939,-0.03053131,-0.1212036,-0.5298912,0.01144888,-0.6232942,0.1581205 +1,user1129,0.925324,0.9847546,-0.9817629,-0.6558705,0.8952876,-0.5017673,0.2390635,0.2618227,0.09589034,-0.6075751 +1,user1130,0.3836827,-0.1361128,-0.8612073,-0.7007878,0.05145213,0.8926164,-0.2454342,-0.2911443,-0.9186924,-0.214757 +1,user1131,0.5127338,-0.5183508,-0.7003004,0.0154508,0.3521708,-0.7182383,-0.8706121,-0.2753615,0.3595529,0.9167349 +1,user1132,-0.1228533,-0.8420374,0.5219498,-0.9747979,0.1042001,0.4604543,0.07591182,0.6155513,-0.6377849,-0.7741024 +1,user1133,0.2517175,0.2487571,0.05400694,0.2613993,-0.6535105,0.9803569,-0.1954148,0.3497648,-0.3717982,0.08516217 +1,user1134,0.4591171,0.9485767,0.63758,0.2459126,-0.8293275,0.6600314,-0.1635426,0.6205814,0.1328104,-0.9627856 +1,user1135,-0.5787191,0.7320877,0.2905513,0.6523073,0.8870071,-0.2234354,0.9927397,-0.1181585,-0.2600846,0.6933423 +1,user1136,0.7869592,-0.3879327,0.6170726,0.9864967,-0.4685722,-0.5719819,-0.8932347,-0.1363735,0.1191094,-0.1798556 +1,user1137,0.5596065,0.2612605,-0.3610457,0.7349437,-0.3186864,0.235528,0.353594,0.4218494,-0.9021712,-0.8548163 +1,user1138,0.9193847,-0.6749645,0.1085512,0.0001682756,-0.2815388,0.8872953,-0.4542056,-0.6621445,0.8589,0.4842904 +1,user1139,-0.3395869,0.6002531,0.7652184,0.6848117,0.2810115,-0.666504,0.6994347,0.01427456,0.4458212,0.1268942 +1,user1140,-0.9060728,0.4501977,-0.5273074,-0.8753016,-0.7253413,-0.2078793,0.3162294,0.8861536,-0.1365034,-0.368303 +1,user1141,-0.8602033,0.5837025,0.08759416,-0.7762181,0.9345393,0.8305482,0.6678014,0.2461173,0.9404523,0.2960736 +1,user1142,-0.9073193,0.3257388,-0.2288843,-0.3115807,0.451625,-0.3468361,-0.4723209,0.03892217,0.9103513,-0.8447772 +1,user1143,-0.6211452,-0.1294092,0.8967984,0.4162971,0.9906216,-0.4435552,-0.04205464,0.1531654,-0.7271486,-0.3563907 +1,user1144,0.6269002,0.0141403,-0.7511895,-0.9853818,-0.7839767,0.2854298,-0.8596201,-0.1964501,-0.6801979,0.1216202 +1,user1145,-0.7191793,-0.2174896,0.3019274,0.2360395,-0.7779206,0.4413898,0.260042,-0.9562834,0.7333939,0.1671559 +1,user1146,0.5993866,0.877599,-0.8457954,0.2894057,0.7496816,0.7137612,-0.9208634,0.7385908,0.4387453,0.8647887 +1,user1147,0.3273428,-0.7877146,-0.6696521,0.3843923,0.8368266,0.2605758,-0.5414759,0.9165927,-0.9487925,0.4961253 +1,user1148,-0.8355807,0.2576015,-0.7941267,0.7067236,0.7085535,-0.709083,-0.0373828,-0.2846582,0.1538539,0.4537013 +1,user1149,-0.6695741,0.224098,-0.5844749,0.3929114,-0.5790405,-0.9361561,0.4386834,0.4023718,-0.3377555,0.5229158 +1,user1150,-0.2763996,0.8004389,0.85778,0.9191524,-0.203946,0.5611804,0.9490311,-0.5905095,-0.8274042,0.4392901 +1,user1151,0.239402,-0.4849464,0.7484978,0.06782225,0.9033224,-0.8986877,0.152206,0.01370983,-0.2185396,0.3059993 +1,user1152,-0.2957379,-0.7753178,0.6279371,-0.5185318,0.2154662,0.5279247,-0.6378805,-0.2300857,-0.19391,-0.2148697 +1,user1153,-0.3346009,0.5865259,-0.3036049,-0.8801014,0.8123326,-0.332254,0.8041038,-0.5639438,0.1475998,-0.6386258 +1,user1154,0.4367639,0.2942268,-0.963541,0.5596968,-0.2740835,-0.1706313,-0.7131553,0.03688304,-0.7800924,0.03499562 +1,user1155,-0.4599419,-0.7922167,0.1745631,0.4487906,-0.1663619,0.2018402,0.6892017,-0.896359,0.8597243,-0.4218785 +1,user1156,0.8635184,-0.1117713,0.2462479,0.5203667,0.7504649,-0.5803059,0.8408851,0.7381069,0.08199227,0.02710121 +1,user1157,-0.409866,0.7706239,-0.7094785,0.5457897,-0.02986069,-0.107224,-0.9573981,0.0400609,-0.1002044,-0.9039469 +1,user1158,-0.5246137,0.4200724,-0.7846903,-0.1951582,-0.9738121,0.3405046,-0.3035593,0.8100502,0.5293251,0.7344458 +1,user1159,-0.9892464,-0.9665905,0.7580366,0.6264667,-0.2800664,0.2984905,-0.689006,-0.2504442,0.4586981,-0.8147783 +1,user1160,-0.4845419,0.7553784,-0.6912413,0.8899192,-0.1345731,0.3910087,0.2816654,-0.6981164,0.9956859,-0.511522 +1,user1161,0.859069,-0.7160404,-0.6458977,0.104054,0.07764004,0.2331211,0.4510065,-0.4810941,0.6106326,-0.4803112 +1,user1162,0.5234874,-0.4849413,-0.9422638,-0.3580825,-0.9278956,0.5802522,-0.5596181,0.4741943,-0.181749,-0.8980434 +1,user1163,0.3926048,0.913341,0.8307085,0.9151212,0.969627,-0.148537,-0.6424228,0.917435,-0.642099,-0.2856245 +1,user1164,0.1107865,0.5327167,0.4081093,-0.6345467,0.4241295,0.2134779,-0.7444083,0.8686706,-0.7611656,0.604851 +1,user1165,-0.01739551,-0.5363646,0.6953161,0.8878301,-0.7572231,0.2402836,0.2768393,0.09477566,0.9510614,-0.860829 +1,user1166,0.8138857,0.6454287,0.1212598,0.5674285,0.8566341,0.6280276,-0.649683,-0.2007235,0.09781644,-0.5922822 +1,user1167,-0.1022543,-0.855216,0.02518186,-0.64805,0.9555573,0.641496,-0.637643,-0.2677029,0.3579438,-0.5750047 +1,user1168,-0.457789,0.7248959,-0.6657295,0.6227737,-0.07590958,-0.5241883,-0.3695667,-0.483375,-0.9511098,-0.7156453 +1,user1169,0.7332704,0.9704642,-0.770189,-0.4324032,-0.4249047,0.5153229,-0.1038886,0.1371319,-0.04328358,0.8920082 +1,user1170,0.5581588,0.7450371,-0.2095998,-0.9632383,0.2365687,0.974992,-0.9382082,0.7465717,-0.196235,0.5518895 +1,user1171,-0.3638618,0.1750936,-0.193037,0.7474721,0.1987491,0.2679324,0.9466627,-0.5972214,-0.0876132,-0.08394823 +1,user1172,0.8730671,0.5541667,0.3174052,-0.2086213,-0.4903654,0.3458712,-0.4360872,-0.6167508,-0.1028312,0.1880818 +1,user1173,0.6508395,0.07077592,0.561516,-0.2748191,-0.3118063,-0.3718441,-0.4105291,-0.2145062,-0.2858837,0.7071123 +1,user1174,0.01499294,-0.9543156,-0.2962386,0.1637692,0.1893707,0.8243773,-0.0953919,0.555944,0.1852382,0.5596611 +1,user1175,0.4999673,-0.431693,0.5662156,-0.1940031,-0.2743421,-0.368699,-0.2957073,0.1867992,0.2169708,-0.690298 +1,user1176,0.9316602,0.8532863,-0.1365566,0.9612204,-0.08972681,-0.9304543,0.8495128,-0.1707896,-0.5524898,-0.1257318 +1,user1177,-0.3856204,0.9232833,-0.142034,-0.5468251,-0.06094769,0.5381385,-0.01625526,0.2945348,-0.3760166,0.4244498 +1,user1178,-0.1726899,-0.2194076,0.8965635,-0.8096107,-0.4375154,0.8918768,0.1628168,0.1033919,0.2681783,0.8058272 +1,user1179,-0.9039205,0.1108878,0.0693167,0.667944,-0.3811733,-0.6395372,-0.18787,0.5445523,0.6013641,-0.6720305 +1,user1180,-0.05519457,0.1473813,0.2734911,0.8460863,0.3600118,0.6019824,-0.5775719,-0.3030935,0.286228,-0.0526344 +1,user1181,0.5509104,-0.4189686,0.7543435,-0.8904583,0.3585386,0.4530572,0.1118479,0.5128825,0.4407741,0.2451173 +1,user1182,0.3354814,0.6259413,-0.1821855,-0.2642337,-0.4778509,-0.5382249,0.9643361,-0.4417379,-0.6171755,0.6339688 +1,user1183,0.6490675,0.3720635,-0.09857173,-0.6724455,-0.424522,0.1299071,-0.2154524,0.4668209,-0.907682,0.7324959 +1,user1184,-0.7836905,-0.8324427,-0.5492614,-0.7705598,0.1708712,-0.8791969,-0.08404825,0.9489386,-0.4116261,0.6064915 +1,user1185,-0.2277547,-0.07983186,-0.1457265,-0.7045369,0.2480656,0.2911438,-0.7488192,0.5951451,-0.3972679,-0.3310356 +1,user1186,-0.8108743,0.5798468,-0.9240086,0.7763451,0.4091161,-0.6682527,-0.5262507,0.5704619,0.9520423,-0.6893827 +1,user1187,-0.9201721,0.05578605,0.6969865,0.749807,-0.07866392,-0.4595028,-0.2431631,0.6870456,0.6703662,-0.3664073 +1,user1188,0.3623793,-0.309208,0.144795,0.8412528,-0.7817951,-0.8160802,-0.7062173,-0.364794,0.5025277,-0.2349825 +1,user1189,-0.335488,-8.086441e-05,-0.7086989,-0.4188131,0.435304,0.6722519,0.1701901,0.3805121,0.4813673,-0.9549369 +1,user1190,-0.9094185,0.08919556,0.4550232,0.3762737,0.6412697,0.8389878,0.06783088,-0.5633986,0.1290643,-0.1811856 +1,user1191,0.8778374,-0.5538296,0.4535537,0.731172,0.08363177,0.5749285,0.5754482,-0.06291033,0.4982136,0.2534955 +1,user1192,-0.476419,0.2838787,-0.3545965,0.6852409,-0.487056,-0.09462701,-0.3788034,0.8994179,0.09199998,-0.4352481 +1,user1193,0.6140689,0.6042543,0.5127593,-0.9818088,0.7133741,0.41924,0.5082128,0.9107957,0.9473153,-0.07922902 +1,user1194,0.2704422,-0.6404885,0.2842622,0.6462932,0.05325878,-0.5736085,0.9330254,-0.1454754,0.8561146,0.967871 +1,user1195,0.6343675,-0.1834046,-0.9464873,-0.9493058,0.9370735,-0.8811491,-0.1232117,0.7680886,0.3308344,-0.8303971 +1,user1196,-0.4033266,-0.9321103,0.2080755,0.9060212,0.956151,-0.3404763,-0.2149479,0.005571334,0.8983767,0.05994198 +1,user1197,0.08432795,-0.9950598,-0.594478,0.2137217,-0.09010713,-0.9455808,-0.7166576,0.6538011,-0.04606896,-0.6244112 +1,user1198,-0.4678868,-0.03862066,0.0786946,-0.5973558,0.8926308,0.7603469,0.2391454,-0.4996143,-0.3112218,-0.4054018 +1,user1199,0.1388844,0.7927856,0.5423459,0.528795,-0.1197586,0.1353353,0.4154854,0.5221963,0.9472669,0.3442967 +1,user1200,-0.1824016,0.9754043,-0.364667,0.7813185,0.4849882,0.5697421,0.1794538,-0.209067,0.9106475,-0.732403 +1,user1201,-0.909728,-0.2935836,0.8690949,-0.5605941,0.1291995,0.7353389,0.3009371,-0.7530426,0.4925432,-0.8535123 +1,user1202,0.7750225,-0.03212073,-0.650691,0.2762671,-0.9210095,-0.5967322,0.3621481,0.9249749,-0.1403463,-0.7396515 +1,user1203,-0.3093345,0.5295711,0.9527382,-0.4273028,0.9946228,-0.08438675,0.7433666,0.1741822,-0.1921838,0.4556788 +1,user1204,0.7411115,0.7771924,0.4306108,0.1645868,0.8173933,-0.6365051,0.890408,0.03245121,-0.7933405,0.8535999 +1,user1205,-0.2099845,0.01356367,0.05307043,-0.5599637,0.2683612,-0.772355,-0.7332438,0.4809189,-0.9551082,0.8200096 +1,user1206,-0.8093673,-0.9021219,0.5189538,0.3786942,-0.2797192,0.5469142,-0.5523407,-0.6390186,-0.975213,0.7653807 +1,user1207,0.6727718,0.6304787,-0.7059458,0.1258073,-0.2723336,-0.5669594,0.7399208,0.8616616,-0.3458302,-0.2721319 +1,user1208,0.404395,-0.06315298,0.9110365,-0.1067888,-0.7925865,0.7657835,0.250501,-0.2245463,-0.3311247,0.2444594 +1,user1209,0.01794278,-0.1215295,0.4155173,0.5690834,0.2827653,0.438791,0.6104761,0.4643733,0.2929654,0.571208 +1,user1210,0.7688512,-0.2586335,0.3633709,-0.2062487,0.3464931,-0.2064966,-0.4479491,0.4062139,-0.7444661,0.05583755 +1,user1211,-0.6507995,-0.9157717,0.1845276,-0.2607025,0.5674253,0.3677659,0.6729291,0.4723602,0.9551033,-0.808175 +1,user1212,-0.4311468,0.4595019,0.1698608,0.6786251,-0.3586961,-0.1081518,-0.2776759,-0.02274423,-0.2662605,-0.1836747 +1,user1213,0.1043327,-0.6326922,-0.8188146,0.5295176,0.8686422,0.2552784,-0.4836131,0.964476,-0.3616416,-0.3101937 +1,user1214,0.998268,0.4562919,-0.9140441,0.06685202,-0.8570967,-0.502327,-0.5425233,-0.0608189,-0.9525787,0.9243208 +1,user1215,-0.2148373,0.6270592,0.6205994,0.9080653,0.812175,0.01265133,0.6382758,-0.07380559,0.3221134,-0.5771833 +1,user1216,0.876578,0.287476,0.0354589,0.8249808,0.1167078,-0.4535778,-0.2324323,0.5596211,0.2410905,0.3587707 +1,user1217,-0.8126063,0.03613863,-0.8380527,-0.1568029,0.5520194,-0.1705797,-0.06877395,-0.490357,0.9994635,-0.7650619 +1,user1218,-0.1350094,-0.3171547,0.317586,0.6578723,-0.2664889,0.5531486,-0.6048873,-0.38676,-0.007520383,0.05640942 +1,user1219,0.2389573,0.978268,-0.8197461,0.6662336,0.3349126,-0.269658,0.06135048,-0.8051729,-0.2563818,-0.8762118 +1,user1220,-0.1480943,-0.9639422,-0.5467516,0.424384,-0.01267665,-0.4983277,-0.8985839,0.890155,0.4808309,-0.7199987 +1,user1221,-0.04442791,0.7720408,-0.2273909,0.03414596,-0.6252192,0.3921364,0.4629436,0.0498414,-0.8784561,0.8752238 +1,user1222,0.1167947,-0.5755616,0.6338076,0.3974056,-0.5814556,-0.6947295,-0.3632014,0.1319168,-0.7581682,0.3772837 +1,user1223,0.3754867,0.3199365,0.09865187,0.1096249,0.5002674,0.4070452,-0.2773873,0.789573,-0.4271691,-0.1552468 +1,user1224,-0.430359,0.3762951,-0.7146315,0.05233712,-0.9118451,-0.1886236,-0.02884361,-0.03936292,-0.9311408,-0.2040052 +1,user1225,-0.6127631,-0.2160502,-0.08193018,0.04369881,0.4718032,-0.2683379,-0.430176,0.9864414,-0.9020536,0.3451547 +1,user1226,0.009854157,-0.8634681,0.1521646,0.1603191,0.4373409,0.5258962,0.599401,0.5576616,0.9036652,0.01435603 +1,user1227,0.1663144,0.4441848,0.493444,-0.04164166,-0.9556941,0.4709001,0.7562085,0.9662084,0.9672359,0.8559368 +1,user1228,0.4715649,-0.21111,0.3235918,-0.7425795,-0.6183039,-0.2139188,-0.1468336,0.6402425,0.05187747,0.7207435 +1,user1229,0.5419674,0.09791122,-0.7691408,0.5629634,0.3299717,0.2862431,-0.1614536,-0.9419527,-0.4075566,0.6089542 +1,user1230,-0.6948012,0.2369705,0.03578989,-0.5128467,-0.07545276,-0.3937646,0.1716939,0.4884048,0.9145028,0.2002335 +1,user1231,-0.7108368,-0.2357056,0.9589248,-0.961261,0.8666843,-0.6441767,-0.9673798,-0.5688245,-0.03747506,0.9883405 +1,user1232,0.6322394,0.8043277,-0.9000459,-0.9976307,-0.5408288,0.02158201,-0.8605165,-0.6949954,-0.9150133,0.7554419 +1,user1233,-0.9197787,-0.7951503,0.3850989,0.7634204,0.003537732,0.009503158,-0.466158,0.4133797,-0.2258435,0.460582 +1,user1234,-0.0201713,-0.7061346,0.911663,-0.3885637,0.8613071,0.2714366,0.7759868,0.6053577,0.7703412,0.4440192 +1,user1235,0.3733509,0.58152,0.5305649,0.1669561,-0.7234355,0.3850769,-0.9701085,0.3374558,-0.7083538,0.6090418 +1,user1236,-0.1297632,0.2184134,-0.5618307,-0.7965433,-0.7281011,0.2371482,-0.1994017,-0.1057014,-0.1809517,0.2805916 +1,user1237,0.1704614,-0.6082565,0.4306168,0.9901304,-0.4184122,-0.1816492,-0.7763539,0.9663391,0.7951282,0.2094 +1,user1238,0.04612266,0.2119987,0.8246191,-0.7072366,0.004230903,0.8181175,0.7698124,0.1991175,-0.05418405,-0.6630901 +1,user1239,-0.7253682,-0.8447396,-0.6507942,0.09666787,-0.5206876,0.002931701,-0.9489008,0.6697522,0.4879236,-0.4749491 +1,user1240,-0.8115958,0.270214,-0.1538659,0.5592139,0.8643532,-0.7428582,0.8341223,0.4307124,0.08809359,-0.2193921 +1,user1241,-0.1850261,0.9533652,0.18799,0.08651472,-0.649276,-0.3883792,-0.6781368,-0.3946686,0.2013498,0.3927474 +1,user1242,-0.3761678,-0.7605112,0.5337334,0.8359654,-0.9532623,-0.6293024,0.7240283,0.1421125,0.4430269,-0.2831241 +1,user1243,-0.2427426,-0.2702841,-0.9840051,0.2378389,-0.494343,0.14899,-0.4435537,-0.5920318,0.8218331,0.5969332 +1,user1244,0.9193065,-0.679327,0.3691754,-0.3839677,-0.7806338,0.8668993,-0.1617498,-0.4301927,0.8397082,-0.9174463 +1,user1245,-0.3778997,0.6957806,0.6196892,-0.0971826,-0.810359,-0.1316294,-0.818495,-0.9187064,0.4904481,-0.3588033 +1,user1246,0.5424201,-0.6432249,0.6365943,0.1459043,-0.6821679,-0.8383587,-0.8052778,0.3341626,0.1439465,-0.9802501 +1,user1247,0.7958845,0.608149,-0.5953657,-0.5589869,0.336074,-0.5866785,0.6058179,-0.8705716,0.08079869,0.4413245 +1,user1248,-0.1905061,-0.2680808,0.7816365,0.7460145,0.7416604,0.6977909,0.1127311,-0.4090635,0.4899117,-0.1238652 +1,user1249,-0.5925893,0.03962034,-0.04581966,-0.1962234,0.05134321,0.7147899,-0.4101651,0.9474026,-0.8635739,0.07615936 +1,user1250,0.03484183,0.5864169,-0.4151118,-0.8927533,-0.3290134,0.1436635,-0.3328316,-0.6757445,0.8244169,0.5651127 +1,user1251,0.6613996,-0.232023,-0.7651151,0.1703985,-0.2710162,-0.8005368,0.2141472,-0.5189084,-0.02925745,0.1561361 +1,user1252,0.3629827,-0.1883388,0.7267895,0.8379225,0.426124,0.1069263,-0.9472215,-0.002755989,-0.7420299,-0.04861684 +1,user1253,-0.8483635,-0.9891447,-0.7813041,0.5046523,0.08953103,0.448934,0.303967,0.4561723,-0.9337513,-0.05760365 +1,user1254,0.03688628,-0.9120865,0.3335368,-0.7199766,-0.7707489,0.6065084,0.9367599,-0.7293354,0.5435734,-0.9991107 +1,user1255,0.9326237,-0.8120437,-0.987842,-0.1097404,0.514279,0.9183027,0.02393488,0.9578811,-0.6731707,0.7473779 +1,user1256,-0.4611265,-0.2051948,0.1367657,-0.4516489,-0.4386658,-0.8194039,0.8737911,0.4426137,-0.8358048,-0.712449 +1,user1257,-0.9532596,-0.7755546,-0.5142986,0.4403425,0.666592,0.1324046,0.536161,0.8283261,0.4472386,0.0152453 +1,user1258,0.09893812,0.6321411,0.5056019,0.848618,0.5585848,0.3892027,-0.2198566,0.9240895,-0.7059348,0.6033147 +1,user1259,-0.9895616,0.5836952,-0.5396425,-0.1942284,-0.05696972,-0.03332271,-0.2730425,0.08285619,0.2160726,-0.9917055 +1,user1260,0.5887078,0.3223566,-0.2834394,0.003305867,-0.003436288,-0.5813523,-0.6252926,0.8863734,-0.9603179,-0.3758005 +1,user1261,0.4041369,-0.1308884,-0.4586082,-0.6642287,-0.5168679,0.9954381,0.9518373,0.4124943,-0.791432,-0.1964518 +1,user1262,-0.7003984,-0.6520105,-0.5807178,-0.1554893,-0.1902855,0.3225006,-0.2404223,0.5140317,-0.8214024,0.9966349 +1,user1263,0.2209472,0.1266843,-0.1834853,0.005675146,0.4557349,0.4402297,-0.4858091,-0.808622,-0.8753313,-0.6203587 +1,user1264,0.4843582,0.07396131,0.9264907,-0.9008083,0.4866698,0.004941282,-0.5143207,-0.1741261,-0.01727554,-0.7358698 +1,user1265,0.2794303,-0.3581451,-0.6690548,0.455947,-0.3289784,-0.4060629,-0.4644355,0.1193894,0.9489387,0.4406542 +1,user1266,-0.4057019,-0.2917957,-0.6529204,-0.8273687,0.7322994,-0.1746934,-0.4559176,0.5288338,-0.5836851,0.9886831 +1,user1267,-0.6454051,-0.7076253,-0.63534,-0.6973516,0.7585687,-0.7579105,0.2862776,0.7201725,0.8017728,0.5447217 +1,user1268,-0.5501083,0.03359843,0.761562,0.4460774,0.2526094,0.4122879,-0.2407894,0.08572848,0.744067,-0.3499459 +1,user1269,0.6404207,0.920203,-0.8283013,-0.5346053,-0.2634697,-0.356576,-0.6861052,-0.2720487,0.3621309,-0.674407 +1,user1270,-0.3707733,-0.5523649,-0.2861343,0.3993162,-0.7621189,0.2450212,0.3373768,0.3899247,0.2896964,-0.9302273 +1,user1271,-0.3617041,-0.6961876,-0.392304,0.005291233,0.1169626,0.6694298,-0.4066671,-0.4835591,-0.1678395,0.4306621 +1,user1272,-0.5446054,0.8735682,0.3596887,0.5519094,0.08725431,0.2550449,-0.364242,0.3332826,-0.4365193,0.7183404 +1,user1273,0.253059,-0.3128761,-0.7524009,0.2352816,-0.7153812,0.6157188,0.06140512,-0.4679628,-0.2672767,-0.2133515 +1,user1274,0.3955533,0.03352831,-0.3763091,-0.7568698,0.6226196,-0.1815802,0.1497792,-0.07559088,-0.3460064,0.02759525 +1,user1275,-0.6252989,-0.8057588,-0.271136,-0.8320583,0.3066205,0.1219441,0.4740082,0.9030899,-0.5968111,0.8008941 +1,user1276,0.8751592,-0.6170955,0.8672883,-0.861901,-0.5257401,-0.5159106,0.2429102,-0.3866692,-0.7768285,0.4278452 +1,user1277,-0.06202662,0.3903034,-0.7397147,0.3890344,0.9404517,-0.01993891,0.3445014,-0.7414283,0.7979402,0.04734518 +1,user1278,-0.8294143,0.8023902,0.1334983,-0.3910451,-0.3573055,0.5352656,0.07982608,-0.9674816,0.4839875,0.2422186 +1,user1279,-0.3153468,0.1148238,0.6489249,0.8841135,-0.7840797,-0.8181197,-0.6443588,0.2042673,0.7130831,-0.69602 +1,user1280,0.345384,-0.5700763,0.2144656,-0.807189,-0.008205103,-0.305149,0.9343363,-0.7940257,0.9343663,-0.8764955 +1,user1281,0.2054275,0.3888071,0.7183865,-0.2837984,0.3136811,-0.3210709,0.7469945,-0.6432261,0.3084044,-0.1926687 +1,user1282,-0.6539472,0.8828008,0.8838098,0.05451197,-0.05509598,-0.6186565,0.5697884,0.6853589,-0.3161743,0.4601161 +1,user1283,-0.2916332,0.2415849,-0.05874489,-0.9692665,-0.5820811,0.8017773,0.9871148,0.2032184,-0.8076636,0.07488769 +1,user1284,0.357064,0.3996625,0.9370823,-0.7791462,-0.5967879,-0.8721369,0.05096151,0.8129462,0.3746532,0.7497276 +1,user1285,0.3829391,0.9707143,0.2173466,0.3345353,0.1741552,0.987852,0.5065484,0.9560234,-0.7726009,0.4610054 +1,user1286,-0.3590095,0.4295412,-0.04658693,-0.07900685,0.9321979,0.7200799,0.01104967,0.1610994,-0.4808344,-0.1777344 +1,user1287,0.8959375,-0.8055324,0.07384802,-0.2307951,-0.03545364,-0.6915408,-0.07524742,0.2555599,0.5388483,-0.9627214 +1,user1288,0.4296795,-0.8048403,0.7030481,-0.2251222,-0.1592528,0.1202566,0.04270933,0.7843496,0.6746377,-0.5237493 +1,user1289,0.7399287,0.06168226,-0.540985,-0.2303889,0.4907827,0.1092827,0.791193,0.08518894,-0.1867692,-0.5744197 +1,user1290,0.9063759,0.7781628,0.5342055,0.5749766,0.9075766,0.2751365,0.6517101,-0.6615839,-0.245079,-0.9544269 +1,user1291,0.01838728,0.5175163,-0.5803913,0.7781837,0.8373109,0.5389043,0.4174167,0.6707229,0.7143198,0.1004502 +1,user1292,0.1440655,0.9307938,0.000406791,0.1053824,0.9739148,0.1047208,0.7430303,-0.5023168,0.02179876,0.2291285 +1,user1293,-0.7940225,-0.8738477,0.9534877,-0.5805127,-0.2827088,-0.4023629,-0.5887122,0.8524478,-0.06648145,-0.9577919 +1,user1294,-0.7606656,-0.3557994,0.2361234,-0.2161412,0.2930458,-0.02086605,0.9316076,0.8621009,0.8389885,0.4800916 +1,user1295,-0.3715763,0.004755142,-0.07310253,0.2045741,0.4605846,-0.8903379,-0.7712904,0.3235571,-0.9954768,0.4932587 +1,user1296,0.4854078,-0.2319927,-0.7155671,0.8754342,0.3883128,0.1915742,-0.05314772,-0.02816286,-0.1175427,0.4828622 +1,user1297,-0.1663675,0.3524049,0.583203,-0.0435099,0.02534518,0.8044405,-0.52431,0.3909347,-0.7446966,0.4687747 +1,user1298,-0.01698141,0.2971299,0.2915575,0.5072225,0.2191533,-0.6482484,0.5149871,0.04372963,0.806296,0.0379804 +1,user1299,0.9352995,0.8016057,-0.9540051,0.3215116,-0.3590778,-0.3961378,0.7060629,-0.9424344,-0.3734758,-0.8670836 +1,user1300,-0.5259468,0.2726079,0.7549017,0.4218848,0.7618755,-0.5521354,-0.2104153,-0.881114,0.6174343,0.7943677 +1,user1301,0.6122453,0.744765,-0.9945768,-0.09346129,0.4570344,0.5967727,-0.1476361,-0.5663456,0.09599245,0.1077531 +1,user1302,-0.4264046,-0.8945819,-0.3463091,-0.6731972,0.7578848,-0.7267081,-0.7006042,-0.4259935,0.4586848,0.5635784 +1,user1303,-0.07055219,0.1461761,0.1145904,-0.02620584,-0.1508702,0.7029094,0.4253427,0.4521686,-0.819085,0.512708 +1,user1304,-0.1346958,-0.5681111,-0.7469777,-0.8581797,0.7416532,0.2124915,0.913769,-0.03430843,0.8287158,0.8944016 +1,user1305,0.9691487,0.1389464,0.2773819,-0.430067,0.3805044,0.09171166,0.449175,0.4984157,-0.8873216,-0.4088263 +1,user1306,0.304149,0.3404174,0.8434544,0.1417359,-0.8442497,-0.1751464,-0.1006491,0.3552586,-0.4158962,0.3136022 +1,user1307,-0.2595365,-0.1852065,-0.8796893,-0.7200807,-0.7840869,0.6965809,0.1566792,0.5790223,-0.9481128,0.3222468 +1,user1308,-0.09287794,-0.4707502,0.5376672,0.9589674,0.3209561,-0.9282273,-0.2063236,0.7569874,0.9106186,0.6385189 +1,user1309,0.4747346,0.1428076,-0.02304732,0.7506908,-0.2015552,-0.6398808,0.979177,0.387777,-0.9319086,-0.4441792 +1,user1310,0.4251166,0.9296172,0.7692355,-0.8359672,-0.5681666,0.8784613,0.5123204,-0.2167104,0.7649704,0.6262268 +1,user1311,-0.7474939,-0.04082645,-0.2478672,-0.8482216,-0.687249,-0.2333763,-0.2719873,0.9629617,0.8449849,0.7620234 +1,user1312,-0.3198379,-0.4683853,-0.3046608,-0.5331077,-0.8878741,0.03904824,0.7261715,0.7445509,0.3764958,0.3631521 +1,user1313,0.7711694,0.812418,0.6530454,0.2185448,0.3767374,-0.7401952,0.08210887,-0.5313515,-0.5512039,0.08634299 +1,user1314,-0.0391271,-0.7992416,0.6933879,-0.8174881,-0.2693301,-0.431599,-0.2848725,0.1661801,-0.9626787,-0.1630889 +1,user1315,-0.9627739,0.9312772,-0.3675785,-0.3122538,-0.484662,0.1669114,-0.222867,0.5574971,-0.248851,0.1128797 +1,user1316,0.1541084,0.7831323,-0.129608,-0.4469199,-0.4491074,-0.7523432,-0.4113428,-0.5753281,-0.3238048,-0.4526516 +1,user1317,0.6018634,0.6302996,-0.353199,0.1035051,-0.3371322,-0.711519,0.7261771,-0.6727205,-0.443513,0.6591767 +1,user1318,0.9331636,-0.8742552,0.7062695,0.4569511,0.4798844,0.4753705,0.7018855,-0.186943,-0.7100027,0.1501583 +1,user1319,-0.4162121,0.978292,-0.4265599,0.327958,0.3916397,0.3679133,0.6313666,-0.7909785,-0.6491671,0.02359912 +1,user1320,0.3417921,-0.3080181,0.1058159,0.8731162,-0.8463495,0.3977636,0.5173702,0.4124685,0.3697178,-0.915243 +1,user1321,0.8395395,0.9039076,0.240475,0.0319277,0.387461,-0.249493,0.3535956,0.1514731,0.04491829,0.1957314 +1,user1322,0.6021752,0.4958083,-0.006951229,0.1061416,0.2289507,-0.09318242,0.04878327,0.8797444,-0.9348473,-0.8759507 +1,user1323,-0.5141424,-0.3772243,-0.8937773,-0.02150136,-0.8724347,-0.4975156,0.2604004,0.9101517,-0.6084835,0.3138855 +1,user1324,-0.954483,-0.9699401,0.1939628,0.451415,-0.8952478,0.3481441,0.7648834,0.00392086,0.9784368,0.2379395 +1,user1325,0.8415096,-0.8599911,-0.7708278,0.8900005,-0.4780035,0.8859515,-0.01960916,0.7418454,0.9041412,0.6041409 +1,user1326,0.1142813,0.6275308,0.03312021,-0.8169273,0.5881498,-0.3878535,0.48911,0.2337088,-0.6039603,-0.1928558 +1,user1327,0.5309247,-0.2019329,0.4783957,0.3268492,0.493065,-0.4602817,-0.2882643,0.975758,-0.1391059,-0.2791983 +1,user1328,-0.3248579,0.4924137,0.8123752,-0.1535094,0.5473416,0.690392,0.4560808,0.1327801,-0.8405554,0.07291557 +1,user1329,-0.9027001,-0.07533929,-0.6753223,0.6902952,-0.1926969,-0.03610191,0.004097123,-0.7225616,-0.7976642,0.8451246 +1,user1330,0.4662242,-0.4003272,0.5243906,-0.3516392,-0.8660128,0.1435805,-0.5822014,-0.9666764,0.4874184,-0.1462819 +1,user1331,0.1491953,-0.2349784,0.567277,-0.7316247,0.3092171,-0.8617434,-0.7543344,0.2516661,0.7768789,-0.1327168 +1,user1332,0.7095452,-0.3305743,-0.6698991,-0.4031661,-0.7356625,-0.4393292,0.856461,-0.2889072,0.2983282,-0.04712232 +1,user1333,-0.9601804,-0.2949091,-0.8219185,-0.02483634,0.8918719,0.4168724,-0.2828056,-0.3926698,-0.05389683,-0.5827034 +1,user1334,-0.9213569,0.9111977,-0.3181327,0.2421695,-0.8416531,0.841166,0.6710083,-0.2961652,0.9577939,-0.6200087 +1,user1335,-0.4251506,0.1013147,-0.4168768,-0.2613457,-0.9940093,0.7731624,0.7702301,0.6767844,0.127044,-0.1527207 +1,user1336,-0.9910318,0.8440373,0.4554634,0.5450967,0.2723763,-0.491416,-0.8336306,-0.8942542,0.05878161,0.008470263 +1,user1337,0.3827921,0.2516151,-0.4746783,-0.6160946,-0.6859029,-0.3339804,-0.4296408,-0.9409066,-0.4581022,0.6935935 +1,user1338,0.3153129,0.9161081,-0.2965662,0.0185736,-0.7780961,0.4697433,-0.07309073,0.2558067,0.1789312,-0.8304739 +1,user1339,-0.08390971,-0.6267129,-0.006869445,0.5040641,-0.4066676,-0.4196432,-0.03995424,0.8627332,-0.03059978,-0.3530109 +1,user1340,-0.1424733,-0.6055773,0.5022744,-0.8654038,0.1125419,0.02613874,-0.4504638,0.4468703,-0.3900109,-0.7505858 +1,user1341,-0.2595705,0.8457254,-0.5273306,0.1826064,-0.3462628,0.3482045,-0.5607703,-0.9609037,-0.05609838,0.7957529 +1,user1342,0.1685964,0.3324607,0.7452633,0.6558425,-0.09391662,0.3469805,0.6880585,0.8256949,-0.1856148,-0.5909875 +1,user1343,0.5376888,-0.07396258,-0.8023864,-0.3985115,0.2246678,-0.934813,-0.7242924,0.1914212,0.9864849,0.6125663 +1,user1344,-0.4884011,0.6581434,-0.8742853,-0.5988488,-0.9695254,0.6080093,0.5213386,-0.4922552,0.3926977,-0.1179041 +1,user1345,-0.8705307,0.5332191,0.4386512,0.8383545,0.6367533,0.9153815,-0.5968141,-0.008124977,-0.1482935,0.2459236 +1,user1346,0.5749149,-0.1426854,-0.1699649,0.2892347,0.7400058,0.2320983,0.05284058,-0.2510818,-0.2623661,-0.274554 +1,user1347,0.6657073,0.4412757,-0.003893287,-0.04576864,-0.4186328,0.8556661,-0.8900042,-0.06758324,-0.9311071,0.4294443 +1,user1348,0.7313327,0.1635187,-0.9145478,-0.05814042,-0.7003789,-0.7961375,-0.8706369,0.3191546,0.4081935,-0.09489963 +1,user1349,0.5080786,-0.01694061,-0.4636953,-0.2538142,0.2198902,-0.2925311,-0.2452739,0.5619752,0.02763123,0.8756043 +1,user1350,-0.7505048,0.4195677,0.5695468,-0.7178107,0.9730069,0.2235794,0.7413624,0.1414383,-0.5802742,-0.5469566 +1,user1351,0.0731248,0.8555006,0.1912681,-0.1850242,-0.5467284,0.6016261,0.6467332,-0.268377,-0.2220888,-0.01014258 +1,user1352,0.3476181,-0.113033,0.7767797,0.7781135,-0.3926488,0.4579759,-0.8916783,-0.2865517,-0.9274505,0.07133569 +1,user1353,0.8516704,-0.084624,-0.4374045,0.388331,0.2019576,-0.869603,-0.2098544,0.02118271,-0.5151215,-0.4229072 +1,user1354,0.5589824,-0.5217237,0.2974909,0.7934744,-0.4191631,-0.8958894,-0.09286634,-0.3582253,0.1694277,-0.696257 +1,user1355,0.393135,-0.08297317,-0.02925757,0.2295285,-0.2878966,-0.19388,0.8732051,0.7173692,-0.9490136,-0.6907248 +1,user1356,0.69318,0.05538486,-0.2082323,0.2783314,0.7239541,-0.9836515,0.7705365,-0.2369719,-0.6109803,-0.8187663 +1,user1357,-0.3267363,-0.8941928,-0.6693889,0.9765472,-0.8310133,-0.2837429,-0.6037563,0.8754835,0.5654675,0.1108872 +1,user1358,-0.07594025,0.715094,-0.5508619,-0.4436223,-0.7948317,0.3458383,-0.4150592,0.6931272,-0.08811949,0.03007693 +1,user1359,-0.6316778,-0.4522014,-0.395857,-0.875178,0.2712957,0.7067406,0.2266173,0.8958082,-0.4515356,0.2541492 +1,user1360,-0.2294364,0.03046787,-0.3447113,0.6668424,-0.0237102,0.6801552,0.4003408,-0.8470781,0.7678032,-0.0439882 +1,user1361,-0.6097161,-0.6852332,0.9735287,0.2047385,-0.6608445,-0.5105813,0.002739345,0.7264508,-0.6007011,0.8837951 +1,user1362,0.5175175,0.3128202,-0.8285801,-0.6068027,-0.4194872,0.8449972,0.4722829,0.1474744,-0.6746567,-0.8785675 +1,user1363,-0.5198913,0.6998936,-0.0146104,-0.7363236,0.2406273,-0.759174,0.2568018,-0.1359853,0.06613144,0.9088895 +1,user1364,-0.5698965,0.01985768,-0.8483898,-0.8200979,-0.7689726,0.9062911,0.7199337,-0.6662191,0.3454021,-0.6989084 +1,user1365,0.5961606,0.2240179,-0.1467127,0.6353668,-0.2611403,0.6861632,0.1432911,0.8513092,-0.7168628,-0.4985763 +1,user1366,0.05495812,-0.1987917,0.5685128,0.002330621,0.2466181,-0.9860116,0.0270319,-0.459201,-0.8068246,-0.2438313 +1,user1367,-0.5609283,-0.136105,0.6070736,0.7249988,0.5034037,-0.5851249,0.8863031,-0.5604733,-0.5958163,0.3095619 +1,user1368,-0.02104729,-0.524367,0.378609,-0.9807278,0.0529568,-0.6478172,0.7136503,0.9104025,-0.174965,-0.8049828 +1,user1369,-0.629729,-0.2826836,-0.7280534,-0.9790958,0.4685219,0.4837316,0.9539412,0.7966057,0.3721066,-0.07430519 +1,user1370,0.355162,0.2371821,-0.3997958,0.2290629,-0.9032639,-0.004768144,-0.1536512,-0.6977401,0.3735839,0.956551 +1,user1371,0.8364794,-0.1299443,-0.1191166,-0.8461316,-0.8345013,0.3783215,-0.7368135,0.3572729,0.4350241,-0.5555686 +1,user1372,0.1107005,-0.4369583,-0.255384,0.2035106,-0.8777409,-0.1680638,-0.6068291,0.835702,-0.6839917,-0.2785523 +1,user1373,-0.4762416,-0.4303572,-0.6545325,-0.1150946,0.002819516,-0.6577876,-0.4655927,-0.8720451,-0.812031,-0.6344364 +1,user1374,0.3741682,0.7960931,0.078497,-0.2446431,0.3901665,0.4435085,-0.4611059,-0.451306,0.421509,-0.9430023 +1,user1375,0.6222993,-0.7788149,-0.1296693,0.6046619,-0.8472662,-0.5600545,0.9145094,-0.6565532,0.708706,0.6035436 +1,user1376,-0.3467723,-0.897138,0.7841187,-0.2767401,-0.3604272,-0.7424061,-0.0624068,0.1198299,0.03967554,0.6114872 +1,user1377,-0.05091686,-0.3465923,0.9085321,-0.9554084,0.1301723,-0.3243932,0.5917347,0.2976123,-0.840857,-0.2175564 +1,user1378,0.2880066,0.6624608,0.8664374,-0.4411068,-0.265899,-0.7043884,-0.9754947,0.2758636,0.7775989,0.03298794 +1,user1379,-0.6154396,0.2663807,0.8695709,0.6651195,-0.06080613,-0.5385437,0.06695625,-0.5610155,-0.552131,-0.4834124 +1,user1380,-0.5428383,0.6364671,-0.5551632,-0.2092227,-0.6499375,0.3830757,-0.6535392,-0.1404125,0.1867742,-0.3419521 +1,user1381,0.5375018,0.0820285,0.4359842,-0.1589174,-0.2928921,0.519191,0.7658676,-0.5826981,-0.8026754,0.4860314 +1,user1382,0.4576852,0.1218813,0.06083904,-0.5199047,0.3924654,-0.9369176,-0.2863105,0.1706075,0.2257802,0.506445 +1,user1383,0.8047798,-0.4765659,-0.7783836,-0.4311092,-0.04258638,-0.1589484,-0.5452175,0.5730358,0.2593237,0.7293836 +1,user1384,0.3891722,0.9974045,0.9985797,-0.7705865,0.9090655,0.6495879,-0.4439867,0.4384846,-0.3177969,-0.9368759 +1,user1385,0.01666764,0.6001577,-0.6416701,-0.7264302,0.9733023,-0.832807,0.6208231,0.8123822,-0.6047921,0.810188 +1,user1386,0.1979148,0.4404609,0.1923589,0.7984193,0.669517,0.6471716,-0.6720124,0.290405,0.3103101,-0.9613412 +1,user1387,0.08235228,0.05278936,-0.2096525,0.5077449,0.6330196,0.6659365,-0.6734502,-0.7984874,0.07122285,-0.7556422 +1,user1388,0.6899313,0.7059648,-0.3110591,-0.749883,-0.857711,-0.1165499,-0.9829332,0.6878656,0.9606754,-0.07892484 +1,user1389,-0.8780255,0.1555548,0.641497,-0.645203,0.8746853,-0.006990158,-0.08707157,-0.01646781,-0.7778094,0.06873569 +1,user1390,0.4506744,0.6005879,0.3944905,0.6325669,-0.09568473,0.372677,0.5531671,-0.9026791,0.6196872,0.498507 +1,user1391,-0.5395051,-0.2635673,0.3442297,0.9169594,0.1185788,-0.4363947,0.4174076,0.8407875,0.7284786,0.877087 +1,user1392,-0.4877415,0.4703216,0.6150257,0.5595355,-0.7861592,0.4824286,0.9156678,-0.290017,-0.3785105,-0.04746925 +1,user1393,-0.03180808,-0.08659187,0.5659104,-0.9742358,0.4848281,0.2176742,0.02544995,0.2447952,0.9450305,0.6199395 +1,user1394,-0.05939642,-0.5636737,-0.6703807,-0.8193643,-0.6407939,-0.1955687,-0.3257906,-0.2951978,-0.2053899,0.7859764 +1,user1395,-0.05763802,-0.5098207,0.7666359,0.7394376,-0.5551318,0.3887197,0.6356015,0.04376392,0.9668915,0.2536224 +1,user1396,-0.4356475,-0.8625739,-0.5808023,0.6611311,-0.7763123,-0.0961626,-0.8312589,0.09610436,-0.7718323,-0.8786368 +1,user1397,0.9955617,0.2375345,0.8981321,0.1829664,0.6058242,-0.1815804,0.7012413,0.2456012,-0.01221449,-0.4578548 +1,user1398,0.3814337,0.3540743,0.3737095,0.4644364,0.9482719,0.8035947,0.5219045,0.4832907,-0.6289248,-0.4368157 +1,user1399,0.5433053,-0.3869409,0.7978067,0.6804033,0.2766445,0.2560202,0.8823914,0.006506893,0.05320271,-0.6836196 +1,user1400,-0.6341673,0.9548509,-0.8299213,0.2038706,0.07434608,-0.6978487,0.6551825,0.04220691,-0.6401078,0.46784 +1,user1401,-0.2634043,-0.4087435,0.9739137,-0.3065007,-0.954992,-0.2011734,-0.6317466,0.7855506,0.7446591,-0.4802647 +1,user1402,0.3797847,0.4831148,-0.3213099,0.8342717,0.4421432,-0.3656583,-0.8544221,-0.6362202,-0.5117732,-0.2391882 +1,user1403,0.4765332,-0.4821074,-0.08530531,-0.5926188,0.1966052,0.1340874,-0.9516467,-0.1220911,-0.3240996,-0.8107123 +1,user1404,0.2603541,0.1608993,-0.6806188,0.5784047,0.04782757,0.141039,-0.09733936,0.9135055,0.9326281,-0.1147011 +1,user1405,-0.2460471,0.2792079,0.7571871,-0.4103714,-0.1676903,-0.9221498,-0.315528,-0.08752619,0.9097358,-0.1821905 +1,user1406,0.09883248,-0.2609223,0.7850254,-0.9879569,0.349339,0.5740329,0.9628628,0.2213558,-0.6153936,0.7928314 +1,user1407,0.9135819,0.2637613,-0.8965,-0.6983353,0.6874004,0.3986328,0.8402538,0.03333538,-0.02769632,-0.5032139 +1,user1408,0.703036,0.9326156,0.6657192,-0.3657798,0.962482,-0.246543,-0.7237934,-0.7899139,-0.9311212,0.6002531 +1,user1409,-0.6131609,-0.5984615,0.6514629,-0.4290637,-0.91656,0.8696445,0.987368,-0.5027806,-0.8377948,-0.1741807 +1,user1410,-0.7018577,-0.469858,0.9730709,0.9667842,-0.3734058,0.8600892,-0.09278991,0.4723198,0.4201727,0.01337364 +1,user1411,-0.8398023,0.5690827,-0.889444,0.4249975,-0.6874555,-0.8634673,-0.3773326,0.06967362,0.255653,-0.741699 +1,user1412,0.9243409,0.483567,0.08744707,0.4120189,-0.2094521,0.3888354,0.7532357,-0.08547878,-0.6404701,-0.6881493 +1,user1413,0.7558275,0.6520233,0.0339099,-0.5531205,-0.9809403,0.9231716,0.6208996,-0.3570727,-0.3540471,-0.4801814 +1,user1414,0.9649775,-0.9074832,-0.6678276,0.9938884,0.2699581,-0.02241565,0.07744995,-0.3572905,-0.4850233,0.9876846 +1,user1415,0.3135132,0.4809715,0.08602682,0.6414324,-0.3003866,0.03842336,-0.690751,-0.6469942,0.04173303,-0.6250252 +1,user1416,-0.2275048,0.252181,0.3922398,-0.2795508,0.992362,-0.9096354,0.2417227,-0.5446905,0.04116082,-0.6699934 +1,user1417,0.1628923,0.5329777,0.5245313,0.7923077,-0.06052493,-0.3752441,0.4054376,0.9331145,0.8252868,-0.9736566 +1,user1418,-0.6041345,-0.4662391,0.8763743,0.1491773,-0.667367,-0.2956402,-0.3642013,-0.4454816,-0.8870441,-0.3806674 +1,user1419,-0.5375735,-0.04185422,-0.9188193,-0.02943382,-0.865349,-0.0261853,0.2587895,-0.8568249,0.001836233,0.2510818 +1,user1420,0.2848668,-0.3114674,0.1660283,-0.8528954,-0.1858396,0.6177658,-0.681634,-0.08335334,-0.9525226,0.09507908 +1,user1421,0.8465399,-0.8656512,0.2708648,-0.2182557,0.2369482,-0.9229631,-0.8110342,-0.3481607,0.7326431,-0.8821604 +1,user1422,-0.07707864,0.6945785,0.4254104,-0.1124744,0.2532298,0.53742,-0.323803,0.9839627,-0.2696851,0.1281687 +1,user1423,0.7971253,-0.8411458,-0.218946,0.7066401,0.02800112,0.1001943,-0.7659662,0.6266296,-0.3310332,-0.9523902 +1,user1424,-0.1852682,0.04775696,-0.1632248,-0.1924915,-0.2782237,0.2947111,0.2144158,0.8966345,0.6776736,0.7377791 +0,user1425,0.8635249,-0.8690952,0.7550297,0.06816128,0.6124359,-0.6581488,0.3504064,-0.3112351,0.524925,-0.08585483 +0,user1426,-0.2605127,-0.3509665,-0.4523101,0.4460777,0.4728693,-0.511086,0.8696353,-0.3296064,-0.3641416,0.3012322 +0,user1427,0.3790844,0.185183,0.2559729,-0.5313604,-0.05453596,-0.8014515,0.3831568,-0.007261147,0.9058413,0.8591423 +0,user1428,0.8590866,0.3684393,0.6531617,-0.7488724,0.2182601,0.1602709,0.05164771,0.9343661,-0.4872895,0.4562904 +0,user1429,-0.879079,-0.9968922,0.9213994,-0.08948594,0.4211412,-0.7074913,0.3915398,-0.8463158,0.006933593,0.8644165 +0,user1430,-0.07761037,0.7982421,0.05377953,-0.8509571,-0.7778914,0.4545686,0.2655483,0.9992457,-0.04095598,-0.8244773 +0,user1431,-0.7750807,0.3232902,0.8232404,0.4549982,-0.7073939,0.4624221,-0.2931698,-0.02342702,-0.1273974,-0.07586964 +0,user1432,-0.1424833,-0.4056358,0.8953131,0.6040134,0.4661493,0.09133533,0.7597932,0.9392348,-0.2484073,-0.6158483 +0,user1433,-0.6978257,0.2813569,0.7324696,0.9833146,0.6642518,-0.9110897,0.4111261,-0.6369745,0.4472708,-0.06366548 +0,user1434,0.7014525,0.8411828,-0.2620649,0.8623794,0.4892114,-0.4034905,-0.2448165,0.8544819,0.5485031,0.1134181 +0,user1435,-0.8821291,0.7552635,-0.7853057,0.1824181,-0.4860232,-0.7676257,-0.3375462,0.8527403,-0.3157792,0.2694506 +0,user1436,0.05612719,-0.4394352,0.4896567,-0.4270568,-0.5034385,-0.8332395,-0.9044019,0.2754993,0.3570067,0.754144 +0,user1437,-0.199715,-0.4197394,-0.4770395,0.8744225,-0.1614496,-0.8294576,-0.2819537,0.07583769,0.9331095,-0.09375057 +0,user1438,-0.9685473,0.01902478,-0.6818057,0.4840828,-0.7986228,0.6310071,-0.4972924,-0.1139243,0.6565245,0.7662367 +0,user1439,-0.2408368,-0.5068196,0.1553759,0.2071634,-0.5409565,-0.07978242,-0.6281953,0.4855854,0.4258855,0.3543972 +0,user1440,0.1871241,-0.01820091,-0.8255766,-0.5546411,-0.07800965,-0.9598131,-0.2945857,0.5730571,-0.9046853,0.7320687 +0,user1441,-0.670405,0.5491668,-0.7087349,0.4508669,-0.1720286,0.4910963,0.4099177,-0.6416045,0.07669717,-0.2203897 +0,user1442,-0.08063905,-0.9377369,0.2659319,-0.3678391,-0.228412,0.05675033,-0.00552784,-0.444741,-0.3184616,0.6126982 +0,user1443,0.111465,-0.5346339,0.2618704,0.8573778,0.7125383,0.4290223,-0.54135,-0.5124217,-0.5451554,-0.9560806 +0,user1444,-0.9145775,0.2011901,0.3251751,0.8977464,-0.1529689,0.4142679,0.03081728,0.001322844,0.72265,0.2994289 +0,user1445,-0.1156616,-0.84522,0.5981044,-0.3739508,-0.9584539,-0.9656653,-0.9280779,0.1979685,0.1965151,0.6003828 +0,user1446,-0.5750218,0.9463376,-0.6521028,0.4988102,-0.5878483,-0.5325543,-0.2321011,-0.1594159,0.4965776,-0.5811058 +0,user1447,-0.1420823,-0.546629,-0.2825852,-0.3818044,-0.1606069,0.5046325,-0.72746,0.4566323,-0.2361891,0.6294356 +0,user1448,-0.9527693,0.6877577,0.1226357,-0.5816431,-0.01897882,-0.3409094,0.4773597,0.131083,0.02180189,0.6267262 +0,user1449,-0.1791563,-0.5199015,-0.7757285,-0.3520125,-0.2552154,0.1718055,0.4036977,0.3951025,0.6095335,0.03822683 +0,user1450,0.3203442,0.4115168,-0.2014044,0.5887618,-0.02595593,-0.5215528,0.5313294,0.5998075,0.7656471,-0.1194827 +0,user1451,0.3320976,-0.6237098,-0.7113361,-0.4345385,0.7951815,-0.7231436,0.7957257,-0.9522704,0.06927925,-0.2781947 +0,user1452,-0.3326164,-0.3855526,0.4951363,0.4297318,0.9817329,0.2488424,0.5926635,-0.9530583,0.3421766,0.1560665 +0,user1453,-0.7567345,0.1060953,-0.775994,-0.5237126,-0.7727262,-0.9841328,-0.7924736,0.5837701,-0.504038,-0.9913139 +0,user1454,0.1292229,-0.4648556,0.06971791,-0.7278984,-0.1768173,0.3770507,-0.9702404,0.6743593,0.7382461,-0.2305849 +0,user1455,0.4821154,0.6622043,-0.6680885,-0.7627597,-0.2964908,-0.4564466,-0.1929208,0.9435762,0.01985018,-0.1061544 +0,user1456,-0.8932095,0.237,0.9790356,0.5444486,0.8397097,-0.6422816,0.5579329,-0.727465,-0.9791131,-0.07716875 +0,user1457,0.8687102,0.1841779,0.6174078,0.7181793,-0.703948,0.8659647,0.8993948,-0.6552472,-0.6258955,-0.9293527 +0,user1458,-0.1388002,-0.1526127,0.5878844,-0.2941202,0.6489732,-0.2578981,-0.8097639,-0.06368491,-0.07430851,-0.2470121 +0,user1459,0.9658771,-0.3945607,0.6321974,0.7955763,0.05796979,0.5179893,-0.3904194,-0.793099,-0.4664026,-0.6208784 +0,user1460,0.9896312,0.1872856,0.5388072,-0.3713066,0.7171932,-0.8415266,0.2909346,-0.501563,0.3810381,0.9350638 +0,user1461,0.7835894,-0.3543706,-0.3583361,-0.1450773,0.8710818,-0.8033295,0.4557843,-0.06443916,0.8847355,-0.07148938 +0,user1462,-0.8092036,0.9287295,0.4554378,0.2505745,0.3505759,-0.01958863,0.3164107,0.183474,0.4062001,0.303252 +0,user1463,-0.1528521,0.7816499,0.4341203,-0.7672933,0.1833424,0.2498087,0.05072781,-0.5623281,-0.8673692,-0.6807845 +0,user1464,-0.9142363,0.9269863,-0.6258665,-0.1617627,0.5353336,-0.7144192,-0.1330895,0.2985863,0.3320063,0.8648451 +0,user1465,0.8922489,0.7699124,-0.8066272,0.1129539,-0.1602127,0.5769209,-0.9284058,0.03795594,-0.04529687,-0.58333 +0,user1466,-0.03498118,0.5369134,0.6488146,0.4151248,0.6973192,0.482183,0.7131816,-0.7095878,-0.1831484,0.5886661 +0,user1467,0.1418909,-0.5124489,0.8637902,0.4111805,-0.9681049,-0.5476586,-0.03749143,-0.4259143,-0.310987,0.6189891 +0,user1468,-0.3074661,-0.6498271,-0.2836667,-0.01262359,0.6783377,0.7474633,-0.2103595,-0.8862064,-0.1121874,0.3229195 +0,user1469,-0.003528452,-0.4440618,0.9670089,-0.1007924,0.8986964,0.1131901,-0.7841107,0.1764879,-0.5266239,0.3549028 +0,user1470,0.9010541,-0.01926853,0.01916618,-0.3816561,-0.5090613,0.372559,0.3343133,-0.9403289,-0.8851016,-0.0266137 +0,user1471,0.879658,0.331972,-0.1092433,0.4327353,-0.399672,0.7876502,0.4950548,0.6868507,-0.01687272,0.05498821 +0,user1472,0.3260666,-0.8948951,-0.7417259,-0.6499255,-0.2733322,-0.3957136,0.625807,0.5348834,0.5500733,-0.8654869 +0,user1473,-0.1795849,0.04299462,-0.7149019,0.2505048,0.2625267,-0.5706907,-0.6712145,-0.3850699,-0.2035631,-0.4139155 +0,user1474,-0.008877023,0.7973381,-0.8473729,0.290113,-0.6871337,0.2166725,0.9537048,-0.825571,0.4379719,0.09890761 +0,user1475,0.4114891,0.306295,0.5834491,-0.7521791,0.5736989,-0.9814457,-0.3433757,-0.4637938,0.2727233,0.433942 +0,user1476,0.7047535,0.1977746,0.8832025,0.876554,0.3040728,-0.536356,-0.5992924,0.8128986,0.992952,-0.8135327 +0,user1477,0.4161012,0.7436758,-0.4994756,-0.2110768,-0.2749821,0.6841182,-0.2783963,0.01501301,-0.06545049,0.5178018 +0,user1478,-0.7305932,0.759666,-0.699136,-0.1339835,-0.586908,0.5231867,-0.07083578,0.9928386,-0.9634658,0.06337761 +0,user1479,0.7519843,-0.1144677,0.005838114,-0.7050891,-0.714906,0.1227346,0.8780673,-0.05601845,0.01475388,0.8131935 +0,user1480,-0.7630551,-0.7762257,-0.2752041,0.4369107,0.4698026,-0.1440763,-0.8746987,-0.5898845,-0.455917,-0.4439713 +0,user1481,0.589751,0.1711828,0.09945954,-0.5452217,0.3871361,-0.9983661,-0.5395064,0.592646,0.8021813,0.9438949 +0,user1482,0.08408185,0.2618225,0.2945021,-0.1396276,-0.9197245,0.3995909,0.673793,-0.008288823,-0.9159669,-0.4650012 +0,user1483,-0.09567154,-0.1617783,-0.7800678,-0.1333575,0.4515354,-0.8952339,0.7179648,-0.5429428,0.8862596,0.7120951 +0,user1484,0.8330165,-0.7227219,0.3234655,-0.06893432,0.6144099,-0.9824989,-0.3319799,0.1764161,-0.7018568,0.952581 +0,user1485,-0.7866952,0.7969669,-0.63578,0.132474,-0.09654179,-0.2233584,0.7035526,-0.3339296,0.8222792,0.3044139 +0,user1486,-0.6135561,-0.499574,-0.4481563,0.1038828,-0.8449554,-0.3516805,-0.474956,-0.5993665,-0.09389022,-0.3940593 +0,user1487,0.9398069,0.5142781,0.3025012,-0.5244857,0.4541197,-0.6247805,-0.7740471,0.4489511,-0.6809698,-0.1245877 +0,user1488,-0.917985,-0.01885528,0.9816278,-0.1493467,0.1995102,-0.3573937,0.6029474,0.01082327,-0.8036163,0.3750612 +0,user1489,0.2476437,0.3478133,-0.8602719,0.8097626,0.8040178,0.3904213,-0.2847199,0.3369486,0.8318013,0.3589286 +0,user1490,0.9056841,-0.8802826,-0.06530149,-0.7289094,-0.4879105,0.8932087,-0.1644665,0.6558522,-0.1473724,0.2545339 +0,user1491,-0.9283538,-0.8315697,0.520435,0.4793467,-0.08329668,-0.1989204,-0.1061179,0.5092603,0.5774218,0.310125 +0,user1492,0.03123311,0.9934428,-0.218608,-0.3353146,0.6750996,0.5870918,-0.8289355,-0.7274906,0.7165368,-0.7125608 +0,user1669,-0.08618945,-0.5926816,-0.9822199,-0.2816779,0.3242202,0.4256193,-0.04237718,0.8512167,-0.2119822,0.3457502 +0,user1670,0.7133808,-0.648953,0.6311105,0.1805982,-0.1905045,-0.772115,-0.7505197,0.1313194,0.3719945,-0.454308 +0,user1671,0.4574514,0.7369055,-0.570349,0.2150431,-0.4971214,0.6586757,-0.3400395,-0.01307092,0.4246795,0.335703 +0,user1672,-0.504931,-0.7563059,0.6848617,-0.6036668,0.0443088,0.8331523,-0.01717039,0.3703058,0.6854569,0.6976441 +0,user1673,-0.9542697,-0.8799515,0.9315961,-0.2606713,0.791471,-0.1379956,-0.4042909,0.005155816,0.4837988,0.9134334 +0,user1674,0.8980536,-0.5622674,0.5097014,-0.8996184,-0.4386246,-0.09340404,-0.5971715,0.02221276,-0.5591636,0.235909 +0,user1675,0.9816722,0.6166235,-0.5070794,-0.6367089,0.9333017,0.4465072,-0.5077138,0.4499268,0.08256149,0.2133263 +0,user1676,-0.6216188,-0.8715166,0.7649677,0.472503,0.2725627,0.1343018,-0.01379179,-0.8860715,0.05730498,-0.3971117 +0,user1677,-0.9025667,0.9196407,0.604509,-0.536708,0.7928103,0.1275728,-0.5139396,0.4461768,0.6625633,-0.8290687 +0,user1678,0.01195831,-0.6999351,0.8153946,-0.8203753,0.5251335,0.2401736,-0.2723516,-0.4741871,0.7627792,0.5682149 +0,user1679,0.9584269,-0.3994574,0.9505004,-0.03065825,-0.5147965,-0.386646,0.8726521,0.9407173,0.1669797,0.2559038 +0,user1680,-0.9375337,-0.7613953,-0.7302836,0.1985706,-0.8945878,0.9694574,0.5105879,0.02120227,0.4256671,-0.170228 +0,user1681,-0.5522917,0.9749568,0.9056844,0.7745211,-0.2456001,0.6545283,0.8172301,0.1703409,0.3179664,-0.789986 +0,user1682,0.0004725498,-0.1908432,-0.3673378,-0.8279523,0.4014018,0.9724047,-0.7072358,0.8564503,-0.2790571,0.8531234 +0,user1683,-0.3288611,0.1519881,-0.8571536,0.506055,-0.8971223,-0.952172,0.4141351,-0.3525631,0.01361142,-0.140682 +0,user1684,-0.4752293,-0.4971769,0.3334377,-0.121397,-0.01637749,-0.7335111,-0.7473406,0.5534406,0.27627,-0.2282234 +0,user1685,-0.3913861,0.4077888,-0.07230764,-0.2231684,0.05592451,0.5913814,-0.465745,0.179214,0.166263,-0.2252316 +0,user1686,0.2435824,-0.6780741,0.4450116,0.0335046,-0.6372495,-0.4906097,-0.2361675,-0.284569,0.2314693,-0.8603984 +0,user1687,0.1636268,0.5880903,-0.5406859,0.9890517,-0.23622,0.157742,0.7003301,0.108357,-0.9175362,-0.7703725 +0,user1688,0.2593371,-0.7372692,-0.2392209,0.6896688,-0.1494004,-0.8554319,0.3069936,-0.0885107,-0.7212774,0.213783 +0,user1689,0.3194572,0.6667444,0.3112767,-0.604059,0.1375308,0.8459255,0.5483194,0.4074903,0.5302287,-0.2335157 +0,user1690,0.260314,-0.1278887,0.2333311,-0.8823161,0.5906329,-0.5901477,0.4764365,0.01719536,-0.2728237,0.3912308 +0,user1691,-0.1635377,0.5832438,0.04646344,0.8268997,0.1526026,-0.6093961,0.3771185,0.3297432,-0.5249729,-0.95975 +0,user1692,-0.9468215,0.1667123,0.5331226,-0.68073,-0.9477873,0.977341,-0.8740424,-0.5160509,-0.04582489,0.1757047 +0,user1693,-0.3375413,0.6566689,0.1999919,0.1985325,0.9609159,-0.8101011,0.08629764,0.3291462,0.01796183,-0.09853205 +0,user1694,0.6342338,-0.9067852,-0.2872062,-0.950403,0.9117459,0.8489281,-0.6153098,-0.5586107,-0.4179946,-0.3651199 +0,user1695,0.496388,0.8837897,0.01387699,-0.7257988,-0.788659,0.1270091,0.2583216,-0.4527951,-0.05211505,0.4716685 +0,user1696,-0.4175957,0.1424934,-0.2839663,0.4498453,-0.9865105,-0.4506282,-0.2939614,0.6553744,0.5069691,-0.4214775 +0,user1697,-0.4345003,-0.5318927,0.2940268,0.6686106,-0.2334271,0.2185929,-0.02981314,0.7192311,0.6068849,-0.7523974 +0,user1698,0.3574239,0.3665573,-0.9634303,0.05524341,-0.8972743,0.4910294,0.6894587,0.3761587,0.8250115,0.904876 +0,user1699,0.5315164,-0.5553846,-0.3247008,-0.4491281,-0.1642708,-0.6640793,0.280422,-0.567719,0.1592403,-0.3846327 +0,user1700,0.4793103,-0.1245743,0.3118068,-0.6130673,-0.9092069,-0.3557878,0.9278097,0.5704478,-0.6050973,0.5933527 +0,user1701,0.07080471,0.7176043,0.6676803,-0.7641584,-0.08777887,0.7189145,0.938939,-0.4925219,0.1970059,-0.549432 +0,user1702,-0.01103228,-0.8184792,0.1049503,0.7659149,0.3386079,0.9945964,0.9403826,0.4192101,-0.4160801,0.9510703 +0,user1703,0.9743793,0.1191198,-0.003331465,-0.2167341,0.1351019,-0.5226355,-0.08936071,-0.05924641,-0.9196404,0.2909969 +0,user1704,0.116535,0.8376527,0.5992763,-0.02482976,-0.2963079,-0.4190811,-0.4653518,0.5126339,-0.3191952,-0.6359986 +0,user1705,-0.1129787,-0.3807466,-0.3853484,0.8662965,0.8999833,-0.09880759,-0.6567889,-0.5585772,0.02475624,0.1869793 +0,user1706,0.9560515,-0.2642568,0.4895892,0.146557,0.06840364,0.9238717,0.4029255,-0.6093196,0.1629211,-0.4956768 +0,user1707,0.4949163,0.9661361,0.364244,-0.5523268,0.9762548,0.7152206,0.5208564,0.6265624,0.7381097,-0.03311028 +0,user1708,-0.01554537,-0.4611059,-0.7808394,-0.6704115,0.6927936,-0.9712348,-0.1707286,0.8875996,-0.3126804,0.3579106 +0,user1709,-0.03199016,0.03580816,0.3049837,0.3261816,-0.4064629,0.1640454,-0.8694261,-0.08350666,-0.07429969,-0.9274619 +0,user1710,0.4533432,-0.4333212,0.3147445,0.417015,-0.5385417,-0.6714253,0.3935085,0.5672797,-0.09491057,-0.7772065 +0,user1711,0.0469209,-0.2225011,-0.511123,0.5281591,0.7982058,0.9982226,-0.6601407,-0.09119814,-0.8870133,-0.8123174 +0,user1712,0.4157182,0.01076496,0.2106682,0.1007028,0.3479369,-0.1814263,0.947804,-0.9131658,-0.7563333,-0.7174479 +0,user1713,-0.5461843,0.3758355,0.9474067,0.5890627,0.8628601,-0.6990206,0.6862727,0.42373,0.6260323,-0.9240831 +0,user1714,0.7180598,0.929487,-0.3682766,0.03421419,0.9010835,-0.9539495,0.7539944,0.5562387,0.1265981,0.04700062 +0,user1715,0.9404889,0.513588,-0.4558941,0.9793057,-0.6684405,0.0850626,-0.7995366,0.6402748,0.5199367,0.05432867 +0,user1716,0.06242966,-0.2163757,-0.1249009,-0.6341058,-0.08121541,0.8923608,-0.7794724,-0.3970559,-0.2077047,-0.1493147 +0,user1717,-0.03835777,-0.7485871,-0.923265,-0.9322812,-0.736166,-0.4445592,-0.4821731,-0.7283303,-0.6419326,0.1866022 +0,user1718,0.1041157,0.1016784,0.003419973,0.9683574,0.09533944,-0.7571954,0.9007935,-0.2513682,0.6024005,0.2839562 +0,user1719,-0.6782333,0.04635512,0.6358782,-0.944437,0.7693842,-0.9630711,0.5275212,0.5144334,0.07101784,-0.9355318 +0,user1720,-0.7189006,0.9181573,0.3880117,-0.5363403,0.4013648,-0.5986337,-0.9338536,0.67916,0.8882961,0.9530866 +0,user1721,-0.6355703,0.9737897,-0.7632489,-0.9139586,-0.3140276,-0.3473431,0.37723,0.7658272,-0.6704232,-0.324813 +0,user1722,0.158229,-0.3704011,-0.3176584,0.8824626,-0.07801317,-0.5724672,-0.0953603,-0.1558234,0.546045,-0.8952818 +0,user1723,-0.6657221,0.08486955,-0.07886568,-0.2170703,0.4535775,-0.6212927,-0.807896,-0.8368909,-0.1575288,0.1287913 +0,user1724,0.02688841,0.6304586,0.4367429,0.2845739,-0.3531117,-0.1574442,-0.5364724,0.09497341,0.3475387,0.5766549 +0,user1725,-0.2075372,-0.2771862,0.3951354,0.9320596,-0.1662673,-0.7235391,0.2893299,0.2855658,-0.8719497,-0.2604017 +0,user1726,0.8306659,-0.03134075,0.9350113,0.05713096,0.6649185,0.5057164,0.4504255,-0.289686,0.7903562,-0.3995402 +0,user1727,0.6092927,-0.227048,-0.8472234,-0.2655808,-0.3396222,0.3919276,0.1695662,-0.2496522,-0.1454922,-0.8448226 +0,user1728,0.3579626,0.1909211,-0.3108378,0.6006702,0.6003056,0.4950538,-0.7404833,0.004796925,0.7349352,-0.01279915 +0,user1729,0.1880898,-0.6647834,0.9715811,-0.8876256,0.7676442,-0.003254205,0.1398842,-0.9135273,0.6153676,-0.4946642 +0,user1730,0.1408091,0.2175673,-0.1719242,0.2852911,0.4961071,0.7278484,-0.5500117,0.1826288,-0.9862519,-0.2294553 +0,user1731,-0.1627271,-0.9336533,-0.999031,0.9876029,0.6910987,-0.860734,-0.8126736,-0.4247553,-0.870162,-0.4194464 +0,user1732,-0.7411055,-0.9471792,0.6392613,-0.6517841,-0.3201347,-0.2843397,0.07882327,-0.4060493,-0.1876264,-0.04409622 +0,user1733,-0.8702232,0.3990882,0.9330261,0.05120604,-0.1652851,0.7224448,-0.6096292,-0.3981611,-0.4023321,-0.278385 +0,user1734,-0.1883478,0.1854665,-0.00236249,-0.2291313,-0.1737994,-0.3833694,0.09796568,0.5159983,-0.7898024,0.8715505 +0,user1735,0.3754296,0.8904736,0.2385377,0.3233862,0.3835574,0.2965791,0.6134714,-0.8934154,0.4931783,0.3199052 +0,user1736,0.01679815,-0.9816584,-0.4523222,-0.08249742,-0.2653017,-0.3763628,-0.2664181,0.04326169,0.6224242,0.9085943 +0,user1737,-0.2322963,0.9212097,-0.5127733,0.9174257,0.8946043,-0.4594977,-0.4991088,0.9066787,0.3731187,-0.6241263 +0,user1738,-0.1296541,0.8566097,-0.3972183,0.7710594,0.3598122,0.01179976,0.1343278,0.733147,0.2312881,-0.7132051 +0,user1739,-0.9987472,-0.4427643,-0.2331616,0.2470911,-0.5725081,-0.3475976,0.5628533,-0.06913872,-0.6902562,0.2665049 +0,user1740,0.7357135,-0.04298212,0.7922104,0.2436073,-0.5118586,0.7045477,-0.3685349,-0.1768279,-0.701181,-0.5515882 +0,user1741,-0.676311,-0.5767115,0.9175262,0.1880743,0.8212705,0.3403744,-0.4721638,0.3004267,-0.8636225,-0.4904116 +0,user1742,0.04817368,0.3347346,0.2557155,-0.2247497,-0.7743024,-0.349375,0.9027127,0.8396631,-0.5772695,0.4541875 +0,user1743,0.1514317,0.9677828,0.002878591,-0.6556899,0.8360783,-0.4768787,-0.4207309,-0.0899937,-0.4575143,-0.2690361 +0,user1744,-0.2224953,0.799124,0.8649329,-0.222863,0.6841306,0.6413538,-0.7858911,-0.2758433,0.7624098,-0.4144947 +0,user1745,-0.2337665,0.2642216,0.8874389,0.8094644,-0.8732189,-0.3033245,0.6567071,0.3959019,0.5493286,-0.4988118 +0,user1746,0.09192061,0.4813709,0.5469845,-0.6763842,-0.8323622,0.6081839,-0.2202674,-0.4497189,-0.9375776,0.7852925 +0,user1747,0.8399344,-0.4172517,-0.259968,0.1430312,-0.3970848,0.5337146,-0.5653635,0.3271008,-0.4452949,0.4361905 +0,user1748,0.7278758,0.5156344,0.9641738,0.8771832,-0.6093849,0.2521164,-0.825466,0.6675716,0.907396,0.6877904 +0,user1749,-0.8039637,-0.4169508,-0.4495956,-0.7080268,0.2629772,0.8509886,-0.3194739,0.2989129,0.6648229,0.06924873 +0,user1750,-0.8382989,0.6291034,-0.6240899,0.1985942,-0.6277006,0.5706435,0.9621577,-0.1584658,0.625723,0.5006588 +0,user1751,-0.9910248,0.4337917,0.3521855,-0.659157,0.7919799,0.6534827,-0.7593196,0.3467316,0.7956921,0.640877 +0,user1752,-0.4395339,-0.4431611,-0.2128445,-0.6219854,0.9489496,-0.4963545,-0.9422439,0.06474009,0.9943997,0.7444357 +0,user1753,0.3199301,-0.7412977,0.05825173,0.08105682,0.2942862,0.9981763,-0.1332026,0.6857108,0.1717679,0.605377 +0,user1754,-0.6567469,-0.4813387,-0.7266801,0.1237727,0.2455574,-0.96781,-0.5672156,0.5098408,-0.3618367,-0.2303317 +0,user1755,0.5873545,-0.8127025,-0.7761016,0.6625886,-0.4041621,0.3462013,-0.4787163,-0.8402865,0.3419384,0.3210906 +0,user1756,-0.8876071,-0.0184839,-0.5466129,0.01311646,-0.8719811,-0.7253628,-0.8438728,-0.0287234,0.2998182,-0.6550247 +0,user1757,-0.826081,0.4873205,-0.7916688,-0.8190964,-0.08952415,0.5379063,0.8832099,-0.7798452,-0.5714805,0.370128 +0,user1758,0.1966472,-0.03975059,-0.6233249,-0.6029922,0.2562157,-0.261871,0.69085,-0.08993868,-0.8035539,0.476268 +0,user1759,0.4703555,-0.8275628,0.1425493,-0.3862133,0.7283246,0.7696911,-0.5843561,0.9760735,0.03475345,0.3321761 +0,user1760,0.3620088,0.8225371,-0.8200878,-0.706722,-0.32188,-0.4653479,0.02309414,-0.6933725,-0.9561129,0.8754638 +0,user1761,-0.6625437,-0.8221832,0.2047509,0.6822988,-0.2476773,-0.5340227,-0.8591618,-0.9073099,-0.7898058,-0.7531873 +0,user1762,-0.6923716,-0.7612161,0.1435183,-0.3986104,0.4194233,0.9089571,-0.3970297,-0.4486817,0.1645914,0.9127297 +0,user1763,0.6209034,0.8753579,0.8191736,-0.3585061,0.3579853,0.2503124,-0.8980826,-0.09942179,-0.1437393,-0.1686324 +0,user1764,-0.5327668,0.5769049,0.137777,-0.2664951,0.5870377,-0.8115778,-0.4687909,-0.305471,-0.1921378,-0.03157223 +0,user1765,0.1192805,0.4242504,-0.8588442,0.3722583,-0.7543761,-0.4744124,0.700936,-0.9326834,0.374789,0.7842802 +0,user1766,-0.00366704,0.7658315,0.05771125,0.9648801,-0.2584573,-0.4531085,0.7153888,0.007162845,-0.650561,-0.8487273 +0,user1767,0.4840313,0.5952465,0.6854548,0.6510075,-0.6782641,-0.1879406,0.2647909,0.7377907,-0.5697136,-0.1229779 +0,user1768,0.8869842,0.3454601,-0.3716176,0.289684,-0.8597718,0.0660899,-0.7981728,0.9739953,-0.2520923,-0.8398461 +0,user1769,0.8666788,0.6224412,0.660493,0.7359395,-0.8986451,0.5586913,-0.1502834,-0.2596901,0.5807271,-0.5619323 +0,user1770,0.4852841,-0.8475177,-0.5477068,-0.1019014,-0.2507722,0.4644618,-0.1723558,-0.331348,-0.2599699,-0.856473 +0,user1771,0.6226978,-0.6975221,-0.5794072,-0.4667087,-0.3716304,-0.2293624,-0.1667077,-0.2028326,0.04672677,-0.3914344 +0,user1772,-0.8096321,-0.9542704,0.5780192,-0.07598617,0.9226254,-0.1009343,0.3775528,-0.9592634,0.7171046,-0.05234394 +0,user1773,-0.4665422,0.4872168,0.7080087,0.6733488,-0.02507457,-0.8849132,-0.2696431,-0.4916849,0.1627606,0.5977145 +0,user1774,-0.2258705,-0.7297392,0.4234714,-0.1223986,-0.5355521,0.2937589,0.4125615,0.7071737,0.5892125,0.3395295 +0,user1775,-0.03212741,0.8448536,0.4429521,0.7011508,0.6067559,-0.4595804,0.5916617,-0.2351067,0.4795145,0.5331613 +0,user1776,0.2996913,-0.2485616,0.5954476,0.4828133,0.1017066,-0.1882377,-0.612936,0.904217,-0.2879109,-0.9010973 +0,user1777,0.8660501,0.7516316,-0.0295441,0.2012172,-0.3679143,-0.09805716,-0.807706,-0.7425453,0.6516349,0.124822 +0,user1778,-0.192193,-0.5723981,-0.8170159,-0.155818,-0.7903289,-0.9258658,-0.9737018,-0.9080058,-0.9657804,-0.03064814 +0,user1779,0.02756706,-0.7329272,0.5596214,0.3599965,0.4923216,-0.9361213,-0.438402,0.5717886,-0.3805149,0.7866931 +0,user1780,-0.9379136,-0.6653192,0.5208603,0.4931904,0.8950629,-0.2470686,-0.1271799,0.5563677,0.3164578,-0.8059293 +0,user1781,-0.03049191,-0.9432947,-0.4411058,-0.9572238,-0.4180296,0.6447777,0.9884559,-0.06647167,0.6599425,-0.5299894 +0,user1782,0.03654223,0.7008645,-0.08819307,0.7008394,0.2843015,0.7173614,-0.1977216,-0.08147981,-0.5848228,0.4275701 +0,user1783,-0.3774475,-0.1084803,-0.6919842,0.8712051,0.8440125,0.2565769,-0.06942378,-0.3788923,0.3108575,0.9385064 +0,user1784,-0.7105618,-0.6845924,0.6171459,0.123833,0.8762566,0.642954,-0.1447467,-0.3807609,-0.1682896,-0.9246124 +0,user1785,0.3797953,-0.7804742,0.1851268,-0.1753879,-0.4701411,0.7495513,0.2350627,-0.571639,0.05334047,-0.8027617 +0,user1786,-0.790093,0.07881719,-0.4680857,0.5337936,-0.5601497,-0.3972218,0.45186,-0.2191787,-0.3472041,0.259597 +0,user1787,-0.5981689,0.2969237,-0.9294669,-0.8630505,-0.9957244,0.9175913,0.01138052,0.5905157,-0.8684714,-0.5796371 +0,user1788,0.5537143,0.7068463,0.393458,0.005515747,0.4403348,0.2874577,0.1182726,-0.3514843,0.4818599,0.5673663 +0,user1789,0.4065543,-0.9609334,-0.09141065,0.9308014,0.696066,0.3409071,0.1427099,0.6908826,-0.150758,-0.264135 +0,user1790,0.8721866,0.4693609,0.2130823,-0.2492638,0.7326001,0.6872823,0.4270245,0.5665892,0.1662821,0.7525391 +0,user1791,-0.08427689,0.5293833,0.5733702,0.2987938,-0.8815452,0.8221098,-0.8586332,-0.0448568,0.525747,0.4428301 +0,user1792,0.7440106,-0.7831166,-0.8866598,0.6131002,-0.5516113,0.8068845,0.2835482,0.7835727,0.05943626,-0.01732226 +0,user1793,-0.820185,0.7081448,-0.6433994,0.3521257,0.1520234,0.5962394,-0.9700052,-0.8820925,-0.6691265,0.6652688 +0,user1794,-0.4633735,0.4047412,0.3925438,0.9402877,0.4764401,0.07242217,-0.7567158,0.8557214,-0.6179923,-0.7258023 +0,user1795,-0.7887562,0.7937883,0.2511173,-0.6533949,-0.9645736,0.9953066,0.8147572,-0.5218983,0.8672984,0.9511055 +0,user1796,0.2990955,0.1323951,-0.5022437,-0.275616,0.3976474,-0.878173,0.7309308,-0.8147759,0.7056625,0.449549 +0,user1797,0.5329594,0.1705727,-0.549745,0.9051678,-0.7820172,0.6193137,0.958673,-0.1371157,-0.2685533,-0.5745296 +0,user1798,0.6952751,0.3890348,-0.06342791,0.9976126,-0.6428377,-0.192634,0.07954815,-0.7841077,-0.7024152,-0.1718724 +0,user1799,0.1860797,-0.5221448,0.1261388,-0.985932,0.5378756,0.1879169,0.932758,-0.8407806,-0.5464298,0.6097029 +0,user1800,0.3996383,-0.2069862,-0.889252,0.6411073,-0.6806623,0.178005,-0.1916104,0.6031941,-0.6878262,-0.1364619 +0,user1801,0.1805592,0.5415171,0.3888653,-0.1042889,0.1063901,-0.7281721,0.9071924,-0.1154557,0.03761493,-0.02834543 +0,user1802,-0.1912226,-0.2196669,0.5467316,-0.4526407,-0.8337548,0.9585545,-0.2339497,-0.04361327,0.500297,-0.7817315 +0,user1803,0.5900061,-0.1612565,0.6887671,-0.4348789,-0.758037,-0.9229293,-0.8140576,0.6439307,-0.9707215,0.8111941 +0,user1804,0.714017,0.02873392,0.09687403,-0.43094,-0.9186844,-0.6130853,-0.3624508,0.3928594,-0.7996245,-0.4306309 +0,user1805,0.5829069,0.0505939,-0.02979695,0.4249607,-0.3693069,0.2523134,-0.8213882,-0.3364396,0.08950948,0.557798 +0,user1806,-0.4421213,-0.3164029,0.1317192,-0.733728,0.8487189,-0.3825098,0.7776041,-0.5911759,0.5087929,0.3443555 +0,user1807,0.0137083,0.7801723,-0.3076784,-0.9481268,0.1830221,0.198677,0.0246132,0.2970764,-0.08753533,-0.3317282 +0,user1808,0.448957,-0.1977745,0.9406589,-0.3738222,0.2627787,-0.8457438,-0.6290942,-0.07898487,-0.2588556,-0.31738 +0,user1809,0.3656857,0.111199,0.3147033,0.110454,-0.94161,-0.3083756,0.8039023,-0.4991818,0.5430125,-0.6862927 +0,user1810,-0.9587246,-0.9527549,-0.748057,0.4118697,-0.3246562,0.2625557,0.5862112,-0.1311351,0.5319498,-0.5450351 +0,user1811,0.5110434,0.1369064,0.4615193,-0.8806317,0.1578416,-0.09281241,0.2437259,-0.5226172,-0.9423979,-0.1233093 +0,user1812,-0.6648062,0.1679043,0.8735975,0.1532302,-0.3596395,-0.6635979,0.7923582,0.4343466,0.202955,-0.216282 +0,user1813,0.0778176,0.7481096,0.1637499,0.1127091,0.9596453,-0.02008293,-0.6115105,0.7873851,0.947127,0.8825349 +0,user1814,-0.8664041,-0.9715739,0.7695351,0.9905733,0.001854072,-0.8362355,-0.8256979,0.09849054,0.3684597,-0.1848029 +0,user1815,-0.375368,0.4833119,0.4907434,-0.7229368,-0.4833829,0.9793562,-0.3523885,-0.9464143,-0.9653346,-0.1408944 +0,user1816,-0.5423871,0.9676354,-0.6511233,0.9373212,-0.5104958,-0.2705316,0.6235523,-0.7842539,0.0004674257,-0.9202267 +0,user1817,-0.656497,0.1072433,-0.6985506,0.5243669,0.4417044,-0.2334573,0.6261621,0.8793118,-0.9787444,-0.9252059 +0,user1818,0.02646309,-0.2197644,0.5612765,-0.5859873,-0.4791073,0.8969474,0.658992,0.6441014,-0.8338059,0.2794685 +0,user1819,-0.9886728,0.6744817,0.7423347,-0.05716302,0.929839,-0.983074,-0.2581751,-0.1357382,-0.5176726,0.6471396 +0,user1820,0.7500572,0.1463099,0.2100388,0.4551683,0.1377704,-0.8925502,-0.231128,0.5701944,-0.1295024,-0.1893409 +0,user1821,-0.1013503,-0.7504035,-0.2256412,0.1647488,-0.7465072,0.5842297,0.0860165,0.2106906,0.3324762,0.03200758 +0,user1822,-0.07294973,0.203865,0.3157048,-0.7583693,-0.9517062,0.8390358,-0.1168084,0.819405,-0.9919256,0.08996973 +0,user1823,0.4940678,0.3631933,0.323379,0.06826854,0.5861591,0.9143343,-0.9475798,0.3537671,0.9299339,0.7933368 +0,user1824,0.07846464,0.9577413,0.1309594,-0.4831255,0.4055162,0.1804691,0.1160113,0.3285981,0.6633497,-0.3027236 +0,user1825,0.4636767,-0.3913938,-0.2917514,-0.8180816,0.5247339,-0.08854201,0.1264758,0.6751264,-0.6099179,0.3641674 +0,user1826,0.7053116,0.1569816,-0.4255037,0.4148737,0.6215855,0.9096409,0.8671775,0.8318687,0.7972323,0.7444423 +0,user1827,-0.6224399,0.09013638,0.6287157,0.2412585,-0.1968364,0.3022961,-0.1530579,0.5138221,0.3690122,-0.8531746 +0,user1828,-0.00336381,0.7791789,0.1585036,-0.9129138,0.7427166,-0.4692283,0.08514881,-0.4619893,0.1215289,0.7896378 +0,user1829,0.4005867,-0.4539836,0.5110684,0.4124862,0.9787479,-0.282993,-0.0532744,-0.9522389,-0.9051829,-0.4274301 +0,user1830,0.5636398,0.5679916,-0.2451455,0.2553265,-0.6589609,-0.509787,-0.2202999,0.6730415,0.8225824,0.7565282 +0,user1831,-0.6037255,-0.4278073,0.2692516,0.7281936,-0.9379457,0.7087766,0.8935384,-0.8587952,0.4337027,-0.3468241 +0,user1832,-0.4188541,-0.9124665,-0.1000663,-0.6918027,0.08513802,-0.01116518,-0.146082,-0.06769461,0.132432,0.5442245 +0,user1833,-0.6275828,-0.6516753,-0.6984139,0.8026858,-0.4927157,-0.5512325,0.5457504,-0.3705717,0.3228794,0.9747967 +0,user1834,0.9862806,0.4109362,-0.04198131,-0.7066853,-0.6959827,0.7858473,-0.9205192,0.7851356,0.4629812,-0.53563 +0,user1835,-0.7048371,0.1162674,0.9968078,-0.1227427,0.1664536,0.3757495,0.4914672,-0.6748352,0.3328075,-0.8864064 +0,user1836,0.9553241,0.3989186,0.2717892,0.2276465,0.1379773,0.7010808,0.7243622,0.2929886,-0.5876111,0.5325947 +0,user1837,-0.4558407,-0.9054667,-0.9102621,-0.4404133,-0.8472637,-0.5966624,0.857085,-0.8060404,-0.02822594,0.8087255 +0,user1838,0.3088712,-0.1035603,-0.3108707,-0.07086949,-0.6505243,-0.4255735,-0.4839196,0.6222412,-0.7547278,-0.2181346 +0,user1839,0.4042811,-0.7988559,0.2124481,0.8538243,-0.5992439,0.855337,-0.904732,-0.7859962,0.1535332,-0.7847854 +0,user1840,0.909845,0.2057323,0.4044412,0.6700406,-0.7888737,0.09496203,0.6609873,-0.3052221,-0.4852135,-0.8775672 +0,user1841,0.3501466,-0.05631515,-0.05892769,-0.6589998,0.02481949,0.8369822,-0.8977084,-0.5088939,0.777222,0.2368302 +0,user1842,-0.08467548,0.3380505,-0.3260326,0.9731926,0.5585977,-0.2374754,0.338994,-0.3086134,0.2111354,0.09190528 +0,user1843,-0.7549613,-0.6263634,0.2780387,-0.1767292,-0.1485133,0.4313642,0.4533455,-0.8708755,0.7177415,-0.09384923 +0,user1844,-0.5720358,-0.3082055,-0.8951778,0.4537093,-0.01553519,-0.1831007,-0.5092189,-0.7215088,0.7243489,0.1193652 +0,user1845,0.04892045,0.3664766,-0.5564975,0.9637659,-0.4395483,-0.07371093,0.5132961,0.7898771,-0.420405,0.9071024 +0,user1846,-0.1303293,0.8569485,-0.2312179,0.100334,0.3681039,0.4107203,-0.8990429,-0.8172899,0.752407,0.7652564 +0,user1847,-0.1144229,-0.3405701,-0.5463011,0.3910305,0.4739691,0.5463677,-0.8856666,-0.5057627,-0.2751836,0.1991384 +0,user1848,0.3924234,-0.5262801,-0.2550481,0.4881328,-0.9978439,0.6928317,0.1394582,0.6691889,-0.3991494,0.9818964 +0,user1849,0.8961338,-0.3628159,-0.6699414,0.5143467,0.8889965,0.3076677,0.7599491,0.8268115,0.918601,0.04472489 +0,user1850,-0.1030958,-0.6660884,-0.8039664,-0.6661325,0.4038081,0.5632937,-0.1438417,0.3584991,0.2071437,-0.1537219 +0,user1851,0.1424806,0.6200298,0.9549907,-0.05669886,0.1399265,0.8002815,0.9083303,0.2393833,0.4713482,-0.2074445 +0,user1852,-0.2052165,-0.1132193,0.1044174,-0.3209045,-0.8575107,-0.1081025,-0.1540344,0.03750206,0.2510772,-0.9232675 +0,user1853,0.8239545,0.5377766,0.5117384,-0.4245018,0.4521019,0.4023295,0.7393499,0.1779041,0.2152181,0.9362478 +0,user1854,-0.3634515,-0.01677697,0.2783697,-0.9884303,-0.2739143,0.7146158,0.9607505,-0.4068497,0.4012821,-0.4141077 +0,user1855,0.8732481,-0.1554781,-0.7646232,0.19597,0.5480055,-0.9276334,0.9619769,-0.6338999,-0.08557312,-0.2259912 +0,user1856,0.2876312,-0.8536172,-0.780013,-0.2425833,-0.02316426,-0.6862125,-0.1341743,-0.1469694,0.6053003,0.3004152 +0,user1857,-0.6581399,-0.8597954,0.852866,0.4264433,-0.6523288,0.6242567,0.8279279,-0.5749809,0.1985144,-0.6696654 +0,user1858,-0.7491917,0.9346583,0.8640925,-0.5627714,-0.6488309,0.3746626,-0.191081,0.8799223,-0.716561,-0.07916578 +0,user1859,-0.7157326,0.9255616,0.3784906,-0.1554971,-0.2804476,-0.1554408,0.9509745,0.3910413,-0.2731709,0.09005303 +0,user1860,0.7424468,-0.313779,0.3639344,-0.1610705,-0.6735809,-0.6587363,-0.2253465,-0.5272199,0.2933315,-0.09709547 +0,user1861,0.814448,0.5026499,-0.381053,0.6925551,-0.3077918,0.8648756,0.5886191,0.5529638,-0.8939786,-0.3226376 +0,user1862,-0.3194581,-0.5022456,-0.3522578,-0.4273035,-0.2183933,-0.4466642,0.8445129,0.5322461,-0.8394682,0.7432289 +0,user1863,-0.6764073,-0.2262455,-0.7361319,0.1471269,0.4115571,0.3300985,0.6285715,0.4050855,-0.5742365,-0.552871 +0,user1864,-0.8131347,0.8509745,-0.07946684,0.4952409,0.1994925,-0.6863569,0.1343695,-0.817608,0.4289008,-0.3478409 +0,user1865,-0.3331776,0.9086906,0.6057609,-0.1339889,0.085624,-0.6608168,0.9239938,0.3173816,0.623513,-0.792401 +0,user1866,-0.3812444,0.8900219,-0.7393242,-0.9756158,-0.4219893,-0.294152,0.1200387,0.7302503,0.758571,-0.4392774 +0,user1867,-0.8578106,0.2498931,-0.8076777,-0.2771126,-0.6625301,-0.9852761,-0.1412683,0.4753807,0.8412897,-0.8152462 +0,user1868,0.2109817,-0.9967761,0.6954988,0.4255978,0.2383603,-0.2574792,0.7810787,0.5113413,-0.404713,-0.9836755 +0,user1869,0.9276268,-0.2135384,-0.05019483,-0.04648529,-0.07251362,0.2802745,0.6361192,0.3524915,-0.9961567,0.3425879 +0,user1870,0.5464705,0.4510372,0.4047704,-0.4232883,-0.2617741,0.8700609,-0.04600025,0.6893845,-0.005177043,-0.6000316 +0,user1871,0.1208267,0.2089562,0.09993995,0.09563844,0.4494865,0.8374828,0.442066,-0.7938808,0.1100736,-0.8612427 +0,user1872,0.2777734,0.7301465,0.8908775,0.2945149,0.9523059,0.1172567,0.7384107,0.8435976,0.7810652,-0.4205819 +0,user1873,-0.538205,-0.2109123,-0.9212622,-0.4500957,-0.7031764,-0.3674145,-0.7070063,-0.619229,-0.7940417,0.4918737 +0,user1874,0.3658654,0.5825928,-0.6220214,0.9189093,-0.6990267,0.268847,-0.1045884,-0.6647564,-0.1721849,0.04490805 +0,user1875,0.7057376,-0.578059,0.9956997,-0.2517758,-0.06322932,0.9341559,-0.7708082,-0.8779112,0.5054142,0.6987833 +0,user1876,0.5107155,-0.8444357,-0.4777597,-0.4863298,-0.1427247,0.5588746,0.8062898,-0.8293519,-0.2144466,0.398976 +0,user1877,-0.7644638,0.4395413,0.1467607,0.01924327,0.6690771,-0.3204327,-0.003631379,-0.4820463,-0.4197779,-0.1898356 +0,user1878,-0.4086854,0.08137085,-0.5506014,-0.8607452,-0.5892603,0.4805236,-0.6564748,-0.3836739,-0.7697695,-0.1020782 +0,user1879,-0.09686114,-0.3707159,0.2671922,-0.998197,-0.1405685,0.2517063,-0.05425198,0.839837,0.386404,0.3808725 +0,user1880,-0.86833,-0.9232746,0.4768193,-0.46641,0.5580737,0.987235,-0.2436823,-0.6552348,-0.5011769,0.8548893 +0,user1881,0.4882189,0.4152824,-0.3545678,-0.5268777,0.8145478,0.04381731,0.1996835,0.9748252,0.4373743,0.7441998 +0,user1882,-0.9543805,-0.7506861,0.2221829,-0.05489584,0.999358,0.05198785,-0.1459217,0.07922028,-0.1422478,-0.826572 +0,user1883,-0.07354652,-0.03649392,-0.4187632,0.2126855,0.700563,-0.1208675,0.6022833,0.3822673,0.7499003,0.9316218 +0,user1884,0.3121733,-0.04694104,-0.8428294,0.04862052,0.2666497,-0.5538532,-0.06096662,0.1527294,-0.3474076,0.6804476 +0,user1885,-0.3178321,0.2325369,-0.4994474,-0.04332616,-0.2745564,-0.2333963,-0.1851712,0.6723706,-0.7409657,-0.2406797 +0,user1916,-0.6560802,-0.146972,0.4081848,-0.6995652,0.3054661,0.6512232,0.6228379,0.6712577,-0.1864965,-0.2548209 +0,user1917,-0.9775286,-0.485978,0.203999,0.7035674,0.9953709,-0.1567031,-0.4378735,-0.7075135,-0.2047849,-0.4759603 +0,user1918,-0.3885277,-0.789558,-0.2925616,-0.1431536,-0.05891279,-0.1698202,-0.9431566,-0.2266758,-0.1963727,0.9246324 +0,user1919,0.3679478,-0.7742305,0.7616033,-0.3164481,-0.621419,-0.9579164,-0.7344054,0.7686473,-0.7289479,0.834834 +0,user1920,0.07298132,-0.7432916,0.8847051,-0.4505485,0.5951085,0.1694586,-0.06469435,0.9207762,0.7429813,-0.8494955 +0,user1921,0.4955443,0.2354453,-0.5369133,0.5073865,0.904125,-0.5653267,0.8126771,-0.8298745,0.7883491,0.9955483 +0,user1922,-0.8655774,-0.715268,0.4789562,0.9055987,0.7781148,-0.2257924,0.6830049,-0.6611829,0.02193225,0.8273935 +0,user1923,0.9379392,0.5020445,0.1843583,0.0878907,0.8870543,0.3604959,-0.1028961,0.1020297,-0.203231,-0.5456682 +0,user1924,0.06015816,-0.2417969,-0.1335229,-0.269377,0.6487695,-0.4074973,0.4130236,0.09917274,-0.06639725,0.8096931 +0,user1925,0.2244901,0.117449,0.4601772,-0.7252277,-0.4107942,-0.1635698,-0.2710133,-0.6859276,-0.8014241,-0.7329181 +0,user1926,-0.01023758,-0.4016448,0.4045446,0.1215708,0.3784926,0.8651763,0.9932717,-0.5343248,0.2794575,0.4103183 +0,user1927,0.2915945,-0.8103486,-0.1243716,-0.1801293,-0.520962,0.08951529,-0.0626361,0.3456017,0.7023694,0.8314368 +0,user1928,-0.06668686,0.840188,0.702074,-0.3316699,0.9783074,0.6045007,-0.1049928,-0.9804219,0.1337906,0.2674929 +0,user1929,-0.816225,-0.05544094,0.8170532,0.8781384,0.2074008,-0.6154194,0.9481713,0.3047014,0.6034358,-0.4489415 +0,user1930,-0.2659875,0.6243236,-0.4197215,-0.6652838,-0.4523332,-0.6709514,-0.7572171,0.1033718,0.06642309,0.869505 +0,user1931,-0.4302369,0.3493886,-0.1062239,-0.9845974,-0.7051046,0.6528457,-0.3028531,0.07757522,-0.9271515,0.6104918 +0,user1932,-0.07574195,-0.2581999,0.6343323,0.2114177,0.774535,-0.2259542,-0.1429294,0.833112,-0.07776303,-0.9082329 +0,user1933,0.2972573,-0.7320479,0.3848685,-0.05479983,-0.9342179,0.4060648,-0.009732082,0.06726119,0.5405504,-0.9536695 +0,user1934,0.4839864,-0.4112644,0.9763556,-0.34301,-0.4362107,-0.1815526,-0.7623027,0.9791699,-0.2070284,-0.4670911 +0,user1935,-0.8734639,0.3281288,0.5303491,0.09460124,-0.3615073,0.7960966,-0.9410364,-0.2577063,-0.5530035,0.1243495 +0,user1936,0.2263674,-0.5058267,-0.4325628,0.4745934,0.8848707,0.7519279,-0.3668355,0.3663941,-0.1575073,0.268064 +0,user1937,-0.8960527,-0.7499763,0.05463476,-0.9531983,0.7694538,0.9182051,-0.9925605,0.002853502,0.01850899,0.1541093 +0,user1938,-0.1604704,-0.9299782,0.9486062,0.491455,-0.6402743,-0.6229781,0.06714635,0.8221235,-0.2426906,0.5559078 +0,user336,0.2923505,-0.6439533,-0.18179,-0.9971561,0.5461124,0.8600085,-0.3845142,0.5546275,-0.6854702,0.3799971 +0,user337,-0.3134865,-0.5306399,0.007590361,0.6951007,0.8428244,0.5284205,-0.7521154,0.4788613,-0.4906398,0.04563812 +0,user338,-0.8030399,0.9759585,-0.6482814,-0.07292244,0.2196071,0.08797646,0.3088836,-0.4233629,-0.7232289,-0.5762416 +0,user339,0.6020352,-0.8573757,0.3210477,-0.6908799,0.8403209,-0.7900937,-0.6581356,0.4474891,0.956767,-0.8388179 +0,user340,-0.9429983,0.03880135,-0.96084,0.6209282,0.3134456,-0.6801442,-0.8071282,-0.4059562,-0.3747497,-0.941713 +0,user341,-0.9033297,-0.1247937,0.006110868,0.2238173,0.6314307,0.2041753,-0.7570783,-0.3974581,-0.7996259,0.6181257 +0,user342,0.5477779,0.2441274,-0.753494,0.8712051,-0.1091429,0.2465712,-0.08334883,-0.2853973,0.4904,0.922876 +0,user343,0.8719734,-0.5503089,0.3745766,-0.8930467,-0.9629896,-0.0826979,0.2560725,0.8483762,-0.9159242,-0.5782126 +0,user344,0.3609894,0.9882906,-0.7019698,0.8016118,-0.5414032,-0.07690825,-0.4069083,0.9947409,0.7573166,0.4602361 +0,user345,0.9979946,0.188295,-0.793973,0.01532143,0.8154713,-0.6061967,-0.7218202,0.07532869,0.577942,-0.8405032 +0,user346,0.3212411,0.6437523,-0.85824,0.431613,0.9781586,-0.1861181,0.9083735,-0.9242406,-0.8014946,0.2342376 +0,user347,0.7884853,0.4691984,0.005864973,0.855669,0.4132455,0.3493108,0.7966132,-0.3172107,0.3227594,-0.3308432 +0,user348,0.5936695,-0.6326936,0.8186673,0.6480021,-0.7668535,-0.01117435,0.001413166,0.2995145,-0.796093,-0.8722598 +0,user349,0.5336909,-0.5022343,-0.8466654,0.7181034,-0.4329373,-0.3351885,-0.3568543,-0.7228824,-0.483741,-0.3784094 +0,user350,0.8825189,0.4189032,-0.2159497,0.2753009,0.9603826,0.1075388,-0.8854279,0.07491671,0.4777222,-0.5023213 +0,user351,0.5320168,0.557483,-0.1745064,0.3308165,0.2273553,0.01672564,0.3579792,-0.4917403,-0.918254,0.08382299 +0,user352,0.2501727,-0.4178995,0.04911726,-0.2962873,-0.8726948,-0.8588197,0.07462943,0.4153186,-0.5332991,0.3673219 +0,user353,-0.158798,-0.8751146,0.2940591,-0.330375,0.3722233,-0.7179024,0.549713,-0.5871634,-0.455675,-0.386106 +0,user354,0.1882797,0.8226552,-0.48571,0.8191003,-0.9268962,-0.9734228,-0.6181913,0.878171,0.4295092,-0.9270202 +0,user355,0.01252043,-0.4882925,0.4157385,0.4044221,0.005820188,-0.2315389,0.3683041,0.1309425,0.8038702,0.1833028 +0,user356,0.3962105,-0.8527256,0.2377379,0.772806,-0.553734,0.1757762,0.6173255,-0.1353522,0.1782083,-0.5993155 +0,user357,-0.4845265,0.8769323,0.9269288,0.1886632,0.5704514,-0.2877133,0.6900962,0.6408718,0.3707781,0.1267613 +0,user358,0.4170116,-0.2945009,0.9067543,-0.7481478,-0.2915105,-0.5451577,0.9325552,-0.5069309,0.3476812,-0.8695331 +0,user359,0.4844566,0.8254596,-0.1995983,0.921597,0.335485,-0.2518288,-0.4469746,-0.2551261,-0.7565905,0.3907319 +0,user360,-0.1794149,-0.0281785,0.5975476,0.9664172,0.7881277,0.199874,0.6410806,0.1023362,0.8733048,0.1060525 +0,user361,-0.7807433,-0.06548985,0.7148635,0.3311389,0.5918619,-0.8254385,0.8003351,-0.4766366,-0.7996758,-0.8892327 +0,user362,0.5065219,-0.9665985,-0.5108699,-0.4959514,0.07225842,0.3852574,-0.8816266,-0.1492391,0.8943746,-0.9966945 +0,user363,0.2653139,0.7973806,-0.8912301,0.2430039,0.6605829,0.9867096,0.8551934,0.724627,-0.3791838,0.7361322 +0,user364,0.8524158,0.163651,0.6292841,0.1879653,0.1096953,-0.4645778,0.9338757,0.7283137,0.8952382,-0.2033384 +0,user365,-0.5207769,0.08072051,0.7315499,-0.3925126,-0.6845141,0.9858872,-0.5911407,0.2559807,0.05917951,0.2484535 +0,user366,0.9450469,-0.6044237,0.1469695,0.6824334,-0.9642186,0.956715,0.9865815,0.9136068,0.7429971,0.6412118 +0,user367,0.1447663,0.5196977,-0.5525059,0.1908092,-0.3441923,-0.6045693,-0.4506384,0.2829412,-0.790232,-0.8233413 +0,user1939,0.3910138,-0.8400642,-0.1032335,0.02322982,0.3730364,-0.2226418,0.2724297,-0.8165192,-0.2753428,0.2999619 +0,user1940,0.3152228,-0.007317461,-0.4176874,0.5758681,0.3858581,-0.5015136,-0.879293,-0.3571368,0.4742769,-0.3267686 +0,user1941,-0.544338,0.4411989,0.6340556,0.8901118,-0.0596098,-0.7903464,0.02107717,-0.2582098,-0.5459736,0.3683386 +0,user1942,0.6873301,0.9024238,-0.2970847,0.1054562,-0.5807242,0.7900236,-0.3319874,0.3453327,0.1056447,0.1867492 +0,user1943,-0.9852828,0.1506238,-0.2445773,0.5780568,-0.1831898,-0.8774151,0.433658,-0.7423019,0.367419,0.9365534 +0,user1944,-0.8825861,0.06168995,-0.4583122,0.2338727,0.5204127,0.09427312,0.8290863,-0.2593227,0.008495558,0.3541974 +0,user1945,-0.08990003,-0.3915822,0.09030077,-0.5996319,0.1660782,0.6818215,0.6658788,0.8894518,0.2365326,0.005158256 +0,user1946,-0.9736151,-0.7383759,0.08570339,-0.371134,0.514412,-0.8071697,0.6856423,0.02526239,0.9131536,-0.119677 +0,user1947,-0.5386663,0.9147179,0.9498725,0.5343075,-0.1741212,-0.2545037,0.4519241,-0.588065,0.821999,-0.9006235 +0,user1948,-0.06742859,0.1224398,-0.7057002,-0.8960645,0.1614491,-0.4748816,-0.7719947,-0.8180617,-0.9682524,0.5291979 +0,user1949,-0.3621428,-0.5279339,0.7931418,0.4857124,-0.5445008,0.02301011,0.7424857,0.7985866,-0.2832191,-0.1950445 +0,user1950,0.8292815,-0.8595126,0.7114758,-0.7821405,0.2044598,-0.2124201,0.7175187,-0.8194177,-0.9069489,0.9342105 +0,user1951,-0.9944473,0.3791482,-0.820995,-0.346613,-0.2434424,0.694577,0.1633109,-0.8972855,0.7747289,0.6797025 +0,user1952,-0.8665985,0.7075114,-0.7437716,-0.006901077,-0.6403757,0.4576834,0.5551628,0.9687121,-0.49487,-0.1994963 +0,user1953,0.9637041,-0.5747805,0.190432,-0.8765419,-0.01742539,0.5617875,0.4005236,-0.4806006,0.1149834,0.761604 +0,user1954,0.943492,-0.1188073,0.3633632,0.7412777,-0.3563881,0.05507294,-0.9395851,0.2047442,-0.4285021,-0.8659657 +0,user1955,0.1935597,-0.5342855,0.1227056,0.723722,-0.9916062,-0.9498139,-0.0318136,0.06788483,0.4387327,-0.3898032 +0,user1956,0.1881941,0.5426685,-0.3493908,-0.6017696,0.5717804,-0.6017824,-0.8704897,-0.1665281,0.3135593,-0.9713141 +0,user1957,-0.0667456,0.479548,-0.2320922,-0.1371515,-0.9778955,-0.07975076,-0.9463134,0.6704194,0.8509555,0.5443526 +0,user1958,-0.5148459,-0.3446341,0.998334,-0.4564073,-0.5125682,0.1397014,0.9055503,-0.5865135,0.1411021,-0.5583664 +0,user1959,-0.8784927,0.3828565,-0.6473168,0.06656046,0.5500878,-0.9972817,0.02451745,-0.14695,-0.5526502,0.2961788 +0,user1960,0.1170294,-0.575893,-0.4150389,-0.2590131,0.2295053,0.3048298,-0.9981421,-0.02487928,0.4543912,-0.9045889 +0,user1961,0.2191666,-0.7203105,-0.4213875,-0.121691,0.03509855,0.4687501,-0.8516668,0.5168583,-0.7924748,-0.6888614 +0,user1962,-0.3087297,-0.267755,0.2464593,0.08196306,0.8449832,0.6555641,0.7216643,0.9306252,-0.4798017,-0.09332935 +0,user1963,-0.9587126,0.1659071,-0.7807067,0.9524046,0.004040299,-0.9211243,-0.1410715,-0.1917673,-0.6233718,-0.8128218 +0,user1964,-0.4835761,-0.4523585,0.963481,0.8235091,0.1008806,-0.1251852,0.1386011,-0.4158805,0.7480756,-0.642531 +0,user1965,-0.8247433,0.3209806,0.2228148,0.7389531,-0.5912275,-0.5259885,0.9593616,0.9097951,0.3131699,0.4395795 +0,user1966,-0.8321764,-0.5059641,0.7496424,0.0470058,0.642533,0.8749723,-0.08210798,0.5505264,-0.1763753,0.3115277 +0,user1967,0.7427914,0.04181483,-0.4690819,0.2981025,-0.01424873,-0.3732573,0.7717656,0.9505136,-0.4094317,0.625533 +0,user1968,-0.7207961,0.5710043,-0.7225504,0.7857548,-0.8217736,-0.6077834,0.9668011,-0.08735137,-0.6683211,-0.4063112 +0,user1969,0.007353211,-0.4359423,0.6982487,-0.4615392,-0.9977413,-0.7480058,0.9850384,0.3726499,0.5809342,-0.1325645 +0,user1970,0.1338052,0.2017506,0.4276847,-0.6786677,-0.6412123,0.4041009,0.04419528,-0.8660055,0.3152255,-0.07450503 +0,user1971,0.5944267,-0.4363132,-0.1402378,0.3616229,0.5640845,-0.109297,-0.9124919,0.5555118,0.8059558,0.2669202 +0,user1972,0.4630152,-0.9947434,0.3323042,-0.5714274,-0.05735113,-0.5383522,0.006115542,-0.8855599,-0.9650394,-0.7642259 +0,user1973,-0.1788647,0.1041744,-0.8694,0.4267885,-0.2219365,0.1941246,0.7122079,0.4793271,-0.5791299,-0.8877558 +0,user1974,0.6091439,0.7143107,0.6151849,-0.06032034,-0.6191052,0.01328791,0.5211661,0.8132099,0.1733749,0.2034737 +0,user1975,0.5804291,0.0669466,0.873992,0.6624453,-0.5369384,0.5559209,-0.1647982,-0.1448826,0.04345612,0.5899715 +0,user1976,0.7312353,0.7125922,0.2209007,0.8271566,0.9441417,-0.1240539,0.3780867,0.3687789,0.6574027,0.1174024 +0,user1977,0.6355288,0.9759348,-0.2991117,0.5685457,0.8953068,0.2061183,0.2068084,-0.1615277,0.08652847,-0.9162033 +0,user1978,-0.9582372,-0.01833547,0.8238645,0.1967528,0.2889404,-0.6985828,-0.712874,0.2670524,-0.1345449,0.689348 +0,user1979,-0.3361933,-0.164968,0.5152005,0.9310921,0.1055907,0.4010645,0.606092,0.5507173,0.6891503,-0.3533996 +0,user1980,-0.726614,-0.5519992,-0.50597,0.0542581,-0.6491939,-0.7708716,-0.05070585,-0.3629411,0.8033093,-0.1112478 +0,user1981,0.8710443,0.122152,0.5353403,0.4146123,-0.5065998,0.08899705,-0.9953553,0.4476347,-0.04149374,0.6235585 +0,user1982,-0.3306406,-0.7858198,0.6942055,-0.4155209,0.8621483,0.09564157,-0.2305971,0.6534318,0.4638793,-0.6736972 +0,user1983,-0.5932125,-0.8444878,-0.2497415,-0.952643,-0.2895697,0.6868118,-0.4955431,-0.394229,-0.6915607,0.6892559 +0,user1984,0.8347484,0.5473714,-0.2742277,0.5380704,0.4759748,-0.3492155,0.4051683,0.9670341,-0.9265104,0.3851625 +0,user1985,-0.3871486,0.09537295,0.05756876,-0.6742432,-0.4942398,-0.8492855,-0.1701822,-0.141824,-0.9646228,-0.5396628 +0,user1986,0.6003472,-0.3787733,0.872964,0.771079,-0.2811759,0.7369979,0.4726433,0.6736558,0.747172,-0.7005473 +0,user1987,0.02294249,0.09003991,0.3763815,0.9363008,0.04775517,0.04900215,0.5346786,-0.199494,0.3870489,0.4138484 +0,user1988,0.5461058,-0.4250791,0.8254766,0.1886053,-0.4721353,0.07096374,-0.1164956,-0.4714046,0.8863327,-0.9953103 +0,user1989,-0.9144987,0.2765926,0.871298,-0.6853283,0.2062559,-0.1233006,0.3781936,-0.9128577,-0.1117259,-0.2589137 +0,user1990,0.1444498,-0.5271036,0.7290647,0.002861247,-0.402157,0.05172049,-0.440804,0.653556,0.8343987,-0.2899728 +0,user1991,-0.3368648,-0.0009720442,-0.5895623,0.9295922,0.75737,-0.6242064,-0.1146377,0.5037161,0.3407239,-0.8998992 +0,user1992,0.304668,0.5562821,-0.5500895,0.1929807,-0.7586456,-0.6545506,0.5265268,0.6040006,0.09579934,0.05222482 +0,user1993,0.8357201,0.2051414,-0.02447602,-0.9151757,-0.5571738,-0.2927154,-0.7191397,0.5841812,-0.645403,0.6166979 +0,user1994,-0.2955774,-0.8350649,-0.370269,0.8819968,-0.2385897,-0.5453307,0.7442908,-0.6880512,0.7173522,-0.712721 +0,user1995,0.8210919,-0.8960764,-0.5866085,0.01648978,0.342235,0.2202642,-0.334872,-0.8118798,-0.1561251,0.4096939 +0,user1996,-0.9890232,-0.473878,-0.8016612,0.8237774,-0.1484012,0.1812961,-0.7597781,0.4939763,0.667767,0.05627743 +0,user1997,-0.1277538,-0.341029,-0.6206266,-0.07099742,-0.5960567,-0.6703585,-0.3378172,0.8624752,-0.4590231,0.5988067 +0,user1998,0.5638832,0.1457384,-0.05569035,-0.6854077,-0.6720137,0.8470069,-0.5631064,-0.8613662,0.4344432,0.03522688 +0,user1999,-0.7098193,-0.9028737,-0.5242116,0.6095322,0.02982514,0.5735127,-0.792977,-0.5933751,0.9994459,0.6499662 diff --git a/psi/psi/demo/data/carol.csv b/psi/psi/demo/data/carol.csv new file mode 100644 index 00000000..746da5c3 --- /dev/null +++ b/psi/psi/demo/data/carol.csv @@ -0,0 +1,1471 @@ +idx,id,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30 +0,user1,0.9264572,-0.2929374,-0.8102698,-0.2389197,0.9038257,0.5116486,0.262709,0.8158678,-0.9859694,-0.5366778 +0,user2,-0.7295412,-0.6834114,-0.5448893,0.5960859,-0.9760182,-0.8475172,-0.3086954,0.06318247,0.09662622,-0.7656276 +0,user3,-0.6125294,-0.5361283,-0.2478287,-0.8610704,0.7026384,0.2703772,0.387367,0.4892415,0.5108559,-0.4672541 +0,user4,0.8794397,-0.1839131,0.1844956,-0.8045972,0.124954,-0.806879,-0.8923489,0.4593228,0.253117,0.6504625 +0,user157,0.5497817,-0.2228892,-0.7678228,0.1874041,0.6222314,-0.007237837,0.7844857,0.6900367,-0.3272147,0.8203362 +0,user158,0.1890433,0.9440814,-0.6163664,0.03494944,-0.5090827,0.9760629,-0.4883336,0.6697729,-0.1845304,0.3325072 +0,user159,-0.5829085,-0.7506065,0.8588074,0.2254693,-0.3503985,0.478229,-0.7085337,0.06871525,0.4683033,0.7787093 +0,user160,-0.04253117,0.296676,0.6209085,-0.2262552,-0.08834724,0.31911,0.2677762,-0.7830622,-0.726899,0.2184698 +0,user161,0.1395033,-0.8483865,0.9577324,0.6879787,0.4679057,-0.2492476,-0.7750843,-0.3542998,-0.4423741,0.6897514 +0,user162,-0.2719464,0.6634818,0.8005402,0.1618461,-0.8311669,0.0996451,0.1435296,0.4486895,-0.1804239,0.5226853 +0,user163,0.4470552,-0.7529652,0.1123422,0.05661929,-0.9260769,-0.6043113,-0.5769407,-0.8185567,-0.8484741,0.7633763 +0,user164,0.4970434,-0.9370066,0.0229695,0.774241,-0.4215399,-0.003976156,-0.4978104,-0.3680707,0.594436,0.7570249 +0,user165,-0.8568498,0.824216,0.8698483,0.1068566,0.9510027,0.9950583,-0.2513249,0.9438933,0.07986587,-0.1441378 +0,user166,0.8425598,-0.4471723,-0.8636348,-0.09182396,0.3070072,0.09927037,0.9529257,0.04669775,-0.4241684,0.5389653 +0,user167,-0.57604,-0.6865276,0.8734428,0.1822812,-0.2001302,0.8447172,0.3065081,0.6922692,-0.5092842,-0.4214327 +0,user168,-0.5208924,0.2601998,0.2190683,0.9068072,-0.569721,-0.4042503,-0.6356393,-0.1655156,-0.8437813,-0.9353777 +0,user169,0.03468102,0.7166605,-0.08383674,0.1214933,0.1672079,-0.09919379,0.3326526,-0.7813788,-0.8930202,-0.6403743 +0,user170,0.4130026,-0.6966978,0.5249773,0.1616897,0.4605943,0.7370435,0.5282072,0.3231317,0.7645445,-0.01624327 +0,user171,0.2145252,-0.04822708,-0.2191949,-0.9240378,0.9542027,-0.07454624,0.4577636,0.6991101,-0.6942417,0.9993069 +0,user172,-0.4421518,0.6341628,0.9611048,-0.517011,-0.1883503,0.693022,0.07266352,0.03733538,-0.391843,-0.925319 +0,user173,-0.9591573,0.8167906,0.5705478,0.1221356,0.5884511,0.1741871,0.9205993,0.6054457,0.5432704,0.6675746 +0,user174,0.1287506,-0.4235098,0.4960633,0.2567411,-0.9036416,0.769934,0.3228984,-0.9364072,-0.4493514,0.5053499 +0,user175,-0.8310334,0.6583576,-0.03329431,0.6890631,-0.2006357,-0.49175,0.4769055,0.1388864,-0.3217478,0.9006806 +0,user176,-0.2459709,-0.8172767,-0.6588459,0.3463415,0.8862969,-0.1414556,0.4000481,0.747067,0.6601421,-0.9640269 +0,user177,-0.257683,0.7522708,-0.6234478,0.7985904,-0.4434783,-0.2710529,-0.082635,0.1097647,0.5884524,0.3501353 +0,user178,-0.196013,-0.5139863,0.6577512,0.1457111,0.9407057,0.4921569,0.5547023,0.4493487,-0.9910805,-0.2435472 +0,user179,-0.5819018,-0.5489203,0.7722551,0.542438,-0.9274769,0.1209997,-0.6465689,-0.1606627,0.6782998,-0.6455108 +0,user180,0.7676755,-0.317886,0.8789732,-0.3510918,-0.9869762,0.390909,0.2963977,0.6630503,-0.6922463,0.295559 +0,user181,-0.3817166,-0.9296777,-0.2250242,-0.9751172,0.9541211,0.7685458,0.9557333,0.5677047,-0.7370045,0.04280058 +0,user182,0.05213162,0.1457357,0.6185781,0.1989911,-0.6579811,-0.8606595,-0.6378137,0.9103225,0.4939497,0.06971283 +0,user183,0.4857462,0.727221,0.4125048,0.9865563,-0.7569205,0.5132169,0.5017667,0.1086306,-0.5012049,0.1250972 +0,user184,-0.784896,-0.1950793,-0.6098701,0.4235949,-0.7348975,-0.8240542,0.6498936,0.01018076,0.7032916,0.04310164 +0,user185,0.03554335,0.7536744,0.1659279,0.07028458,-0.2995177,-0.583904,-0.9074624,0.6782815,-0.6347076,-0.9880444 +0,user186,0.4622067,0.5046275,0.3384557,0.8461146,0.5795746,-0.03483707,-0.3992612,-0.6973984,0.09202364,0.796855 +0,user187,0.8004796,0.4149788,-0.5892234,-0.715878,-0.2910378,0.515851,0.8698638,-0.7295483,0.8354175,0.7872297 +0,user188,-0.4146749,-0.4692148,0.3981051,-0.7423114,-0.6772863,0.4088582,0.8770233,0.3683182,0.03807762,0.8322918 +0,user189,-0.3487501,0.4487089,0.7220893,-0.118936,-0.9295081,-0.05877417,0.1124052,0.9723745,0.9074932,0.1293623 +0,user190,-0.7824289,0.6643723,-0.730416,0.5095913,0.3585637,-0.005920036,-0.83867,0.3391669,0.3037208,0.5659389 +0,user191,0.5427939,0.8274612,0.01901352,0.0314334,0.2343664,-0.2720318,0.1447995,0.585256,0.3111786,0.05076156 +0,user192,0.7907533,0.6003224,0.6798217,-0.4309573,0.5383976,0.6919783,0.3373209,-0.3819252,-0.5348809,-0.1808864 +0,user193,-0.05437532,0.327854,-0.9298758,-0.3285625,0.5273968,-0.9062749,0.3048596,-0.2121436,-0.8767031,0.08862424 +0,user194,-0.0101509,-0.925504,-0.8686443,-0.9119473,0.3082895,0.1236568,0.5678587,0.7666993,0.4627045,-0.1858622 +0,user195,0.2877966,0.6633158,-0.2972088,-0.6567163,-0.8831424,-0.3119979,0.8395105,0.2500041,-0.9404449,-0.4238614 +0,user196,0.08877483,0.15207,0.9399725,0.7782941,0.4783995,-0.9112167,-0.9464653,-0.2682503,0.2031628,0.9444865 +0,user197,-0.1675911,-0.3726763,-0.732279,-0.003771275,-0.3847032,-0.7770728,0.5207844,-0.186603,-0.9614638,-0.6468969 +0,user198,0.7117566,0.9767882,-0.423766,0.5255649,-0.08327257,-0.4672807,0.1460186,-0.05772677,-0.4497291,0.1547058 +0,user199,0.5678824,-0.5877302,0.1590408,0.6851013,0.9086786,-0.315467,-0.5821045,0.5662341,0.3593815,-0.9908913 +0,user200,0.8670899,-0.6560158,0.1838842,-0.8822779,0.7825047,0.1237334,-0.146563,0.03201825,-0.854484,-0.2872712 +0,user201,0.1247592,-0.7199096,-0.8987887,-0.3127453,-0.6226782,-0.7302372,-0.3257742,-0.7345951,-0.6851846,-0.8615374 +0,user202,-0.2175924,0.3640427,0.9398459,0.7610635,0.8628812,0.6099868,0.8756591,0.2653442,0.6651398,-0.9915843 +0,user203,-0.5750619,0.978147,0.144989,-0.399289,-0.4058457,-0.1832446,0.9261005,-0.9306464,-0.246327,-0.2125902 +0,user204,0.1656019,-0.903119,0.6717591,0.8093903,0.9657729,0.4439499,-0.405175,0.8708506,0.8580858,0.8060372 +0,user205,0.9111582,0.9405329,0.4359093,0.01780458,0.9592396,0.3799209,0.1985574,0.3289371,-0.7842115,0.5137656 +0,user206,-0.4060953,0.6365046,-0.8883053,-0.7102258,0.3935187,0.3250054,0.403006,0.20824,0.4319252,-0.3119095 +0,user207,0.919631,-0.7203956,-0.9870867,0.1557317,0.8520697,-0.6975057,0.9948732,0.6179176,0.5182279,0.8420103 +0,user208,-0.3465249,0.6928037,0.8124614,-0.183605,-0.4842387,-0.891132,-0.8840776,-0.5612983,0.8042409,-0.1360992 +0,user209,0.3978917,-0.8774817,0.7694459,0.4354852,0.3342243,-0.1828376,-0.0422917,-0.3424112,0.4408448,0.4445432 +0,user210,-0.6622708,-0.2693159,0.7851683,-0.3018303,0.9245928,0.423494,-0.6516958,-0.542745,0.1965278,-0.8035005 +0,user211,-0.5788494,-0.6250822,0.6914346,0.4653032,-0.4712149,0.499777,0.4123202,-0.898248,-0.8880054,-0.8405402 +0,user212,-0.9838249,-0.8071594,-0.4555783,0.4603681,0.2883455,-0.4142919,-0.08655838,-0.7747066,0.7038403,-0.5126562 +0,user213,0.3898608,0.8764197,0.4037465,0.8971608,-0.7333883,0.5628345,-0.2895095,-0.6324225,-0.3095225,0.2662123 +0,user214,0.9068968,-0.8978613,0.1039395,0.4518595,-0.2281354,0.01299387,-0.08591313,0.2103826,-0.3892102,0.2845571 +0,user215,-0.7687209,-0.002238651,-0.06544836,-0.116037,0.553448,-0.2383461,-0.4366647,0.2354742,0.4071319,0.5304455 +0,user216,-0.5745959,0.6300941,-0.4303256,-0.03255462,-0.03290598,0.9789305,-0.1969719,-0.954141,0.05576982,0.278168 +0,user217,0.3691035,0.6067663,-0.5576049,0.2979741,-0.6485608,0.9781568,0.5148256,0.5129842,0.7028134,0.08141208 +0,user218,-0.9682413,-0.5872599,0.3453283,0.168085,-0.7375898,-0.7224951,-0.566801,0.5059259,0.2425494,0.3176751 +0,user219,0.01072923,-0.8391208,0.9677794,0.225134,0.2898077,0.3877887,-0.3199486,0.4141772,-0.9061526,0.1104598 +0,user220,-0.9796466,0.05547518,-0.8355156,-0.8209619,-0.5780689,-0.08061737,-0.3727692,0.4853587,0.6103066,-0.7892256 +0,user221,-0.7506703,-0.9228876,0.6149123,-0.3223236,0.6209739,0.2715849,-0.4054709,-0.1549072,-0.4537298,-0.1163859 +0,user222,-0.4464769,0.9883404,-0.01320705,-0.7434326,-0.4758258,-0.8842431,0.8248509,-0.0005668844,0.4050261,-0.8387787 +0,user223,0.8111067,-0.3442025,0.8443061,-0.2519192,0.9603287,-0.3886391,0.9645517,-0.8965666,-0.9245743,0.029888 +0,user224,0.1949544,0.4049664,0.6850365,0.3491139,0.1483707,0.36531,0.8993887,0.6329492,-0.3304329,0.9722383 +0,user225,0.5433722,-0.9371636,0.1181487,-0.6553799,0.8324637,0.2394137,0.3927096,-0.2338676,-0.1322694,-0.02464087 +0,user226,0.0989033,-0.6808867,-0.4529027,0.09136448,-0.9228137,0.299363,0.8040623,0.3534375,-0.8650192,0.6060266 +0,user227,-0.7162707,-0.4429636,0.625009,0.1274079,-0.3732298,0.4540933,0.9529235,-0.6353011,0.87273,0.9167248 +0,user228,-0.6242188,-0.3098399,0.3858697,0.3408488,-0.5522395,0.4623409,-0.086506,0.5795294,-0.09373325,0.3284623 +0,user229,-0.1893401,-0.7040985,0.1233313,-0.3830706,-0.006086261,0.8320823,-0.04991917,-0.7042893,-0.3147483,-0.2392676 +0,user230,0.8516116,-0.0306938,-0.2159502,-0.1874908,-0.4645513,-0.8613737,-0.6291811,0.930933,0.2321114,0.9258335 +0,user231,-0.7571289,0.03414428,-0.4302461,0.4585709,-0.7697349,-0.4139257,0.766931,-0.3884524,0.05178273,-0.9588089 +0,user232,0.9354191,-0.4240081,0.2245427,0.3041841,0.3712355,-0.8981549,0.6243066,-0.4388844,6.714417e-05,-0.100805 +0,user233,-0.3659808,-0.6666511,-0.2761043,-0.4264273,-0.6016701,0.7486132,-0.753522,0.1962772,-0.1027487,0.9342492 +0,user234,-0.3321908,0.01229131,0.7147429,-0.9407181,-0.1755806,0.4028297,0.6930315,-0.3190987,0.8054557,-0.1713991 +0,user235,0.101021,-0.3271271,-0.1036982,0.1135743,0.3370084,0.545795,-0.7808683,-0.5680337,-0.1418471,-0.2947678 +0,user236,-0.4548226,-0.7261182,-0.840195,0.5913772,-0.6424305,0.128534,0.4450354,-0.4747858,0.1130398,0.4480148 +0,user237,0.2617139,-0.3512041,0.8264377,-0.6509439,-0.7820619,-0.2721649,0.09603751,0.8891413,0.237381,0.5166914 +0,user238,0.02065197,-0.04752273,-0.09078492,-0.7306939,0.1890781,0.8482893,-0.7859952,-0.9501161,-0.6236191,-0.4527575 +0,user239,0.1986525,0.9666855,0.9722664,-0.5922277,-0.1266692,0.237402,0.5609579,-0.03608405,-0.08271938,-0.6880844 +0,user240,-0.3403944,-0.2286857,0.5958836,0.7845413,0.5521624,0.5449975,-0.9462542,-0.4532699,-0.3217743,-0.03876538 +0,user241,0.3583811,0.6831613,-0.3056166,-0.03252418,0.1136709,0.2717833,-0.437691,-0.4928611,0.5729087,-0.256258 +0,user242,0.6198031,-0.6583967,0.6637011,0.8730755,0.402116,-0.262821,-0.02672194,0.06566795,0.02927523,-0.5286246 +0,user243,-0.3242193,-0.03584507,-0.8596947,0.2449094,-0.1594921,-0.8692944,-0.03281257,-0.2279765,-0.617934,0.4485784 +0,user244,-0.2517581,0.559581,-0.9018701,-0.1353634,0.3802826,-0.1653823,0.2727996,-0.1252836,-0.7366139,-0.9900456 +0,user245,0.5266999,-0.5562579,-0.2323594,0.324935,-0.8260194,0.7501729,0.8873649,-0.7239495,0.640065,0.7559325 +0,user246,-0.09294025,0.9619163,0.0748569,-0.8711276,-0.6060442,-0.1076405,0.5305227,-0.9925024,0.7891979,-0.02097611 +0,user247,0.1736461,0.1896751,-0.3321957,0.832082,-0.6526234,-0.1864518,-0.9241724,-0.07942466,0.319156,0.2881223 +0,user248,-0.1041966,-0.9494917,0.2100357,-0.377091,-0.4745802,0.7283297,0.4021906,0.7890347,0.3428784,-0.1626555 +0,user249,-0.06118158,-0.6253436,-0.5798148,0.2969574,-0.343634,0.1698645,0.9637217,0.5134235,0.03174734,-0.703301 +0,user250,-0.8156247,0.3505543,-0.3644163,0.05721602,0.6371844,-0.798663,-0.244121,-0.6652475,0.4130034,-0.6014179 +0,user251,-0.08384316,0.1059835,0.3745201,-0.1980529,-0.05264916,-0.3522877,-0.9705786,0.2743933,-0.04681501,0.0481189 +0,user252,0.1881482,-0.5482312,-0.9649025,0.9746338,-0.7226601,-0.5585506,-0.4417492,-0.6414837,0.5780175,0.1803131 +0,user253,-0.2621015,0.3388948,0.6223767,0.3137834,-0.8386415,-0.6829062,-0.4192701,0.3341856,-0.1819706,-0.4401966 +0,user254,-0.2727365,0.7617811,0.2188262,0.5500279,-0.09232049,0.2590732,0.9939731,0.3778268,0.02861073,-0.9219931 +0,user255,-0.6168974,0.8567352,0.7201339,0.3237477,0.4257106,0.8067593,-0.5423605,0.9914655,-0.7524153,0.1525514 +0,user256,-0.7187293,0.4017312,-0.2594746,0.6584035,0.9938222,0.5565076,0.9734395,-0.899682,0.68576,0.5351625 +0,user257,0.8261668,-0.9191056,0.7659236,-0.3586076,-0.01513418,-0.4415638,0.7980354,-0.2687357,0.1635915,0.6840335 +0,user258,-0.3331682,-0.5862284,0.3451429,-0.5488444,-0.9475193,0.2608526,-0.589437,-0.6438356,-0.8796854,0.06927616 +0,user259,-0.3429481,-0.9081088,-0.873605,-0.0007476429,-0.5584173,0.01884847,-0.1130665,0.6798474,-0.4079732,-0.1363752 +0,user260,-0.3631733,-0.6232041,-0.1107451,0.2583218,0.9787796,-0.6094815,-0.2518838,0.02697501,0.8488433,-0.5552341 +0,user261,-0.4815565,0.3830778,-0.8708073,0.2636648,-0.4120705,0.399479,-0.2186181,-0.7129027,0.3524261,-0.004890308 +0,user262,-0.100077,0.1260355,-0.3038511,-0.5421768,-0.3281522,0.6049228,-0.3461355,-0.708605,0.6438095,-0.09518415 +0,user263,-0.4277542,-0.04721218,-0.8862024,-0.4374941,0.3500151,-0.5076364,-0.6275772,0.5880906,-0.1510896,0.3439609 +0,user264,0.1524627,0.7164267,-0.1469116,0.8372374,-0.01374061,0.1480921,0.02785989,0.4833745,-0.7503226,-0.07064112 +0,user265,0.5677322,-0.8616732,-0.5891082,-0.4828949,0.4962672,0.00775251,-0.6531039,-0.02770372,0.4492653,0.7334168 +0,user266,0.6732668,0.6256607,0.0100994,0.6760802,-0.3129766,-0.9618414,-0.4084455,-0.9799431,0.7070633,-0.9508069 +0,user267,0.6976401,0.9903085,0.01289339,0.4286147,0.3438289,-0.7233739,-0.5271047,-0.9914113,0.3627171,-0.6226264 +0,user268,-0.1705539,-0.2128772,-0.7626705,-0.1338388,0.7142053,0.7355876,0.4429336,-0.1385624,-0.3133538,0.2501081 +0,user269,-0.3060813,-0.421862,0.9193145,0.9453863,0.8761015,0.8864479,-0.1944407,-0.9300592,-0.9165558,-0.4035644 +0,user270,-0.1037074,0.9569941,-0.01484017,0.836387,-0.7828403,0.5140281,-0.9661468,-0.02749531,-0.7200022,-0.3107108 +0,user271,0.4890518,0.558437,0.833213,-0.3492975,0.2663677,0.2805851,0.4966794,0.4081676,0.364872,-0.7886572 +0,user272,-0.9477001,-0.7387007,-0.3863021,-0.08713789,-0.01022756,0.1582312,0.3678684,-0.4229203,0.6563529,0.3401776 +0,user273,-0.4839043,-0.7014026,-0.3511391,0.7094624,0.6192757,-0.7487928,0.007131271,-0.9618274,0.309273,0.1606646 +0,user274,-0.8351676,-0.477408,0.9735183,0.8956119,-0.8931245,0.4112907,-0.5361332,-0.8198089,0.746938,0.6599212 +0,user275,-0.1994582,0.8208804,-0.2881722,0.7774987,-0.629945,0.9928489,-0.3593321,0.4517961,0.919739,0.350132 +0,user276,-0.9572043,-0.2576605,0.4165015,0.03439739,0.7932563,-0.9986199,-0.1055038,-0.6857768,-0.05066204,-0.08340292 +0,user277,0.07189219,-0.5154918,0.04837521,-0.9755157,-0.4991686,-0.6963498,0.9943895,-0.8123112,0.5361359,-0.3610549 +0,user278,0.9741879,0.01055546,0.3796321,0.6095807,-0.2825683,-0.1936029,-0.2835044,-0.6276286,0.238895,-0.3617457 +0,user279,-0.06140087,-0.2071522,-0.3734628,0.6573064,-0.681324,0.7297098,-0.7033132,-0.8967422,-0.7077837,0.7539416 +0,user280,-0.9892894,-0.1408353,0.4685604,0.3214418,0.1571974,0.4735147,0.9581112,0.7011123,-0.4321167,-0.0643559 +0,user281,-0.8414368,-0.6388902,-0.9847842,-0.3332032,-0.645384,0.00773409,0.4723746,-0.2928761,-0.3481016,0.03683635 +0,user282,0.854756,0.8988313,-0.9989427,-0.5407464,0.2660269,-0.6225779,-0.6738919,0.3776512,0.2454013,-0.1979395 +0,user283,0.1988588,0.3109334,0.5036579,0.2960756,0.4345374,0.9149641,-0.483638,-0.9403715,-0.8540992,-0.8840428 +0,user284,-0.1035383,0.7000046,0.6375925,0.9805802,-0.4840255,0.3248279,-0.9468955,-0.9586905,0.4699278,0.5966397 +0,user285,-0.4179805,0.6606124,0.2198835,-0.9907185,-0.8262936,0.6364953,-0.6799187,-0.2445221,-0.725988,-0.1199326 +0,user286,0.5819614,0.1676686,0.2237918,-0.3801768,-0.1397521,0.7217234,-0.02599844,-0.948906,-0.6065145,0.2685086 +0,user287,0.1777324,0.1017357,-0.6218822,0.6389837,-0.4902032,-0.1186645,-0.9734559,-0.8583725,0.1556879,0.1318022 +0,user288,-0.5918137,0.7415068,-0.01419294,-0.3493261,0.1585722,-0.8050685,-0.8818834,0.4867422,0.4376036,-0.4358991 +0,user289,-0.7512068,0.5814403,-0.4310653,0.07097879,-0.08727131,-0.01742394,0.3845645,-0.5927416,-0.4861999,-0.6622153 +0,user290,0.8347843,0.193627,-0.4954872,-0.3617639,-0.04862057,0.900184,-0.08652239,0.8214748,0.7477147,0.995427 +0,user291,0.04501298,-0.8816973,0.875062,0.9089956,0.1373517,-0.4145499,-0.1337672,-0.4862828,0.2864468,0.00886676 +0,user292,-0.2327633,-0.03548191,-0.3018726,-0.6653564,0.5006582,-0.617945,-0.8340536,-0.3056443,0.8662262,0.3328944 +0,user293,-0.2652927,-0.6803375,0.2006617,0.09605933,0.6232272,0.5051068,0.5673422,-0.8871302,0.3915242,-0.09975712 +0,user294,0.6172588,0.07109048,0.9888595,-0.5284985,-0.5126332,0.07781372,0.2386557,-0.8981921,-0.8646427,-0.6471724 +0,user295,0.9196994,-0.3190552,0.5512158,-0.828119,-0.5130825,0.5301472,0.1938063,-0.8222698,-0.8840964,-0.7377467 +0,user296,-0.6975605,-0.5420107,0.6115536,0.6131645,0.1194944,-0.4871407,0.9142382,0.08516613,-0.1592105,-0.3663404 +0,user297,0.2905256,-0.3032488,-0.001041057,-0.8524183,0.1743903,0.1159723,0.8302102,-0.8781352,0.8424206,-0.5979793 +0,user298,0.6173395,-0.3287466,-0.4358909,0.6004957,0.8307465,0.8067733,0.6667016,-0.813681,0.4786207,-0.3603731 +0,user299,0.1318856,0.245112,0.8488831,-0.5206743,-0.1663004,-0.7515531,0.3571718,0.9466037,0.5274357,0.8837678 +0,user300,0.9844443,0.2748892,-0.08172658,-0.907032,0.0504918,0.002420252,-0.3642305,-0.8081944,0.9258648,-0.001543724 +0,user301,-0.4863678,-0.3717526,0.549269,0.4368826,-0.9520938,0.3208015,0.7005549,0.1588237,0.7586184,0.3289161 +0,user302,-0.3790626,-0.1964509,0.6820961,0.1300282,-0.8999327,0.529032,-0.1461488,0.3547714,-0.1076924,-0.9048895 +0,user303,-0.9632558,0.5361886,0.5319713,0.005830118,-0.9597358,-0.8393486,-0.9963621,-0.2311148,0.5822177,-0.6613661 +0,user304,0.0297279,-0.07315516,-0.8018701,0.1463451,0.6671819,0.5720086,-0.2923139,0.1969963,0.06789143,-0.5104193 +0,user305,-0.2142302,0.326141,0.6556144,0.02564017,-0.7930572,-0.05967731,0.317718,0.5349625,-0.3607544,0.7550317 +0,user306,-0.162714,0.3570689,-0.7562009,-0.2166711,-0.5896807,-0.8464997,-0.3556942,-0.7793187,0.5019568,0.6887658 +0,user307,0.0725236,0.6691843,0.6146314,-0.8192576,0.4604382,0.5733887,0.6021823,0.5112195,-0.9827706,0.4061778 +0,user308,0.857662,0.8106493,-0.2960104,0.0501245,-0.2922258,0.2439729,0.3121075,0.7226512,-0.8246184,-0.6060232 +0,user309,-0.188526,-0.6323756,0.6234312,-0.6070904,0.127751,-0.04010255,0.3608014,-0.4069473,-0.2591482,-0.6729799 +0,user310,-0.9888773,-0.5379679,-0.7588315,0.8380489,0.7791142,0.3030985,0.8988691,0.6144773,-0.6905543,0.1601194 +0,user311,0.8683726,-0.3301861,-0.82745,-0.6284337,0.8649717,-0.2825124,0.2702187,0.4237635,-0.2567351,0.3296209 +0,user312,-0.02996279,-0.2712658,0.638647,0.05970636,0.482367,0.9676315,-0.166824,0.3001766,0.3927502,0.3638565 +0,user313,0.8658787,-0.6391366,-0.7577742,-0.7026976,0.04514107,0.6805206,-0.7750228,-0.00787154,0.554847,0.96218 +0,user314,0.06723136,0.9807474,0.6762079,0.6676418,0.299509,-0.3675483,0.7865808,0.483392,-0.1108343,0.4455781 +0,user315,0.8664989,-0.5712613,0.2762395,0.04028656,0.9983415,0.2924595,-0.1137194,0.341486,-0.137322,-0.0395038 +0,user316,-0.5521018,-0.9785242,0.4621093,-0.6934161,0.2188474,0.3170159,-0.4549415,0.7476064,0.8288591,-0.1577526 +0,user317,-0.3508073,0.148416,-0.1000004,-0.712535,-0.840243,-0.6458249,-0.2394177,0.5344861,0.2826512,-0.2859134 +0,user318,0.04423131,0.5304745,0.6543573,-0.3207297,-0.4918617,-0.826205,-0.08717534,0.4831135,-0.9816341,-0.9077016 +0,user319,-0.1439156,0.7629825,-0.5520837,-0.04274223,-0.6225804,0.5119474,-0.3368249,0.2343486,0.2664626,0.4063483 +0,user320,-0.1020141,-0.2701437,0.4689343,0.3584438,0.07248565,0.3367512,-0.8548531,0.9417445,0.7964513,0.05187137 +0,user321,-0.1209844,-0.2758986,-0.8411298,0.3175064,0.4595177,-0.9260211,0.8263023,0.3045883,0.7660806,-0.9122745 +0,user322,0.9010974,0.8812852,-0.6770217,-0.1337466,0.5147714,-0.9026025,0.5294079,0.7480658,-0.4470905,-0.584785 +0,user323,0.6652226,0.6943743,-0.8329383,0.6930874,-0.4268562,0.7188063,-0.6889067,-0.3638998,0.6626775,-0.6152342 +0,user324,0.6137229,0.04376389,0.3595319,-0.5864343,0.08274494,0.5790857,0.3936444,0.4174582,0.1576048,-0.01203164 +0,user325,0.5183562,-0.04762431,-0.6881621,0.3377549,-0.9978618,0.1752112,-0.2319364,0.8498737,-0.3117333,-0.2319573 +0,user326,0.5849221,-0.6246808,0.7182775,0.8649684,0.06006134,0.2489534,0.5048996,-0.1861696,0.7785811,-0.3529809 +0,user327,0.9161623,0.5017532,-0.02891453,-0.9732698,-0.7977607,-0.9080551,0.3078826,-0.4973757,0.9983942,0.621628 +0,user328,-0.1911183,0.6491269,0.3107968,0.4853366,0.1765285,-0.7088164,-0.4017262,0.9717385,-0.4693127,0.1700634 +0,user329,0.2022616,0.04657253,-0.7176134,0.4654641,-0.1091922,0.05572676,0.1716012,0.000149393,0.2572017,0.286646 +0,user330,0.04804793,-0.2531348,-0.1800315,-0.4939441,0.03593896,-0.6596082,-0.3349456,-0.550772,0.5258299,0.5053958 +0,user331,-0.2066739,-0.07598386,-0.7709298,0.5783046,-0.7729797,0.2936038,0.2340433,-0.836456,-0.5434478,-0.8314804 +0,user332,0.7158937,0.67482,0.8316556,-0.09765328,-0.061286,-0.6234718,-0.1278439,-0.8410269,0.01582018,-0.3844379 +0,user333,0.6689853,0.5504142,-0.4979354,0.6360841,0.1360063,0.8694238,0.5189056,0.8039994,-0.5818625,0.6005063 +0,user334,-0.1699297,-0.5397953,0.7610415,-0.4158652,-0.7327155,0.4542552,0.2376812,-0.06757073,-0.9612301,-0.4928465 +0,user335,-0.2543784,-0.3983352,-0.9702145,-0.9513082,-0.3941041,0.9485369,0.5798422,0.3559694,-0.9162884,0.1051429 +0,user336,-0.5452449,-0.1234448,-0.842321,-0.3382757,0.3429491,-0.1902535,-0.1633764,0.3389619,0.05738318,0.355538 +0,user337,0.6673563,0.8172736,-0.9951593,0.3674636,-0.3223962,0.6077555,0.881987,0.1531106,0.5407267,-0.8040807 +0,user338,0.8181452,-0.7291509,0.6444168,-0.7705658,-0.9336659,0.5219256,0.1820245,-0.1328112,-0.899059,-0.4886793 +0,user339,-0.687583,-0.3127955,-0.1383314,0.7118488,-0.9492767,-0.9462806,-0.8512689,0.0616131,0.2327648,0.7495149 +0,user340,-0.5211697,-0.815102,0.6282719,0.7603732,0.8053548,-0.432347,0.2427884,0.7461633,-0.7184215,-0.4770606 +0,user341,0.829268,-0.2671188,0.8855854,-0.9325169,0.8454483,-0.174976,0.08089362,-0.5183339,-0.5896133,0.6714402 +0,user342,-0.8192104,0.3570184,0.03421859,-0.916585,0.915695,-0.2287929,0.4189499,-0.5146234,0.9760297,0.07913576 +0,user343,0.4488675,-0.08636787,0.2669189,-0.1799204,0.2877218,-0.4647155,-0.9240355,0.04633983,0.6743286,0.8867959 +0,user344,0.6951467,0.09374463,-0.8721888,-0.6352145,-0.1094106,-0.4944554,0.3058708,0.4737946,0.9652337,0.6336201 +0,user345,0.248021,0.3377658,-0.2895736,0.7510568,0.215204,0.4036588,0.2055306,0.9687686,-0.1348046,-0.4752862 +0,user346,0.3153664,0.3423709,-0.4568416,0.8603661,0.2860633,0.827744,-0.03775496,-0.6121741,-0.4629934,-0.1527079 +0,user347,-0.8569552,0.1152204,0.5899204,-0.3286306,-0.8905632,0.8225605,0.8509293,0.221401,0.7940928,-0.5241325 +0,user348,0.8972137,-0.5138182,0.6104261,-0.9614781,0.374961,0.7578339,0.966113,0.5032547,-0.8521534,0.2388005 +0,user349,-0.6404023,-0.1271547,-0.8024843,-0.4603636,0.7942016,-0.998461,0.8750697,0.8709394,-0.4446275,-0.06040944 +0,user350,-0.0008707731,-0.121797,-0.9621632,0.6286272,-0.5131436,0.3345079,-0.4858956,-0.5442504,0.0605554,0.8822158 +0,user351,-0.2048003,0.216038,0.07936043,0.3969657,-0.5525534,0.09458514,-0.8887402,0.4449992,0.9442979,-0.7093281 +0,user352,0.2386133,0.5969468,-0.6436141,0.8571428,0.2537193,-0.9244821,0.701372,0.1755277,-0.6785469,0.02731603 +0,user353,-0.09977338,-0.2405118,-0.6391849,-0.5051194,-0.9983722,0.4319054,-0.9564877,-0.7961846,0.6134649,-0.7025692 +0,user354,-0.5395777,-0.08958761,0.2464221,0.09005311,0.02059044,-0.1866086,-0.5776469,-0.9189007,0.6069754,-0.3245624 +0,user355,-0.1476639,-0.3592893,0.7159177,-0.7292915,-0.6635357,0.6546035,0.09501638,-0.4070141,0.4790579,-0.9847156 +0,user356,-0.5814172,0.7118639,-0.3273471,0.8326356,-0.996234,-0.3928834,-0.188424,-0.9463109,-0.6982684,0.06547344 +0,user357,-0.9546556,0.2857316,-0.0353004,-0.04497848,-0.9193482,-0.9376552,0.9272528,-0.1050702,0.3855564,0.3224567 +0,user358,-0.2315015,-0.8575362,-0.3129968,-0.7025613,-0.4612964,0.7465485,-0.597101,0.09561022,0.4774521,0.6369124 +0,user359,0.2274645,0.3609908,0.9834497,0.3179722,0.1802945,-0.1016998,0.4098497,-0.9745725,-0.167581,-0.7644632 +0,user360,0.247606,-0.6676959,0.2470862,-0.5795144,-0.02854042,0.1180716,0.09885401,0.8950792,-0.3572419,-0.3908973 +0,user361,0.8165464,-0.110671,0.5069717,-0.1965054,0.5746426,-0.9130597,0.06795344,0.5448382,0.003281961,0.1423082 +0,user362,-0.9792094,-0.7149931,-0.78748,-0.1037232,0.4073148,-0.808096,-0.356107,-0.8110284,0.2889712,-0.5959436 +0,user363,-0.03650031,-0.992876,0.07874179,0.3228323,0.9101736,0.4945998,0.9710101,-0.9459478,0.6585783,0.2246649 +0,user364,0.4855317,-0.5602568,-0.9909637,-0.5604213,-0.2893512,0.9563641,-0.413141,0.3488376,0.4214195,-0.2571855 +0,user365,-0.1491392,-0.2547884,0.9735615,0.4804116,0.6745993,0.6461592,0.8815742,0.1214008,0.3277411,-0.08879007 +0,user366,0.7091213,-0.3912111,0.1085273,0.3715241,-0.4839305,0.4431367,0.5508523,0.4100216,0.7422899,-0.6701922 +0,user367,0.9402867,0.3162985,-0.8332847,0.1013029,-0.9464021,-0.2338894,0.4234826,-0.3122005,-0.5211973,-0.9016475 +0,user368,-0.4817828,-0.4375148,0.9784022,-0.1521248,-0.6477969,0.2539147,0.7635612,-0.7254886,-0.1315322,0.1071293 +0,user369,0.5272666,-0.120362,-0.2470559,0.6009583,-0.4175964,-0.03493777,-0.2671231,-0.7227896,0.8432309,-0.1588715 +0,user370,-0.7472962,-0.996497,0.02838393,-0.1868483,-0.8956787,-0.18017,0.5722137,0.7494126,0.7115675,0.8478674 +0,user371,-0.002952578,-0.2526168,0.6066741,-0.3917516,-0.8424422,0.8215677,0.006349667,-0.9793253,0.1500462,0.6300687 +0,user372,0.3565345,0.6125192,-0.3614705,0.6684414,-0.5721481,0.7900862,0.8137705,-0.2411235,-0.7463824,-0.4874314 +0,user373,-0.5665066,0.3605214,-0.9373975,-0.1034333,-0.9799837,0.5910371,-0.008836402,-0.7652108,0.6875971,-0.07299687 +0,user374,-0.5540851,0.6610153,-0.126407,0.428328,0.4452796,-0.6431477,0.08231413,0.06701449,-0.1756251,0.5168646 +0,user375,0.0516812,-0.2937362,-0.2336594,-0.9667731,0.3184412,-0.7043692,0.1196413,-0.7673289,-0.7811487,-0.8538112 +0,user376,0.6815144,-0.3017128,-0.226971,-0.3523765,0.2352203,-0.005304137,-0.8033058,-0.7964422,-0.4472075,0.451717 +0,user377,0.7612813,0.003386166,0.4167514,0.2886941,-0.2686571,-0.8154038,-0.9554408,0.4548404,0.3613815,-0.6358433 +0,user378,0.194726,0.8214842,-0.6437389,-0.2954036,0.427878,-0.8818087,-0.02942939,0.454072,-0.9870559,-0.3779437 +0,user379,0.5787281,0.184469,-0.616545,-0.3138546,-0.3898187,-0.2474702,-0.8371928,0.7068126,-0.299361,-0.3094825 +0,user380,-0.879121,0.8762315,0.6142671,0.8283305,-0.4744555,-0.8138648,0.9196289,0.3257797,0.9167541,0.3037473 +0,user381,-0.8061448,-0.3003128,-0.6059022,-0.6667764,0.9147344,0.4526992,0.484675,0.9098216,0.0734995,-0.495728 +0,user382,-0.6260722,-0.599493,0.4628155,-0.9168889,0.05762793,0.8471149,-0.7259329,0.1518117,-0.3550631,-0.01881068 +0,user383,0.3594923,0.4731783,0.9706529,0.6854733,0.7792639,-0.7383469,0.6210008,-0.4986925,-0.7617928,-0.6689367 +0,user384,0.09408185,0.4591754,-0.2450871,-0.1718958,0.9163622,-0.1153954,0.5281873,-0.886363,-0.3130356,-0.1982972 +0,user385,-0.1656499,0.3109194,-0.2907624,0.1731642,-0.9217816,-0.3394937,-0.3035798,0.2329111,-0.7480878,0.656627 +0,user386,-0.7881716,-0.8861111,0.6865707,0.9561818,-0.8842719,0.9162566,-0.2839828,0.09429336,0.717265,-0.6536523 +0,user387,0.5126646,0.1710393,0.4275659,-0.3392602,0.9201283,0.4917212,-0.6602367,-0.8326739,-0.01130397,0.8671762 +0,user388,-0.1203056,-0.403349,0.6739372,-0.8718142,-0.8411299,-0.2771488,-0.376327,-0.8721592,0.6374686,-0.02091629 +0,user389,-0.01967316,-0.7436473,-0.6264261,-0.7463795,-0.3455683,0.6628051,0.1189162,-0.8100964,0.1947171,0.9832601 +0,user390,-0.2598708,-0.46797,0.4110156,0.978712,0.1004227,-0.6099787,0.749613,-0.8072464,0.821115,-0.897287 +0,user391,-0.8726996,-0.07104495,-0.07897657,-0.4513286,0.1303297,0.8409227,0.722527,-0.97708,-0.7197732,0.5881865 +0,user392,-0.2031268,0.1456817,0.8805456,0.05711507,-0.7709257,0.7497454,-0.8131303,0.7347418,-0.802001,0.1255683 +0,user393,-0.2390802,-0.182963,0.6235356,-0.1250112,-0.4922625,-0.4180747,-0.6064939,-0.6182748,0.1100862,-0.4932305 +0,user394,0.09080007,-0.0639209,0.9997652,0.8715037,0.0405033,0.3355226,0.6935371,-0.9230278,0.9388051,-0.1871487 +0,user395,-0.7175951,0.5854249,0.8895819,0.4966937,-0.06027687,0.7061095,-0.2262713,0.08357946,0.6194185,0.8683827 +0,user396,0.6117806,0.5622486,0.5970971,-0.6445996,-0.8176632,-0.7719155,-0.7249197,0.503126,-0.5621728,0.4179794 +0,user397,-0.2000786,0.544868,0.1082925,0.2430278,0.5565728,-0.2213408,0.2443894,0.4869938,0.681095,0.1426591 +0,user398,-0.7773084,-0.09827661,-0.9437027,-0.4020033,-0.00667893,-0.5277799,-0.8027886,0.7713789,-0.9017788,0.9667352 +0,user399,-0.8700022,-0.8752662,0.5754993,0.2032756,-0.4654601,0.4819992,-0.9613584,0.7776374,0.306295,-0.4748913 +0,user400,-0.672812,-0.5754941,0.8612366,-0.1560139,-0.8610237,0.7437214,0.9772663,0.7642042,0.524326,0.9837876 +0,user401,-0.5246046,-0.09477363,0.08468121,0.4111483,0.09764235,0.2920501,0.7694251,0.5207915,0.8097887,0.8146026 +0,user402,0.1270452,-0.127883,0.1821733,0.811524,-0.3079023,0.3035669,0.04499123,0.798312,-0.5436588,-0.8448226 +0,user403,0.6837225,-0.9629749,-0.500234,-0.4875725,-0.4331718,0.5338077,0.7910368,-0.4769193,0.7779436,-0.5036438 +0,user404,-0.09111118,-0.7342522,0.1472837,-0.692285,0.1176586,-0.1169128,-0.2394113,0.7555807,0.4973858,-0.2583943 +0,user405,0.5729601,-0.4668677,-0.9442337,0.239852,-0.8626227,0.6604192,-0.8726946,-0.1346735,0.2807161,0.672042 +0,user406,-0.2645963,-0.2567111,0.2661066,-0.4543456,0.8852694,0.8294385,-0.08932192,-0.2442483,0.9967949,-0.357455 +0,user407,-0.4095968,-0.03596502,0.9203127,-0.04466141,-0.6471211,0.877783,-0.04271706,0.9591386,-0.9498217,-0.8066773 +0,user408,0.3342414,0.5365184,0.4725177,-0.4714539,-0.1312798,0.8450154,-0.8281355,-0.6798331,-0.3579024,-0.9638013 +0,user409,0.9301297,-0.4352268,0.6223677,0.2502508,0.3131474,0.9476298,0.8812487,-0.7901763,-0.990261,0.2646012 +0,user410,-0.8308687,-0.851496,-0.6962323,0.641484,-0.03693976,-0.3696872,0.1200902,0.6659511,-0.2491827,-0.1161598 +0,user411,0.4551204,0.4127499,0.08678473,-0.6431234,0.3942647,-0.9688494,-0.9085066,0.6459466,-0.4411483,0.339946 +0,user412,-0.876015,0.2644604,-0.9835345,0.5834744,0.2278818,0.400329,0.3659237,-0.8803547,0.08323851,0.7688733 +0,user413,-0.4569409,-0.450989,0.7665832,0.7245951,-0.9793118,-0.5225722,0.3941572,-0.1822372,0.3957542,0.8650295 +0,user414,-0.1853874,-0.1140718,0.05743764,-0.9576502,0.1735286,-0.7071963,0.7124942,-0.8527459,-0.2029412,0.6710094 +0,user415,0.2180668,-0.2763642,-0.2286215,-0.5884214,0.1442441,-0.7150665,-0.105889,-0.7667177,0.7702029,-0.4294239 +0,user416,0.3774092,0.8599304,-0.5241792,-0.1022406,-0.9010935,0.1379341,-0.9094226,-0.9493261,0.6476664,0.5216565 +0,user417,0.026441,-0.0001828815,-0.2559917,0.9985317,0.2892567,-0.7909397,-0.5714885,0.2415474,-0.4856761,-0.9826429 +0,user418,-0.2692685,0.8946751,-0.8010557,0.07231842,0.06437234,0.7766547,0.2338743,-0.5993916,-0.2411011,-0.5622477 +0,user419,-0.7428964,-0.5434186,-0.850242,0.02594515,-0.7422233,0.8607852,-0.2857496,-0.8214853,0.2851351,-0.4992598 +0,user420,-0.9932322,0.2561698,0.1175822,-0.7478478,0.9436885,0.8718654,0.5474277,0.431451,0.7090409,-0.9993828 +0,user421,0.4708607,-0.5732949,0.6099599,0.05103041,-0.8352049,-0.833324,-0.01651263,-0.406638,-0.4199861,-0.4595347 +0,user422,-0.615596,0.3855365,0.07078148,0.5746165,0.3881064,0.701708,-0.5632226,-0.7985653,0.5653619,-0.9110734 +0,user423,-0.1963589,-0.5981484,-0.001872206,0.3092672,-0.8272372,0.6216108,0.7342974,0.1661929,0.90704,0.1261854 +0,user424,-0.7682196,0.2437421,0.2334955,0.9260192,-0.3274674,-0.2513987,0.3769935,-0.02491289,0.6901001,0.04723479 +0,user425,0.475204,-0.6783844,0.07054669,0.4461202,-0.5713903,0.03723053,-0.8696855,-0.721593,0.504167,-0.09822202 +0,user426,0.08604596,0.9872765,-0.1122903,-0.194039,0.1124859,0.3277203,-0.4919739,-0.7502277,0.5264585,-0.005431852 +0,user427,0.843561,-0.1940093,-0.1694074,-0.7185803,-0.1451306,-0.0233142,0.6520738,-0.5217869,-0.8720727,-0.5347858 +0,user428,-0.7248746,0.8664835,-0.8211608,-0.310852,0.9851825,0.8158898,0.3747039,0.7654007,0.185262,-0.9555629 +0,user429,0.3087376,-0.1110001,-0.05599298,0.4039576,-0.894193,0.7999403,-0.2947625,-0.9788487,0.6246797,-0.03869661 +0,user430,0.9735588,-0.06927554,-0.5939081,0.4846953,0.3894093,-0.541315,0.6907154,-0.7441495,0.4342223,-0.009677165 +0,user431,-0.3976866,-0.7090106,-0.9599243,0.533134,-0.8758412,0.5596112,0.3519701,0.5296049,-0.290412,-0.9717753 +0,user432,0.784133,0.7942263,-0.9713118,-0.1848941,0.2034493,0.09199042,-0.5253374,0.5419428,0.4344684,-0.224094 +0,user433,0.100604,0.8028414,0.5882652,0.2962193,-0.918493,0.762252,-0.2642934,-0.9458375,0.8905635,0.1455002 +0,user434,-0.7139641,-0.6719854,-0.4601582,-0.9544385,-0.309013,0.0934189,0.1430069,-0.9473144,-0.5124684,-0.4754191 +0,user435,-0.3069782,-0.940026,0.175972,0.122821,-0.6788921,0.9750776,0.2352513,0.2975235,-0.06814578,0.5175118 +0,user436,-0.3264359,-0.6640263,0.6440315,-0.4639287,-0.7811157,0.4226712,-0.1369881,-0.08051095,0.1712796,-0.1824578 +0,user437,0.02143966,0.07130352,0.8059484,-0.4087841,-0.4237436,-0.07714258,-0.946315,-0.1915627,-0.5156735,0.1671259 +0,user438,0.283425,0.02400903,0.09628466,-0.9218404,-0.3260131,0.8528606,-0.8074658,0.2566621,-0.01796751,0.7108345 +0,user439,-0.9921945,0.8724921,0.1165491,0.06461739,0.08760449,0.2676866,0.03487648,0.2396559,0.8133773,-0.1462591 +0,user440,-0.04843062,0.6360767,0.4283161,0.8414667,0.8894038,-0.1295128,0.9349337,0.01826107,-0.5059344,-0.5682729 +0,user441,0.4525564,0.172513,0.4000524,0.7196436,0.6370471,-0.5168266,0.3126244,-0.07738675,0.7328498,-0.4053253 +0,user442,0.4629258,0.2852421,-0.7966661,0.4214939,-0.5181308,0.2988372,0.1263699,-0.1143974,-0.627771,-0.806313 +0,user443,0.07555435,-0.09946288,0.4447816,0.4249411,0.1172857,-0.7291838,0.3008574,0.1379064,0.5773041,-0.7993996 +0,user444,0.9956155,0.721524,0.1666356,0.4442387,0.6577353,-0.03939883,-0.2932184,0.7403761,0.128604,-0.5402958 +0,user445,-0.7224616,-0.8288297,0.2607715,0.4638438,0.6553978,0.591641,-0.1611359,0.03285666,0.1692878,0.8646963 +0,user446,-0.7063788,0.6241729,-0.7838399,0.8365197,-0.7384703,-0.4442502,-0.8050316,0.3711887,0.347507,-0.2288236 +0,user447,0.3730246,0.5814544,0.6424565,-0.6580019,0.7566419,-0.9014648,-0.202641,0.79105,-0.2237296,0.9813606 +0,user448,0.3039794,0.1709874,-0.9952202,0.4623754,-0.05534549,0.8007013,0.2673756,-0.7255959,0.6836117,0.8820534 +0,user449,0.02435264,0.518848,-0.5848956,-0.09116189,0.3259021,-0.6675955,0.4288427,0.771797,-0.8935941,0.2089287 +0,user450,0.6301282,-0.9619642,0.7922145,0.3679433,-0.9855815,0.9593205,0.5116094,0.9695648,-0.9385945,-0.5178992 +0,user451,0.3107472,-0.5728428,0.122362,0.7145276,-0.111657,0.6725667,-0.1851967,0.7058551,0.3926526,0.8826706 +0,user452,-0.5047867,0.9455531,-0.9749356,0.9598685,0.4906972,-0.5009195,-0.5876699,-0.634841,-0.3135802,0.7493941 +0,user453,-0.9854678,0.4235723,-0.137004,-0.0574402,0.402525,0.6610285,0.9483868,-0.8290005,0.6267674,-0.4289725 +0,user454,-0.8856117,-0.1709912,-0.8795102,0.02379483,0.06110572,0.2941775,-0.4508993,-0.127952,0.2996926,0.00885597 +0,user455,-0.2730063,0.1892952,0.2585599,0.8858878,-0.8367702,0.2476818,0.7893236,0.3402461,-0.6234801,-0.2033711 +0,user456,0.4897362,0.7451879,0.9335427,-0.61132,0.8311347,-0.301741,-0.9212987,-0.5505936,0.1309344,0.4728054 +0,user457,0.2004343,-0.1837147,0.008199543,0.8297558,-0.8264084,-0.3781022,0.05712679,0.1218203,-0.1738489,-0.9965759 +0,user458,-0.4294453,0.9952859,-0.9108475,-0.8326926,0.01809919,-0.7756324,0.4413974,0.8184592,-0.4955527,0.261843 +0,user459,0.7648617,0.6116714,-0.8876181,0.07782796,0.8163172,-0.4858512,0.4534051,-0.7851928,-0.6838036,0.5172425 +0,user460,-0.4908282,0.7052852,0.9522066,0.2337134,-0.7206014,-0.5781619,0.7623642,0.1429716,-0.5491691,-0.03527249 +0,user461,-0.4558865,-0.07398965,-0.5047556,0.6520027,-0.5924915,-0.3169473,0.1321127,-0.9256903,0.9386696,-0.7478341 +0,user462,-0.6328249,0.9026608,-0.8475424,-0.389038,0.940476,-0.92624,-0.1946247,0.7444121,0.02578444,0.5454672 +0,user463,-0.7066952,0.4995114,0.9808948,-0.9511807,0.4828479,0.5138285,-0.7629732,-0.3150856,0.8852993,0.7406335 +0,user464,0.6447175,-0.2711482,-0.9164904,-0.05177796,-0.5109846,-0.5546954,0.8678193,-0.8715278,0.8292331,0.3976661 +0,user465,-0.346789,-0.7693246,-0.3077006,-0.3434765,-0.368537,0.1671789,0.9483822,0.7970977,0.513316,-0.9299519 +0,user466,-0.01367335,0.5594855,0.1568668,0.1716403,0.8039558,0.4889061,0.4722781,0.982438,-0.1828465,0.2581453 +0,user467,-0.6817184,0.06482548,0.727541,0.4842934,-0.2921003,0.8679758,-0.2691688,0.04796126,0.000512782,-0.7847917 +0,user468,0.6746507,0.3019789,-0.5017522,0.2477394,0.2077194,-0.9099637,-0.9979328,-0.3944649,0.9976426,0.237174 +0,user469,-0.7302483,-0.4165055,-0.7468486,0.2497999,-0.5220573,0.3417667,0.6648123,0.2391001,0.799186,-0.03102025 +0,user470,-0.6739129,-0.06268238,-0.1559098,-0.4510892,0.7955042,0.1356623,0.7657077,-0.7123828,-0.1861099,0.06894923 +0,user471,-0.3737799,-0.06194437,0.9265639,0.08920609,0.09712323,-0.03947647,0.9370009,0.6237961,-0.5082919,0.6689011 +0,user472,0.7223081,0.7560075,0.6532038,-0.03055652,-0.8850102,0.8249401,-0.02256334,-0.8382867,0.5320358,0.5636544 +0,user473,0.7890129,-0.7774403,0.04742407,0.9704047,-0.7226266,-0.5655004,-0.1079224,0.1732198,0.186119,0.2626362 +0,user474,0.7017744,0.8385927,0.3713456,-0.4858528,-0.7855911,0.2313397,0.2378582,-0.2382975,-0.9309878,0.8695015 +0,user475,0.7179235,0.4775315,-0.1801605,-0.5863178,0.7727252,-0.2144587,0.6842182,0.9020894,-0.3393602,-0.9766414 +0,user476,-0.9334487,-0.60627,-0.6918044,0.4342485,0.9327712,-0.9738595,0.7309417,-0.7939236,-0.6445932,0.1273325 +0,user477,0.9953956,0.4627657,0.5875057,-0.6493332,-0.5240614,0.7870895,0.4328267,-0.8671088,0.4165192,-0.359322 +0,user478,0.09094817,0.05898596,-0.5377041,-0.2443197,0.529367,-0.1159234,-0.5184227,0.6931394,0.4369102,-0.9952808 +0,user479,0.3705307,0.5647174,-0.6870246,-0.1033761,-0.1225743,0.8268418,-0.001682685,-0.5195195,-0.9609815,0.009385914 +0,user480,0.01974822,-0.01838632,-0.9973899,0.259505,0.8018407,-0.880506,-0.1383306,0.9046883,0.5229251,0.8496067 +0,user481,-0.2789236,0.09702181,-0.7454896,-0.8763764,0.5437856,-0.156603,0.9931867,0.6627042,0.4983157,-0.51318 +0,user482,-0.3187221,0.9918746,0.4353374,-0.3888485,0.7657687,0.4994086,0.8131206,-0.8136643,0.4316711,-0.1079435 +0,user483,0.5149615,-0.07283319,-0.9723255,0.2193735,0.2925379,-0.3814255,0.2739995,-0.7301527,-0.7906551,0.5990008 +0,user484,-0.2643914,-0.4794059,0.1175064,0.06618341,-0.05368948,-0.4955745,0.9415735,0.8337037,0.1250831,0.05784747 +0,user485,-0.2043337,-0.1791166,0.5558272,0.6349464,-0.1731256,-0.2064139,-0.6377787,0.05838368,-0.2686363,0.9009124 +0,user486,-0.7580448,-0.883538,0.2862343,0.1052612,0.4557677,0.8662563,0.06332307,0.6100934,-0.4141351,-0.6043704 +0,user487,-0.7746552,-0.734218,0.05104912,0.4548634,-0.2225548,0.2026845,-0.9797253,-0.7168899,-0.7439825,-0.4693471 +0,user488,0.9961005,0.6371687,-0.4359732,0.4647021,0.000465998,0.4154839,0.4193481,-0.819796,0.5575148,0.9043366 +0,user489,-0.18749,-0.8882521,0.3753868,0.2725687,-0.5261331,-0.909376,-0.4952796,0.4285526,0.09031212,0.6574727 +0,user490,0.9902065,0.8774533,0.163431,-0.4673086,-0.4062377,0.7168333,0.4736799,-0.5020827,-0.4277861,-0.9521046 +0,user491,-0.4947276,0.3424539,-0.4837667,-0.3015845,0.2798646,0.837322,0.1817123,0.3231756,-0.9916544,-0.1309359 +0,user492,0.3566235,0.03775825,0.8706312,-0.07542864,-0.1186247,-0.2263234,0.6368331,0.5028623,0.02898171,0.9096385 +0,user493,-0.6426184,0.7801141,0.3158886,0.1436534,-0.4657617,0.7905933,-0.7209449,-0.7576706,0.5979983,0.5933626 +0,user494,-0.2014228,-0.1580346,-0.5028719,-0.2527651,-0.2372875,0.3511505,0.4187391,-0.99191,0.8936449,-0.3903024 +0,user495,0.001341042,0.76661,0.9541408,0.8727934,0.3703908,0.2189812,0.5046524,0.6313345,-0.1417852,0.3073046 +0,user496,0.01059259,-0.9892105,-0.991812,0.8001769,0.1657013,-0.04222788,-0.7725627,-0.9605728,0.1113144,0.6634108 +0,user497,0.7849039,-0.5985491,0.6539949,0.9188752,-0.4333317,-0.1599434,-0.1089828,0.990528,-0.2892016,0.8678429 +0,user498,0.3196227,-0.1685645,0.6816818,0.3570868,-0.9217095,0.08695698,-0.7645163,-0.3207043,0.8587276,0.5225129 +0,user499,-0.3147567,0.3127684,-0.4935643,0.04791626,-0.6265793,0.04780841,-0.7704955,-0.3550378,0.108957,-0.09941521 +0,user500,-0.9453445,-0.01505462,0.9071463,0.1686751,0.044611,-0.8181766,-0.4441705,0.2296281,-0.4900156,-0.1631774 +0,user501,0.6457097,0.7687531,-0.474228,0.9059975,0.8737946,-0.7773807,-0.9988086,-0.03308705,-0.3273823,-0.4085379 +0,user502,0.3114633,-0.7491759,-0.5670003,-0.8628777,0.470544,-0.9916681,-0.8334946,-0.7312416,0.6006651,-0.4305141 +0,user503,0.7769636,-0.2590471,0.5603502,-0.8618814,0.1596008,-0.9932365,0.5332662,0.3913414,-0.9579798,-0.599523 +0,user504,0.4347226,0.9913128,0.5731961,0.8764022,-0.848832,-0.3428811,-0.106731,-0.8598673,0.8587367,0.8540983 +0,user505,0.01323772,-0.9105832,0.8043452,-0.3487305,0.6849528,0.2396717,0.4043636,0.03046091,0.6696773,-0.5610125 +0,user506,0.4948871,-0.7815156,-0.6198104,-0.4481992,-0.067674,-0.2076952,0.2174844,0.2934308,-0.29734,-0.5761644 +0,user507,0.5012739,-0.6149572,0.8813917,0.3106507,-0.9160608,-0.3167405,-0.3757893,-0.6537908,-0.7858565,-0.01856915 +0,user508,0.008633296,0.5521825,0.3918509,0.001936348,-0.8391085,0.0267612,-0.1628097,0.1633521,0.08619647,0.07966543 +0,user509,-0.4141647,0.2774704,-0.1575144,0.3074811,-0.538307,0.6763814,0.6990617,-0.01342973,-0.8604298,-0.5714453 +0,user510,-0.1281954,0.9497602,-0.8056329,-0.7927253,-0.03863507,-0.4898987,0.622528,-0.1733103,-0.746838,0.9908168 +0,user511,-0.9716185,-0.4662038,0.3944609,-0.7385587,0.9627322,0.1462552,0.6988597,0.06804039,-0.3908785,-0.07072787 +0,user512,0.3069117,-0.6255078,0.096996,0.4311047,-0.9945214,-0.4802216,0.6922483,-0.3507256,0.6378859,-0.08462524 +0,user513,0.5530825,0.9416348,0.6297045,-0.1815738,-0.2728664,-0.9904902,0.4356486,0.01302539,0.6848331,-0.1171268 +0,user514,0.543343,0.460963,0.4221354,0.4808148,0.2552701,0.7648297,-0.02714085,0.3378877,-0.1815335,-0.4717271 +0,user515,-0.9574797,-0.1049137,-0.7854976,-0.5027119,-0.04821092,0.02420395,0.6338218,-0.5170219,-0.237031,0.9732222 +0,user516,-0.6512512,-0.2374817,0.1855317,-0.5466275,0.554008,-0.1969041,0.7978699,-0.9285909,-0.5838033,-0.2162143 +0,user517,0.7852982,0.577425,-0.2916303,-0.413924,-0.2889622,0.631086,-0.9638178,-0.05201896,0.4043313,-0.07609748 +0,user518,-0.7321349,0.1608683,0.2655516,0.9521515,0.7292343,-0.7731115,0.6540965,-0.2339118,0.01898646,-0.4961249 +0,user519,-0.6551507,-0.600313,0.7495585,0.9180747,-0.445526,-0.7814202,0.217218,-0.7483869,0.9737115,-0.3118778 +0,user520,-0.4021918,0.6891729,-0.9162434,0.8586446,0.1849046,0.72171,-0.4590973,-0.6234664,-0.5053565,-0.4186248 +0,user521,-0.7419284,0.03832159,-0.5710175,-0.5151571,-0.6770034,0.9437218,0.1277764,0.2640056,0.5912004,-0.4482294 +0,user522,-0.1498783,0.7421409,-0.7342082,-0.3835098,0.8343385,-0.9440983,-0.6010697,0.5747887,0.9820571,0.5571863 +0,user523,0.9544317,-0.2730688,0.9543878,-0.216784,-0.93372,-0.5046134,-0.8222642,0.8793959,0.5236252,-0.5089863 +0,user524,-0.3845468,-0.1815643,0.7448711,0.6284963,-0.142765,0.734315,0.4068315,0.506335,0.1891987,-0.8548668 +0,user525,0.6486989,-0.4158937,-0.2370801,0.3637251,-0.402949,0.4070523,0.8176695,0.5828788,0.8757021,-0.8331161 +0,user526,-0.04422725,-0.5064588,0.9085286,-0.3439906,0.4366707,0.7143678,0.6823882,0.5107304,-0.61816,0.7983183 +0,user527,0.6260458,-0.1707748,0.7530591,0.4286731,-0.9770637,-0.3079129,0.6342688,0.5457622,-0.6994869,0.808544 +0,user528,0.4336027,-0.01444281,-0.5830852,0.2826003,0.1637193,-0.7528911,-0.2913133,0.5734068,-0.4134995,-0.9652733 +0,user529,-0.7246046,0.3249767,0.5902104,-0.9869038,0.5149612,-0.1986752,0.9178719,-0.8099739,-0.7594324,0.3208312 +0,user530,-0.688711,-0.8580063,-0.7405052,-0.5234106,-0.603643,0.7398955,0.8637734,-0.8092756,0.4094701,-0.2908712 +0,user531,0.4882583,0.9705026,-0.6759388,-0.5487246,-0.7916697,-0.5710677,0.2645162,-0.1969651,0.09648493,-0.1284507 +0,user532,0.9211051,0.09372984,-0.8840176,0.9190937,0.3887558,0.02394414,0.9190632,0.1569391,-0.08681469,0.9122933 +0,user533,0.6227524,-0.6071823,-0.3075055,-0.3862883,0.866901,0.7482275,-0.9697213,-0.5405172,0.01013519,0.2786147 +0,user534,0.2652219,-0.2885445,0.8844113,-0.4106061,0.3679311,-0.5643042,-0.2022176,-0.8056237,0.1385051,0.2720263 +0,user535,0.3558277,0.08504268,0.6891784,0.7954959,0.5399239,0.6810631,-0.1876678,0.2970718,-0.228078,0.7663916 +0,user536,-0.3640099,-0.5177654,-0.5031603,0.2649812,0.5518538,-0.01210084,0.4346423,0.4899437,-0.3201875,0.7176022 +0,user537,-0.239891,-0.07006011,-0.7353991,0.1411947,-0.6997429,0.2280006,-0.9847332,0.4878071,0.8411651,0.6958619 +0,user538,-0.1428984,0.4700855,0.5705701,0.1061466,0.6238631,-0.6356775,0.4365429,0.643281,-0.01393449,-0.2521775 +0,user539,0.6446234,-0.965583,0.8886905,-0.7330824,0.7127453,-0.9853396,-0.7281674,-0.3467042,0.766009,-0.2027324 +0,user540,0.3459443,-0.7925897,0.1070865,-0.5513242,-0.2380499,-0.09561803,0.7143285,-0.5256226,0.9807353,-0.8755834 +0,user541,0.7289062,0.4198457,0.7649372,0.3134213,-0.414772,-0.1255762,0.05907087,-0.5300293,0.2392275,-0.2613608 +0,user542,0.6730049,-0.4317868,0.2831515,-0.4716411,0.6754775,0.1609156,0.9706923,0.7213362,-0.6248695,0.7265398 +0,user543,-0.347144,-0.4180975,-0.7959175,0.8797805,-0.2325713,0.4241604,0.4065768,0.1236519,0.6186212,0.03979137 +0,user544,0.2819887,0.3614806,0.3946417,-0.8681525,0.3123616,-0.1160664,-0.5052805,0.4829961,-0.07593946,0.6215124 +0,user545,0.2163479,-0.9708238,-0.2947131,-0.9908263,-0.06925243,-0.07425475,-0.05644859,0.05922387,0.193597,-0.7451873 +0,user546,-0.3046237,0.4769888,-0.5814151,-0.6229313,0.7192178,-0.5516356,0.04039865,0.60663,-0.6184099,0.0130136 +0,user547,0.6307375,-0.8760012,-0.4198266,-0.41478,-0.1336304,0.6870295,-0.7074106,0.5544052,0.3402573,-0.5947019 +0,user548,0.001646154,0.6066012,0.4136566,-0.4047503,0.6417853,-0.4431687,-0.02026636,-0.9927951,-0.4020717,0.1787152 +0,user708,-0.7567242,0.3094405,0.5309371,0.3031857,0.9267091,-0.5691027,-0.5736817,0.982584,0.03517789,-0.2344321 +0,user709,-0.3637175,-0.7497725,0.9565679,0.6952905,-0.7504666,-0.5378985,-0.7780496,-0.5655923,0.606263,0.06082153 +0,user710,-0.08779142,-0.3870854,0.9271502,-0.08888232,-0.2032348,0.9461848,0.4458314,-0.4254011,0.6483734,-0.7381908 +0,user711,-0.5682623,0.6655218,-0.5570627,-0.1652492,-0.9859339,-0.1915089,0.379321,0.848749,-0.647391,-0.546694 +0,user712,-0.7732957,0.5651583,-0.355981,0.8846892,0.3652283,0.1772648,-0.722878,-0.04525137,0.2135598,-0.701128 +0,user713,-0.7185171,0.1432838,0.005794191,0.06873509,-0.9796293,0.2127243,0.03287772,0.2839307,-0.9717814,-0.6119339 +0,user714,-0.5697431,0.9947562,0.4289635,0.3767497,-0.3226806,-0.3326629,-0.0007473701,-0.924444,0.4890265,0.3352734 +0,user715,-0.7279365,0.5773786,0.2408255,-0.945116,0.630319,-0.4281492,0.3762261,-0.8542128,0.6970003,-0.1448507 +0,user716,-0.7112594,-0.7820043,0.4404248,-0.9286992,0.7680059,0.9778624,-0.8027677,0.4164756,-0.2570973,-0.7586663 +0,user717,0.7011716,-0.7403029,0.9776746,0.1717942,0.8232049,-0.3830513,-0.3866061,-0.2670983,-0.7690015,-0.3286191 +0,user718,-0.4003184,-0.6984621,0.04906628,-0.3091905,0.1175343,-0.5566957,0.2569045,0.03369257,-0.083519,0.2584703 +0,user719,-0.2772807,-0.6861213,0.6898745,0.8554193,0.5460785,-0.02380118,-0.8835796,-0.0121037,0.5979101,0.8026583 +0,user720,-0.4909877,-0.3833183,0.7860604,-0.8831977,0.8802704,-0.5525627,-0.3609757,0.02147251,-0.8337013,-0.5829562 +0,user721,0.8215455,-0.03752329,-0.1962099,-0.1599293,-0.7723677,0.9071992,0.1607993,-0.1619727,0.2663818,0.4850248 +0,user722,-0.824136,0.4193105,-0.04908318,-0.99586,0.9318544,-0.0393949,-0.07219174,0.1296925,0.6418334,-0.7871328 +0,user723,0.3421012,-0.1367814,0.007874571,0.7109719,-0.3086545,-0.1621873,0.09608121,-0.7049455,-0.48171,0.9148363 +0,user724,-0.746603,0.4853074,-0.0007134858,-0.1601052,-0.9134555,0.7561888,0.3001229,0.03293022,0.5853429,-0.2448634 +0,user725,0.7217935,0.4095205,-0.8931645,-0.7596106,-0.2470652,-0.7617158,-0.8074982,0.5714684,-0.4867116,-0.7024815 +0,user726,0.6800954,-0.9072129,0.609152,0.4524314,0.1679636,0.9686661,0.8341478,0.557222,0.9610433,-0.289437 +0,user727,-0.437273,0.4053414,0.9802942,-0.6424955,0.5277483,0.6933795,0.3833001,0.87827,-0.6947299,-0.9313365 +0,user728,-0.7342276,-0.009894109,0.9591442,0.4487625,-0.05778948,0.3677449,0.329938,0.8445369,-0.8370399,-0.1816089 +0,user729,0.4143871,-0.06846227,-0.07218944,0.1146702,0.7122113,0.559013,-0.8339823,-0.9183223,-0.9469225,-0.5908784 +0,user730,0.6987597,-0.8605034,0.42785,-0.4376607,0.3870017,-0.550361,0.6353279,-0.5662054,0.5244003,-0.63809 +0,user731,0.1115312,-0.3302765,-0.8354061,-0.2935121,0.3235644,-0.2184777,-0.00397232,0.5447469,0.769246,-0.02021341 +0,user732,-0.03612104,0.2519821,0.6518363,0.4000376,-0.6422045,0.3515009,0.5416584,0.6392223,0.05268981,0.5433788 +0,user733,-0.5468266,0.3817036,-0.5746872,-0.1165039,0.6634466,0.5879331,-0.8892734,-0.8723133,-0.5793763,0.06983813 +0,user734,0.5297316,0.3393832,0.5858358,-0.09035761,0.03935841,-0.03427135,0.4410317,-0.3974838,0.2518273,-0.8864529 +0,user735,-0.6459982,-0.4754605,0.4816064,-0.0775373,-0.6404361,-0.7271301,-0.08333086,0.09744309,0.1338623,0.9599355 +0,user736,-0.003591339,-0.9780022,0.8541175,-0.7295347,-0.8984435,0.2132724,0.5162969,-0.8688638,0.9787755,0.781698 +0,user737,0.9258872,-0.8524847,-0.8605012,-0.1467403,-0.195389,-0.08185376,-0.6119264,0.9366029,0.8778121,0.124163 +0,user738,0.5170452,0.8452627,-0.09466993,-0.9957635,0.5544724,-0.7866127,0.9430117,-0.3428744,-0.4060543,0.2735511 +0,user739,0.2396845,0.3314382,0.3850546,0.573651,-0.9717344,0.6441697,0.9426152,-0.8862799,0.01395337,-0.4527341 +0,user740,-0.4378303,-0.6022572,-0.9039333,-0.4514498,0.05414436,0.3802478,-0.389976,-0.6289895,0.4840751,-0.8150155 +0,user741,-0.5707462,-0.5418227,-0.1675197,-0.08464581,-0.6487624,-0.840428,0.3888432,0.2317245,-0.7576809,0.5353603 +0,user742,0.6714222,-0.003040019,0.8279918,-0.5915982,-0.9576683,-0.5473392,0.3219362,0.9624692,0.3665624,0.0005719741 +0,user743,-0.211126,0.9629011,-0.2599143,-0.5667606,-0.5806273,-0.4424874,-0.112854,0.3257592,-0.3023651,-0.5161435 +0,user744,-0.2892633,0.601461,0.8382744,0.9840893,-0.6283917,0.3722963,-0.5782791,-0.4843447,-0.7294623,0.9234264 +0,user745,-0.898321,-0.008283848,0.2569554,0.7851514,-0.280349,0.1199979,-0.6788112,-0.9619748,-0.1444111,-0.6641546 +0,user746,0.06093755,0.5402797,0.9809112,-0.5118766,-0.9503083,0.1293634,-0.7366279,0.4715464,-0.6053648,0.3390058 +0,user747,-0.0005227807,0.8194567,0.2786993,-0.94461,-0.8603858,0.3501587,-0.3810468,0.9321309,0.01344039,-0.8352399 +0,user748,0.8028506,0.2514133,0.2346299,-0.04305434,-0.4571441,0.7369466,-0.06541722,-0.2290731,0.08658741,0.007226359 +0,user749,0.6606191,0.8418177,0.02997744,0.1789328,0.167226,0.5726678,0.5202766,-0.4947611,0.3111162,-0.4025238 +0,user750,0.7221965,-0.8666646,-0.03142623,0.9108093,0.6856927,-0.6736424,-0.2646264,-0.07997283,-0.3886495,0.9674184 +0,user751,-0.688137,0.8680949,0.02069038,0.07374794,-0.5768737,-0.8156161,0.5736071,0.7923994,0.2528861,0.4242701 +0,user752,0.4821647,-0.1957056,0.8337675,-0.9809964,0.3948583,0.479867,-0.3189241,0.3432663,-0.4225021,-0.917499 +0,user753,0.8980605,0.5526458,0.9194906,0.9149493,0.6175472,0.2869627,0.6631818,-0.9502803,-0.7468161,-0.8197144 +0,user754,0.6539641,-0.2686865,-0.9714351,-0.2152802,0.1144718,0.02219667,-0.3303117,-0.9125461,0.7711761,0.3391064 +0,user755,0.7355616,-0.7103982,-0.166946,-0.1411016,0.4814028,0.2360558,0.9811988,-0.6238035,-0.8371592,-0.1623624 +0,user756,0.619854,-0.03783366,-0.9736739,-0.8446613,-0.6295181,0.5252468,0.8556836,0.6211881,-0.2335277,-0.5221959 +0,user757,0.3340595,-0.1758994,0.637717,-0.7628488,-0.7175646,-0.009137228,-0.4961639,0.6446759,0.7322195,-0.9503306 +0,user758,-0.7017114,0.6949432,-0.1866518,0.2164029,0.009151074,-0.07056478,0.3644989,-0.7455336,-0.5318891,-0.09369895 +0,user759,0.8856265,0.9522722,0.9854703,0.6041011,0.3126925,-0.1070083,0.1856216,0.465725,-0.07056765,0.2961951 +0,user760,-0.2515534,0.7556384,-0.4344725,0.3518214,0.9946467,-0.4501243,-0.3301461,0.7263536,0.785297,-0.5412091 +0,user761,0.9970483,0.8344398,-0.7588018,0.7787421,-0.6038472,0.3790742,-0.0001731827,-0.3117389,0.9925112,0.2682111 +0,user762,-0.002842319,-0.3780043,-0.8499358,-0.6894109,-0.3637431,0.674514,-0.8183507,0.01047192,-0.3013217,-0.7240183 +0,user763,0.7123256,0.007620433,-0.7826361,-0.248141,-0.6475578,0.9013767,-0.7884878,0.365576,-0.1620132,-0.9978303 +0,user764,-0.5497783,0.2161434,-0.333489,-0.3377618,-0.9404006,-0.03299264,0.1105534,-0.1840522,-0.5868651,-0.6619508 +0,user765,-0.4731107,0.961379,0.7359,0.2202315,0.6756153,-0.3597573,0.622681,0.6129882,0.9505057,-0.6104712 +0,user766,-0.9336726,0.5321599,0.6989703,0.6743218,-0.2879939,-0.8257534,0.1281814,-0.5369809,0.9718491,0.9621052 +0,user767,0.4466304,0.2381412,-0.4793716,-0.06729647,-0.8388441,-0.8197202,-0.3731497,-0.05291604,-0.6080897,-0.8802528 +0,user768,-0.5472235,-0.8911057,0.8753987,-0.9265088,-0.5197738,0.5583889,-0.9892455,0.549591,0.8283178,0.5136918 +0,user769,0.5833726,0.3774227,-0.3956996,0.6785583,-0.7335215,-0.6123661,0.0711931,0.1201447,-0.4342052,0.2356563 +0,user770,-0.3136852,-0.4304206,0.905683,-0.4936455,-0.8105785,0.8244495,-0.4305344,0.06080411,0.4058637,-0.3329868 +0,user771,0.01494628,-0.4933629,0.9714654,-0.3779586,0.5343706,-0.06136333,-0.3792215,0.9206016,0.3123929,0.6986763 +0,user772,-0.9873736,0.8355999,0.4367806,-0.4060875,-0.3822839,-0.4527941,-0.5399637,-0.6481308,-0.1918862,-0.2289834 +0,user773,-0.642263,0.5665394,0.7336749,-0.08524371,-0.7682469,-0.7228897,0.8914018,0.02327329,-0.2275739,0.6675852 +0,user774,0.8038203,-0.5304618,-0.2884489,0.05528077,0.9537433,0.4961493,0.5079245,0.2463607,-0.9899722,-0.8174672 +0,user775,-0.276637,0.437061,0.2750551,-0.4219983,-0.01067564,0.9195022,-0.1182428,-0.1324756,0.07865158,-0.3055571 +0,user776,-0.540584,-0.4417444,-0.00936976,-0.3000923,-0.04859586,0.3971082,-0.7874094,0.06129845,0.6280151,-0.9965694 +0,user777,-0.1352422,-0.9901821,-0.3075378,0.5434041,-0.996565,-0.3744873,0.7712967,-0.2820929,-0.5953371,0.5215386 +0,user778,0.7228402,0.2565177,-0.4462456,-0.3666082,0.1289385,0.2696609,0.5007103,-0.2003447,-0.907908,-0.140797 +0,user779,-0.7377333,0.8096689,-0.7747398,0.6568534,0.4942601,0.1340548,0.1471734,0.8322253,-0.2853975,0.01065693 +0,user780,-0.474623,0.8516356,0.7224397,-0.277663,0.170661,-0.8018196,0.2915733,0.2231461,0.7157791,-0.8809852 +0,user781,0.4450368,0.389853,0.5223281,-0.4557989,-0.1853687,0.5960185,-0.7639161,0.7196825,-0.2965575,-0.1733786 +0,user782,-0.4258704,0.6777638,0.2459505,-0.2693987,0.9173864,0.3184388,-0.2792195,0.6246247,0.9674886,-0.5650729 +0,user783,-0.9924584,-0.34407,0.5562072,-0.2586594,-0.4344807,0.6780474,0.9726492,-0.4335877,-0.706723,-0.7984842 +0,user784,0.3430973,-0.05750113,0.4418187,-0.5408496,-0.5678215,-0.1170189,0.8992658,0.7694021,-0.04337363,0.006906994 +0,user785,-0.7719063,-0.5909227,0.2745155,0.5153211,0.03185816,-0.6593646,0.3904688,0.7120786,0.7386647,0.7740335 +0,user786,0.7431033,-0.05446824,-0.6107388,0.6002389,-0.9530779,-0.08589681,0.953848,-0.05739122,-0.5438823,0.03915336 +0,user787,-0.03704868,0.9046652,0.4681448,-0.385511,-0.1973396,-0.591772,0.7549494,0.3905902,0.7230987,0.4847111 +0,user788,0.5621533,0.233178,-0.08776753,0.7524723,0.3142936,0.3314982,0.8943049,0.3567545,0.4708841,0.8237028 +0,user789,-0.9586081,-0.3595251,0.2026093,-0.1833582,0.05607314,0.8435384,0.318347,0.1970752,-0.07577137,0.9454544 +0,user790,-0.1514222,0.8569375,0.4536151,-0.7814098,-0.8846472,0.3012197,-0.05942903,-0.1436848,-0.347469,-0.2190938 +0,user791,-0.6894001,-0.01118366,0.47776,0.1042937,0.3089403,0.8813739,-0.4358412,0.08310816,0.2561811,-0.7175062 +0,user792,-0.9615598,-0.5250852,0.4438075,-0.4046161,0.4522259,0.2226126,-0.6818262,0.8853363,-0.08326017,0.2136655 +0,user793,0.8457355,-0.5210668,0.6036793,-0.4708207,-0.2483903,-0.0242663,0.1222202,0.8667872,0.3512093,0.05688791 +0,user794,-0.9770745,0.9964368,0.6951239,0.8561528,0.6613825,0.7827506,-0.224329,-0.5513159,-0.9058321,-0.7153365 +0,user795,-0.5113382,0.6910582,-0.8896815,0.2576222,0.5118253,-0.81038,0.4287272,-0.2987159,0.3298747,0.5517147 +0,user796,-0.6273752,-0.5596878,0.3395793,0.7494107,-0.572775,0.6159764,-0.2550988,0.4797753,0.301715,0.4464168 +0,user797,-0.910747,0.5285967,0.3940942,0.5304745,-0.6266114,0.9569972,0.9038523,-0.08829679,-0.933983,-0.7532313 +0,user798,0.9352922,-0.07080058,-0.3690531,-0.8096743,0.6729812,-0.6301002,-0.9444225,0.648368,0.721785,0.6714619 +0,user799,-0.1745987,-0.4507936,0.214978,0.8229019,-0.09254876,0.1743653,-0.2443443,0.02936634,0.1300327,-0.03989143 +0,user800,0.6726256,-0.09398062,0.9983946,0.2090328,-0.3601329,-0.6553689,-0.02495457,-0.9681521,-0.3681883,0.482425 +0,user801,-0.378393,0.4987789,-0.46337,-0.3033198,0.8624027,-0.8056507,-0.3749569,-0.2908278,0.1276487,-0.6615249 +0,user802,0.8403476,0.05584351,0.1864434,-0.5550567,-0.5581782,-0.8869981,0.3764342,-0.05003208,-0.5575744,-0.3412151 +0,user803,0.685252,-0.2583807,0.4351753,0.8029453,0.2575832,-0.1081631,0.4350817,-0.616283,0.4399256,-0.7465584 +0,user804,-0.02065598,0.06531828,-0.7296952,0.6114365,-0.9058442,-0.5285404,-0.4835552,0.7324454,0.9000749,-0.9939397 +0,user805,0.6441679,0.5253817,0.8979944,0.500224,-0.6044348,0.6091512,-0.1156412,-0.8036713,-0.5475466,-0.1586823 +0,user806,-0.591385,-0.8213197,-0.2897696,-0.619053,-0.7530925,-0.1886609,-0.6831611,0.2512414,-0.4814228,-0.05211546 +0,user807,0.43876,0.6235739,0.2609351,-0.6886558,0.04555997,0.8685678,-0.2709646,-0.2062561,0.5280899,-0.9905092 +0,user808,-0.4910743,0.5351996,-0.4095433,0.04362816,-0.6009999,-0.7653361,-0.3443446,-0.08576423,-0.1428837,-0.6371437 +0,user809,-0.8685448,0.435198,0.2639847,0.01433876,0.3758461,-0.9189999,0.8175492,-0.9491033,-0.3893309,0.8070876 +0,user810,0.7010267,0.4332427,0.4861952,0.9681976,-0.46018,0.002622638,0.8762088,-0.3740308,-0.7573076,0.02014776 +0,user811,0.03430271,0.3868352,-0.6871037,0.7659651,0.5696611,-0.5671557,0.9472287,-0.8626182,-0.4271046,-0.5181289 +0,user812,0.576492,-0.174949,-0.2136871,0.5585399,-0.8095226,0.6770186,-0.9463668,0.7705792,0.3141116,-0.366291 +0,user813,-0.7248437,0.1110065,-0.2678542,-0.3012011,-0.5427936,-0.6789386,-0.4030107,-0.7494061,-0.789819,0.4550748 +0,user814,0.04184435,-0.9572348,0.8691035,-0.4926943,-0.8648196,-0.8891082,0.9198779,-0.2962059,-0.1338277,-0.3166131 +0,user815,-0.0804107,0.7675499,-0.7718684,-0.9823097,-0.3773442,-0.4400003,0.9528989,0.5399813,-0.729262,0.640616 +0,user816,-0.4967499,0.5200839,-0.9933387,-0.78588,0.4890646,-0.3383032,0.9874581,0.9626725,0.9488457,0.2291083 +0,user817,-0.2150524,-0.01170304,-0.7416353,-0.8924554,-0.8178975,0.02499494,0.8737259,0.6464029,0.3222901,0.7225402 +0,user818,0.8825406,0.6722151,0.6962764,-0.3678207,0.4253162,-0.03177234,0.7078483,-0.06942846,0.9938366,0.125327 +0,user819,-0.9345966,-0.2467382,-0.08110626,0.9665923,-0.1966418,0.9931951,0.881763,0.319427,0.4197298,0.05281114 +0,user820,-0.1736605,0.6287719,0.460974,-0.07581355,0.2381756,-0.1314666,0.1920729,-0.1565219,-0.7534813,0.6679946 +0,user821,-0.2688816,0.5291525,0.1498915,-0.1492305,0.540669,-0.7305527,-0.3515807,0.7868868,-0.3536324,0.9062332 +0,user822,-0.6239967,0.7420782,-0.6033462,0.07088608,-0.8877015,0.874569,-0.5540782,-0.5974648,-0.3240891,0.3353049 +0,user823,-0.1352204,-0.8963133,-0.09521844,0.5195704,-0.3095984,-0.908854,0.5102467,-0.2711856,0.1632585,-0.1183399 +0,user824,-0.4231461,-0.9919143,-0.2464291,0.3799487,-0.7077212,0.245181,0.7706395,0.6536739,0.997577,-0.03687887 +0,user825,-0.6010712,0.738515,-0.9082223,-0.07296113,0.773681,0.6573196,0.2215928,-0.1487807,-0.2299211,0.6199684 +0,user826,0.3534415,0.7947449,0.01510005,-0.2228074,-0.7977731,-0.719234,-0.06102612,0.4300985,-0.5068668,-0.5666252 +0,user827,-0.05052136,-0.5516021,-0.9068499,0.1293595,-0.2804962,-0.1388426,-0.4844593,0.1334493,0.2992919,-0.5904621 +0,user828,-0.5118182,0.2671117,0.4858719,-0.5424866,-0.8529304,0.6143168,0.1254451,0.7629225,-0.1639041,0.8667371 +0,user829,0.2887337,-0.2760557,0.646047,-0.03248172,0.8752081,-0.3493343,-0.005448591,0.07846653,-0.7850817,-0.8951633 +0,user830,0.77488,-0.002395631,0.3081281,-0.04773866,0.626955,-0.9644773,0.2711965,-0.8371844,-0.5706753,0.3696465 +0,user831,-0.8391926,-0.8268689,0.4842665,0.6665462,-0.2130633,0.9589478,-0.8995095,0.7947704,0.4679076,0.3491621 +0,user832,0.9103407,-0.7772768,-0.8173231,0.6641985,0.7376109,-0.154985,0.6195945,0.7876387,0.342567,-0.5566882 +0,user833,0.6152276,-0.9465521,-0.5054285,0.3972046,-0.9312231,-0.8514754,-0.3523693,0.1127835,-0.1282497,-0.9715686 +0,user834,0.8460593,-0.08524961,-0.08055824,0.4694915,-0.9554802,-0.1492152,0.5355723,-0.8215126,-0.09216682,0.6026037 +0,user835,-0.1103153,0.2880415,-0.5470182,0.2756351,0.8317667,0.3164746,-0.8639607,0.5200841,0.2426419,-0.550628 +0,user836,0.2593955,0.5788296,-0.607434,-0.1025714,-0.535658,0.7576758,0.5319894,0.3091122,0.3242036,-0.1302509 +0,user837,-0.7453257,0.09343068,0.6296721,0.8504385,-0.7085726,0.6621239,0.8524111,0.4297289,0.4264104,-0.4495118 +0,user838,-0.6715552,-0.08838468,0.7139168,0.5869793,-0.1226733,0.1850424,-0.1349252,-0.686172,-0.2292682,-0.5411371 +0,user839,0.7683212,0.1140292,-0.01697738,0.9410568,-0.1366578,0.9923397,-0.8123551,-0.7766521,-0.8186801,0.2326054 +0,user840,-0.6138705,-0.4713714,-0.1063432,-0.1352228,0.6672734,0.743124,0.6699604,0.4806256,-0.9629205,-0.6424242 +0,user841,-0.9705285,-0.655142,0.200112,0.5551769,0.4171467,-0.812335,-0.2587164,-0.06020275,0.01342418,0.4790106 +0,user842,-0.1973761,-0.4991356,0.295919,0.7070219,-0.5669967,-0.574816,-0.8651264,-0.6392702,-0.2457847,0.7144765 +0,user843,0.9626215,0.3536796,0.6799697,-0.5766829,0.8577508,0.4201425,0.7235935,0.2512048,0.3511911,-0.008715244 +0,user844,-0.6953722,0.4558646,0.9322578,-0.7460242,0.8743531,-0.4912736,0.3382729,0.1903912,0.2236051,-0.06591453 +0,user845,0.8444683,-0.4563704,0.1650224,-0.7856724,-0.4318163,-0.4639242,-0.9452485,0.0645239,0.6203876,-0.6021367 +0,user846,-0.1177892,0.1212295,0.9081013,-0.5589927,-0.5195934,0.9801422,0.6764925,-0.2088139,0.6219291,-0.3680993 +0,user847,-0.1921222,-0.02405157,0.9389191,-0.5319042,0.3634177,0.1704233,0.325731,0.1530637,0.1724508,-0.8368062 +0,user848,-0.3705841,0.5319266,0.4233871,-0.6781277,-0.2497138,0.5610707,0.9284774,-0.2890732,-0.0573223,-0.8795965 +0,user849,-0.2352486,-0.2065554,0.6043777,0.07318663,0.9057228,-0.05163013,0.3843408,0.7217577,0.6157657,0.7572277 +0,user850,-0.1267188,0.7292103,-0.1421872,-0.5653118,-0.8332241,0.1636183,0.2074941,-0.5275092,-0.4078194,0.2160049 +0,user851,0.4557553,0.1606985,-0.1156389,0.2460587,0.9884618,-0.5703959,0.1205503,0.5544049,0.1891964,0.7883982 +0,user852,0.4958698,-0.6774029,-0.2457308,0.9239561,0.4463919,0.2178172,-0.9672399,0.5086445,-0.7378667,0.663461 +0,user853,0.2492845,0.4712885,0.2544666,0.5055742,-0.7209257,0.03818731,0.6534159,-0.124974,0.2680915,-0.4486902 +0,user854,-0.679465,0.2643852,0.7891427,-0.2343709,-0.3211366,-0.4792499,-0.369203,-0.7167807,-0.6475451,-0.3299417 +0,user855,-0.9272763,-0.6693172,0.5078401,0.3039049,0.7386706,-0.5370017,0.8033996,0.1623184,-0.7402897,-0.3734179 +0,user856,0.6482133,0.2098034,0.3462442,-0.5673869,-0.9472447,-0.3044931,-0.1249914,0.7262453,-0.9618296,-0.8287218 +0,user857,0.6739764,0.05913008,-0.1957573,0.5428217,-0.1189097,-0.198484,0.5697709,0.7133178,-0.1544118,0.103433 +0,user858,0.02220232,-0.2209192,0.6009902,-0.5667357,-0.5418256,0.3241557,-0.6810597,-0.7042323,0.5590022,0.03611999 +0,user859,-0.8636049,-0.5230849,-0.1678839,-0.1098735,-0.8001751,-0.6901763,-0.9995463,0.4891678,-0.1257338,-0.9619847 +0,user860,-0.0372899,0.7830744,-0.5497103,-0.48966,-0.2437016,0.4521818,-0.4356777,-0.2082157,0.06050642,0.2082697 +0,user861,-0.2029177,0.7766851,-0.09088164,0.3855257,-0.9148706,0.3596784,0.5901368,-0.5414167,0.9883269,-0.5942335 +0,user862,-0.7027976,-0.3499538,-0.6836174,-0.4433273,-0.01323849,-0.7312284,-0.8990557,0.2839382,-0.6578262,0.3871774 +0,user863,-0.1269492,-0.9942024,-0.3670333,-0.8254615,-0.5060907,-0.7028032,-0.8160832,-0.420577,-0.5969266,0.6515815 +0,user864,-0.5876902,0.830133,0.4036899,-0.2172697,-0.8460937,0.508203,-0.7622325,0.5713668,-0.1399228,-0.5658022 +0,user865,-0.8567382,0.5647966,0.2358244,-0.9738358,0.03128134,0.1195563,0.6365166,0.4624257,0.250007,-0.01021886 +0,user866,0.7627355,0.293839,0.08594843,0.4501735,-0.674324,0.6136713,-0.6800439,-0.9004928,0.6457153,-0.8990465 +0,user867,0.6717053,0.4089626,0.7962558,0.6801589,-0.3817517,0.2658789,0.7697569,-0.119521,-0.8157192,0.3039469 +0,user868,-0.6020639,-0.3417728,-0.1345035,0.8766027,0.3227087,-0.2183198,0.4889277,-0.1078455,-0.3235827,0.5402694 +0,user869,-0.9088197,-0.7945457,-0.2001348,0.03715284,0.2030026,-0.2012863,0.1850309,-0.5866648,-0.5835529,-0.4401836 +0,user870,0.4400265,-0.4770082,-0.2207216,0.6212157,0.4815905,0.2582186,0.9574018,0.1038269,-0.6343993,-0.4634478 +0,user871,-0.2159344,0.1868559,0.7591533,-0.2586201,-0.01001787,-0.4751958,0.1588881,-0.6272198,-0.2865032,0.8978451 +0,user872,-0.8793483,-0.4496876,0.9999773,-0.4076703,-0.3798507,-0.01362128,0.9263145,0.3531324,0.4298712,-0.961173 +0,user873,-0.7573496,0.02385626,-0.9248026,0.3282376,0.9145937,0.6834026,-0.9077247,0.4645567,0.119816,-0.7489713 +0,user874,-0.2533129,-0.4594645,0.439123,0.1646969,-0.1522671,0.9449467,-0.1175184,0.623985,-0.9353121,-0.1108701 +0,user875,-0.5747205,-0.9938231,0.9322351,-0.1536945,-0.5054975,0.4951051,0.2645874,-0.4564764,-0.3465236,-0.0270875 +0,user876,-0.9128813,0.5674859,0.2402198,0.5425652,-0.5172225,-0.7805216,-0.8529732,-0.4709194,-0.2597964,-0.351108 +0,user877,0.6288979,0.661765,0.3472243,0.6057043,0.3281395,0.9250889,-0.441026,-0.5848289,0.686617,0.5210306 +0,user878,0.2331573,-0.01787464,0.8711542,0.3144013,0.8579202,-0.3344716,-0.4096815,0.6965874,0.8259272,0.1361063 +0,user879,-0.2834655,0.0994125,-0.336393,0.8644375,0.2330637,0.7805492,-0.9244958,0.2400074,0.6828813,-0.2307045 +0,user880,-0.6063507,-0.5447904,-0.04839799,-0.3211091,0.2338624,-0.1265412,0.9433148,-0.8630712,0.3023827,0.2782584 +0,user881,-0.8935615,-0.2886644,-0.271033,0.7490895,-0.975304,0.8291467,0.7978125,-0.8309219,-0.5818923,-0.6478888 +0,user882,-0.8277101,-0.739889,0.5479681,0.1104962,0.2215255,-0.7898467,0.1960545,-0.2055877,-0.1279223,-0.4423063 +0,user883,0.8895191,-0.2221933,0.7058713,-0.397153,-0.3197458,-0.908724,0.9760749,0.6455732,0.5645161,-0.05828066 +0,user884,0.355723,-0.8173759,0.9834336,0.2546637,-0.6962296,-0.132666,0.4512284,0.04410406,0.6861993,-0.096579 +0,user885,-0.5071752,0.5244962,0.3371108,0.8761253,0.9003889,-0.2690967,0.8268515,0.07763168,0.2245327,0.227752 +0,user886,0.9622428,0.1084895,0.2137114,0.9067519,-0.5810751,-0.4457257,0.7794745,-0.1921084,0.8242264,0.5683014 +0,user887,0.003936275,0.3924275,0.3296778,0.6872768,-0.6434743,0.5628409,-0.673763,-0.2296506,0.7243696,0.07469924 +0,user888,-0.8331987,-0.4163737,-0.8586465,0.418947,-0.2185208,0.5324194,0.3966223,-0.2090505,-0.9298792,-0.668815 +0,user889,-0.01555489,0.8875703,-0.1852984,-0.6599838,-0.1229007,0.87843,-0.9015852,0.1036593,0.3832286,-0.3955786 +0,user890,0.1403313,0.8693426,-0.838206,-0.4225967,-0.4436495,0.8726646,-0.6733092,-0.7404828,-0.4013642,0.1127146 +0,user891,0.1295114,-0.6332993,-0.4083567,0.929287,0.5377777,-0.01539885,0.9609446,0.5827339,0.1306273,0.5394548 +0,user892,0.7815274,0.6642554,0.72382,0.7255419,-0.03777134,0.2381083,0.6885516,0.5622426,0.3715555,0.0101879 +0,user893,0.4375338,-0.4806112,-0.5218234,0.1340761,0.5431121,-0.8585638,-0.5723649,0.5434554,-0.05919034,-0.500108 +0,user894,-0.9974378,-0.6275018,0.2246099,-0.8961745,-0.9683131,0.2817979,-0.8551386,-0.8378431,0.5337007,0.1910363 +0,user895,-0.8061628,0.4943885,0.1275098,-0.4917279,0.1161349,-0.2536887,0.9263191,0.1336094,-0.7683673,0.4443857 +0,user896,0.5807956,-0.9158146,0.714001,0.1602403,-0.4256066,0.2609925,-0.9358484,0.00588111,-0.8091834,0.4896732 +0,user897,0.7652977,0.6663373,-0.6894416,0.553999,-0.6426371,-0.1045307,-0.5351825,-0.738336,0.179416,0.2919898 +0,user898,0.8655425,-0.09664892,-0.07623435,-0.811569,0.7343832,-0.9878098,0.696076,-0.9859117,-0.5840865,-0.2516674 +0,user899,0.9787316,-0.2575874,-0.4205026,0.03684293,0.8971021,-0.9573272,0.5530793,0.8980357,-0.132766,0.02994254 +0,user900,0.8564779,0.8717916,0.1104236,-0.4088481,0.5603656,0.694183,0.6498484,-0.3250008,0.595863,0.8518062 +0,user901,0.305569,0.4263429,0.7030441,0.8096467,0.2159737,0.2704088,0.6534778,0.1179153,-0.2184858,0.2848848 +0,user902,-0.2372028,0.9292685,-0.6613492,0.7782228,-0.1129158,-0.432523,-0.2880326,-0.7291842,0.5807308,-0.07221231 +0,user903,0.9771296,-0.577896,0.1104009,0.1834816,-0.8194851,-0.3194383,0.5761629,-0.9718683,0.02573427,0.8906332 +0,user904,0.5482195,-0.5498008,0.7782415,0.1378843,0.1305674,-0.04618859,0.7457531,-0.4175281,0.9013302,0.5359135 +0,user905,0.5094843,-0.530196,0.7777738,-0.05708025,0.7348172,-0.4875763,0.5944489,0.8948008,0.6454188,0.8169176 +0,user906,-0.5975909,-0.571719,0.04263602,-0.9702129,-0.3249826,-0.8243332,-0.1592497,-0.4283447,0.6792107,-0.1364543 +0,user907,0.6353381,-0.9823149,0.01846133,-0.3195505,0.6133449,0.1732898,0.8927799,0.1115525,-0.3584662,-0.8151945 +0,user908,0.1383821,-0.868431,0.1249981,-0.451376,0.0629567,-0.5624874,-0.846577,-0.6900281,0.3320358,0.3379482 +0,user909,0.6355665,0.4104063,-0.08620979,0.3441885,-0.4670625,-0.1588048,0.4310688,-0.7317573,0.5051378,0.999652 +0,user910,-0.6481273,0.1170976,0.6820683,-0.455113,-0.1535914,-0.04616098,0.9682841,-0.6484401,-0.6755848,-0.04589892 +0,user911,0.5320314,-0.4132214,-0.9233999,0.2275149,-0.7031809,0.3109714,-0.9032622,-0.5530993,-0.3655815,-0.3837934 +0,user912,0.742005,-0.8782581,0.6427572,0.09327795,-0.4423664,-0.3296581,0.2288813,-0.5626792,0.9232456,-0.6482368 +0,user913,-0.4758374,0.3772086,0.2300364,0.6553833,-0.9320659,0.1639923,0.1643386,0.1459723,0.1964929,0.5117948 +0,user914,0.4215505,0.3645853,0.7824713,0.8303619,-0.02292671,0.4022474,-0.9271873,-0.9075261,-0.8010654,0.5579259 +0,user915,0.09772797,-0.695634,0.6261908,-0.6520583,-0.138596,0.5376759,-0.3198903,0.4814248,0.6094448,0.2551842 +0,user916,0.01698741,-0.09829524,-0.4328528,0.5315086,0.968323,0.8948956,-0.008809895,-0.776396,-0.5789744,-0.2604532 +0,user917,0.3837933,-0.5269252,-0.003817293,0.7371138,0.3959982,0.9565217,0.8522872,-0.09963447,-0.976839,0.1262274 +0,user918,-0.8983358,0.6967936,-0.04413134,-0.9647815,0.2179296,0.1005168,0.006346697,-0.7482258,0.3338145,-0.6701166 +0,user919,0.1837887,0.485331,-0.2914993,-0.04954439,-0.2501977,0.427315,-0.6121876,0.01455345,-0.5088535,0.07073179 +0,user920,-0.6317616,-0.6393549,0.8108843,-0.92287,-0.7269026,0.8349517,0.950702,-0.9959752,0.4063896,0.7306488 +0,user921,0.2419956,0.5661362,0.1176626,-0.3873782,0.7742802,-0.02681851,0.3330375,-0.4887086,0.9324503,0.442598 +0,user922,-0.6867,0.8520317,0.300144,-0.1202574,-0.7124201,-0.5880838,-0.651243,-0.4027127,0.6217737,-0.3898135 +0,user923,-0.8502342,-0.9750995,0.5347043,0.8026719,0.2353261,0.07306001,0.6392536,0.5662674,-0.2220548,-0.2591633 +0,user924,-0.3204706,-0.914475,0.5958392,0.7466979,0.3173922,0.1146177,0.7606725,-0.9452532,-0.12674,0.9424901 +0,user925,-0.6841378,-0.77547,-0.4752461,-0.01643191,-0.6807331,0.6937141,-0.5063816,-0.2405558,0.1554744,0.8012228 +0,user926,-0.656397,0.519289,-0.3377859,-0.689056,-0.648539,0.8193713,0.5655727,-0.3001232,0.009577834,-0.8147776 +0,user927,-0.7396751,-0.8302897,0.3098402,-0.09306182,0.8917856,-0.6243898,0.8248242,0.06062794,0.0640766,0.4321632 +0,user928,-0.9188401,0.8908673,-0.1646877,-0.4624329,-0.3233702,-0.4108167,-0.04156414,0.02110823,-0.6651096,0.09321259 +0,user929,-0.7908544,-0.5773599,0.5859797,-0.500625,-0.9141558,0.8315615,0.2616487,-0.2860349,0.4254913,-0.066445 +0,user930,-0.7609435,-0.08787705,0.8893376,0.9437811,0.7888877,-0.581717,0.3779035,-0.0413364,0.9313106,-0.5378942 +0,user931,0.9376378,0.7626589,0.9457359,0.128719,-0.7630046,-0.7166337,-0.3917158,0.6961075,0.9307534,-0.05498121 +0,user932,0.5147146,0.848983,0.2890238,-0.6909783,0.3018179,0.1019703,-0.08487351,0.8318803,-0.7929945,-0.7815602 +0,user933,0.001853737,-0.1586086,-0.7720116,0.7220039,-0.3240281,-0.01424002,-0.9101291,0.2294794,0.5120414,0.3898935 +0,user934,0.9147675,-0.8152371,0.05613686,-0.6877994,-0.5824897,-0.03607199,-0.8155529,0.7242392,-0.04351235,-0.164348 +0,user935,0.06293405,-0.7008178,0.06726532,0.446906,-0.5676147,-0.9442183,-0.3391204,-0.5856477,-0.8916643,0.7543534 +0,user936,-0.488662,0.3111954,-0.9942378,-0.3350763,-0.5892109,0.4981837,0.6843198,0.1242802,0.1574602,0.206811 +0,user937,-0.6828234,-0.3869561,-0.9012271,-0.6580123,0.09252763,0.1395948,0.02519735,-0.7041055,-0.3643017,0.6991978 +0,user938,-0.3017278,-0.6831328,-0.9142733,-0.8726445,-0.9542698,0.2290716,-0.4463404,0.5259048,-0.2501305,0.9391589 +0,user939,0.6497201,0.4427644,0.1307603,0.2135477,0.4737458,0.9356963,0.8377428,0.4342521,-0.510504,-0.4552408 +0,user940,0.9527431,-0.9765498,0.01256309,0.6861762,0.6254652,0.98079,-0.5437339,-0.4358629,-0.8591639,0.6988498 +0,user941,0.05014487,0.4339648,0.767795,-0.3277574,-0.1078613,-0.8170894,-0.4780563,0.8774647,0.07428473,-0.10674 +0,user942,0.1817515,-0.970457,0.2073603,-0.5589374,0.7705648,0.2466677,0.9344806,0.8811527,0.1239145,0.1609658 +0,user943,0.694748,-0.8548079,-0.3446797,-0.2205459,-0.8169012,-0.3488681,0.6851474,0.001457928,-0.9359183,-0.9493871 +0,user944,0.5743074,-0.1888267,-0.002168648,-0.6723742,-0.03992716,0.3469029,0.6862823,0.02343701,-0.7292223,-0.5949452 +0,user945,-0.396698,0.3941283,-0.01016831,-0.7285755,-0.2523619,-0.3510848,-0.9927067,0.9736266,0.3228492,-0.2811082 +0,user946,-0.207524,-0.5504418,-0.7184889,0.1273958,0.04450272,-0.8111921,-0.6347429,-0.5171172,0.6735265,0.3057971 +0,user947,-0.4087052,0.7128781,0.5649785,0.8591344,-0.07160411,0.2417985,-0.3225276,0.247041,-0.3081967,0.1446015 +0,user948,0.9870953,0.8672031,0.9860144,-0.9914616,-0.8563637,-0.3945631,0.8595805,-0.1260078,0.3460102,0.8451191 +0,user949,-0.1058597,-0.8536483,0.2373798,0.1626143,-0.7375676,0.2893247,0.3716037,-0.265343,0.007340992,0.6356805 +0,user950,0.7750835,0.1982092,-0.7265207,-0.19041,0.6781982,-0.3308865,0.06528485,-0.7384056,0.1829498,-0.7846667 +0,user951,-0.6446663,-0.7721518,0.7968987,-0.9143316,-0.5832663,-0.5596114,0.8102825,-0.121983,-0.2476002,0.5757679 +0,user952,-0.8638641,0.7124879,-0.6449576,0.7752361,-0.9632875,-0.7374938,-0.2953588,0.2459484,-0.06020871,0.07827853 +0,user953,-0.9116164,0.05024087,0.5736233,0.6893326,0.9657781,0.08102969,0.4140419,-0.1411183,-0.1952765,-0.1744801 +0,user954,-0.4949005,-0.7472513,0.331603,0.8883403,0.6520597,0.5134486,0.4495361,-0.5557156,0.530345,-0.6833954 +0,user955,-0.1843348,0.7980129,0.9508816,0.521934,0.3541047,0.3771239,-0.5346862,0.3006952,0.8130512,0.02076858 +0,user956,-0.5957542,0.2747708,-0.9016228,-0.3270993,-0.714955,-0.2252562,0.9076603,0.6183259,0.9601979,-0.3732573 +0,user957,-0.1512975,0.7720377,0.9938171,-0.8007157,-0.9964793,0.33282,0.01510873,0.1441612,-0.4600772,-0.498173 +0,user958,0.07599017,0.9677232,0.2607218,-0.5711278,0.2458904,0.7527341,-0.7098621,-0.6386769,-0.1228721,-0.5470682 +0,user959,-0.5145943,0.1656381,-0.06631048,0.2104678,-0.03832519,0.3639271,-0.1339039,-0.3605659,-0.7049117,0.7199553 +0,user960,0.05784804,-0.8053222,0.5797968,-0.3013407,-0.9106351,0.1643815,-0.7232426,0.8581263,0.9654141,0.435382 +0,user961,0.3150467,-0.1201539,0.1500595,-0.6273467,0.03477806,-0.8289829,0.6680414,0.3199867,-0.1915616,-0.08496243 +0,user962,-0.5769565,-0.07170302,-0.1205745,-0.6608132,0.1986702,0.6472934,0.4743804,-0.6644584,-0.7741584,-0.3350259 +0,user963,-0.4274374,-0.9563392,-0.1311794,0.007681012,0.3911828,-0.7336482,0.1918839,0.6900066,-0.8275804,0.6538219 +0,user964,-0.6830995,0.7212376,0.3780478,-0.9053428,0.71075,0.1567771,0.7579123,-0.4505339,-0.6795201,-0.695069 +0,user965,-0.6621891,0.1130599,0.9355623,-0.3486126,0.6161804,-0.3887786,0.6588274,-0.9402192,0.1823293,0.5006261 +0,user966,0.6354967,-0.6571571,0.936086,-0.545413,0.8235681,-0.6778665,0.8527635,-0.8956411,-0.7192447,0.4081752 +0,user967,-0.1717616,0.03243302,0.38381,-0.2404191,-0.878461,-0.3450393,0.4422321,0.6737463,0.4779401,0.5117421 +0,user968,-0.3450125,0.7261037,-0.9656648,-0.006624846,-0.2912919,0.7508163,-0.3159752,-0.6443248,0.8180276,0.1998239 +0,user969,-0.6662311,-0.3402898,-0.9781874,-0.4180575,0.8692982,0.5512051,-0.5935769,0.6302637,0.03062483,0.3473341 +0,user970,-0.5220414,-0.5248025,-0.4854297,0.9731286,0.5952848,-0.4093429,0.2799748,0.1079984,0.9674361,-0.9434987 +0,user971,-0.3922694,0.7495539,0.04689829,-0.3204487,-0.6658268,0.7316063,0.1402909,-0.08018762,0.9588638,-0.1013264 +0,user972,0.3839137,-0.9063251,0.7896076,0.2541851,-0.238563,0.7341157,-0.07163323,0.5077284,-0.8950904,-0.7594059 +0,user973,0.6597101,-0.4952595,0.7219306,-0.5858088,0.3658496,0.8373248,0.2144554,-0.01084883,0.09135059,0.2174671 +0,user974,-0.6975214,0.894746,0.7022186,0.4590055,-0.482728,-0.6172618,-0.1745618,0.9212703,-0.9770545,-0.05071344 +0,user975,-0.04177882,-0.09515173,-0.2125611,0.5818109,0.7215098,0.08101858,-0.3853509,-0.4688346,-0.6243128,-0.3543511 +0,user976,-0.7369879,0.8988688,-0.2882377,-0.3143843,-0.8865122,-0.51376,0.2217487,-0.03722219,-0.5858002,0.9363589 +0,user977,0.09495461,-0.6556958,0.9837298,-0.4135987,0.5617747,-0.4284539,0.1906953,-0.5958469,0.696472,-0.7449163 +0,user978,0.549516,-0.3822736,-0.6475825,0.4409453,-0.3500943,-0.6771829,0.2921215,0.7782064,0.06749053,0.7902504 +0,user979,-0.7498926,0.7660718,-0.3022233,-0.3058459,-0.742876,0.0916769,0.08132918,0.83677,0.7602099,0.781478 +0,user980,0.9890949,-0.5093441,0.2211096,0.7490156,0.8242071,0.8608708,-0.437701,0.13881,-0.296187,0.8907642 +0,user981,0.3245995,0.8159355,-0.3741033,-0.7494646,-0.6718961,-0.008069369,-0.6425937,-0.9601992,-0.7495597,-0.9944162 +0,user982,-0.394559,0.99392,-0.5053246,-0.2201775,-0.3261423,0.5320655,-0.1083884,-0.285213,-0.4873902,0.3572459 +0,user983,-0.8747693,-0.7968563,0.576152,0.5242517,0.8609196,-0.876623,0.2669403,-0.6152416,0.6436043,-0.03095729 +0,user984,0.4129831,-0.1338236,-0.80048,0.939868,-0.706118,-0.9270397,0.7714482,-0.1013175,0.0551638,-0.1688964 +0,user985,0.1105405,-0.7533313,0.8262784,-0.3318372,-0.6740826,0.04551413,-0.6588523,0.1590714,-0.9570452,0.6738506 +0,user986,-0.05910404,-0.9988434,0.5270337,0.0461857,0.2150243,0.5005008,0.732254,0.6854536,0.4566555,0.9898113 +0,user987,0.8172289,-0.8590528,-0.7021028,-0.3872313,-0.421073,-0.1522959,0.6791085,-0.4829916,0.01536171,0.4578463 +0,user988,0.959243,-0.9812936,0.8200955,-0.1325529,-0.6705618,-0.6216659,0.3562564,-0.6967674,-0.4171224,-0.8243224 +0,user989,-0.9831139,0.9688798,-0.2122445,0.4750579,-0.5390853,0.2532349,-0.9776081,-0.9532233,-0.6662166,-0.5572569 +0,user990,-0.6973655,0.3065853,0.2315867,0.8232365,0.5406018,-0.7883688,-0.4547954,0.1564426,0.31045,0.1778016 +0,user991,0.01709102,-0.7866158,0.3998923,0.5661064,-0.5811969,0.5427156,0.6330138,-0.8386412,-0.4517083,0.6110596 +0,user992,0.3319328,-0.1512741,0.9378149,0.8477112,0.4956927,0.424252,0.6904333,0.3667635,0.1422218,0.3577807 +0,user993,-0.274322,-0.7651177,-0.8889878,-0.8375767,-0.260728,0.8589247,-0.980415,0.4919842,0.5362916,0.8427757 +0,user994,0.5896536,-0.7429551,-0.7312871,-0.4262126,0.8099859,0.8090674,-0.1751022,0.8513654,-0.2792887,0.2648815 +0,user995,0.6488333,-0.4300365,0.3158628,0.9423684,0.2064427,-0.4189709,0.4483456,0.9162296,0.4627017,0.6627117 +0,user996,0.06348891,0.3479422,-0.9534255,-0.1861892,-0.6445476,-0.5298539,0.6784124,0.5517649,-0.2813791,0.3434018 +0,user997,0.2251503,-0.4001121,-0.7952011,0.02837443,0.633554,-0.868799,-0.3223387,0.9557243,0.001466593,-0.3269433 +0,user998,-0.5229283,0.6023965,-0.3003272,-0.2980507,0.3279818,0.2359898,-0.1094223,0.5899759,-0.05935824,0.1744537 +0,user999,0.7184764,0.07404592,-0.9190903,0.8071859,0.06416046,-0.7790376,-0.6375628,0.9074402,-0.4633515,-0.4567744 +0,user1000,0.5589192,0.259598,-0.7733885,0.610317,0.5028522,0.6824061,0.08408439,0.585988,-0.9679086,-0.9796091 +0,user1001,-0.04496971,-0.922406,0.214243,-0.3249221,-0.07673345,0.8266469,-0.8294475,-0.3020257,-0.09192218,0.2309551 +0,user1002,-0.673793,-0.1764002,0.127808,-0.5132627,0.3983337,0.9525687,0.502728,-0.1727475,-0.5044877,0.4418993 +0,user1003,-0.05716706,0.353273,-0.9837809,-0.1354979,-0.7357108,0.4165218,-0.9875488,0.09371645,-0.862999,-0.739015 +0,user1004,-0.3852596,-0.4176655,-0.06382634,0.08926908,-0.7108838,0.6639717,0.3850079,0.6871255,0.9994284,-0.5515778 +0,user1005,-0.3713144,-0.2816542,-0.1699734,0.9457427,0.9156057,-0.6646931,-0.6718337,-0.2514772,-0.4815423,-0.6088142 +0,user1006,0.9010541,-0.7418788,-0.196342,-0.553687,0.985799,-0.5024597,-0.3728998,0.6248819,-0.4873118,-0.09336614 +0,user1007,-0.1222476,-0.5187968,0.647936,0.7748848,-0.5973961,-0.8497884,-0.3932434,-0.3500967,-0.5863718,-0.6152189 +0,user1008,0.7236402,0.06264999,-0.1862436,-0.467856,0.4773804,-0.09314703,0.5188616,0.1526759,-0.7850703,-0.3537305 +0,user1009,0.4505701,-0.1241524,0.1560755,0.8872583,-0.3642953,-0.1796426,0.9192217,0.4030883,0.5801787,-0.3031157 +0,user1010,0.1278598,-0.7527249,-0.6542873,-0.5309611,-0.340272,0.2418885,0.6880858,-0.5133267,-0.8261619,-0.8337409 +0,user1011,0.712735,0.5533059,-0.9651341,-0.7188404,0.3015874,-0.2322762,-0.9188394,-0.708514,-0.08125726,-0.4629663 +0,user1012,-0.2248303,-0.3082169,0.7819722,-0.8622063,-0.03619143,0.8122881,-0.723372,0.4428891,0.830619,-0.2975319 +0,user1013,0.7333008,-0.7588049,-0.1596119,0.2488614,0.3335857,-0.2260459,-0.4203025,0.2014603,-0.3135521,0.523505 +0,user1014,0.8379658,0.7564496,0.6110179,0.8054113,0.162507,-0.1088993,0.3481008,-0.3237556,-0.437653,0.5060764 +0,user1015,-0.8118472,0.5579595,0.9814922,-0.9223384,0.2576905,0.8852484,-0.9519237,-0.6584284,-0.1142172,0.5335717 +0,user1016,-0.1561587,-0.5121363,-0.3333335,0.9170242,0.6595031,0.8194682,-0.07915483,-0.6394683,-0.2705974,0.1973556 +0,user1017,-0.2211383,0.7576062,0.1380516,-0.148403,-0.6224687,-0.6083984,0.08035485,-0.638302,-0.9809975,0.4958876 +0,user1018,-0.9946183,0.6989068,-0.7206106,-0.3095697,0.8366175,-0.2670475,0.7271848,-0.1414199,0.9011445,-0.008581968 +0,user1019,-0.1969157,-0.4934299,-0.5132381,-0.2155287,0.9889413,-0.8021977,-0.7228984,-0.3362358,0.3122802,0.3730332 +0,user1020,-0.2042522,0.726486,0.925807,-0.6733451,-0.161554,0.6448365,0.1027468,-0.5915253,-0.6472141,0.9386307 +0,user1021,-0.6919838,0.005492097,0.5109761,-0.4863332,0.3772193,-0.05541629,-0.7276106,-0.9849774,0.2115945,-0.8307804 +0,user1022,0.8201753,-0.2800457,0.8866542,-0.6494223,-0.5922556,0.7405179,0.9101154,-0.1748769,0.8605719,-0.0159072 +0,user1023,-0.8723193,-0.4247881,0.863622,-0.8256339,-0.6658612,0.06908849,-0.2068199,0.7752382,0.4950077,0.2964114 +0,user1024,0.0336942,0.2403744,0.6219883,-0.3239098,-0.8835087,-0.1964916,-0.7080256,0.5070068,-0.2521139,-0.9880047 +0,user1025,0.4098289,-0.02300078,-0.8446329,-0.07563489,-0.7822697,0.5495853,-0.2649868,-0.3235115,-0.4187168,-0.7510257 +0,user1026,0.776514,0.1451754,0.1794847,-0.8832655,0.5405815,0.6501176,-0.7584743,0.6914677,-0.04229057,-0.04087693 +0,user1027,-0.9028169,-0.4116834,0.6685628,0.489901,-0.5280563,0.2736545,0.9703868,0.05877174,0.4665071,0.3553971 +0,user1028,-0.3650207,0.5768871,-0.639834,0.9527395,0.8512844,0.6807863,0.4126745,-0.3677872,0.5827498,-0.07796897 +0,user1029,-0.7464143,-0.2524281,0.8791575,-0.1813161,-0.1314367,-0.1138926,0.1321034,0.2814437,0.8983512,-0.8664232 +0,user1030,0.8156595,0.6623625,0.7494725,0.2970869,0.5361041,0.4946168,-0.667176,-0.03378811,-0.9968444,0.8986227 +0,user1031,-0.8061015,-0.1635149,-0.4132225,0.5630565,0.3541366,0.3631923,-0.5032411,-0.7817992,0.6148412,-0.05757811 +0,user1032,0.208616,-0.1748341,0.09340056,0.4937618,0.7918298,-0.2872458,0.3026559,0.979418,-0.193571,0.3645319 +0,user1033,-0.8581335,-0.5140377,-0.1227195,0.7838242,-0.06556219,0.4471855,0.835552,0.7934644,-0.5013321,0.3405219 +0,user1034,0.1367314,-0.8102419,-0.3970035,-0.5724414,0.6184258,-0.2202859,-0.49079,0.3119172,0.7518422,0.2034069 +0,user1035,0.8233563,0.4075004,-0.9704258,-0.4169692,-0.919054,-0.6232741,-0.3123362,0.6665435,-0.1941426,0.8129541 +0,user1036,-0.2294479,0.2043082,0.7073071,0.7295669,-0.1499565,0.7824924,-0.8362817,-0.4580127,0.01712564,0.7317078 +0,user1037,0.03778554,-0.5521207,0.4066545,-0.1261285,0.6042248,0.2772544,0.1363103,-0.06320087,-0.7354696,-0.8899593 +0,user1038,-0.2988912,0.8887036,0.6775102,-0.6420844,-0.5164501,-0.4730625,0.2944205,-0.6835531,0.2194856,-0.8022648 +0,user1039,-0.5058078,-0.7330418,-0.4789366,-0.7382891,-0.6725762,-0.3106547,0.6825798,0.6946632,0.2320554,-0.6220228 +0,user1040,-0.5116443,0.3237269,-0.43727,-0.2388701,-0.7600705,-0.9023881,0.05553197,-0.6601126,0.8447091,-0.193075 +0,user1041,0.8289685,-0.8640213,-0.9767772,-0.1730455,0.1432779,0.7688261,-0.01749372,-0.1968798,0.3933237,-0.6360057 +0,user1042,-0.7930727,0.820264,-0.4440706,-0.4571295,0.6290113,0.4570691,0.7637404,0.9861492,-0.8492019,-0.0849891 +0,user1043,0.2635254,-0.98449,-0.6552979,-0.1010765,0.2037381,0.9098999,0.33216,0.7827765,0.6753281,0.5093931 +0,user1044,0.5622693,-0.6228262,-0.1363891,-0.9241841,-0.5231364,-0.4572199,0.5622038,-0.9954195,-0.9202285,0.8874993 +0,user1045,-0.955107,0.5767136,-0.8330527,-0.6517183,-0.2084817,-0.6518302,0.1118413,-0.3376065,-0.2868549,-0.5789127 +0,user1046,0.4516781,0.5734696,-0.6738057,-0.02341484,-0.5385714,0.7951483,0.3802363,-0.8756518,-0.438889,0.04296478 +0,user1047,-0.5938893,-0.1349625,0.5302774,0.9928401,-0.8636333,-0.6377517,-0.5169511,-0.6348878,-0.1908259,0.08485492 +0,user1048,-0.1762452,0.3343198,0.3049988,0.1998787,0.1690496,-0.2602286,-0.8078039,0.02409149,-0.2678523,0.9169749 +0,user1049,0.4570598,0.2723763,-0.3944163,0.6670155,-0.7019539,-0.4718992,0.107421,-0.01707174,-0.5377445,-0.9656172 +0,user1050,0.2091949,0.3716076,-0.9829607,-0.2226886,-0.874692,-0.4399494,-0.2398495,0.02887642,-0.8785457,-0.5421119 +0,user1051,0.6195026,0.06080574,0.2308059,0.5265336,-0.9925043,-0.6153921,0.2949429,0.4325662,0.0849336,0.8556056 +0,user1052,0.765076,-0.7221316,-0.8834402,-0.8193177,0.6752655,0.4726845,0.3798104,-0.0020491,0.67385,-0.7963976 +0,user1053,0.02937024,-0.9084381,0.9036935,0.1278891,-0.4669475,-0.6994316,-0.329734,0.8539995,0.9820262,0.4419809 +0,user1054,0.7471833,0.6360176,0.09442785,0.7008996,-0.6583656,0.4536963,-0.911877,0.2078043,-0.4200587,0.152017 +0,user1055,-0.2012298,0.5182428,0.7385481,-0.1432275,0.7917567,-0.7238071,0.6717849,-0.4950423,-0.5782639,-0.7844023 +0,user1056,-0.5608008,0.06856112,-0.9409394,-0.9477458,-0.2492172,0.8501537,0.4052792,-0.4695121,-0.4366906,0.6909552 +0,user1057,0.5236972,-0.218807,-0.7260874,0.8176342,0.882216,0.1038139,-0.6703513,-0.1007279,0.5376508,-0.8888599 +0,user1058,-0.1040467,-0.8934406,0.4071108,-0.6533265,-0.7362996,0.5498473,0.6421717,0.5637294,0.8882432,0.5709948 +0,user1059,0.07417847,-0.3545518,-0.5807734,-0.9950063,-0.3979328,0.53094,-0.1820463,0.1627007,-0.8539408,-0.3870138 +0,user1060,0.7772829,0.5287649,-0.8469299,-0.363682,-0.2492208,0.9899213,0.4617521,-0.8192842,0.436002,-0.7552831 +0,user1061,-0.2883871,0.7689219,0.1565833,0.6437604,0.7998045,0.04446414,0.9749957,-0.4700587,0.8913988,0.4696175 +0,user1062,0.2680769,0.4819333,0.006004013,0.5680502,0.9562037,-0.1058677,0.3147125,0.3809015,0.7609004,0.5554081 +0,user1063,-0.01410107,-0.6460692,0.2464707,-0.8699202,-0.457391,-0.2973245,-0.235592,-0.8398662,-0.757569,0.6092488 +0,user1064,-0.1465206,-0.7451157,-0.9661362,0.4275845,-0.2657577,-0.5083504,0.8105477,-0.6765942,-0.6099333,-0.1898605 +0,user1065,-0.5951916,0.6716914,0.6090005,0.9956088,0.5746295,0.6738464,0.8239226,-0.3071813,0.5127425,-0.241185 +0,user1066,-0.1907447,0.7614312,0.2760449,-0.2868894,-0.376445,0.07940136,0.4520718,0.8266773,0.04828838,0.4222029 +0,user1067,0.6240314,0.4591924,0.7411709,0.1571514,0.5842858,-0.725858,0.974266,-0.134607,0.4071923,-0.4581528 +0,user1068,0.4425939,-0.8804293,0.01565505,-0.1305197,0.1788543,-0.0488992,-0.03976714,0.6296179,0.7772729,-0.1311443 +0,user1069,0.510364,0.6501348,-0.04644494,0.07102627,0.107105,0.6063389,-0.2535078,-0.8568758,-0.7322261,0.619938 +0,user1070,-0.8817763,0.7261506,-0.7377656,0.4188623,0.9117096,-0.0365127,0.6568458,-0.4399438,-0.3607523,-0.08017554 +0,user1071,0.9309496,0.4432976,0.578385,0.6306102,0.4187838,0.04871266,-0.9842352,0.9695053,0.621982,0.6757807 +0,user1072,0.3393326,0.7861135,-0.0232221,0.8979808,-0.7496171,0.3751649,0.7289985,-0.05375564,0.6610976,0.9839323 +0,user1073,-0.6748491,0.5464146,-0.1818363,0.9617328,0.5407209,-0.5794436,0.4205862,-0.4537946,-0.2099542,0.8348354 +1,user1074,0.1944749,0.4588077,0.9230872,-0.4704663,-0.3774781,-0.04138742,0.3479248,0.7522818,0.2973102,0.1851738 +1,user1075,-0.09839807,-0.8367127,0.8403888,0.9737967,-0.2727536,0.9179451,0.2912023,-0.04917516,0.7408691,0.8714317 +1,user1076,-0.629956,0.1231282,-0.014889,-0.6899854,-0.6677608,-0.2312738,-0.4675725,0.2085989,0.503191,-0.7440774 +1,user1077,-0.3538469,0.03227722,-0.7507185,0.5061188,0.08395057,-0.2462391,-0.2718389,0.87663,0.8584211,-0.7718614 +1,user1078,0.3077126,0.02832483,0.3706662,0.9666368,-0.1363868,-0.7198066,0.7742512,0.315937,-0.4499567,-0.04371341 +1,user1079,0.1937987,-0.542552,-0.7098902,0.5098933,0.5012888,0.5084976,-0.2753764,-0.7673096,-0.7646614,-0.8271025 +1,user1080,-0.8967871,-0.6953465,-0.1451348,0.1731343,0.3819967,0.2818617,0.8355822,-0.1404417,-0.6793234,-0.7374786 +1,user1081,-0.4830925,-0.6000675,0.3877055,-0.2560518,-0.01107882,-0.1597561,-0.4655983,-0.6551866,-0.3285024,0.4141747 +1,user1082,-0.1866987,0.5182537,0.5209157,0.03642683,0.5087845,0.8931054,-0.9804335,0.6652566,0.3202722,-0.9714968 +1,user1083,0.8682889,-0.417478,-0.02857505,0.3538167,0.05726217,-0.2454538,0.2153926,0.8575092,0.9945266,-0.5338762 +1,user1084,0.5462778,-0.5085056,0.291399,0.8718372,0.5219736,0.1408124,0.2046677,-0.8011871,-0.3464762,-0.1438444 +1,user1085,-0.4395154,0.1542713,-0.3846564,-0.2626735,0.850419,0.3468018,-0.8923105,-0.1269391,0.9002136,0.1805202 +1,user1086,-0.3329409,-0.8992352,-0.290027,-0.7894108,-0.1509811,0.03073911,-0.1128225,-0.6375331,-0.5837372,-0.3182784 +1,user1087,0.985477,0.5600555,0.3504596,0.9240914,-0.7272436,-0.009033903,-0.3900531,-0.2706991,0.2168332,-0.4528892 +1,user1088,-0.9158182,0.9354644,-0.1107438,-0.4450394,0.7326349,-0.5493843,-0.5626617,0.772333,0.4378643,0.2916603 +1,user1089,0.5630124,-0.7926758,-0.8829162,-0.4427374,0.1127193,-0.4194136,-0.4706509,0.9261963,-0.6954941,-0.7472836 +1,user1090,0.05965542,-0.7944963,0.7696862,0.9290851,-0.1251764,-0.4780939,0.4279005,0.8920016,0.3628923,0.160097 +1,user1091,0.8614647,0.4642293,0.04232627,0.1912786,-0.5165859,-0.5594631,0.8990904,0.9530488,-0.1261337,0.5363772 +1,user1092,-0.7253747,0.9762462,0.2736672,-0.798977,-0.08747619,0.6250506,-0.4956552,-0.5438623,-0.8040953,0.7223339 +1,user1093,-0.6722676,0.687437,-0.2243098,0.4971353,-0.1689727,0.4160384,-0.2573869,0.2729031,0.1237927,-0.2844948 +1,user1094,-0.1526363,0.8181601,-0.7112031,0.3213584,0.02602313,0.1432124,-0.3365016,-0.8868174,0.1162973,0.145626 +1,user1095,0.1281046,-0.7688696,0.307531,0.6286075,0.6467661,-0.8832998,-0.6851075,-0.2204566,-0.4140286,-0.4675267 +1,user1096,-0.2674593,0.3591285,-0.6153093,0.492744,-0.5943432,0.08988477,-0.4334643,0.9657219,-0.3634647,0.4743201 +1,user1097,0.656619,0.5795913,0.5648418,-0.9655309,0.6495781,-0.7773862,-0.8844298,0.9398599,-0.8354144,-0.4321711 +1,user1098,-0.247864,0.6903229,0.04870192,-0.2142411,0.2310519,-0.6091578,-0.7108415,0.6449365,0.9931637,0.07432053 +1,user1099,-0.8248654,0.4786992,0.4003458,-0.6377757,0.5845112,-0.9590144,0.5267685,0.5953397,-0.5861918,-0.6568242 +1,user1100,0.166983,0.2297261,-0.4816031,0.1054953,-0.2433169,0.8289527,-0.1379376,-0.9170159,-0.5676404,-0.8122331 +1,user1101,-0.1296403,0.4164735,0.3109363,-0.7953788,0.1427616,0.3543295,0.9460043,-0.7950073,-0.3675886,0.994145 +1,user1102,-0.8939158,-0.0780032,-0.02126922,0.9928345,0.003295027,0.08969822,0.5425333,0.564845,-0.9642098,-0.9810434 +1,user1103,-0.4936844,0.01583967,0.4951748,0.003476128,0.007065963,0.2041176,-0.408939,0.02922842,-0.9065428,-0.8283007 +1,user1104,0.1955107,-0.03711194,-0.8709,-0.8336459,-0.3165175,0.7748858,0.3665905,-0.2488019,0.4224573,0.8289803 +1,user1105,0.3005591,-0.6191955,-0.09818205,-0.4776318,0.625817,-0.9516892,-0.1095418,0.3171269,0.3331004,0.2041304 +1,user1106,0.4079175,0.179127,0.3355636,-0.02272716,0.7343124,0.1220627,0.8822632,0.9800533,0.8343263,-0.9568691 +1,user1107,0.5655546,-0.9139837,0.114211,-0.5236314,0.01572172,-0.4563879,0.899018,0.959797,-0.07435178,-0.915097 +1,user1108,0.9467122,0.4130817,0.1510994,-0.971513,-0.2902325,-0.1979283,0.6186193,0.1937569,0.1915215,0.4322689 +1,user1109,-0.2843699,-0.7925482,-0.2937702,-0.05609036,-0.4020744,0.402256,0.6565145,0.2959903,-0.6156304,-0.000582485 +1,user1110,-0.2406466,-0.4565358,0.4043208,0.9862619,-0.4829894,-0.9478904,-0.3763584,-0.8075126,0.1609869,-0.7421995 +1,user1111,-0.9500749,0.7177352,-0.9940354,0.2016214,-0.9082358,-0.9160666,0.4542014,-0.9466848,0.5121981,0.6947903 +1,user1112,0.2325376,-0.3926157,-0.9060646,0.6878578,0.5868468,-0.7575,-0.8090838,0.6408037,0.05586715,-0.5864078 +1,user1113,0.5726547,-0.9382821,-0.07476344,0.02268873,-0.9742049,0.9452151,-0.3567919,0.8577439,-0.5187409,-0.7136963 +1,user1114,0.9182139,-0.6997428,-0.02261043,-0.444562,0.1490264,-0.1615204,-0.330406,0.9108243,0.5067247,-0.8390859 +1,user1115,-0.2211846,0.09887862,0.3853344,0.559695,0.1088204,0.3833123,0.3955839,0.8396166,0.7093909,0.2697478 +1,user1116,-0.8668607,0.2159893,0.5405801,0.7600152,0.876214,0.2920169,-0.2491024,-0.2691951,-0.6185274,0.4668239 +1,user1117,-0.414727,-0.598978,0.6873626,-0.2339728,0.9980453,0.8692188,0.5567715,-0.7267088,0.9229875,-0.1573644 +1,user1118,-0.2357077,-0.3410659,-0.264206,0.4837864,0.3815768,-0.6257216,-0.9944693,-0.4310825,-0.07377589,0.8168586 +1,user1119,-0.7826789,0.1514536,-0.5701637,-0.6850242,0.6088489,0.7426326,0.1882359,-0.4968621,0.819337,-0.2415158 +1,user1120,-0.8517146,-0.3916538,0.8044464,0.3232898,0.1107646,-0.5501948,-0.9138794,-0.8005125,-0.7725066,0.09535201 +1,user1121,0.8239477,-0.1355622,-0.4945198,0.4128715,-0.7435996,-0.1038155,0.4334313,-0.5390809,-0.7108836,-0.02304434 +1,user1122,-0.9212142,-0.3843171,0.4721626,0.5062544,-0.907737,-0.8168305,0.08732627,-0.5438134,-0.3067967,-0.7051386 +1,user1123,-0.5770893,-0.4154076,0.0781136,0.5243128,-0.9767116,-0.9251442,-0.4095346,-0.3443748,-0.5766018,-0.1823141 +1,user1690,0.304356,0.2812498,-0.6377095,0.03713356,0.977646,-0.06152577,0.5358419,-0.4172087,-0.728402,0.6640231 +1,user1691,-0.422856,-0.2692318,-0.9906413,0.134391,-0.06043114,0.8913001,-0.4614573,-0.7224024,-0.332403,-0.3105863 +1,user1692,-0.392291,-0.3901305,-0.6448371,0.9503972,0.02626108,0.8561254,0.7661415,-0.5895434,-0.5009186,-0.8481337 +1,user1693,-0.2064928,-0.1850813,0.5529626,0.5699721,-0.2652946,-0.7461704,0.4593558,0.8501316,-0.9385416,0.9060565 +1,user1694,0.9595382,-0.7173601,0.5640361,-0.1559573,-0.2542948,-0.2931235,-0.9110348,-0.490496,0.5160753,-0.4058332 +1,user1695,-0.3584262,0.11951,0.3569938,-0.7790799,-0.7352436,0.946633,-0.06576437,0.03024953,0.04783123,-0.4147984 +1,user1696,-0.2551765,-0.3090865,0.2236523,0.7057829,-0.777913,0.8500972,-0.4562779,0.444912,0.5934012,0.3886639 +1,user1697,0.1047925,-0.6671229,0.5352694,-0.9428899,0.6816845,-0.6078628,-0.4638212,-0.8298276,0.2162169,-0.7171194 +1,user1698,-0.1742813,-0.4763142,0.6247835,0.6867906,0.1177542,-0.6989196,-0.1294852,-0.6791539,0.653966,-0.1126911 +1,user1699,0.2286531,0.9105306,0.2118818,-0.1780415,0.5917282,0.4878869,-0.8553195,-0.4455456,0.300963,-0.5962601 +1,user1700,0.9984942,-0.800853,0.2732759,-0.3432557,0.9795868,-0.8035363,0.4691616,0.4051793,-0.4226537,-0.6047338 +1,user1701,-0.9417929,-0.7763932,0.6387641,-0.5757989,0.8791806,0.9187125,0.1152115,-0.4158214,0.3597215,-0.1646865 +1,user1702,-0.1495778,-0.3909291,-0.5855111,-0.5460089,0.9833884,0.4417928,0.4116745,-0.7440134,-0.3797977,0.242915 +1,user1703,0.7931157,0.3285363,-0.8383257,-0.1202017,-0.3933557,-0.5724124,-0.2967882,0.6334821,-0.3276341,0.6306887 +1,user1704,0.5640649,0.8378488,-0.2901396,0.6684665,0.6965576,0.2835848,-0.3983454,-0.6490495,0.4857464,0.2841427 +1,user1705,-0.1408703,-0.5354854,-0.5260258,-0.1494594,0.1710557,0.1235577,-0.2255683,0.102659,-0.333999,0.05145477 +1,user1706,-0.3373245,0.4922136,0.3955491,-0.04476967,0.336735,0.1883218,-0.2468629,-0.1475826,-0.5140545,-0.2308901 +1,user1707,-0.2414514,0.8375875,-0.9398296,0.7661475,0.289292,0.6239387,0.4197443,0.9392368,0.7580775,-0.7274072 +1,user1708,-0.5690869,-0.1855704,0.7002951,-0.1660316,-0.6767438,0.9644563,0.3736315,-0.8496092,-0.6183804,-0.3562357 +1,user1709,0.7202569,0.2362788,-0.3814622,0.3964996,0.221868,-0.2609002,0.7963549,-0.7086582,0.3577263,0.8982516 +1,user1710,-0.4091375,-0.4435957,-0.8369286,0.9015464,0.3725341,-0.3961105,-0.6900245,0.2853434,-0.3650369,-0.4099962 +1,user1711,-0.1852389,0.9762694,0.7288863,-0.5946098,0.5461482,-0.8237737,0.3645716,-0.9776276,0.9721886,0.4201222 +1,user1712,0.5135744,-0.2359425,0.8483229,0.01742458,0.2704544,-0.3012571,-0.7371519,-0.7697315,0.7664374,0.5110602 +1,user1713,-0.08839985,0.8986234,-0.0307475,-0.4299412,0.3447469,-0.06425649,0.8306745,-0.7632298,-0.5748535,0.3660172 +1,user1714,0.2217109,-0.2271788,0.2217325,-0.1705418,-0.6367926,-0.7849033,0.3338295,-0.3918983,-0.7327297,0.5708082 +1,user1715,-0.321991,0.1992676,0.9533081,-0.8840662,0.4164464,0.3261814,0.5824181,-0.4783567,0.785173,-0.1196593 +1,user1716,-0.8086372,-0.2295332,-0.4981077,-0.4109957,0.9133787,0.9016135,-0.9461918,0.7376346,-0.2288599,-0.7796787 +1,user1717,-0.8592994,-0.08704126,0.7590861,-0.6213631,0.6340221,-0.3696668,0.2010976,0.1866982,0.3580219,0.3262362 +1,user1718,0.4447667,-0.9688585,-0.1997995,-0.7065828,-0.3941059,0.1464835,0.1580082,-0.6233243,0.3725007,0.5779775 +1,user1719,-0.6746571,-0.8978899,0.8857316,-0.9684903,-0.3311387,0.9647335,-0.4651103,0.4521558,0.6544074,0.8258991 +1,user1720,-0.8165472,-0.9618876,0.2597581,0.1626566,0.01773586,0.8811588,-0.5729722,-0.2770823,0.6386904,-0.6166401 +1,user1721,-0.2508773,0.3123913,0.162491,0.3305507,-0.4164599,-0.9150423,-0.3061499,-0.04053293,0.6440987,0.2420006 +1,user1722,-0.09751313,-0.1671217,0.8950903,0.1659008,0.6084302,0.8560336,0.07343242,0.7297534,-0.6779956,-0.4846872 +1,user1723,-0.2088382,-0.3520181,0.614921,0.1130537,-0.9560031,0.7372841,-0.8068308,0.1333743,-0.8622282,-0.4647738 +1,user1724,0.5426299,-0.87269,-0.2845464,-0.09947716,0.3182455,-0.6612127,-0.8467941,-0.1904013,0.7055571,0.1480571 +1,user1725,-0.137975,0.1155182,0.4591264,-0.9900566,-0.6458646,-0.4370899,0.1623976,-0.7607426,0.8380797,0.1094796 +1,user1726,0.4327356,0.7674919,-0.02808524,0.3339738,-0.6912466,0.6839171,0.1274049,-0.8363761,0.185603,0.1204278 +1,user1727,-0.7125466,-0.1817765,0.9391058,-0.3936943,0.5403325,-0.8111155,-0.303072,-0.7454893,0.2989583,-0.463279 +1,user1728,0.9668175,0.4483953,-0.00560422,-0.9329465,-0.9641801,-0.04495265,0.6985764,-0.5905702,0.05429657,0.3923602 +1,user1729,-0.7415457,-0.7088223,-0.4033018,0.02076444,0.4265075,0.9849976,0.9979196,-0.5155301,-0.160431,-0.9922633 +1,user1730,0.5161066,-0.2712459,0.1509876,0.4282642,0.1320607,0.6767714,-0.1583915,-0.1910349,-0.4000787,-0.05953916 +1,user1731,0.9653117,0.6475423,-0.7323284,-0.2762022,-0.9845933,0.151511,0.167738,0.8146091,0.6316429,0.7876264 +1,user1732,-0.6833386,-0.4852155,-0.7645376,0.4449656,0.3056881,0.9037101,0.1131311,0.06864851,-0.8007095,-0.1569498 +1,user1733,-0.6334712,0.337825,0.5654765,0.8822553,0.1154492,0.1185642,-0.7467169,0.06495173,0.2201235,-0.8166241 +1,user1734,0.7584274,-0.02392136,-0.570654,0.6035962,-0.3779491,0.5790986,0.8709498,0.4480911,-0.6959912,0.4183151 +1,user1735,0.8807263,-0.6473667,-0.0546772,0.113432,0.00224576,0.1872949,0.7147858,0.419599,0.6850369,-0.8728072 +1,user1736,0.2256584,0.8023396,-0.9605493,-0.2672041,-0.7134951,-0.7578781,0.0277148,-0.8323893,0.8861246,0.2348306 +1,user1737,-0.5788971,-0.5317077,0.8248951,-0.4411735,0.9587859,-0.2325796,-0.3759131,-0.6994915,-0.2100457,-0.812575 +1,user1738,-0.3607251,-0.8097792,0.005493168,-0.1204205,-0.7084622,-0.1887664,0.1345301,0.3588358,0.4431143,-0.6002143 +1,user1739,0.6565716,-0.3832308,0.7397458,0.5667643,-0.390239,-0.7934218,-0.5986537,-0.6819985,-0.7322558,0.8785949 +1,user1740,-0.8586402,0.7045711,-0.5565672,0.9553261,0.1806539,0.5065201,-0.5795582,-0.4081497,-0.8523194,-0.9143234 +1,user1741,0.2301374,-0.2533749,0.1685646,-0.218874,0.6640719,0.4151231,0.4445055,-0.3558208,-0.9219226,-0.01021047 +1,user1742,-0.5286673,-0.4069614,0.4686321,0.9721545,-0.8440908,-0.6171955,0.7659179,-0.6596261,-0.7600673,0.2987171 +1,user1743,0.6549342,-0.5313714,-0.7082442,-0.02724935,-0.5488917,-0.794737,-0.3167101,-0.1778812,0.914118,0.5967368 +1,user1744,-0.8582625,-0.3547516,-0.8621829,0.3511848,0.008818813,-0.6491334,0.27518,-0.1190506,-0.496776,-0.6441933 +1,user1745,0.6930436,0.3658598,-0.3096353,-0.1983872,-0.4808834,-0.4020988,0.09974736,-0.05152447,-0.4927969,-0.1304747 +1,user1746,-0.6670568,0.6678962,-0.7549362,0.08868446,0.8675546,0.5314444,-0.734292,0.3437621,0.699291,-0.5229225 +1,user1747,-0.6668996,0.4157152,-0.3602906,0.9401891,-0.07780252,-0.7475199,0.3289882,-0.381416,0.2743641,-0.423872 +1,user1748,0.8337441,-0.7211815,-0.5505492,0.1802496,-0.8468613,0.2282344,-0.6991551,-0.8648263,0.865225,-0.8042385 +1,user1749,0.7777099,0.6990377,0.04526431,0.3821016,-0.5265513,-0.3220722,0.4237162,0.7204378,0.07179171,-0.9449451 +1,user1750,-0.3415568,0.5178253,-0.474559,0.9716989,0.5910588,-0.7827864,0.8638779,-0.9292602,-0.0712285,-0.5979729 +1,user1751,-0.9828031,-0.6830691,0.7092089,-0.6570938,0.1708746,0.1093932,-0.2721273,-0.1419086,0.5039153,-0.4208786 +1,user1752,-0.4731673,0.01142901,-0.7922447,-0.2873476,0.0569888,-0.2371145,-0.8824337,-0.3200951,-0.2841096,0.2970555 +1,user1753,0.5609301,-0.6492964,-0.5794687,0.1375996,0.199489,-0.9267528,-0.06268965,0.8004932,0.2507759,-0.08266008 +1,user1754,-0.1916413,-0.03508716,0.3241298,0.4559599,0.2148715,-0.1533227,-0.07895807,0.9914657,0.6416871,0.1143476 +1,user1755,-0.9305375,0.1387391,-0.07679111,0.6131752,-0.6247657,0.1016728,-0.7292278,0.4895035,-0.5785525,-0.5548874 +1,user1756,-0.5770448,0.4662217,0.8796578,0.1475431,0.5536244,-0.3638426,-0.900292,-0.9602494,0.08885556,-0.9731805 +1,user1757,-0.7589057,-0.2675953,-0.7039554,-0.2100662,0.5236249,-0.4694056,-0.9515532,-0.8449104,-0.1727099,-0.7652246 +1,user1758,-0.6430841,0.9569626,-0.1376853,-0.7805191,0.9155668,0.2905572,-0.03229978,0.7440142,0.7204058,-0.01816645 +1,user1759,-0.6102273,-0.08538296,-0.1259465,0.2145966,0.5894443,0.5912047,0.7982844,-0.5508196,-0.8568479,0.4191798 +1,user1760,-0.5004514,0.02358244,-0.1072572,0.8106982,-0.04986759,-0.484408,-0.9536336,-0.3604405,0.6668591,-0.7574879 +1,user1761,0.8730225,-0.3142834,-0.9866977,0.6477451,0.04762753,-0.03267137,0.8093088,-0.4470206,-0.6796729,0.9222944 +1,user1762,-0.6449156,-0.4378406,0.1417252,0.9383944,0.604851,-0.2572842,-0.03397762,-0.7362105,0.774795,0.2068061 +1,user1763,-0.18379,0.538367,0.1282052,0.2556637,-0.7441794,-0.5806979,0.1594975,0.708208,0.8661496,0.08556229 +1,user1764,-0.7604487,-0.9764583,0.5787789,0.5300004,-0.8369233,-0.9141071,-0.9374082,0.6179311,0.5404506,-0.8943298 +1,user1765,-0.8864882,0.538238,0.5710712,0.5419906,-0.7730981,-0.6781856,-0.1630278,0.7118807,-0.9211963,-0.3748788 +1,user1766,-0.3030637,0.8910003,-0.926472,-0.6309042,0.2580663,0.6065969,-0.1257167,0.1278071,0.5511864,0.2127551 +1,user1767,0.4652097,0.8258812,0.6182296,-0.7372037,-0.5504184,-0.6719852,0.09030663,0.7855418,0.4265752,0.3405009 +1,user1768,-0.4653853,-0.9934697,0.3959662,-0.899183,-0.8143122,0.08923473,0.4610591,-0.9876109,-0.131242,-0.1874538 +1,user1769,0.3362112,-0.918779,0.07902113,0.2486753,0.5496041,-0.5821695,-0.9911867,-0.5133571,-0.005699226,0.6125408 +1,user1770,0.1217813,-0.5573496,0.3579754,0.8295607,0.05934263,-0.465407,0.4916529,-0.8964567,0.6943194,0.2190958 +1,user1771,-0.3240255,0.7111013,0.8393991,-0.9438569,0.3663417,-0.4042452,0.8815008,-0.3957606,0.01643857,-0.1017772 +1,user1772,-0.4336514,-0.1721539,-0.7524143,-0.9701987,0.213676,0.8329536,0.4533189,0.1308221,0.07237822,-0.3976696 +1,user1773,0.593114,0.03568906,-0.1733925,0.8017152,0.2152519,-0.08260249,0.2575708,-0.5560828,0.9342521,-0.4821871 +1,user1774,-0.6690913,-0.82027,-0.8688452,0.02889376,0.81745,-0.1989822,-0.4352093,0.4263582,-0.06944344,-0.5050404 +1,user1775,-0.2919139,0.4730946,-0.6145972,0.3809861,-0.7775052,-0.8161799,-0.2715011,-0.9882285,0.5756022,-0.04186295 +1,user1776,0.2861575,-0.5984511,0.5169722,-0.396672,0.7343685,0.5152987,-0.6426818,0.3923927,-0.5585448,0.3873382 +1,user1777,-0.3361481,0.8476261,-0.6237814,-0.8824218,0.6850046,-0.6675378,-0.1695013,-0.2298797,-0.3701525,-0.02796293 +1,user1778,0.0411865,-0.1111902,0.02511213,0.3211752,0.1446923,-0.5636998,-0.9425129,-0.3696446,-0.1500337,0.534265 +1,user1779,0.1199017,-0.3196326,0.966423,0.7835776,0.8875072,-0.2564669,-0.3418369,0.5275664,-0.6933199,0.5830997 +1,user1780,-0.5584381,0.5466638,0.4214829,0.4996799,-0.8415467,0.01038999,-0.7457851,-0.5094419,0.7016392,0.02709199 +1,user1781,0.6996297,-0.5933649,0.5505531,0.2928741,-0.2642489,-0.3464861,0.921365,-0.2989048,0.7787378,0.9362921 +1,user1782,0.1370986,-0.002701629,0.6756318,-0.8735162,0.05838175,0.8529263,0.3860358,-0.6143421,0.8105955,-0.8377789 +1,user1783,-0.03160548,-0.4419072,0.6292383,-0.7876678,0.2154421,0.7732755,-0.6282189,0.170463,-0.5824703,-0.6758525 +1,user1784,0.2605599,-0.2426613,0.9710845,-0.5695263,0.9352401,-0.2732389,-0.1413246,-0.4984116,0.02951366,-0.146368 +1,user1785,0.9454573,0.9622112,-0.0002383492,0.5824437,-0.7267468,-0.3003964,-0.6929223,-0.6228764,0.4522826,0.2765687 +1,user1786,0.03785705,0.6968319,-0.4475528,0.8255074,0.5906764,-0.1250518,-0.3574467,-0.3400335,-0.1610228,-0.2307399 +1,user1787,0.683515,-0.7764396,0.8507422,0.5780168,0.4888645,0.3629185,-0.04161668,-0.458661,-0.8816308,-0.1195484 +1,user1788,-0.8134484,-0.3053841,0.2958062,-0.6276225,0.7968781,0.230198,-0.6444755,-0.4677868,-0.7204273,0.5113441 +1,user1789,0.394773,0.6537944,0.4147619,-0.9550116,0.5062432,-0.8344945,0.6102536,-0.5960193,-0.440617,0.7510936 +1,user1790,-0.9267123,0.1381774,-0.2752042,-0.2073866,0.07830885,-0.04587674,-0.2433323,-0.009480517,-0.7384787,-0.7003687 +1,user1791,-0.3138997,0.7181984,-0.811451,-0.8169243,-0.2529895,0.74579,-0.5981091,0.1717727,0.9464318,0.7538563 +1,user1792,0.2677955,-0.6604889,0.4280642,0.6927335,-0.4461293,0.1328341,0.4195623,-0.04303991,-0.1202899,0.673388 +1,user1793,-0.5716279,0.7003368,0.866521,-0.2689922,-0.3168402,0.696839,0.7226901,0.254309,-0.9636837,0.5064375 +1,user1794,0.5023103,0.2565653,0.3167542,0.4387394,0.002831074,-0.8349079,0.5613885,-0.1200192,0.8125813,-0.1605815 +1,user1795,0.5073468,-0.6369473,0.006843096,0.2227339,-0.2830526,0.218727,0.4821541,-0.4251088,-0.5798392,0.7790582 +1,user1796,-0.4581161,0.2385748,0.4375921,-0.7270016,-0.0899383,-0.9813466,-0.4403378,-0.03381035,-0.8848799,-0.8684413 +1,user1797,-0.8007534,0.1475656,0.3902822,0.8078352,-0.7391026,0.771689,-0.5643283,-0.9922122,0.3637678,-0.9478263 +1,user1798,-0.02744351,-0.8110661,-0.3749273,0.4855303,0.166529,0.5467418,-0.4275392,-0.639567,0.846736,0.1195591 +1,user1799,0.07649855,0.2451051,-0.1664416,-0.6261846,0.09574951,0.1078881,-0.9792787,-0.02142122,-0.01612191,-0.05589508 +1,user1800,0.5354578,0.2287866,-0.5306967,0.05651054,0.8105015,-0.8104805,-0.5555149,-0.5055693,-0.6419315,0.6647145 +1,user1801,-0.9056622,-0.3684156,0.9830481,0.3150909,-0.7741284,-0.9186652,-0.9358863,-0.5360237,0.5410554,-0.6613451 +1,user1802,0.7524731,-0.04379358,-0.3270426,-0.5700415,-0.5379088,0.703643,0.9022222,0.5828182,-0.9996833,0.8423277 +1,user1803,-0.8981936,-0.9433672,-0.283111,0.08631184,0.02417752,-0.9775269,0.897804,0.6252528,0.4304468,-0.7329551 +1,user1804,0.6874518,0.6672734,-0.1903444,0.1168061,0.4411235,-0.001267718,0.3216845,-0.09210649,0.4753075,-0.1435322 +1,user1805,-0.9166182,0.1359364,-0.1958878,0.4588523,-0.7204588,-0.4953392,-0.5329871,0.009176393,-0.06912678,-0.6627127 +1,user1806,-0.1901075,0.5297273,0.1022917,-0.532702,0.2466723,-0.7937068,-0.3736972,0.6370242,0.006048984,0.2251819 +1,user1807,-0.02639071,-0.9311777,-0.6733722,0.7201341,0.175492,-0.485969,0.6790026,-0.6997138,0.9167627,-0.756194 +1,user1808,-0.2527663,-0.0164375,0.1803309,0.5764305,0.9645458,-0.162877,0.2975116,0.7792967,0.5607207,0.3093244 +1,user1809,0.851079,-0.5814629,-0.8725961,0.7884732,-0.6086353,-0.3574065,-0.3162101,-0.7326203,0.8560153,-0.2405531 +1,user1810,-0.9064891,-0.2508103,-0.7069493,0.5037117,0.0629992,0.2575641,-0.6628343,0.8278527,-0.7765572,0.8269057 +1,user1811,0.1887956,-0.4697737,-0.3981862,0.07611036,-0.8770009,0.847513,0.5517265,-0.7301453,0.26236,-0.6635836 +1,user1812,0.5507088,-0.1748278,0.677957,0.0813473,0.1271158,0.2961074,-0.3948451,-0.03152514,0.634753,-0.3042609 +1,user1813,0.2306095,0.7464881,0.9686826,0.6301955,-0.878619,0.1104904,0.7232015,-0.7864895,-0.9659617,0.9891269 +1,user1814,-0.8428099,0.08831911,-0.7689479,0.2884426,0.3384412,0.6207885,0.9235076,0.4403177,0.6798896,-0.3394361 +1,user1815,-0.1887314,0.5825109,0.6490415,0.511821,0.06235593,-0.9771315,0.4638303,0.4700633,-0.3357333,0.5493711 +1,user1816,0.1760668,0.7086993,-0.03155579,0.2126392,-0.6053658,0.810094,-0.9697208,-0.4093659,0.4863209,0.2656956 +1,user1817,0.1950472,-0.214849,-0.2165008,0.11395,-0.07088236,-0.5042633,-0.4339391,-0.8997158,-0.4811331,0.429824 +1,user1818,-0.5052164,0.8060713,0.4997837,0.0898378,-0.4487795,0.385787,-0.5777864,-0.9885977,-0.2173641,-0.5701774 +1,user1819,0.3626185,-0.5966848,-0.7357496,0.5850167,-0.8084877,0.04029203,-0.6141963,0.1228473,0.7658936,-0.2229603 +1,user1820,-0.4101798,-0.5610546,-0.8017389,0.1589383,-0.5646392,-0.3387578,-0.8236855,-0.4957351,0.07824988,0.1809176 +1,user1821,-0.4319287,-0.05575129,-0.7754206,0.8824512,0.6295293,-0.6600897,0.1788813,0.001921802,0.04415726,-0.2705461 +1,user1822,-0.9512812,-0.8784864,-0.5472005,0.7680923,-0.06147717,-0.213918,-0.2123054,-0.7053799,0.7123254,-0.469104 +1,user1823,0.8576157,-0.2215436,0.6263254,-0.1483282,-0.01076848,0.7940763,0.5958768,0.461225,0.95796,-0.1456945 +1,user1824,-0.003556559,-0.3554145,-0.9088996,-0.386541,-0.6873109,-0.9632507,-0.09842864,-0.7437692,0.08047359,-0.7641086 +1,user1825,0.5510291,0.378079,0.7695537,0.2068318,0.9413539,-0.04882589,-0.6509169,0.1746009,0.5249067,0.3703145 +1,user1826,0.3649624,0.1415092,-0.3668315,-0.9255942,0.7061789,0.0128033,0.07803094,-0.9638838,-0.6218792,-0.3666362 +1,user1827,0.5383273,0.8831603,0.5286925,-0.1135426,0.2227509,-0.9445973,0.4612336,0.2224204,0.1955937,-0.6325499 +1,user1828,0.7502757,-0.4743554,0.1598358,0.01466696,-0.7977487,-0.2771369,-0.2152452,0.1823887,-0.1113255,0.4224882 +1,user1829,-0.6624811,0.3304431,0.2582411,0.559936,-0.1272921,-0.4404549,0.6504917,-0.6034508,-0.7751432,0.7529229 +1,user1830,-0.3851742,0.1282654,-0.6377491,0.2602728,-0.6814996,0.1632909,0.4819549,-0.7990008,-0.8205282,0.311555 +1,user1831,0.2857335,0.7544312,0.6291391,-0.9288225,-0.9872472,-0.08761735,0.2292399,0.6768194,0.246743,0.08720273 +1,user1832,-0.5681433,0.9620275,0.2412892,-0.124973,0.09857957,-0.3591202,0.7146054,-0.1394745,0.7659122,-0.9084222 +1,user1833,-0.6327011,-0.9155282,0.03520826,0.6902313,-0.2194084,-0.1330662,0.384177,0.7838174,-0.8202116,0.1538827 +1,user1834,0.3875399,0.811064,-0.6539719,0.1574893,0.03693032,-0.06514427,0.1270439,0.3020722,-0.3228102,0.3542476 +1,user1835,-0.8806915,0.6293009,-0.9490552,0.9918331,-0.4602969,0.6396121,0.03628991,0.768419,0.2412197,-0.05195444 +1,user1836,-0.5493193,0.2204082,0.8393205,0.1490836,0.06013277,0.3715946,0.8511899,-0.2070062,0.1106617,0.4911701 +1,user1837,-0.8025676,0.3407913,0.4483199,0.6247873,-0.7163973,0.1411489,0.7533467,-0.06090354,0.6832388,-0.4205705 +1,user1838,0.09291774,0.6981232,-0.6224275,0.7119672,0.7151951,-0.8463569,-0.2847074,-0.9312948,0.1579824,0.1918516 +1,user1839,0.1979145,-0.7960293,0.01965133,-0.2744859,0.02467856,-0.7912824,0.1487015,-0.4277095,-0.3286176,-0.1995055 +1,user1840,-0.9514886,0.7593284,0.5757237,0.4132605,-0.3250327,0.7837424,-0.5628634,0.2064761,0.5392541,0.3388765 +1,user1841,0.1864287,-0.5526871,-0.3293767,0.2156789,-0.2218057,0.4112073,0.05245828,0.8965579,0.3814252,0.01875727 +1,user1842,-0.6132899,-0.265803,0.6214651,0.8016245,0.1476777,-0.9437694,-0.299572,-0.1578548,0.9337424,0.1369109 +1,user1843,0.5992202,-0.4154994,0.2536807,-0.5053922,0.8020831,0.07984977,0.04229152,-0.825049,0.1740071,-0.9653845 +1,user1844,-0.5829618,-0.806199,-0.3606942,-0.1541256,-0.1004248,-0.4783023,-0.2243402,-0.8899316,0.4154636,0.007884137 +1,user1845,-0.4560998,0.8225161,0.8525172,0.09006705,-0.5138811,0.6770191,-0.3760644,-0.7175371,0.613632,0.7974748 +1,user1846,-0.5895112,-0.8329885,-0.09727781,-0.9935712,-0.135561,0.1027182,-0.4938782,0.6450142,0.8382738,0.5839866 +1,user1847,0.5931051,0.9025003,0.6077501,-0.9414865,0.2942094,-0.6682083,-0.194061,-0.2992974,-0.09821551,-0.7264203 +1,user1848,0.7389474,-0.3923329,-0.3639836,-0.795983,0.4152366,-0.8272442,0.1899965,-0.6172529,-0.8675011,0.2272988 +1,user1849,-0.09472758,0.9730827,-0.5974941,0.0962666,0.4156595,-0.5114947,-0.07166459,0.6564166,-0.3790903,-0.9861908 +1,user1850,-0.04427645,-0.6941844,0.8720005,0.6435302,0.4857218,0.3720837,0.1917427,0.8235499,-0.3323219,0.05061948 +1,user1851,-0.6712325,0.04661248,-0.1657224,0.3629554,0.8505974,-0.166002,0.366311,-0.112988,0.2107488,-0.5917837 +1,user1852,0.4733438,-0.08266856,-0.3729147,-0.0212822,0.04518885,-0.1715845,-0.8927833,-0.3416616,0.6650669,-0.2567368 +1,user1853,0.004442323,-0.5726708,-0.6752,0.4116225,-0.5757554,-0.8418342,0.9794373,-0.88183,-0.6199965,0.5815155 +1,user1854,-0.8136168,0.8250689,-0.5393971,-0.7853728,-0.1601711,-0.3719257,-0.03781224,-0.6517629,0.1687088,0.2625219 +1,user1855,-0.5302128,0.561917,-0.2818143,0.5921768,0.357878,-0.1348352,0.008788062,-0.08543083,-0.2544595,-0.02084543 +1,user1856,-0.4445286,0.8054082,-0.9056463,-0.3815457,-0.6344015,0.1093399,-0.6714796,0.2927709,0.9049102,-0.04816998 +1,user1857,0.5513456,-0.03342191,0.09377139,-0.710967,-0.4539922,0.6408776,-0.9597813,-0.6156468,0.5468296,0.8958857 +1,user1858,-0.9918855,0.4450773,-0.7531218,-0.5213658,-0.4193711,-0.07943246,-0.5299784,-0.8630104,0.9411342,0.3466047 +1,user1859,-0.6942528,-0.6689473,0.2541895,0.6331212,-0.4321502,0.832203,0.1132752,-0.5248404,-0.2064153,-0.6256818 +1,user1860,0.8888645,-0.7029788,-0.6479875,0.848969,0.4187158,-0.7995773,0.6907104,-0.2190976,0.7716863,0.6488086 +1,user1861,-0.3770597,-0.4266573,-0.3908709,0.7389069,-0.1008708,-0.9161416,0.9519765,-0.6620112,-0.879394,-0.3418403 +1,user1862,0.5914807,-0.9145161,-0.1166714,0.7042987,-0.4193974,-0.2554143,-0.6574849,-0.848021,-0.9596723,0.461521 +1,user1863,-0.6792788,-0.7409513,0.5933017,-0.2760041,-0.4827047,-0.1586975,0.4053158,0.6414279,0.5375985,0.7403863 +1,user1864,-0.009760716,-0.3421854,0.6443373,0.4291383,0.6797208,-0.04920778,0.3361536,-0.8781937,-0.6996056,0.8120424 +1,user1865,-0.02097942,0.8965479,0.2293568,-0.138212,0.6175329,0.6794414,0.469559,0.4540512,-0.2824824,-0.1842314 +1,user1866,-0.5599703,0.8883496,0.6442465,-0.2841709,0.05699842,-0.5190854,-0.5583943,0.4098469,-0.2211818,-0.3115681 +1,user1867,0.44092,0.8782228,0.4836578,-0.4217781,-0.2601464,-0.6776131,0.1873435,-0.08519991,0.411056,0.3032125 +1,user1868,0.176453,0.2373392,-0.3223234,-0.5134247,0.9011355,-0.1794097,0.2229056,-0.6068524,-0.5992437,0.3951981 +1,user1869,0.5329474,0.5864728,-0.978181,-0.5722037,-0.2278065,-0.3654422,0.1568983,0.4785522,0.9368006,0.8802835 +1,user1870,-0.3611655,-0.9178065,-0.4966909,0.303736,0.7645321,-0.4688955,-0.663955,0.4870906,-0.9175616,-0.896293 +1,user1871,0.2249644,-0.003332404,-0.7465996,0.8998358,-0.4238971,-0.3956672,0.6600422,0.5996238,0.9400104,-0.2659254 +1,user1872,-0.2806239,-0.9662143,-0.3075577,0.6434752,0.5503878,-0.9542349,-0.7906434,0.3751101,0.3182258,-0.1009593 +1,user1873,0.02554457,-0.1836095,-0.8752257,0.1053604,-0.08779018,-0.4126649,0.03647301,-0.6707642,-0.9838192,0.2406179 +1,user1874,-0.1758154,0.5811682,0.5070811,-0.6055564,-0.621814,0.6841825,-0.2976663,0.7745747,0.1140175,-0.2313099 +1,user1875,0.1364143,-0.7724133,0.3317481,-0.5106505,-0.5500369,-0.4325372,-0.01498366,0.4851785,-0.2663106,0.9069249 +1,user1876,0.5694448,-0.3610934,0.9772915,-0.8045725,0.3983288,-0.7356458,0.6604086,-0.3883013,0.6298129,0.03809268 +1,user1877,0.2346733,0.7481797,-0.5901967,-0.5991276,0.242625,-0.2130992,0.2084555,0.419589,-0.04770875,-0.6473233 +1,user1878,-0.2704806,-0.8699129,-0.06050181,-0.4521369,0.7441725,-0.1007455,0.7909553,-0.8141189,0.6354739,-0.8194954 +1,user2059,-0.8685538,0.9462278,-0.5396993,-0.2116732,-0.9048725,0.06777051,-0.06282233,-0.773578,0.6543137,-0.2678792 +1,user2060,0.6156147,0.1460923,-0.02409447,0.8513225,-0.4020662,0.4113796,0.8450972,-0.3149609,0.939389,-0.9283476 +1,user2061,0.2018011,0.02223539,-0.5978269,-0.7059318,0.442311,-0.1642432,0.4515843,0.2753337,0.5541986,-0.05966392 +1,user2062,-0.2877389,0.09761819,-0.383516,0.9782465,-0.901326,0.9477119,-0.7517756,-0.2297231,-0.480062,-0.4920574 +1,user2063,-0.9263812,0.2354031,0.5525421,0.9506759,0.4379991,0.01535846,0.09092729,0.5311804,-0.1909265,-0.633446 +1,user2064,0.7246468,0.3837585,0.2249244,-0.8008521,0.8151159,-0.6616977,-0.5644942,0.4036576,0.8044548,-0.4026737 +1,user2065,-0.6525788,-0.342676,0.4909308,-0.9930383,-0.2339246,0.01985141,-0.3886468,0.719956,-0.7021151,0.9881051 +1,user2066,0.3016655,-0.3169053,0.5740882,0.6342027,0.8787195,0.3155961,-0.804391,-0.005401287,0.8813149,-0.2780194 +1,user2067,0.9861457,0.5249301,0.698777,0.4994296,-0.3949821,-0.172794,0.9254498,0.01200398,0.3878462,-0.5647347 +1,user2068,0.9327447,0.9858539,0.3535222,0.1175916,0.3452447,0.3256424,-0.8730109,0.4241032,0.40891,-0.199104 +1,user2069,0.3195609,0.9292173,-0.2690103,-0.8205723,0.0250746,0.6375443,0.116299,0.5732545,0.2419735,-0.4025855 +1,user2070,0.6284322,0.9384261,0.818333,0.5679347,-0.9719475,-0.0429477,0.1689649,-0.2606183,0.3048137,0.8223469 +1,user2071,0.5703193,0.1502926,0.592135,-0.3747224,-0.465747,-0.60741,0.3393099,-0.3504358,-0.2993447,-0.2389733 +1,user2072,0.9485518,0.008871053,0.01445469,-0.7860884,-0.4437548,-0.4551558,0.8403091,-0.5043832,-0.1236757,0.9375339 +1,user2073,0.1882892,-0.9218754,-0.008529869,0.9682681,0.118411,-0.6541696,-0.004667488,-0.9920882,0.8198878,-0.6995879 +1,user2074,0.09160015,0.9596073,-0.5693596,-0.7804261,-0.451197,-0.9889035,0.9326115,-0.1104428,0.4988208,-0.8318832 +1,user2075,0.3205105,-0.6967587,-0.02431622,0.9053538,-0.3255259,0.7681749,-0.2487392,-0.3456742,0.9358473,0.8395763 +1,user2076,0.4108986,-0.1662416,0.279661,-0.01157367,-0.2204758,-0.6045934,-0.6460316,0.4467095,-0.1813954,-0.6916338 +1,user2077,0.8442601,-0.6181904,-0.9991243,0.3818669,-0.3415824,0.7517887,-0.6624949,-0.9332073,0.1186213,0.586002 +1,user2078,0.1012488,0.5095632,-0.001311523,0.4372691,-0.4049628,-0.892486,-0.3299675,-0.3362544,-0.9494329,0.3143701 +1,user2079,0.7059748,-0.9092566,-0.9935364,0.7334958,-0.9966654,0.2276303,0.5992695,0.3632775,0.5863982,0.5059043 +1,user2080,-0.9364848,-0.3979807,-0.6381108,0.6418197,-0.2913606,0.0646316,0.652264,0.2729728,0.3741264,-0.1567471 +1,user2081,0.4182047,0.4878321,0.231034,0.1993782,0.257045,-0.7413977,0.9066557,-0.8367839,-0.01266476,0.953077 +1,user2082,-0.2746951,0.2717322,-0.03646156,-0.3609711,-0.1247074,-0.5844868,0.9914662,0.7164601,-0.2730099,0.5133147 +1,user2083,-0.7320472,-0.3902183,-0.8892935,-0.6113293,0.6862394,-0.5592595,-0.6790758,0.9545155,-0.135697,0.9433351 +1,user2084,-0.06658093,-0.1375173,0.6561645,-0.729068,-0.005072892,-0.1139997,0.5105006,0.2808017,-0.9348888,0.8028571 +1,user2085,-0.7618549,-0.9027691,-0.10813,-0.1083259,-0.7337904,-0.6699265,-0.3636145,0.9841074,-0.3789706,-0.5374433 +1,user2086,-0.7871883,-0.09789021,0.6064357,-0.3348172,-0.9003724,-0.7285484,-0.2749618,0.02305699,-0.02743971,0.7646957 +1,user2087,-0.2993622,0.4612289,-0.1893796,-0.4992927,0.9351334,-0.5261593,0.05479926,0.7446298,0.4096813,-0.4448368 +1,user2088,0.5294414,-0.4380527,0.4915918,-0.3431062,-0.2129272,-0.2928234,-0.4758334,-0.4749286,-0.6350036,-0.9063102 +1,user2089,-0.8799108,-0.663962,0.5001544,-0.4122823,0.7248545,0.4504147,0.8984376,0.2658409,-0.1019772,0.4498153 +1,user2090,-0.167916,0.4074567,0.2709211,0.2890341,-0.9697391,0.5416112,0.9919769,0.9710517,0.06399496,0.2872839 +1,user2091,0.1450561,0.7080396,-0.5325027,-0.4917838,0.3850066,-0.8814438,-0.6307361,0.2101105,-0.6956145,-0.8346578 +1,user2092,0.3218903,0.3582734,0.9023275,-0.1182141,0.1671655,-0.7138285,0.3500219,-0.4588254,-0.5477786,-0.6098486 +1,user2093,0.5443451,-0.4949251,0.8874051,0.2672807,-0.871065,0.4893231,-0.7597987,-0.2586714,0.583933,0.7952266 +1,user2094,0.2186749,-0.0565573,-0.9799606,-0.5411079,-0.1769943,0.1339147,0.4601911,-0.2587091,0.113459,-0.4681037 +1,user2095,0.04653704,-0.2579681,0.1272519,0.08093381,-0.01771859,-0.3755261,0.7855277,0.9448322,-0.7433238,-0.01252235 +1,user2096,0.8917663,0.1623989,0.3783359,0.2742423,-0.1049897,-0.4908255,-0.1484455,-0.5387153,0.8818179,0.7833316 +1,user2097,-0.4796596,0.6265374,0.5941276,-0.9069052,-0.2982749,-0.5504892,0.6558002,0.7358896,-0.005226149,0.2538768 +1,user2098,0.03268277,-0.733038,-0.1739712,-0.4196366,0.5872993,0.4516798,0.7109776,-0.0431638,0.6445225,0.422743 +1,user2099,0.824511,0.1482528,-0.2681419,-0.6081661,-0.759745,0.8348169,-0.02145642,0.8853878,0.2907279,-0.4157724 +1,user2100,0.8399013,0.5557547,-0.6748827,-0.7274774,0.7267997,-0.9129449,-0.2279008,0.3091441,-0.7632526,0.8512913 +1,user2101,-0.338885,-0.7946119,-0.3556381,-0.8517019,0.6153518,-0.5912679,-0.1200576,0.6962179,-0.05066388,0.2450899 +1,user2102,0.3948303,-0.7014546,-0.6760069,0.01711149,-0.225492,-0.772593,-0.6821465,-0.465048,0.9913832,0.3452543 +1,user2103,0.7884531,-0.4353743,0.339572,-0.5135658,-0.7169551,-0.3681007,-0.3875917,0.8047609,0.1130717,0.7888252 +1,user2104,0.8494042,-0.7164873,0.635832,-0.8834338,-0.2662372,-0.2454375,0.8752749,0.7041297,-0.2307761,0.545502 +1,user2105,-0.5135695,-0.7418473,-0.2453665,0.2366854,0.323311,-0.7614966,-0.749535,0.4245092,0.490204,0.5133711 +1,user2106,0.1089636,-0.1321329,-0.6847442,-0.608212,-0.04248103,-0.5999258,0.3636691,-0.5409132,0.04891898,0.6284014 +1,user2107,0.2603027,0.1172711,-0.08450702,0.1049925,0.513287,0.1499692,-0.7707566,0.1508392,0.5878285,0.8538681 +1,user2108,-0.6693094,-0.3600377,-0.2444908,-0.3814477,0.9817286,0.9902921,-0.4120299,0.4913019,-0.3911747,0.09937312 +1,user2109,-0.7897877,-0.6225697,0.3139443,0.8290571,0.5525562,-0.4924118,-0.9662984,0.1228324,0.09948609,-0.05722852 +1,user2110,-0.03372243,0.2080145,-0.07804347,-0.1615117,0.5166216,-0.6224005,0.8285129,-0.4858833,0.1742267,0.3597725 +1,user2111,-0.6057942,0.2419817,0.1173985,-0.739628,-0.309632,0.05492373,-0.7597659,-0.2357253,0.9829517,0.942626 +1,user2112,0.628417,0.8652623,-0.4550218,0.02843533,-0.1903988,-0.2338095,0.9403573,0.2860485,-0.9131787,-0.1041515 +1,user2113,0.6915824,-0.5202532,0.885495,0.4775172,-0.6080858,-0.2068873,0.8199791,-0.7694232,0.9012167,-0.1269128 +1,user2114,-0.3378414,0.8517634,0.228105,-0.3509573,-0.6233926,0.4956643,-0.4388417,-0.2812097,-0.1527453,0.8859611 +1,user2115,-0.4381639,-0.272255,-0.7988573,0.2993673,0.8045283,0.6521908,0.4508579,-0.4331498,-0.8480674,-0.3012944 +1,user2116,0.9297275,-0.4230224,-0.222635,-0.6308087,-0.3418762,0.1231862,-0.5436354,-0.7853157,-0.4777539,0.3356439 +1,user2117,-0.1250297,-0.2461268,-0.1654594,0.3142255,-0.523765,0.7671159,0.2861966,0.7418473,0.819815,0.6506568 +1,user2118,0.2624739,-0.8110261,0.01176316,0.8000746,0.7396617,-0.8739685,-0.4943428,-0.6885201,0.5616139,0.2538688 +1,user2119,0.459169,0.1389249,-0.7310433,0.02608502,0.4451965,0.8303628,-0.01946873,-0.2602443,-0.1127574,0.4293337 +1,user2120,-0.004940478,0.08991117,-0.6653049,0.9019432,-0.7989105,0.2175306,0.1846342,0.007688137,-0.2821622,0.1004721 +1,user2121,-0.9054421,0.5964306,-0.7173157,0.08910876,0.7699227,0.6676428,-0.5023659,-0.7174683,-0.3743912,-0.4588473 +1,user2449,-0.09571038,-0.6146363,-0.3887929,-0.9240716,0.1704835,-0.07642916,-0.2928081,-0.554026,0.3647162,0.05208602 +1,user2450,0.07116669,0.6757656,-0.9780775,-0.6261915,0.4492939,0.5713442,0.7197791,0.491502,0.905051,-0.4920821 +1,user1124,-0.8483199,-0.4481252,0.2811703,-0.08999321,0.08742772,-0.6877771,-0.8239557,0.7338222,0.4129092,0.6924608 +1,user1125,-0.07385052,-0.566157,0.7609595,-0.1723871,0.1182862,0.3263819,0.7508246,-0.4306308,0.8095005,0.4404874 +1,user1126,0.5510153,-0.1842772,-0.6143554,0.1529203,0.6700545,-0.808444,-0.09464206,0.4351687,0.009369587,0.3501592 +1,user1127,-0.1157792,0.9110033,0.6658611,-0.5972492,0.4930846,0.4021077,-0.25742,0.699544,-0.9505556,0.166781 +1,user1128,-0.4172316,-0.9865657,0.3258013,-0.1379181,-0.2321357,0.5489957,0.8663948,-0.490771,0.9740862,-0.9916838 +1,user1129,-0.6968486,-0.4939543,0.4343465,0.9386792,-0.09889352,-0.4176019,0.1945164,0.08010513,0.002533329,-0.5755203 +1,user1130,0.05935547,0.3897024,0.06620683,-0.2350248,0.07759574,0.4430932,-0.7306515,0.2948838,-0.5367474,0.5099568 +1,user1131,0.7497514,0.2431604,0.8441982,0.9675772,0.5245474,0.3779483,-0.2715427,-0.4077869,-0.5935542,-0.8039168 +1,user1132,0.1735111,0.9225192,-0.2547172,-0.8566996,-0.9561319,0.9367276,0.1405207,0.2850978,0.6349448,-0.5813753 +1,user1133,0.1654397,-0.6883008,-0.9550624,-0.2421903,-0.9191092,-0.4672085,0.8118818,-0.1402712,-0.5009572,0.5289134 +1,user1134,-0.743933,-0.7409999,0.339373,-0.02894663,-0.4683866,-0.417934,0.3195182,0.6214415,-0.5000971,-0.6322176 +1,user1135,-0.6309783,-0.1145928,-0.1256172,-0.6903455,-0.2726494,0.7116134,-0.4928888,-0.9637041,0.05740204,-0.752395 +1,user1136,-0.5340012,-0.3074963,-0.05324444,0.2801779,0.7067077,-0.4188977,-0.29766,-0.8231444,0.8321431,-0.2669563 +1,user1137,0.6639845,0.4381271,-0.3250634,0.9483262,-0.7340742,0.7041286,0.2017814,0.6014948,-0.6657707,-0.5890867 +1,user1138,0.9345764,-0.02857652,0.9885938,-0.2139769,0.7430723,-0.7447745,-0.5938708,0.9960929,0.9830503,-0.667492 +1,user1139,-0.587289,-0.8944147,-0.902145,0.3086649,-0.5835247,0.383174,-0.6790407,0.3706125,0.02366462,-0.8346874 +1,user1140,-0.6203854,0.6455789,0.3811664,-0.1077642,-0.1361486,0.1063847,-0.1417041,-0.1025149,-0.2814012,0.4103309 +1,user1141,-0.3060703,0.5148877,0.3929146,-0.227715,-0.7399172,-0.6926649,0.02977081,-0.8114197,0.1440371,-0.4096915 +1,user1142,-0.5373639,0.8233206,-0.8961804,-0.4897137,-0.4917605,0.4671074,0.7751608,0.4239277,-0.4641373,0.8601029 +1,user1143,0.6121523,-0.7470368,0.4751018,-0.4199064,-0.5493019,0.3488846,0.0492121,-0.4617112,0.774466,0.8239231 +1,user1144,-0.7334156,0.5766057,-0.6818488,0.7949737,-0.7141221,-0.7474498,0.6729789,-0.9536758,0.6252962,-0.1233879 +1,user1145,-0.6191499,-0.8764223,0.08120918,0.06572428,0.6572659,-0.694413,-0.5552452,0.334752,-0.9574126,-0.978983 +1,user1146,-0.6090324,0.3518418,-0.1395639,-0.8602113,0.5595185,-0.267803,-0.555204,-0.6220946,0.4838569,0.09367088 +1,user1147,-0.6002763,-0.2074051,0.8587313,0.5549889,-0.8379081,0.5445671,-0.5761235,-0.222871,-0.9932312,-0.656564 +1,user1148,-0.03387692,-0.4754003,-0.2314282,0.8317514,0.6553112,-0.8251942,-0.9984737,0.6080432,0.9655749,-0.1363474 +1,user1149,0.15526,-0.9892241,0.5962301,0.6235751,-0.05890469,0.1064754,-0.5496733,-0.05317711,-0.589919,-0.08947051 +1,user1150,-0.3829553,0.9440486,-0.7114324,0.8699647,0.7709408,0.2871996,0.6121124,0.2802669,0.8261058,0.1019202 +1,user1151,0.1144085,0.1329459,-0.4269818,0.1550412,-0.2339242,-0.375389,-0.9123531,0.8075308,-0.8069317,0.9590047 +1,user1152,-0.02079231,-0.1247863,-0.8982897,0.0364466,0.1974957,-0.9973401,0.883758,0.407742,-0.3008025,0.8874852 +1,user1153,-0.3041695,-0.4402685,0.7607302,0.3762192,0.8632039,0.4703691,-0.3005613,0.7364535,-0.4806909,0.3967815 +1,user1154,0.5373192,0.7175383,0.6511318,-0.320646,-0.2106358,-0.3005332,-0.3218876,-0.536844,-0.3835335,-0.2233095 +1,user1155,0.1308878,0.4270885,0.3828806,0.9464534,-0.7150766,-0.6851172,-0.9401977,0.1415641,-0.8878934,0.579946 +1,user1156,0.62198,-0.006425511,0.5216897,-0.796168,-0.01850995,-0.203249,-0.5497367,-0.6941773,-0.6711904,-0.1627311 +1,user1157,0.08833457,-0.4667389,-0.9632236,0.8322743,-0.5405812,-0.1089773,0.5834703,0.8983246,0.6258361,-0.8731503 +1,user1158,-0.9848914,0.3380917,0.04874171,-0.6507958,0.778008,0.7169904,-0.1976177,-0.1588918,-0.838449,-0.2532731 +1,user1159,-0.7952515,0.00700878,-0.1525089,0.06591395,0.7493544,-0.6542533,-0.6833419,-0.1849483,-0.6971042,-0.1544148 +1,user1160,0.391486,0.03930681,0.4711229,0.7709535,0.3605253,0.4734208,-0.2220133,-0.02157023,-0.3716306,-0.4486706 +1,user1161,0.07446409,-0.2722058,-0.8850515,0.1141794,-0.1443963,0.1600837,0.07173074,-0.8640081,-0.3751964,-0.7433163 +1,user1162,0.9544999,-0.7498308,-0.3083107,0.03349119,0.2739018,0.723695,0.04511539,0.4072648,-0.2906584,0.04166833 +1,user1163,-0.435003,-0.03817403,-0.7835943,0.9142539,0.4043933,0.4101484,0.9185074,-0.7364724,-0.7366858,-0.0300459 +1,user1164,-0.7600962,0.03949341,-0.8401138,0.8719891,-0.06350552,0.6928751,-0.1163874,-0.00427931,0.1238464,0.7855971 +1,user1165,-0.7894331,-0.4908307,-0.9689378,-0.9954554,0.8055152,-0.694239,-0.6353664,0.02870635,0.2092445,0.4094507 +1,user1166,-0.06598121,0.8472332,0.09078847,-0.7760917,-0.8682561,0.1217618,-0.5743814,-0.7001765,0.3207162,0.2175591 +1,user1167,-0.2940974,0.7319971,0.1066417,0.1521669,-0.3567978,-0.7260226,0.5859526,0.1725763,-0.04401046,-0.4813592 +1,user1168,0.8745515,0.9472964,-0.2940012,0.9528708,-0.9285591,-0.9901104,0.566415,-0.3697989,0.5434738,0.8203641 +1,user1169,-0.1314048,-0.1813433,0.07938224,0.009931414,0.8748162,0.3769873,-0.1682522,-0.7040836,0.3037665,0.5500671 +1,user1170,0.1186136,0.8375824,0.2044967,-0.5391682,0.05967747,0.6571514,0.9069119,-0.4568111,0.9796542,-0.3160466 +1,user1171,-0.7458339,0.5928753,-0.9128348,-0.1548934,-0.0647077,0.1162743,-0.5752891,0.5276862,-0.7379274,0.2306949 +1,user1172,0.5625249,-0.6664556,-0.5277032,0.7822164,-0.8651009,0.6843224,0.8615186,-0.5155033,-0.5521964,-0.8596244 +1,user1173,0.5812497,0.660903,0.3083163,-0.02888191,0.567917,0.1242588,0.6820726,0.9671166,-0.4844831,-0.4559437 +1,user1174,0.8663184,0.8458385,0.562267,0.4252002,0.3859904,-0.5348411,0.473923,-0.934025,-0.9634614,0.05461799 +1,user1175,0.8291093,0.91015,-0.209552,0.5771901,-0.579223,0.9368726,0.5344975,-0.4691791,-0.9269002,0.01698771 +1,user1176,0.9620998,0.7844807,-0.6104745,-0.9631576,0.2251829,0.4298458,-0.8731726,0.3018686,-0.4418957,-0.4349267 +1,user1177,-0.742714,0.1976803,-0.5772969,0.5649889,-0.05449109,0.1973559,0.918719,-0.5561196,0.5203955,-0.8517111 +1,user1178,-0.771167,-0.297255,-0.3508207,0.1321791,-0.4171311,0.4814396,0.9583741,0.3079499,-0.9201314,0.3604237 +1,user1179,-0.07177713,-0.6909196,0.1580973,0.8685938,-0.1195059,0.6046516,-0.8716463,-0.09008821,-0.4763208,0.4287259 +1,user1180,0.4125459,0.2084562,-0.9810667,0.188564,0.8866042,-0.6961687,-0.6309543,0.3907033,0.9304766,0.05881835 +1,user1181,-0.1541223,-0.3532065,-0.06225308,0.002143807,-0.6461902,-0.2313608,0.5704865,-0.4117832,0.9059744,-0.5376562 +1,user1182,-0.9573686,0.4420264,0.7311154,0.02363506,0.64657,-0.7707374,-0.7839994,-0.2825574,-0.2832524,0.3877306 +1,user1183,-0.6082464,-0.9163302,-0.8793564,-0.7749894,0.08409992,-0.6935088,-0.7471963,-0.2015548,-0.370326,-0.0536965 +1,user1184,0.5417083,0.206525,-0.3015229,-0.621637,-0.7829864,-0.7609917,-0.7300749,-0.6753297,-0.5747165,0.8591254 +1,user1185,0.5799506,0.1595646,0.3822472,0.7029891,-0.5640658,-0.07127068,-0.105887,0.1805985,0.3332141,-0.8355789 +1,user1186,0.5226414,0.5107583,0.5035242,-0.828536,0.3690233,-0.378626,-0.687394,0.9400094,-0.2582193,-0.4737505 +1,user1187,0.1636883,-0.7999005,-0.7798332,-0.417805,0.1985037,0.03575931,-0.2798116,-0.369507,-0.2459068,-0.3036057 +1,user1188,-0.3317148,0.6928258,0.4190236,0.5352634,-0.104647,0.819752,-0.5224167,0.07892317,-0.04094985,-0.7087292 +1,user1189,0.5377501,-0.1511499,-0.4477341,-0.4793318,0.1470313,-0.6616356,0.1149882,-0.2188825,-0.09666829,0.2729764 +1,user1190,0.3684368,0.2071083,0.06765791,0.648109,-0.05214196,0.381506,0.03684656,0.4455447,0.05698896,0.5419795 +1,user1191,-0.9402288,-0.2678674,-0.1098535,0.3062168,-0.7441217,0.2931729,0.25557,-0.9426471,0.5874196,-0.1573998 +1,user1192,-0.3877858,0.5766442,-0.3327855,0.6348476,-0.997365,0.4984481,-0.813281,-0.08289058,0.5281353,0.5296601 +1,user1193,0.3229367,0.4572775,0.7593472,-0.3183998,-0.7782402,0.105201,-0.918038,-0.1471904,0.7663305,-0.4163522 +1,user1194,-0.3752318,0.6939585,0.1065522,0.2204707,0.6602716,-0.2966787,0.1740774,-0.6791195,0.8507337,0.8125543 +1,user1195,-0.1478821,-0.3838623,-0.1728994,0.5068367,-0.06087047,0.1913232,0.07033155,0.9128301,-0.3480183,0.3152571 +1,user1196,0.5335036,0.9664468,0.7904094,-0.3138553,-0.972725,0.410962,-0.5534045,0.8815159,-0.02442497,0.9930985 +1,user1197,0.558787,0.5411917,-0.8026594,0.444379,0.7920155,0.8250831,0.5996961,-0.3792959,0.17145,0.03011344 +1,user1198,0.5580205,-0.6518653,0.9337423,-0.3409964,0.5823317,0.4653006,-0.3437159,0.08540644,0.6079713,0.8338979 +1,user1199,0.4080551,0.9137432,-0.5035918,-0.3609845,-0.9012841,0.4208516,-0.9869894,-0.4882829,-0.4809512,0.8134626 +1,user1200,-0.5726178,-0.6401516,0.2767229,-0.5456896,0.6668318,0.2020704,-0.5685561,-0.08337952,-0.5247835,-0.4198194 +1,user1201,-0.3233658,-0.8142828,0.138239,0.1198355,-0.3579908,0.122452,-0.436804,0.6285953,0.5876255,-0.4821487 +1,user1202,0.6622212,0.5066185,-0.4164265,0.4841221,0.03400822,-0.462874,-0.5622785,-0.9605967,-0.2188786,0.04415752 +1,user1203,0.9899071,-0.3066072,0.7490197,-0.7634732,0.8017308,-0.1136072,-0.7070375,0.4011172,-0.07697992,-0.2794439 +1,user1204,-0.7421161,0.8466202,-0.5534446,-0.9090464,-0.7900738,-0.7532893,-0.7547314,0.5957119,-0.8968577,0.06190758 +1,user1205,0.5285395,0.3524569,-0.8541596,-0.09067771,-0.5800014,0.002284904,0.9116446,-0.8946218,-0.18234,-0.9012245 +1,user1206,0.8190164,-0.3964572,-0.4605323,0.813717,-0.7774922,-0.1767347,0.8274601,0.931938,-0.003880125,0.7375439 +1,user1207,-0.7800163,0.6311009,-0.1639191,-0.8722041,0.4351091,0.6765565,-0.627904,-0.1024196,-0.3387533,0.6269809 +1,user1208,0.7858255,-0.4498628,-0.4314565,-0.5256888,0.3655075,-0.8003592,0.8303636,-0.4507414,-0.6619445,-0.7529356 +1,user1209,-0.9521506,0.3062878,0.188647,-0.05410396,-0.1946233,-0.6952951,0.7858341,0.2398879,0.07598851,0.09796755 +1,user1210,0.1482066,0.9401813,0.9941781,0.9963897,-0.6843968,0.2812081,-0.4995503,0.8074922,0.1849259,0.0557068 +1,user1211,0.1983715,0.7585933,-0.4125232,0.6628752,0.2521118,-0.4965279,-0.8005907,0.9399619,-0.7314679,0.3058827 +1,user1212,-0.1062729,0.9530813,-0.8736061,0.9480398,0.1591865,0.07334417,0.3563206,0.8281047,-0.01803704,0.5603114 +1,user1213,0.190838,0.3822077,0.7252935,0.0200248,0.9621732,0.5104707,-0.2835496,-0.4750652,0.9016735,-0.5565626 +1,user1214,0.5901251,0.8422632,-0.2918796,0.8878858,-0.6637883,-0.1900367,-0.547787,-0.2615929,-0.1017939,-0.7478138 +1,user1215,-0.5645647,0.1596064,-0.175129,-0.6735972,0.3762001,0.3123525,0.6262457,-0.8472249,0.4072465,0.4194368 +1,user1216,-0.2292114,-0.4582277,0.1075407,-0.2769861,-0.6018926,-0.5608,0.6105634,0.7055333,0.2348875,-0.3921415 +1,user1217,0.1127666,0.3530215,-0.7883554,-0.9406502,0.705235,0.4313373,-0.235181,-0.3215836,0.6399868,-0.2215643 +1,user1218,0.5991236,0.3597059,0.04503788,-0.09140216,-0.4252962,-0.6518882,-0.6535659,-0.2167319,-0.8386603,-0.8841689 +1,user1219,0.4390738,-0.7654019,-0.4734357,-0.7417228,0.2934604,-0.741048,-0.9118533,-0.2155435,-0.8060623,-0.1008707 +1,user1220,-0.3494834,-0.7981284,-0.2360895,-0.419982,-0.1477337,0.7697017,0.8798073,0.459534,-0.4566815,-0.9485879 +1,user1221,-0.03243962,-0.4331858,-0.8873042,-0.4432932,0.5225618,0.7296178,0.3832807,-0.7711872,0.2183286,0.6578105 +1,user1222,0.498845,-0.03326934,0.4167108,0.5644941,0.5493386,0.5521249,0.3437167,-0.1581906,0.7813572,0.7417295 +1,user1223,0.2627308,0.7785158,0.4311249,-0.7851343,-0.1450986,0.2681498,-0.9334737,-0.6233566,-0.9285462,0.5810721 +1,user1224,-0.709503,-0.9759083,0.872043,0.238307,0.7443216,-0.1651812,0.4652426,0.0816224,-0.01534086,-0.7585417 +1,user1225,-0.8763868,-0.3393108,-0.4767371,-0.2150353,0.2096103,-0.7445538,-0.4822059,0.16269,0.632091,0.5542838 +1,user1226,-0.8851513,-0.6053465,-0.7417745,0.7217023,0.7940309,-0.540527,0.1368578,-0.7105265,-0.2765644,-0.1036707 +1,user1227,0.8240006,0.9905385,0.6624524,0.9244517,0.7715966,-0.7542192,0.9118382,-0.03686166,0.9602342,-0.7654432 +1,user1228,0.6824002,-0.7981191,-0.2793965,-0.7706563,0.001625819,-0.9194707,-0.8825098,0.783394,-0.1964591,-0.4156028 +1,user1229,0.6728692,-0.2572118,-0.8080321,-0.619294,0.3763627,0.9247736,0.7931419,0.3748799,-0.6685932,-0.2697728 +1,user1230,0.2320557,0.9042817,-0.8411394,-0.4365329,0.8703125,0.6666325,0.9248488,0.4748554,-0.520717,-0.9519806 +1,user1231,-0.8902177,-0.4382707,0.9973264,-0.3163459,-0.3315424,0.2825996,-0.4510659,-0.2999855,0.2787574,0.1645778 +1,user1418,0.06214261,-0.8103174,-0.2544368,-0.3991226,0.7319174,0.4303385,0.6804561,-0.680246,-0.2744118,0.9201598 +1,user1419,0.9705593,-0.8196382,0.3360479,-0.6479699,-0.837353,-0.3470428,-0.119776,0.3304953,0.8071479,0.81539 +1,user1420,-0.9845425,0.2748231,-0.3555842,0.5482699,0.5557133,-0.1586483,0.8806745,-0.9679446,0.1760689,0.955943 +1,user1421,0.9731905,0.1809404,0.3226307,0.5235486,0.4956398,0.7948909,-0.3231244,0.8061651,-0.03905822,-0.8053852 +1,user1422,-0.2274958,0.0736884,-0.2408747,0.4576551,0.1609711,-0.8147904,0.5224707,0.3605278,0.123789,-0.5953295 +1,user1423,0.5907234,0.5458843,-0.6301519,-0.2797171,-0.998971,0.5762634,0.4785441,0.974802,-0.7778486,0.2393934 +1,user1424,-0.9335042,0.4523581,-0.2966801,0.9489409,0.8084811,0.4149597,0.7423003,0.6102697,-0.02824204,-0.3963647 +1,user1425,-0.5137734,-0.08423318,-0.4421935,-0.9946872,-0.06736603,-0.8497843,-0.2293979,0.5726703,0.8929288,0.9162319 +1,user1426,-0.9250057,0.1833247,-0.1862214,0.7163473,-0.4330131,-0.5584375,0.9682228,-0.5839517,0.06323592,-0.05818032 +1,user1427,-0.9132818,-0.8883441,-0.07466406,0.6683355,-0.88979,-0.2266737,-0.1976527,-0.1313374,-0.06137921,0.3396934 +1,user1428,0.4702694,0.04697407,-0.4654457,-0.2509188,-0.5568912,0.5174398,0.6979583,-0.4335041,-0.08007087,0.03570474 +1,user1429,-0.3778383,-0.4822493,-0.2756746,-0.4140956,0.4581413,-0.1075419,0.4001336,-0.3331063,0.8891258,-0.8062676 +1,user1430,0.5908126,0.1849522,0.2545833,0.01278647,0.5504388,-0.7648853,0.6792479,-0.2141165,-0.4855731,-0.7206736 +1,user1431,0.7555551,0.5120386,-0.5853435,-0.7350003,0.4840311,-0.7309198,-0.4561986,0.806686,0.7959672,-0.3865351 +1,user1432,-0.1735531,0.258566,-0.8874154,0.06169647,0.844047,0.4869994,-0.822152,0.4985269,0.2216474,-0.9133139 +1,user1433,0.6223579,0.4572333,0.8934583,0.5131796,-0.9808683,0.1472464,0.8867684,0.4282405,-0.01525077,0.9844851 +1,user1434,-0.705867,-0.07532517,0.9713379,0.6499747,0.3120964,0.5019087,-0.494711,0.1931592,0.6099194,-0.1016441 +1,user1435,-0.4508894,-0.2982516,0.8809188,0.3179984,0.8808816,-0.9409507,-0.5247628,0.748482,0.02618211,0.6873986 +1,user1436,-0.2378355,-0.1821906,0.6555616,-0.722713,-0.1901355,0.3982171,-0.6855563,-0.1631816,-0.3729536,0.6469805 +1,user1437,-0.0316514,-0.1985389,-0.1211579,-0.1579633,-0.8763909,0.4492683,-0.06972521,0.6164141,0.4264079,-0.402655 +1,user1438,0.6075638,-0.8582141,0.03470456,-0.8516107,-0.1362062,0.6516464,-0.1789048,0.1928141,0.8659622,0.5259046 +1,user1439,-0.945996,-0.9225133,0.02354057,0.6483119,-0.5348259,-0.009676709,-0.8551601,0.3014806,0.05007539,0.7177591 +1,user1440,-0.376835,-0.8565224,0.7246091,-0.2231859,-0.7804154,-0.3086201,0.9774625,-0.1252688,-0.3985352,0.3421849 +1,user1441,0.6173774,0.5118664,0.3495828,0.8013731,0.2542979,0.04557566,0.3164768,-0.234186,0.08378929,-0.2007959 +1,user1442,-0.07092513,-0.08185421,0.1133262,-0.07010992,0.01360753,0.8797832,0.5216406,0.9585547,-0.8544024,-0.2247097 +1,user1443,0.2364857,-0.3396427,-0.5612273,0.1019275,-0.3445636,0.01342967,0.8807718,0.2427067,0.6592481,-0.5405901 +1,user1444,0.8111913,-0.757143,0.1566641,0.3982989,-0.645688,-0.8710736,0.396588,0.0904036,0.97141,-0.4750405 +1,user1445,0.8116048,-0.8233935,0.2653664,-0.5931109,0.600098,-0.8347187,-0.9139528,0.4955961,-0.5440796,0.217463 +1,user1446,0.9003481,0.3795379,-0.6670524,0.8300083,-0.2483921,0.8362114,-0.3889441,-0.9340031,-0.9387816,-0.226105 +1,user1447,-0.9334223,-0.7546438,-0.5954514,0.4970481,0.4971512,0.7742722,-0.9379222,-0.2059357,-0.5941688,-0.3067364 +1,user1448,0.515371,0.02450883,0.2601832,-0.6543386,-0.8218768,0.9075369,0.9304259,0.6632434,-0.7639689,-0.05842994 +1,user1449,-0.03750929,0.5692205,0.07851088,-0.5691143,-0.5164746,0.2665499,-0.708488,-0.6142491,-0.2131934,-0.3059452 +1,user1450,-0.962863,-0.5742821,0.7405966,0.8490782,0.6597982,-0.5727706,-0.05769823,-0.8754404,-0.7870209,-0.4913464 +1,user1451,0.5308285,-0.7006681,0.904599,0.8939314,0.7338365,-0.2511114,0.8111004,0.6952987,0.4121,-0.1024869 +1,user1452,-0.06431879,-0.2498391,-0.5988584,0.9544342,0.9791651,0.06144086,-0.03161246,-0.8080839,0.7477484,-0.1113304 +1,user1453,-0.1903588,0.4994063,-0.5002781,0.3067333,-0.1792308,-0.3875611,-0.5352275,0.4850874,0.336768,-0.08667587 +1,user1454,0.1215519,0.8452162,-0.7255529,-0.3857858,0.7348655,-0.674848,0.2896445,0.6701007,0.6342514,-0.8630935 +1,user1455,0.002177006,-0.797481,0.1044616,0.9033752,0.7876462,-0.5235994,-0.2893122,0.8021858,-0.2804936,0.4923049 +1,user1456,0.2958678,-0.5848269,0.05752842,0.3120461,0.7534032,-0.2373454,0.2353746,0.05775768,0.2296969,-0.170444 +1,user1457,0.1965462,0.02854089,0.08822567,-0.6694385,-0.6981476,-0.2332856,0.2578673,-0.913851,-0.3025126,0.07872618 +1,user1458,0.08889523,-0.6858251,-0.9702025,0.5717107,0.8978562,0.2497269,0.5130351,-0.3291516,0.6581271,-0.1680017 +1,user1459,-0.2338627,0.4621472,0.5920827,-0.9388727,-0.803488,-0.7199055,-0.06666716,0.6242535,-0.850374,0.8652608 +1,user1460,0.8187079,0.5462916,0.812551,-0.08353411,0.7599937,0.6591725,-0.3419991,-0.2469573,-0.4133868,0.2724586 +1,user1461,-0.3202922,0.4991271,0.2843808,-0.4155029,0.448295,0.4848416,0.192283,0.4567319,-0.827446,0.1113246 +1,user1462,-0.4783077,-0.02581417,-0.9932607,-0.673873,0.6805431,-0.4508253,0.4771343,0.4309396,0.9455932,-0.5212744 +1,user1463,-0.3548452,-0.1951424,0.9251357,0.9781624,0.6040407,0.1461719,-0.1641511,-0.7484304,0.8082606,0.3591447 +1,user1464,-0.6979343,-0.04363957,0.1778391,-0.9023232,0.4674267,-0.367912,0.07905141,-0.1150276,0.1573033,0.09580975 +1,user1465,-0.1841747,0.8988607,0.9780772,0.9761017,-0.007360459,-0.9489166,0.9824233,-0.3759012,0.5555126,0.3770815 +1,user1466,0.1942655,0.506606,0.8060545,0.2961608,0.4849223,0.2052212,0.3110862,-0.9999484,-0.1655573,0.0465433 +1,user1467,0.06423015,0.7741698,-0.1665993,-0.6250363,-0.7227088,-0.9696949,0.3934951,0.7217908,0.7843497,-0.2572097 +1,user1468,0.7841739,-0.2996783,-0.1430807,-0.1818616,0.1162486,0.5003516,-0.08730192,-0.7594872,-0.0180795,0.9744265 +1,user1469,-0.1981707,0.6483918,-0.159241,0.44455,-0.6512839,-0.1431324,-0.8678186,0.1928657,-0.2995951,-0.4275521 +1,user1470,0.1182341,0.8516566,0.8569413,-0.9767244,-0.2575348,0.0206284,0.5383349,0.02327149,-0.1655749,-0.5394506 +1,user1471,-0.5926611,-0.1562007,-0.4184716,0.5949525,0.3358332,-0.8082685,-0.1098394,0.115244,0.5833853,0.3166114 +1,user1472,-0.5807932,0.1602582,-0.8096581,0.2459231,0.603014,0.9024433,0.4486582,0.9586797,0.7841942,0.371652 +1,user1473,-0.952691,-0.2301976,-0.02973251,-0.0468343,0.7560728,-0.09958843,0.05997557,-0.01817377,-0.01997733,0.2358397 +1,user1474,0.6438246,0.5041566,0.02030113,-0.30312,0.9912696,0.2051612,-0.2290676,-0.6420493,0.2426334,0.7760213 +1,user1475,-0.7696019,0.4031153,0.347006,-0.355778,0.957326,-0.9686303,-0.1547538,0.0490833,0.7556042,0.8966115 +1,user1476,0.8589138,-0.05359114,-0.7643661,0.3600548,0.3561708,0.06569284,0.1460228,-0.5225777,0.4359431,-0.5466973 +1,user1477,0.5441727,-0.1163055,0.3532488,-0.4731117,-0.2571225,0.04137262,0.3819883,-0.5760523,0.3038518,-0.4500837 +1,user1478,-0.7030242,0.6484714,0.7515546,-0.8587299,0.4544772,0.8056418,-0.09267601,0.8431476,-0.8385646,-0.4101249 +1,user1479,0.3742848,0.9709177,0.4958171,0.7057163,0.534294,-0.02677026,0.0764487,-0.8593343,0.6719742,0.3948727 +1,user1480,-0.4933366,-0.547085,-0.5682404,-0.04222607,0.2264029,-0.6920774,0.6735003,-0.1903014,-0.9093416,0.2439711 +1,user1481,-0.6658873,-0.9258107,0.4921512,0.9903483,0.1142754,-0.7671288,0.8496258,0.9677072,-0.6255856,0.09852874 +1,user1482,-0.09488666,-0.7297504,0.4004161,0.5996476,0.2681304,0.7221183,-0.1124509,0.8359644,0.08407426,-0.7076142 +1,user1483,0.4423446,0.2030759,-0.1670987,-0.08779183,0.2055681,0.3693634,-0.3581122,0.001614643,0.8384068,-0.8673592 +1,user1484,0.1437539,0.5735957,0.9918731,0.2970816,0.9350446,-0.1546898,-0.6856018,0.4527946,0.7111825,-0.9881471 +1,user1485,-0.9733347,-0.8845342,0.6748632,-0.7861381,0.002995892,-0.9527298,-0.8228064,0.5060651,-0.2816743,-0.5707077 +1,user1486,-0.5554784,0.405595,0.9373628,-0.1844166,-0.006785701,0.845764,0.3525756,-0.1961996,-0.4420868,0.6249457 +1,user1487,-0.5603782,0.9887688,0.04940152,-0.3908722,0.6884478,0.6079648,0.5497728,-0.4894478,-0.05912065,-0.1585911 +1,user1488,0.2232115,0.1440067,-0.2369112,-0.4555766,0.3048482,-0.1860153,0.4350609,0.5922141,0.4158131,0.5080185 +1,user1489,0.5334168,0.7197699,0.9671603,-0.612706,-0.1089295,0.09549089,-0.1343892,0.4746488,-0.7839597,-0.5430561 +1,user1490,0.205759,0.450916,-0.3585157,-0.3297449,0.8849598,0.8880593,-0.5168943,-0.8651942,0.09050536,-0.2933304 +1,user1491,0.04191941,-0.3097017,-0.4243601,0.4608893,0.06484193,-0.5268428,-0.9069382,-0.6547433,-0.9975737,-0.219523 +1,user1492,-0.7868754,0.218897,0.2515411,-0.02820884,-0.6606344,-0.4196675,-0.9421062,-0.0686193,-0.6114057,0.5682686 +1,user1493,0.7274514,-0.5748981,-0.3517765,-0.003617924,0.565503,-0.562766,0.9602399,0.5657454,0.03609853,0.1853953 +1,user1494,0.6870743,0.4951559,-0.4992244,0.4390517,-0.3311174,0.6193291,-0.0710893,-0.4031737,0.8106869,-0.8603783 +1,user1495,-0.4848097,-0.8247426,-0.5706198,0.06946794,0.8067922,0.2124205,0.1369452,0.8163531,0.5458976,-0.3359217 +1,user1496,-0.4567233,-0.6760375,-0.3736993,-0.02751624,-0.4418575,-0.5116827,0.9426632,-0.8101558,-0.4083889,-0.4375232 +1,user1497,-0.1186602,0.001761853,-0.6931699,-0.2647876,-0.8461951,-0.1754497,-0.7600032,-0.4031221,-0.3548704,0.186165 +1,user1498,0.5794204,0.9494273,0.2627809,0.4444317,-0.9159166,0.2427256,-0.4695597,0.5381439,0.3302473,0.4068686 +1,user1499,-0.6725493,0.02428425,0.4832201,0.7906221,0.6743911,0.988669,-0.1446387,-0.569643,0.5735316,-0.4630967 +1,user1500,0.6831691,-0.3498463,0.1475891,-0.8202376,-0.497479,0.6814179,-0.6278218,0.7897435,0.3455345,0.758613 +1,user1501,-0.3023455,0.8010838,0.1197222,0.4677073,-0.1734513,-0.736646,-0.9312248,-0.4385846,-0.8353276,0.867418 +1,user1502,-0.2652104,0.8680836,-0.9352515,0.3855746,0.01022432,-0.8195995,0.7455219,0.5456011,0.1569169,0.8535147 +1,user1503,-0.8976241,0.8104119,0.337931,0.4256855,-0.8944649,0.5838612,0.8208364,0.7484232,0.1297287,0.130265 +1,user1504,-0.2550365,-0.4291138,-0.9100103,-0.579127,-0.4173786,0.1637656,0.1287508,0.5432417,0.1446951,0.1032577 +1,user1505,-0.6213859,0.3722402,0.08504961,-0.9175454,0.001493915,0.3855617,-0.4835457,0.9035518,-0.6004497,0.629536 +1,user1506,-0.6672261,0.2135272,-0.315063,-0.9300925,-0.9371389,0.6152309,-0.3339173,-0.2024935,-0.1146671,0.02687652 +1,user1507,-0.3961227,0.517295,-0.6743764,0.7809279,0.9387922,-0.7705416,-0.7252265,-0.9793361,-0.4193618,0.5565604 +1,user1508,0.9227868,-0.7440653,-0.5617016,-0.3906571,0.7443714,-0.5730657,0.8984426,-0.6725006,0.7034021,-0.8205477 +1,user1509,-0.3702503,-0.1380014,-0.5635084,-0.7888223,0.5173383,0.4208727,0.5734067,-0.3593459,0.0467683,0.6167516 +1,user1510,0.9781622,0.4882127,0.8214407,0.4866441,0.4730861,0.2026882,0.3512222,-0.8386704,-0.7473876,-0.04856685 +1,user1511,-0.5705498,-0.2911503,-0.129942,0.5671168,-0.02922563,-0.2651432,0.5719429,0.137198,0.7940605,0.4234235 +1,user1512,-0.03613759,-0.06381206,0.9286428,-0.798474,-0.3683864,0.6537439,0.4230324,-0.3916387,0.4211828,-0.2847196 +1,user1513,-0.1167245,0.7584623,0.2218568,0.08629177,-0.2587834,-0.07519353,-0.7612286,0.997294,0.3366866,0.2438189 +1,user1514,0.8717948,0.9119256,0.7029593,-0.5206751,-0.8236576,-0.8957797,-0.7861693,-0.8611873,0.6324672,0.5560642 +1,user1515,-0.8923836,-0.4902164,0.9205159,0.4986076,-0.4333418,-0.5009459,0.7374307,-0.9388442,0.1323652,-0.2728667 +1,user1516,-0.09005923,0.8739282,-0.1032801,0.3001536,0.7442124,-0.02792328,-0.584035,0.5033591,-0.9449877,0.6731112 +1,user1517,-0.6836836,0.3175206,0.6403221,0.2949083,0.1695567,0.9499843,0.5664063,-0.05738693,-0.8096196,0.1810099 +1,user1518,-0.4527619,-0.5014476,-0.03008255,-0.8922646,-0.7448939,-0.8929811,0.2872035,-0.4282919,-0.9267554,0.5685421 +1,user1519,-0.8668477,0.01793489,0.6598088,0.8445771,0.04906069,0.7860614,0.8510259,0.09557312,0.4708254,0.1811297 +1,user1520,0.8497332,0.03729049,0.6074824,0.6822023,-0.9393727,0.04547517,-0.5679829,-0.5827381,-0.5935793,0.6379538 +1,user1521,0.7529972,0.9494684,0.6114017,-0.2220095,-0.8599341,0.9950782,0.7703091,-0.2934862,0.1637499,-0.7247882 +1,user1522,0.1750717,0.7082332,-0.7645513,0.3054663,-0.8860974,-0.7407814,0.9440877,0.4408299,0.4732517,0.9616068 +1,user1523,-0.9371422,-0.7438125,-0.1409765,-0.3460065,-0.6000072,0.6258077,-0.5100891,0.3486426,-0.204985,0.2062223 +1,user1524,0.4804486,-0.6254297,-0.7403748,0.7743726,0.7055688,-0.5676879,0.730549,-0.7277408,-0.8001515,0.4606071 +1,user1525,-0.1378541,0.2033891,-0.2637758,-0.255482,-0.2172148,0.8785477,-0.1270016,-0.9623438,0.2839386,-0.8987715 +1,user1526,-0.421952,-0.5685551,0.2884037,0.7234614,-0.7932149,-0.1617718,0.626856,0.1649957,-0.6590874,0.8703006 +1,user1527,-0.9762747,-0.3014672,-0.114074,-0.2531437,-0.7362887,-0.07937053,0.6732122,-0.5378966,-0.2085404,-0.9769161 +1,user1528,0.7434857,-0.7948491,0.04305429,0.4797304,-0.06340982,-0.2969021,0.1129952,-0.365466,0.9290682,0.2873935 +1,user1529,-0.8425315,-0.6191279,-0.4488154,0.1678931,-0.7091315,-0.9190462,-0.8427037,-0.2968604,0.6711599,0.2771692 +1,user1530,-0.6488241,0.7228171,-0.630854,-0.4625215,0.9381024,-0.09070157,-0.4714265,-0.1075396,-0.6350089,-0.4400128 +1,user1531,0.4266548,-0.1446954,-0.8093566,0.6594928,0.4391112,-0.6154842,0.4851735,-0.5757225,0.2746027,0.04600651 +1,user1532,-0.144877,-0.818044,0.6709069,-0.3643996,0.1174171,-0.6556922,-0.7739285,0.264555,0.8358323,0.1445873 +1,user1533,0.08596549,0.5909006,-0.5661055,0.9230531,-0.05167326,0.08969891,-0.7259046,-0.5619386,0.521908,-0.5864981 +1,user1534,0.5290306,-0.3342835,0.5285744,0.08517839,0.5446463,0.968377,0.3060099,-0.8272992,-0.5956686,-0.8237285 +1,user1535,0.6000865,-0.2471579,0.7608966,0.05647346,0.7000386,0.5080734,0.3548223,-0.1922033,-0.01947259,-0.752155 +1,user1536,0.4645796,-0.03685913,0.5189441,-0.9944923,0.9498207,-0.5247394,-0.2094503,-0.6583868,0.9214583,-0.9569621 +1,user1537,0.8618045,0.8792437,-0.7864886,0.1550859,0.6075074,0.5836079,0.9720926,-0.02979271,0.2896643,0.203148 +1,user1538,-0.7960362,-0.7298628,-0.9134798,-0.1625987,0.6388308,0.7375319,0.6295958,-0.1715394,0.5611656,0.8044054 +1,user1539,0.3873664,0.2190756,0.9572425,-0.3851494,0.6941921,-0.09780512,-0.3110077,-0.3308874,0.6248603,-0.7775098 +1,user1540,-0.5084458,-0.2587577,-0.349997,0.3662636,0.1248456,0.004480565,0.5454992,0.6108614,-0.6635674,-0.1801003 +1,user1541,-0.817874,0.7583499,0.9079609,-0.6759546,0.1119169,-0.05977994,-0.01918197,-0.01020978,0.813778,-0.2441615 +1,user1542,0.8168166,0.9279253,-0.1726995,-0.8180327,-0.3350335,0.6370517,-0.7390648,0.8063107,0.4189208,0.6459137 +1,user1543,0.4554166,0.6774302,-0.4213542,0.5677896,0.7564593,-0.3417755,-0.03146839,-0.7807773,0.7576154,0.5351801 +1,user1544,0.06540148,0.5168122,0.1298177,0.4103372,0.8531335,0.8650265,0.2195894,-0.0129158,0.1504646,0.9996575 +1,user1545,0.6886113,0.839851,-0.4697402,-0.3387077,-0.1586911,0.741272,-0.5252342,0.9451233,0.05138802,0.2019779 +1,user1546,0.563033,-0.8127862,-0.5008383,0.06639719,-0.6768825,0.1572786,-0.2940377,-0.7196215,-0.1100194,-0.7376867 +1,user1547,0.9753422,0.3907404,-0.9734624,-0.2895091,0.5973459,-0.1628968,0.6355544,-0.5095567,0.205477,0.6727687 +1,user1548,-0.9950723,0.1573716,-0.8294181,0.9562006,-0.9891344,0.6912563,-0.9588278,-0.1122636,0.2417684,-0.6170122 +1,user1549,-0.8897289,-0.3142338,0.4690792,0.1741326,-0.4217764,0.2642974,0.9931657,-0.1479135,-0.03677482,0.8308555 +1,user1550,-0.8915055,-0.5913248,0.6863464,-0.4449321,-0.3535934,-0.3768354,0.4865803,0.5860164,-0.3236976,-0.1461016 +1,user1551,0.8546609,-0.805338,0.7780643,0.6384029,-0.9285071,-0.2632686,-0.5268108,0.3049983,0.6481891,-0.9790584 +1,user1552,0.8632683,-0.3647653,0.08048088,0.9521231,-0.2817105,0.2593756,0.7634749,0.5586004,-0.8730249,-0.8939327 +1,user1553,0.2835662,-0.8830916,0.9217951,0.8605343,-0.2396908,-0.1176168,0.4306679,0.02684624,-0.8504459,-0.1844948 +1,user1554,0.9175187,-0.5491505,-0.3629122,-0.7076036,-0.5285143,-0.6374609,-0.03689989,-0.3463592,-0.5567959,0.2271639 +1,user1555,0.3437168,0.009804963,0.3401061,0.7264957,-0.5761417,0.6916877,0.4940239,0.8308596,-0.6731764,0.5666743 +1,user1556,-0.8542879,0.3202975,-0.3419807,-0.3949478,0.5430945,-0.2390691,-0.6963337,0.06450239,0.4334926,-0.0832663 +1,user1557,-0.5044333,-0.1177056,0.9254915,-0.9841422,-0.3217292,0.2007673,-0.4100438,0.8186365,-0.2158833,0.09746454 +1,user1558,0.3674421,0.7083378,-0.7739679,-0.526648,-0.3124304,-0.3876828,0.1672361,-0.7070371,0.1182832,0.5897582 +1,user1559,0.8891977,0.5254484,0.7010736,-0.9152174,-0.5203153,0.4640288,0.4166615,0.6990364,0.3625608,-0.7958728 +1,user1560,-0.3469648,0.2631665,-0.5233239,0.1837509,-0.03086073,0.2817211,-0.2527475,-0.4782239,-0.5447233,-0.6253662 +1,user1561,0.718618,0.4311548,-0.4048219,0.01083045,-0.3743279,0.5216156,0.6958096,0.1854233,0.4832743,-0.8502546 +1,user1562,0.3158525,-0.619247,0.891717,0.7442755,0.9187959,0.8485446,-0.09816501,-0.876686,-0.3628365,0.2501337 +1,user1563,0.5081581,0.4451225,-0.852417,0.8193513,-0.9134436,0.626029,-0.02667605,0.7863312,-0.708891,0.5192211 +1,user1564,-0.1954165,0.02205547,0.02907261,-0.06611645,0.5739988,-0.3886855,0.9699051,0.6234848,0.005182317,-0.4367527 +1,user1565,-0.1551169,0.04646952,0.4202914,-0.1705461,0.4634422,0.8169216,-0.7921551,-0.7039853,0.0414949,0.4264053 +1,user1566,0.1082446,-0.8020354,0.9084795,-0.1241752,0.786595,0.1341024,-0.6718538,-0.4058721,0.2716364,0.767066 +1,user1567,-0.7308369,0.9851963,-0.4519833,-0.06060875,0.5238195,0.08657511,-0.2395452,0.965098,-0.0733594,-0.3937149 +1,user1568,-0.2933124,-0.07428681,0.6338028,0.9845398,0.07094955,0.4005295,-0.8200626,0.266222,-0.6688408,-0.3704467 +1,user1569,0.3122084,-0.5318982,0.9949997,0.7132261,0.4254258,-0.1283657,0.957742,0.4225885,-0.167198,0.5714714 +1,user1570,0.6565295,0.2042719,-0.4947408,0.5542418,0.2180116,0.98877,0.4494471,-0.3657894,-0.4484991,-0.1712246 +1,user1571,0.1982418,0.6669554,-0.7161942,0.3508034,-0.8042048,-0.59499,0.7254367,-0.1229166,-0.3324082,0.449453 +1,user1572,0.4943344,-0.7735484,0.9029606,-0.9627284,-0.4626573,0.8118544,-0.06143998,-0.5876213,-0.35342,-0.67269 +1,user1573,0.4733461,0.1321972,0.3325597,0.7362091,0.882978,0.6258217,0.7103822,-0.5594787,0.9704217,-0.525311 +1,user1574,-0.3463416,0.3443856,-0.1375484,-0.08140706,0.9522545,0.06323452,-0.3060317,0.09630608,-0.5747928,-0.01536695 +1,user1575,-0.4402641,0.7432638,0.03277829,0.4476088,-0.6095238,0.6768809,-0.8418506,0.3994629,0.7970446,-0.6730325 +1,user1576,0.1619575,-0.02795181,0.8628195,-0.6024986,-0.2757131,0.3670937,-0.8148519,-0.6143554,0.02180973,0.6766669 +1,user1577,-0.7833086,0.5315994,0.3616133,0.9849901,-0.724628,-0.7794869,0.3999305,0.3766846,0.3151878,0.2469464 +1,user1578,-0.4649219,0.1340042,0.05931591,-0.8419004,0.9878221,-0.4860159,0.7937038,0.8899062,0.002521537,0.9997362 +1,user1579,0.1668852,-0.8705803,-0.9665987,-0.646298,-0.2648474,0.05834996,-0.7736798,0.2733809,-0.7364219,-0.9403453 +1,user1580,-0.6730376,-0.7826344,-0.1693075,0.1591227,-0.1464044,0.4848105,0.3930963,-0.7712289,-0.7215871,0.07780184 +1,user1581,-0.3564274,0.5426794,-0.2543377,-0.2868325,-0.3657713,0.1371488,0.280284,0.4759225,0.6788239,-0.1463654 +1,user1582,0.02154603,-0.6759182,0.8114656,0.9921049,-0.1933545,0.7950814,-0.3004905,-0.4216208,0.9117672,-0.9194038 +1,user1583,-0.8097693,-0.1473997,0.9111733,0.1112458,0.571885,-0.2558139,0.1565711,0.7873715,-0.5946119,0.1838691 +1,user1584,0.9271388,0.6595878,-0.3325426,-0.4262982,0.3945379,-0.980468,-0.289048,-0.4972312,0.828378,0.6691399 +1,user1585,-0.06093531,-0.2250687,-0.5514466,-0.7154987,0.2781312,-0.8423795,0.6626096,0.23202,-0.6450286,0.3077601 +1,user1586,0.5339475,0.8624053,0.2512795,-0.1622586,0.9957433,-0.5641262,-0.349405,0.618231,-0.2677884,-0.2494566 +1,user1587,-0.9271491,-0.02011466,0.3254768,0.178754,-0.0623676,-0.2195372,0.01461826,0.5672712,0.2618706,-0.4141265 +1,user1588,0.4346314,0.6572257,-0.6259552,-0.6996409,0.956402,0.3583878,-0.7474343,0.05065652,0.1390881,-0.5947753 +1,user1589,-0.09861042,0.570743,0.4773115,0.3110934,-0.316687,0.04819097,0.8178312,0.911194,0.8504948,-0.6596983 +1,user1590,0.9620486,-0.4946662,0.02655038,0.2635367,0.4173171,-0.7555084,-0.5687202,0.2663076,-0.3755686,-0.2099992 +1,user1591,-0.9123334,-0.07960776,-0.1492791,0.48411,-0.07445875,-0.359891,-0.0001818077,0.5724326,0.5943648,-0.2201415 +1,user1592,-0.3799924,0.001897865,-0.9275104,-0.6780761,0.308985,-0.4301934,0.5136408,0.09661732,0.3337691,-0.509953 +1,user1593,0.2779011,-0.1139132,-0.08173261,0.007812167,0.3361129,-0.9069638,0.3331148,0.3896215,0.2615949,-0.9598655 +1,user1594,0.5958247,-0.6344853,-0.001696102,0.3034614,0.01209767,-0.7338621,0.9731421,0.3587638,0.8854738,-0.7009205 +1,user1595,0.4245911,-0.9760467,0.1015622,0.2558074,-0.1170162,0.1811211,0.4835459,-0.2798979,-0.6610486,0.0532943 +1,user1596,-0.8772158,0.9325563,-0.6614412,0.837266,-0.2004449,0.9099578,0.5409597,0.6856363,-0.6969102,0.4665397 +1,user1597,-0.2959307,-0.4365206,-0.09321657,-0.8207138,-0.2013073,0.4002404,-0.6987117,0.9528917,0.1571102,-0.9338544 +1,user1598,0.6937542,-0.9908503,0.649579,-0.8048013,-0.5931967,-0.7323038,-0.7559994,-0.3147999,0.265592,0.6595794 +1,user1599,-0.1705282,-0.1417305,0.9723616,0.8218058,0.8705047,0.3104873,0.7208971,-0.04814169,-0.3657509,-0.903907 +1,user1600,-0.9837222,0.03158113,-0.09821686,0.8925123,-0.7758816,-0.7281253,-0.7409697,0.3754801,0.9899122,0.637617 +1,user1601,0.3502837,0.2134216,-0.8451618,0.7494405,0.6248149,-0.7435338,0.6934477,0.3194107,0.8170929,-0.5116452 +1,user1602,-0.9722863,-0.4747751,-0.7438327,0.1726092,-0.9337001,0.7154973,0.4463338,0.8289417,0.3018409,0.545546 +1,user1603,0.5106122,0.2580328,-0.1952562,0.9297838,-0.2385389,-0.916271,0.1975903,0.7878588,-0.3635078,0.964927 +1,user1604,-0.1763702,-0.6543811,0.4873979,0.4856496,0.5077929,0.8822879,0.4038299,0.7599319,0.7875146,-0.03695618 +1,user1605,-0.3186279,0.8696106,0.1186189,-0.9087979,-0.9814457,-0.2212681,-0.8596979,-0.0747522,0.727048,-0.4698209 +1,user1606,-0.9296519,0.001296583,0.8375221,0.3773926,0.1519373,0.7606099,0.3557397,0.1873217,-0.5664633,-0.7081055 +1,user1607,0.9855873,0.317667,0.3502174,0.8831511,-0.7679202,0.2493816,0.588978,-0.8544235,-0.1906756,-0.3602893 +1,user1608,-0.1019366,0.40121,-0.5197678,-0.9238077,-0.7060737,-0.0007550628,0.5402326,-0.6980676,0.04223577,0.7771254 +1,user1609,-0.3945738,-0.8646992,-0.103162,0.5354922,0.1397594,-0.725406,0.1494435,0.07722788,0.4360583,-0.7083693 +1,user1610,0.1524725,0.4470868,0.3836187,-0.7631469,-0.03276758,-0.6922684,0.8152982,0.4189574,0.07290252,-0.3006346 +1,user1611,0.2250259,0.6185756,0.3109247,0.235315,0.1475219,-0.5159446,-0.06667112,-0.4692966,0.3206487,-0.1450727 +1,user1612,0.2489989,0.6779802,0.6425003,-0.7513402,0.7739881,0.4117428,-0.5702725,-0.4468496,0.1148822,0.1452653 +1,user1613,-0.8259815,0.7711686,0.1950843,-0.771042,0.7738779,-0.897187,-0.4851923,0.9973366,-0.01533024,-0.2200384 +1,user1614,0.4152566,-0.5288241,0.222098,-0.6534392,-0.2805931,0.2282415,-0.9101,-0.6819251,0.7260368,-0.9612036 +1,user1615,0.1761377,0.337568,-0.6900423,-0.1776384,0.1685261,0.4312748,0.1406795,0.05591919,-0.05673983,-0.1855948 +1,user1616,0.1130832,-0.4539001,0.6436377,-0.4865406,0.05200909,-0.7395665,-0.8225827,0.2293566,0.3396411,-0.9122782 +1,user1617,-0.0507959,-0.6664188,-0.5266225,0.1843022,-0.2848498,0.6641153,-0.2595049,0.936306,-0.5417516,-0.2106602 +1,user1618,0.2489885,-0.6825466,0.6354345,-0.9988844,-0.8938415,-0.7882624,-0.8447023,-0.3768096,-0.7948692,0.4002787 +1,user1619,-0.4522854,-0.7966744,-0.9823175,-0.1861815,0.008411071,0.6188213,-0.5700169,-0.7199869,-0.5212708,-0.5070535 +1,user1620,0.8505937,0.9043242,0.950689,-0.5046044,0.3984631,-0.2876937,-0.4416738,0.8475,-0.6912568,0.1296415 +1,user1621,0.2110372,-0.1772129,-0.3380151,0.2646523,0.5234755,-0.5437708,-0.4134225,0.889498,-0.1704378,-0.8097205 +1,user1622,-0.3646189,0.1237178,-0.1315965,-0.7020715,0.9339523,-0.7410697,0.4298012,0.8524458,-0.926906,0.2728049 +1,user1623,-0.5293987,-0.09377789,-0.9768214,-0.1826805,-0.2925518,0.2821128,-0.928033,-0.05588272,0.6425123,0.6196885 +1,user1624,-0.5110617,0.7088739,0.5802522,-0.7275356,-0.1404116,-0.4507346,0.9196923,0.2791195,-0.9088428,-0.769586 +1,user1625,-0.7687942,0.4892326,0.8667074,0.6013899,-0.05395002,-0.4749318,0.4029434,0.2112096,0.9585678,0.5718845 +1,user1626,0.8951924,-0.06982456,0.1247409,-0.9268731,0.590432,-0.5367661,0.5555129,0.6642194,0.9814637,-0.3270172 +1,user1627,-0.3882775,0.6414302,0.918811,-0.8902695,0.6591435,-0.5407768,0.460652,-0.03524422,-0.605753,0.6969538 +1,user1628,-0.06472482,-0.9472881,-0.2265092,0.7806761,0.7447426,0.9253086,0.7042317,0.1641012,0.115678,0.63803 +1,user1629,0.5889465,-0.06067488,-0.2256802,-0.7316744,0.9972353,-0.2690699,0.7995135,-0.6505806,0.2470558,-0.6674377 +1,user1630,0.4411943,-0.5003003,0.8911726,0.9315363,0.5296482,0.7697106,0.1815491,0.9166141,0.02849606,0.7930468 +1,user1631,-0.04844704,0.08429306,0.6752739,0.6731884,0.9688611,-0.8028168,0.9632621,-0.4604186,0.1055902,0.275647 +1,user1632,-0.06076973,-0.8472533,-0.07084199,-0.9822339,0.6220501,-0.01260366,0.4929612,0.6688301,0.0641487,-0.1790829 +1,user1633,0.468908,0.02492469,-0.8526601,0.1041455,0.5959481,0.4852079,-0.3721171,0.7455558,-0.6696631,0.3385928 +1,user1634,-0.5378348,-0.6576742,-0.5199823,0.6029722,-0.2696778,-0.7190878,0.1608524,-0.6725598,0.7420824,0.240574 +1,user1635,0.7628601,-0.5016344,-0.5834441,0.5034157,0.129843,-0.1303158,-0.1032088,0.428762,-0.1483367,0.7839609 +1,user1636,-0.8497199,-0.1054647,0.2659588,0.1953476,0.6145024,-0.7360602,-0.231815,-0.3291964,-0.9426151,0.8687719 +1,user1637,-0.4674868,0.3436224,-0.6824602,-0.01963518,0.8822596,-0.9584779,-0.4834079,0.5147619,-0.8243809,0.5324685 +1,user1638,0.7484474,0.8160326,0.7667733,0.3865668,0.3619229,-0.8809342,-0.5142308,0.5743385,0.6609877,-0.5763284 +1,user1639,0.04834351,-0.7042547,0.746191,0.2715399,0.9084287,0.2631847,-0.6915824,-0.02726402,0.09962071,0.6458973 +1,user1640,0.1379395,0.4789232,0.2143777,-0.4841429,0.02201898,-0.6838838,0.6660356,-0.4080102,0.6116774,0.8240992 +1,user1641,-0.09908016,0.2631194,0.150392,0.6234199,-0.6708447,-0.5732026,-0.6989326,-0.006704098,-0.2661098,0.123037 +1,user1642,-0.7266306,0.9143209,0.05711564,-0.4931451,0.05595056,0.7472401,0.2417465,0.5034394,-0.5797306,-0.4991754 +1,user1643,-0.6130617,0.1569034,-0.143122,-0.2354832,-0.2039929,0.727859,-0.9042369,0.1451402,-0.2734404,-0.03063544 +1,user1644,0.07493833,0.03428801,-0.6545237,0.8523779,-0.8969668,-0.4703897,-0.1841249,-0.009367488,0.71856,0.9029987 +1,user1645,0.688626,-0.6145032,-0.7207864,-0.1465843,0.7753574,-0.02451836,0.3316465,0.8215143,-0.8536938,-0.460379 +1,user1646,0.563076,-0.5055286,0.1668357,0.5868784,0.9645332,0.1591337,0.2364426,-0.7989406,0.6698197,0.7837698 +1,user1647,-0.8119785,0.5803879,0.989114,-0.6341627,0.1550423,-0.2099562,-0.006707562,-0.7800109,0.05820114,0.9907205 +1,user1648,-0.3621699,-0.280922,-0.2474089,-0.9622821,-0.5094924,-0.3604031,-0.9278585,0.7578203,-0.3954454,0.3289608 +1,user1649,-0.1879354,-0.1880752,-0.1977298,0.587994,-0.9293084,0.3708713,0.3917403,-0.1757502,0.8749505,0.1840485 +1,user1650,-0.2642639,0.7837135,-0.9932035,0.1796557,-0.8365466,-0.5911349,0.4232755,-0.4999977,0.5369304,-0.5163331 +1,user1651,-0.5115762,-0.3765977,-0.2967199,-0.4668865,0.8889708,0.3519032,-0.3695322,0.6053203,-0.08670222,-0.5413977 +1,user1652,-0.9768983,0.6347119,0.4642551,-0.1473538,0.5941671,0.8271005,0.9783178,-0.2862523,-0.2954873,0.374328 +1,user1653,0.3711172,-0.09256866,-0.1248001,0.4775842,-0.9025943,-0.3322046,-0.1469233,-0.6475519,0.6100244,0.7564719 +1,user1654,-0.04097496,0.5296244,-0.2735413,0.350433,-0.4035811,-0.365984,-0.2975652,-0.4505625,-0.4441899,-0.9217092 +1,user1655,-0.48796,0.3435858,0.04450733,0.1251106,-0.5462445,-0.6236341,0.8980101,0.9928672,-0.2043301,0.604742 +1,user1656,0.6023231,-0.6033361,-0.2580927,0.07897413,0.04345566,0.1928636,-0.7439799,0.5636577,0.5685922,0.3283563 +1,user1657,-0.1457826,-0.5402002,0.8511995,0.42356,-0.8131491,0.09724996,-0.7420523,-0.7863431,-0.4627262,-0.2487263 +1,user1658,0.1237625,-0.01498391,-0.03668164,0.2348411,-0.8871009,-0.1644108,0.3586621,-0.04237699,0.1899169,0.3016958 +1,user1659,-0.4624018,-0.5506241,0.5153981,-0.1403498,-0.2118017,0.1181721,0.9602518,-0.2722411,-0.3157298,-0.03361366 +1,user1660,-0.5568361,0.3991249,-0.3744806,0.6918856,-0.8159138,0.8281801,-0.9425388,-0.4369237,0.7843296,0.08383594 +1,user1661,-0.4350432,0.4847158,-0.145509,0.1663774,0.6425473,-0.3947003,-0.4597887,-0.1257629,-0.7815871,0.09474255 +1,user1662,0.4891512,0.5336689,0.190672,-0.4671614,-0.2429406,0.3153554,0.9235139,0.2673403,0.7898604,-0.7579666 +1,user1663,0.3823942,0.5518717,0.5546774,0.7096517,0.8061363,-0.1844236,0.5504225,-0.7680936,-0.1515217,0.904753 +1,user1664,-0.9661352,-0.4903595,0.001830881,-0.7294771,0.2384954,-0.9094924,0.1680942,-0.3802071,-0.4512502,-0.5666646 +1,user1665,0.9513164,0.8759947,0.6706897,-0.8641892,0.4873816,0.5962676,0.08436628,0.5947805,0.5319428,0.4826074 +1,user1666,0.1452543,-0.9497627,0.9712333,0.2130674,-0.06402065,0.6852607,-0.5527864,0.6606684,0.7001416,0.6887139 +1,user1667,-0.8158551,0.4041758,-0.7322103,0.4658706,-0.1470023,-0.6455526,0.9362791,0.2905965,-0.3938652,-0.6978927 +1,user1668,-0.5161704,0.2196171,0.9882295,0.1161756,0.3696412,0.6377897,0.6009584,0.1095424,0.7075619,0.01507595 +1,user1669,-0.1062983,0.8662699,0.7380065,-0.4003658,-0.7020978,0.8043265,-0.06701721,0.2350069,0.3611294,-0.8876145 +1,user1670,0.2324884,0.699921,-0.9860193,-0.2625895,-0.2385736,0.6176321,-0.7553033,-0.7366675,0.7057555,0.9480046 +1,user1671,0.6217691,-0.3014597,0.2026072,0.6320326,-0.6083398,0.9539059,0.266994,0.7015322,0.3192392,-0.1608248 +1,user1672,0.7946215,0.1293893,-0.1116015,-0.7769459,-0.3729425,-0.7688761,0.2340502,-0.7716972,-0.9049804,0.2354225 +1,user1673,0.5058578,0.6142419,0.0710963,0.2442654,0.817377,0.3648723,0.4864432,0.7667719,-0.8739751,-0.5511708 +1,user1674,-0.9912926,0.8554437,-0.9405148,-0.6034505,0.1876673,0.6817649,0.3627572,-0.1533276,-0.9542012,0.8085397 +1,user1675,-0.1304401,-0.8363227,0.2338747,-0.924568,-0.2699093,-0.2392658,-0.9500747,0.2189353,0.8135796,0.1384212 +1,user1676,0.1944838,0.9997388,0.3503099,-0.902319,0.5927344,-0.6596461,-0.1819104,0.5882863,-0.7276689,-0.01154982 +1,user1677,0.5717835,-0.6500849,0.226321,0.9834278,0.1522004,-0.1591014,-0.4008002,0.0477318,0.7156185,0.5923095 +1,user1678,0.05758136,0.7440652,0.2229887,-0.5587307,0.885133,0.550778,0.04321776,0.4389244,-0.1282192,0.1291417 +1,user1679,0.8323139,-0.2811832,-0.897099,-0.8646011,-0.9167579,-0.02004918,-0.1097688,0.3461066,-0.1231144,-0.682589 +1,user1680,-0.616152,0.1618398,-0.9714088,0.5714218,0.2228921,-0.7882301,0.9909401,0.8719816,0.590569,-0.223642 +1,user1681,0.7933174,0.5277787,0.2297852,0.620925,-0.9514136,0.9596431,-0.5335068,0.9389267,-0.5912889,0.6128086 +1,user1682,-0.6792624,0.3422191,-0.1938189,-0.3314876,0.9722128,-0.668146,0.520699,-0.04857317,0.7901834,-0.2239867 +1,user1683,-0.5930503,-0.2034483,0.4928463,-0.575932,-0.1829408,-0.9611295,0.9692579,-0.4142707,-0.7049183,-0.849314 +1,user1684,0.1644347,-0.56479,-0.8950149,0.09850923,-0.854008,-0.3725615,0.31957,-0.7086252,-0.9812644,0.3692805 +1,user1685,0.2797627,-0.1281566,0.5326398,-0.9810545,-0.4313682,-0.03412997,-0.7768662,0.5008644,-0.6540065,-0.1456959 +1,user1686,-0.08101028,-0.8598624,-0.4626464,0.5491786,0.2708147,-0.5847636,0.8672681,-0.4214035,0.0907516,0.755428 +1,user1687,-0.2332423,-0.168126,-0.1531076,-0.8225166,0.1894477,0.8203021,0.5755901,0.8550324,0.5873278,-0.3023632 +1,user1688,-0.8660199,0.3316433,0.3838393,0.4425054,-0.2445173,-0.93688,-0.5189185,0.7145213,-0.1167326,0.6055778 +1,user1689,-0.9572478,0.1251537,0.5006719,-0.2159803,0.3837138,0.2508256,0.2259302,0.5362195,-0.7193315,0.05712372 +1,user2451,-0.4251802,-0.2929717,-0.6881832,-0.02352577,0.1920592,-0.2629202,-0.2777251,-0.793024,-0.5887115,0.1071929 +1,user2452,-0.7437539,0.964322,0.2672251,0.2265304,-0.2350426,0.01957172,0.7910183,-0.3147542,-0.3113353,-0.5923871 +1,user2453,0.08977762,-0.3899797,-0.2155599,0.003493819,-0.4047334,-0.01302641,0.8444599,0.2216903,-0.7189179,0.8332062 +1,user2454,-0.1042357,0.09755446,0.1891235,0.7447464,-0.3467414,0.9716084,0.8465491,0.0811602,0.4660043,-0.2257885 +1,user2455,-0.6293009,-0.8166564,-0.5576629,0.1681504,-0.8497953,0.5923406,0.1043862,-0.5176373,-0.2380238,-0.4528643 +1,user2456,-0.1157119,-0.8311026,-0.4688704,-0.5718362,-0.2374773,-0.7553995,-0.5611696,0.8849353,-0.351736,-0.03890426 +1,user2457,-0.3190258,-0.9249177,-0.07430661,-0.3643921,0.6306885,-0.4609045,-0.8057533,0.1663184,-0.1853274,0.7600934 +1,user2458,0.2377744,0.2282702,-0.3026169,-0.08929433,0.6294167,-0.9310263,0.5192141,-0.3859985,0.1582691,0.8819146 +1,user2459,-0.04994272,-0.5286602,0.02785657,0.637051,0.21539,0.9059038,-0.1537083,-0.4707974,-0.257415,0.04253441 +1,user2460,-0.9411438,0.70041,0.8182456,0.7161151,0.1113323,-0.2204961,-0.3098064,0.7006135,-0.009561469,-0.9962764 +1,user2461,0.7322132,0.1846764,-0.8463569,0.04908797,-0.3582052,0.1982363,-0.5724658,-0.9618446,0.2184678,0.09112808 +1,user2462,-0.456201,0.5528481,-0.4180937,0.5148837,-0.1192362,0.5384407,-0.4003094,-0.4855373,-0.7317366,0.4877883 +1,user2463,0.8539395,0.9414173,-0.5321291,-0.2665458,0.5529729,0.7130241,0.06107133,0.8737875,0.6363353,-0.009324021 +1,user2464,-0.2172134,0.417772,0.9732083,-0.1630956,0.1787967,-0.09744182,0.1910351,0.03318431,0.1440274,0.9838486 +1,user2465,-0.4922507,0.8261234,-0.3383473,0.4070062,0.6539688,-0.4753504,0.9802768,0.8357681,-0.5610166,0.9111674 +1,user2466,0.9140868,-0.2440371,-0.02361068,-0.9162724,0.3081603,0.4755478,0.07853336,0.7082388,0.8074275,0.8186277 +1,user2467,0.3173932,-0.9990443,-0.6356997,0.1913087,0.3784339,0.3592714,0.5247289,-0.4286647,-0.9892562,0.4768053 +1,user2468,-0.3322878,-0.6119886,-0.4052186,0.1564166,0.7686223,-0.5039266,0.9032818,0.119648,-0.5905444,0.2938976 +1,user2469,0.6366925,-0.9490859,0.7135026,-0.8812457,0.06441307,0.3572163,0.6429392,-0.4446097,-0.2158324,-0.5839256 +1,user2470,0.8933434,0.2510464,0.7231526,0.6243471,0.3423301,-0.8354673,-0.2965631,-0.344377,0.2564803,0.437821 +1,user2471,-0.183116,-0.01803067,-0.06000819,0.4851107,-0.9453944,-0.5647374,0.9749535,0.01041418,-0.8356366,-0.6742991 +1,user2472,-0.4791837,-0.9211815,0.4877617,-0.09773968,0.3717649,0.1670212,-0.2476132,-0.888519,-0.4751536,-0.4765724 +1,user2473,-0.9836659,0.7497884,-0.08807923,-0.7780652,-0.8937493,-0.1488102,0.2760702,0.3257709,0.7215863,-0.6812286 +1,user2474,0.6260613,0.5922174,-0.4958036,-0.4853929,0.6630756,0.0977202,0.6051544,0.5515542,0.1602121,-0.8642746 diff --git a/psi/psi/demo/data/test_data_generator.py b/psi/psi/demo/data/test_data_generator.py new file mode 100644 index 00000000..9d18529e --- /dev/null +++ b/psi/psi/demo/data/test_data_generator.py @@ -0,0 +1,116 @@ +# Copyright 2023 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import random +import csv +import argparse + + +def gen_test_data( + reciver_item_cnt: int, + sender_item_cnt: int, + intersection_cnt: int, + id_cnt: int, + receiver_path: str, + sender_path: str, + intersection_path: str, + seed: int, +) -> None: + assert reciver_item_cnt > 0 and sender_item_cnt > 0 and intersection_cnt > 0 + assert reciver_item_cnt >= intersection_cnt and sender_item_cnt >= intersection_cnt + + receiver_minus_intersection_cnt = reciver_item_cnt - intersection_cnt + total_cnt = reciver_item_cnt + sender_item_cnt - intersection_cnt + + ids = [list(range(0, total_cnt)) for _ in range(id_cnt)] + + random.seed(seed) + random.shuffle(ids) + + for e in ids: + random.shuffle(e) + + r = 0 + s = 0 + i = 0 + + with open(receiver_path, 'w') as receiver_file, open( + sender_path, 'w' + ) as sender_file, open(intersection_path, 'w') as intersection_file: + fieldnames = [f'id{i}' for i in range(id_cnt)] + receiver_writer = csv.writer(receiver_file) + receiver_writer.writerow(fieldnames) + + sender_writer = csv.writer(sender_file) + sender_writer.writerow(fieldnames) + + intersection_writer = csv.writer(intersection_file) + intersection_writer.writerow(fieldnames) + + for x in range(total_cnt): + rows = [ids[idx][x] for idx in range(id_cnt)] + if x < receiver_minus_intersection_cnt: + receiver_writer.writerow(rows) + r += 1 + elif x < reciver_item_cnt: + receiver_writer.writerow(rows) + sender_writer.writerow(rows) + intersection_writer.writerow(rows) + r += 1 + s += 1 + i += 1 + else: + sender_writer.writerow(rows) + s += 1 + + assert r == reciver_item_cnt and s == sender_item_cnt and i == intersection_cnt + + print('## Test data generated:') + print(f'reciver_item_cnt = {reciver_item_cnt} at {receiver_path}.') + print(f'sender_item_cnt = {sender_item_cnt} at {sender_path}.') + print(f'intersection_cnt = {intersection_cnt} at {intersection_path}.') + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='psi test data generator.') + parser.add_argument( + '--reciver_item_cnt', help='Item cnt for receiver.', default='1e4' + ) + parser.add_argument('--sender_item_cnt', help='Item cnt for sender.', default='1e4') + parser.add_argument( + '--intersection_cnt', help='Item cnt for intersection.', default='8e3' + ) + parser.add_argument('--id_cnt', help='Id col cnt.', default=1, type=int) + parser.add_argument( + '--receiver_path', help='Receiver path.', default="receiver.csv" + ) + parser.add_argument('--sender_path', help='Sender path.', default="sender.csv") + parser.add_argument( + '--intersection_path', help='Intersection path.', default="intersection.csv" + ) + parser.add_argument('--seed', help='Random seed.', default=0, type=int) + + args = parser.parse_args() + + gen_test_data( + reciver_item_cnt=int(float(args.reciver_item_cnt)), + sender_item_cnt=int(float(args.sender_item_cnt)), + intersection_cnt=int(float(args.intersection_cnt)), + id_cnt=args.id_cnt, + receiver_path=args.receiver_path, + sender_path=args.sender_path, + intersection_path=args.intersection_path, + seed=args.seed, + ) diff --git a/psi/psi/demo/psi_demo.cc b/psi/psi/demo/psi_demo.cc new file mode 100644 index 00000000..e43f4c42 --- /dev/null +++ b/psi/psi/demo/psi_demo.cc @@ -0,0 +1,106 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <string> +#include <vector> + +#include "gflags/gflags.h" +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" +#include "yacl/link/link.h" + +#include "psi/psi/bucket_psi.h" +#include "psi/psi/utils/resource.h" + +DEFINE_string(input_path, "", "input csv file path"); +DEFINE_string( + psi_keys, "id", + "which keys in input file should be used in psi, separated by \",\""); +DEFINE_string(output_path, "psi_demo_out.csv", "output csv file path"); +DEFINE_string( + party_ips, "127.0.0.1:9307,127.0.0.1:9308", + "all parties ip, separated by \",\", the order must be consistent"); +DEFINE_uint32(rank, 0, "self rank in link::Context"); +DEFINE_string(psi_protocol, "ECDH_PSI_2PC", "psi protocol, see PsiType"); +DEFINE_bool(output_sort, true, "whether output file should be sorted"); +DEFINE_bool(ic_mode, false, "run in interconnection mode"); + +std::shared_ptr<yacl::link::Context> CreateLinkContext( + const std::string& party_ips, size_t self_rank) { + std::vector<std::string> ip_list = absl::StrSplit(party_ips, ','); + YACL_ENFORCE(ip_list.size() > 1); + + yacl::link::ContextDesc ctx_desc; + for (size_t i = 0; i < ip_list.size(); ++i) { + ctx_desc.parties.push_back({std::to_string(i), ip_list[i]}); + } + if (FLAGS_ic_mode) { + ctx_desc.brpc_channel_protocol = "h2:grpc"; + } + + return yacl::link::FactoryBrpc().CreateContext(ctx_desc, self_rank); +} + +std::vector<std::string> GetPsiKeys(const std::string& psi_keys) { + return absl::StrSplit(psi_keys, ','); +} + +int main(int argc, char* argv[]) { + gflags::AllowCommandLineReparsing(); + gflags::ParseCommandLineFlags(&argc, &argv, true); + + try { + auto link_ctx = CreateLinkContext(FLAGS_party_ips, FLAGS_rank); + link_ctx->ConnectToMesh(); + + auto fields = GetPsiKeys(FLAGS_psi_keys); + + psi::psi::BucketPsiConfig config; + config.mutable_input_params()->set_path(FLAGS_input_path); + config.mutable_input_params()->mutable_select_fields()->Add(fields.begin(), + fields.end()); + config.mutable_input_params()->set_precheck(false); + config.mutable_output_params()->set_path(FLAGS_output_path); + config.mutable_output_params()->set_need_sort(FLAGS_output_sort); + psi::psi::PsiType psi_type; + YACL_ENFORCE(psi::psi::PsiType_Parse(FLAGS_psi_protocol, &psi_type), + "Parse psi_protocol {} fail", FLAGS_psi_protocol); + config.set_psi_type(psi_type); + config.set_broadcast_result(true); + config.set_curve_type(psi::psi::CurveType::CURVE_25519); + + SPDLOG_INFO("Run BucketPsi with config: {}", config.ShortDebugString()); + SPDLOG_INFO("Ic mode: {}", FLAGS_ic_mode); + psi::psi::BucketPsi ctx(config, link_ctx, FLAGS_ic_mode); + auto report = ctx.Run(); + + SPDLOG_INFO("psi intersection_count={}, original_count={}", + report.intersection_count(), report.original_count()); + + double peak_mem_kb = 0; + try { + peak_mem_kb = psi::psi::GetPeakKbMemUsage(); + } catch (const std::exception& ex) { + SPDLOG_WARN("GetPeakKbMemUsage throw exception {}", ex.what()); + } + SPDLOG_INFO("psi finished, peak mem usage {} GB", + peak_mem_kb / 1024 / 1024); + + } catch (const std::exception& ex) { + SPDLOG_ERROR("Failed to run psi, error={}", ex.what()); + return -1; + } + + return 0; +} diff --git a/psi/psi/ecdh/BUILD.bazel b/psi/psi/ecdh/BUILD.bazel new file mode 100644 index 00000000..8892dc6f --- /dev/null +++ b/psi/psi/ecdh/BUILD.bazel @@ -0,0 +1,44 @@ +# Copyright 2023 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//bazel:psi.bzl", "psi_cc_library") + +package(default_visibility = ["//visibility:public"]) + +psi_cc_library( + name = "common", + hdrs = ["common.h"], +) + +psi_cc_library( + name = "receiver", + srcs = ["receiver.cc"], + hdrs = ["receiver.h"], + deps = [ + ":common", + "//psi/psi:interface", + "//psi/psi/utils:arrow_csv_batch_provider", + ], +) + +psi_cc_library( + name = "sender", + srcs = ["sender.cc"], + hdrs = ["sender.h"], + deps = [ + ":common", + "//psi/psi:interface", + "//psi/psi/utils:arrow_csv_batch_provider", + ], +) diff --git a/psi/psi/ecdh/common.h b/psi/psi/ecdh/common.h new file mode 100644 index 00000000..d374566c --- /dev/null +++ b/psi/psi/ecdh/common.h @@ -0,0 +1,21 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +namespace psi::psi::ecdh { + +// Default bin num of HashBucketEcPointStores for ec points. +constexpr int kDefaultBinNum = 64; + +} // namespace psi::psi::ecdh diff --git a/psi/psi/ecdh/receiver.cc b/psi/psi/ecdh/receiver.cc new file mode 100644 index 00000000..b2ffaa24 --- /dev/null +++ b/psi/psi/ecdh/receiver.cc @@ -0,0 +1,145 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/ecdh/receiver.h" + +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "yacl/base/exception.h" +#include "yacl/utils/scope_guard.h" + +#include "psi/psi/cryptor/cryptor_selector.h" +#include "psi/psi/ecdh/common.h" +#include "psi/psi/trace_categories.h" +#include "psi/psi/utils/utils.h" + +#include "psi/psi/psi.pb.h" + +namespace psi::psi::ecdh { + +EcdhPSIReceiver::EcdhPSIReceiver(const v2::PsiConfig &config, + std::shared_ptr<yacl::link::Context> lctx) + : AbstractPSIReceiver(config, std::move(lctx)) { + trunc_intersection_indices_ = true; +} + +void EcdhPSIReceiver::Init() { + TRACE_EVENT("init", "EcdhPSIReceiver::Init"); + SPDLOG_INFO("[EcdhPSIReceiver::Init] start"); + + AbstractPSIReceiver::Init(); + + if (recovery_manager_) { + recovery_manager_->MarkInitEnd(config_, key_hash_digest_); + } + + SPDLOG_INFO("[EcdhPSIReceiver::Init] end"); +} + +void EcdhPSIReceiver::PreProcess() { + TRACE_EVENT("pre-process", "EcdhPSIReceiver::PreProcess"); + SPDLOG_INFO("[EcdhPSIReceiver::PreProcess] start"); + + if (digest_equal_) { + return; + } + + psi_options_.ecc_cryptor = + CreateEccCryptor(config_.protocol_config().ecdh_config().curve()); + psi_options_.link_ctx = lctx_; + + // NOTE(junfeng): Only difference between receiver and sender. + psi_options_.target_rank = lctx_->Rank(); + if (config_.protocol_config().broadcast_result()) { + psi_options_.target_rank = yacl::link::kAllRank; + } + + psi_options_.ic_mode = false; + + batch_provider_ = std::make_shared<ArrowCsvBatchProvider>( + config_.input_config().path(), selected_keys_); + + if (recovery_manager_) { + self_ec_point_store_ = std::make_shared<HashBucketEcPointStore>( + recovery_manager_->ecdh_dual_masked_self_cache_path(), kDefaultBinNum, + false); + peer_ec_point_store_ = std::make_shared<HashBucketEcPointStore>( + recovery_manager_->ecdh_dual_masked_peer_cache_path(), kDefaultBinNum, + false); + recovery_manager_->MarkPreProcessEnd(psi_options_.ecc_cryptor); + psi_options_.recovery_manager = recovery_manager_; + } else { + self_ec_point_store_ = std::make_shared<HashBucketEcPointStore>( + std::filesystem::path(config_.output_config().path()).parent_path(), + kDefaultBinNum); + peer_ec_point_store_ = std::make_shared<HashBucketEcPointStore>( + std::filesystem::path(config_.output_config().path()).parent_path(), + kDefaultBinNum); + } + + SPDLOG_INFO("[EcdhPSIReceiver::PreProcess] end"); +} + +void EcdhPSIReceiver::Online() { + TRACE_EVENT("online", "EcdhPSIReceiver::Online"); + SPDLOG_INFO("[EcdhPSIReceiver::Online] start"); + + if (digest_equal_) { + return; + } + + bool online_stage_finished = + recovery_manager_ ? recovery_manager_->MarkOnlineStart(lctx_) : false; + + if (!online_stage_finished) { + auto run_f = std::async([&] { + return RunEcdhPsi(psi_options_, batch_provider_, self_ec_point_store_, + peer_ec_point_store_); + }); + + SyncWait(lctx_, &run_f); + } + + report_.set_original_count(batch_provider_->row_cnt()); + + if (recovery_manager_) { + recovery_manager_->MarkOnlineEnd(); + } + + SPDLOG_INFO("[EcdhPSIReceiver::Online] end"); +} + +void EcdhPSIReceiver::PostProcess() { + TRACE_EVENT("post-process", "EcdhPSIReceiver::PostProcess"); + SPDLOG_INFO("[EcdhPSIReceiver::PostProcess] start"); + + if (digest_equal_) { + return; + } + + auto compute_indices_f = std::async([&] { + FinalizeAndComputeIndices(self_ec_point_store_, peer_ec_point_store_, + intersection_indices_writer_.get()); + }); + + SyncWait(lctx_, &compute_indices_f); + + if (recovery_manager_) { + recovery_manager_->MarkPostProcessEnd(); + } + + SPDLOG_INFO("[EcdhPSIReceiver::PostProcess] end"); +} + +} // namespace psi::psi::ecdh diff --git a/psi/psi/ecdh/receiver.h b/psi/psi/ecdh/receiver.h new file mode 100644 index 00000000..11de43a4 --- /dev/null +++ b/psi/psi/ecdh/receiver.h @@ -0,0 +1,48 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "psi/psi/core/ecdh_psi.h" +#include "psi/psi/interface.h" +#include "psi/psi/utils/arrow_csv_batch_provider.h" + +#include "psi/proto/psi.pb.h" + +namespace psi::psi::ecdh { + +class EcdhPSIReceiver final : public AbstractPSIReceiver { + public: + explicit EcdhPSIReceiver(const v2::PsiConfig &config, + std::shared_ptr<yacl::link::Context> lctx = nullptr); + + ~EcdhPSIReceiver() override = default; + + private: + void Init() override; + + void PreProcess() override; + + void Online() override; + + void PostProcess() override; + + EcdhPsiOptions psi_options_; + + std::shared_ptr<ArrowCsvBatchProvider> batch_provider_; + + std::shared_ptr<HashBucketEcPointStore> self_ec_point_store_; + std::shared_ptr<HashBucketEcPointStore> peer_ec_point_store_; +}; + +} // namespace psi::psi::ecdh diff --git a/psi/psi/ecdh/sender.cc b/psi/psi/ecdh/sender.cc new file mode 100644 index 00000000..67176384 --- /dev/null +++ b/psi/psi/ecdh/sender.cc @@ -0,0 +1,146 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/ecdh/sender.h" + +#include <spdlog/spdlog.h> + +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "yacl/base/exception.h" +#include "yacl/utils/scope_guard.h" + +#include "psi/psi/cryptor/cryptor_selector.h" +#include "psi/psi/ecdh/common.h" +#include "psi/psi/trace_categories.h" +#include "psi/psi/utils/utils.h" + +#include "psi/psi/psi.pb.h" + +namespace psi::psi::ecdh { + +EcdhPSISender::EcdhPSISender(const v2::PsiConfig &config, + std::shared_ptr<yacl::link::Context> lctx) + : AbstractPSISender(config, std::move(lctx)) { + trunc_intersection_indices_ = true; +} + +void EcdhPSISender::Init() { + TRACE_EVENT("init", "EcdhPSISender::Init"); + SPDLOG_INFO("[EcdhPSISender::Init] start"); + + AbstractPSISender::Init(); + + if (recovery_manager_) { + recovery_manager_->MarkInitEnd(config_, key_hash_digest_); + } + + SPDLOG_INFO("[EcdhPSISender::Init] end"); +} + +void EcdhPSISender::PreProcess() { + TRACE_EVENT("pre-process", "EcdhPSISender::PreProcess"); + SPDLOG_INFO("[EcdhPSISender::PreProcess] start"); + + if (digest_equal_) { + return; + } + + psi_options_.ecc_cryptor = + CreateEccCryptor(config_.protocol_config().ecdh_config().curve()); + psi_options_.link_ctx = lctx_; + + // NOTE(junfeng): Only difference between receiver and sender. + psi_options_.target_rank = static_cast<size_t>(lctx_->Rank() == 0); + if (config_.protocol_config().broadcast_result()) { + psi_options_.target_rank = yacl::link::kAllRank; + } + + psi_options_.ic_mode = false; + + batch_provider_ = std::make_shared<ArrowCsvBatchProvider>( + config_.input_config().path(), selected_keys_); + + if (recovery_manager_) { + self_ec_point_store_ = std::make_shared<HashBucketEcPointStore>( + recovery_manager_->ecdh_dual_masked_self_cache_path(), kDefaultBinNum, + false); + peer_ec_point_store_ = std::make_shared<HashBucketEcPointStore>( + recovery_manager_->ecdh_dual_masked_peer_cache_path(), kDefaultBinNum, + false); + recovery_manager_->MarkPreProcessEnd(psi_options_.ecc_cryptor); + psi_options_.recovery_manager = recovery_manager_; + } else { + self_ec_point_store_ = std::make_shared<HashBucketEcPointStore>( + std::filesystem::path(config_.output_config().path()).parent_path(), + kDefaultBinNum); + peer_ec_point_store_ = std::make_shared<HashBucketEcPointStore>( + std::filesystem::path(config_.output_config().path()).parent_path(), + kDefaultBinNum); + } + + SPDLOG_INFO("[EcdhPSISender::PreProcess] end"); +} + +void EcdhPSISender::Online() { + TRACE_EVENT("online", "EcdhPSISender::Online"); + SPDLOG_INFO("[EcdhPSISender::Online] start"); + + if (digest_equal_) { + return; + } + bool online_stage_finished = + recovery_manager_ ? recovery_manager_->MarkOnlineStart(lctx_) : false; + + if (!online_stage_finished) { + auto run_f = std::async([&] { + return RunEcdhPsi(psi_options_, batch_provider_, self_ec_point_store_, + peer_ec_point_store_); + }); + + SyncWait(lctx_, &run_f); + } + + report_.set_original_count(batch_provider_->row_cnt()); + + if (recovery_manager_) { + recovery_manager_->MarkOnlineEnd(); + } + + SPDLOG_INFO("[EcdhPSISender::Online] end"); +} + +void EcdhPSISender::PostProcess() { + TRACE_EVENT("post-process", "EcdhPSISender::PostProcess"); + SPDLOG_INFO("[EcdhPSISender::PostProcess] start"); + + if (digest_equal_) { + return; + } + + auto compute_indices_f = std::async([&] { + FinalizeAndComputeIndices(self_ec_point_store_, peer_ec_point_store_, + intersection_indices_writer_.get()); + }); + + SyncWait(lctx_, &compute_indices_f); + + if (recovery_manager_) { + recovery_manager_->MarkPostProcessEnd(); + } + + SPDLOG_INFO("[EcdhPSISender::PostProcess] end"); +} + +} // namespace psi::psi::ecdh diff --git a/psi/psi/ecdh/sender.h b/psi/psi/ecdh/sender.h new file mode 100644 index 00000000..2d6e6f36 --- /dev/null +++ b/psi/psi/ecdh/sender.h @@ -0,0 +1,48 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "psi/psi/core/ecdh_psi.h" +#include "psi/psi/interface.h" +#include "psi/psi/utils/arrow_csv_batch_provider.h" + +#include "psi/proto/psi.pb.h" + +namespace psi::psi::ecdh { + +class EcdhPSISender final : public AbstractPSISender { + public: + explicit EcdhPSISender(const v2::PsiConfig &config, + std::shared_ptr<yacl::link::Context> lctx = nullptr); + + ~EcdhPSISender() override = default; + + private: + void Init() override; + + void PreProcess() override; + + void Online() override; + + void PostProcess() override; + + EcdhPsiOptions psi_options_; + + std::shared_ptr<ArrowCsvBatchProvider> batch_provider_; + + std::shared_ptr<HashBucketEcPointStore> self_ec_point_store_; + std::shared_ptr<HashBucketEcPointStore> peer_ec_point_store_; +}; + +} // namespace psi::psi::ecdh diff --git a/psi/psi/factory.cc b/psi/psi/factory.cc new file mode 100644 index 00000000..0407edb9 --- /dev/null +++ b/psi/psi/factory.cc @@ -0,0 +1,68 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/factory.h" + +#include <memory> + +#include "yacl/base/exception.h" + +#include "psi/psi/ecdh/receiver.h" +#include "psi/psi/ecdh/sender.h" +#include "psi/psi/kkrt/receiver.h" +#include "psi/psi/kkrt/sender.h" +#include "psi/psi/rr22/receiver.h" +#include "psi/psi/rr22/sender.h" + +namespace psi::psi { + +std::unique_ptr<AbstractPSIParty> createPSIParty( + const v2::PsiConfig& config, std::shared_ptr<yacl::link::Context> lctx) { + switch (config.protocol_config().protocol()) { + case v2::Protocol::PROTOCOL_ECDH: { + switch (config.protocol_config().role()) { + case v2::Role::ROLE_RECEIVER: + return std::make_unique<ecdh::EcdhPSIReceiver>(config, lctx); + case v2::Role::ROLE_SENDER: + return std::make_unique<ecdh::EcdhPSISender>(config, lctx); + default: + YACL_THROW("Role is not unspecified."); + } + } + case v2::Protocol::PROTOCOL_KKRT: { + switch (config.protocol_config().role()) { + case v2::Role::ROLE_RECEIVER: + return std::make_unique<kkrt::KkrtPSIReceiver>(config, lctx); + case v2::Role::ROLE_SENDER: + return std::make_unique<kkrt::KkrtPSISender>(config, lctx); + default: + YACL_THROW("Role is not unspecified."); + } + } + case v2::Protocol::PROTOCOL_RR22: { + switch (config.protocol_config().role()) { + case v2::Role::ROLE_RECEIVER: + return std::make_unique<rr22::Rr22PSIReceiver>(config, lctx); + case v2::Role::ROLE_SENDER: + return std::make_unique<rr22::Rr22PSISender>(config, lctx); + default: + YACL_THROW("Role is not unspecified."); + } + } + default: + YACL_THROW("Protocol is unspecified."); + } +} + +} // namespace psi::psi diff --git a/psi/psi/factory.h b/psi/psi/factory.h new file mode 100644 index 00000000..c6c26c68 --- /dev/null +++ b/psi/psi/factory.h @@ -0,0 +1,28 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include <memory> + +#include "psi/psi/interface.h" + +#include "psi/proto/psi.pb.h" + +namespace psi::psi { + +std::unique_ptr<AbstractPSIParty> createPSIParty( + const v2::PsiConfig& config, + std::shared_ptr<yacl::link::Context> lctx = nullptr); + +} // namespace psi::psi diff --git a/psi/psi/interface.cc b/psi/psi/interface.cc new file mode 100644 index 00000000..97041db9 --- /dev/null +++ b/psi/psi/interface.cc @@ -0,0 +1,374 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/interface.h" + +#include <spdlog/spdlog.h> + +#include <cstddef> +#include <cstdint> +#include <filesystem> +#include <memory> +#include <numeric> + +#include "absl/time/time.h" +#include "google/protobuf/util/message_differencer.h" +#include "yacl/base/exception.h" +#include "yacl/link/link.h" + +#include "psi/psi/bucket_psi.h" +#include "psi/psi/prelude.h" +#include "psi/psi/trace_categories.h" +#include "psi/psi/utils/csv_checker.h" +#include "psi/psi/utils/inner_join.h" +#include "psi/psi/utils/utils.h" + +#include "psi/proto/psi.pb.h" + +namespace psi::psi { + +namespace { + +std::string GenerateIndexFileName(v2::Role role) { + return fmt::format("psi_index_{}.csv", role); +} + +std::string GenerateSortedIndexFileName(v2::Role role) { + return fmt::format("sorted_psi_index_{}.csv", role); +} + +std::string GenerateIndexFileName() { + return fmt::format("psi_index_{}.csv", + std::to_string(absl::ToUnixNanos(absl::Now()))); +} + +std::string GenerateSortedIndexFileName() { + return fmt::format("sorted_psi_index_{}.csv", + std::to_string(absl::ToUnixNanos(absl::Now()))); +} + +} // namespace + +constexpr size_t kIndexWriterBatchSize = 1 << 10; + +AbstractPSIParty::AbstractPSIParty(const v2::PsiConfig &config, v2::Role role, + std::shared_ptr<yacl::link::Context> lctx) + : config_(config), + role_(role), + selected_keys_(config_.keys().begin(), config_.keys().end()), + lctx_(std::move(lctx)) {} + +void AbstractPSIParty::Init() { + TRACE_EVENT("init", "AbstractPSIParty::Init"); + SPDLOG_INFO("[AbstractPSIParty::Init] start"); + + if (!lctx_) { + yacl::link::ContextDesc lctx_desc(config_.link_config()); + int rank = -1; + for (int i = 0; i < config_.link_config().parties().size(); i++) { + if (config_.link_config().parties(i).id() == config_.self_link_party()) { + rank = i; + } + } + YACL_ENFORCE_GE(rank, 0, "Couldn't find rank in YACL Link."); + + lctx_ = yacl::link::FactoryBrpc().CreateContext(lctx_desc, rank); + } + + // Test connection. + lctx_->ConnectToMesh(); + + CheckSelfConfig(); + + CheckPeerConfig(); + + if (config_.advanced_join_type() == + v2::PsiConfig::ADVANCED_JOIN_TYPE_INNER_JOIN) { + SPDLOG_INFO("[AbstractPSIParty::Init][Inner join pre-process] start"); + + if (config_.recovery_config().enabled()) { + inner_join_config_ = + std::make_shared<v2::InnerJoinConfig>(BuildInnerJoinConfig( + config_, + std::filesystem::path(config_.recovery_config().folder()))); + } else { + inner_join_config_ = + std::make_shared<v2::InnerJoinConfig>(BuildInnerJoinConfig(config_)); + } + + config_.mutable_input_config()->set_path( + inner_join_config_->unique_input_keys_cnt_path()); + config_.mutable_output_config()->set_path( + inner_join_config_->self_intersection_cnt_path()); + + auto inner_join_preprocess_f = std::async([&] { + InnerJoinGenerateSortedInput(*inner_join_config_); + InnerJoinGenerateUniqueInputKeysCnt(*inner_join_config_); + }); + + SyncWait(lctx_, &inner_join_preprocess_f); + + SPDLOG_INFO("[AbstractPSIParty::Init][Inner join pre-process] end"); + } + + auto check_csv_f = std::async([&] { + if (config_.check_duplicates() || config_.check_hash_digest() || + config_.protocol_config().protocol() != v2::PROTOCOL_ECDH) { + SPDLOG_INFO("[AbstractPSIParty::Init][Check csv pre-process] start"); + + CheckCsvReport check_csv_report = + CheckCsv(config_.input_config().path(), selected_keys_, + config_.check_duplicates(), config_.check_hash_digest()); + + key_hash_digest_ = check_csv_report.key_hash_digest; + + report_.set_original_count(check_csv_report.num_rows); + + SPDLOG_INFO("[AbstractPSIParty::Init][Check csv pre-process] end"); + } + }); + + SyncWait(lctx_, &check_csv_f); + + // Check if the input are the same between receiver and sender. + if (config_.check_hash_digest()) { + std::vector<yacl::Buffer> digest_buf_list = + yacl::link::AllGather(lctx_, key_hash_digest_, "PSI:SYNC_DIGEST"); + digest_equal_ = HashListEqualTest(digest_buf_list); + } + + if (config_.recovery_config().enabled()) { + recovery_manager_ = + std::make_shared<RecoveryManager>(config_.recovery_config().folder()); + } + + std::filesystem::path intersection_indices_writer_path = + recovery_manager_ + ? std::filesystem::path(config_.recovery_config().folder()) / + GenerateIndexFileName(role_) + : std::filesystem::path(config_.output_config().path()) + .parent_path() / + GenerateIndexFileName(); + + intersection_indices_writer_ = std::make_shared<IndexWriter>( + intersection_indices_writer_path, kIndexWriterBatchSize, + trunc_intersection_indices_); + + if (digest_equal_) { + SPDLOG_WARN("The keys between two parties share the same set."); + for (int64_t i = 0; i < report_.original_count(); i++) { + if (intersection_indices_writer_->WriteCache(i) == + kIndexWriterBatchSize) { + intersection_indices_writer_->Commit(); + } + } + } + SPDLOG_INFO("[AbstractPSIParty::Init] end"); +} + +v2::PsiReport AbstractPSIParty::Finalize() { + TRACE_EVENT("finalize", "AbstractPSIParty::Finalize"); + SPDLOG_INFO("[AbstractPSIParty::Finalize] start"); + + intersection_indices_writer_->Close(); + + bool sort_output = config_.sort_output(); + if (!sort_output && inner_join_config_) { + SPDLOG_WARN( + "Sort_output turns off while inner_join is selected. Sort_output is " + "enabled."); + + sort_output = false; + } + + std::filesystem::path sorted_intersection_indices_path = + intersection_indices_writer_->path().parent_path() / + (recovery_manager_ ? GenerateSortedIndexFileName(role_) + : GenerateSortedIndexFileName()); + + SPDLOG_INFO("[AbstractPSIParty::Finalize][Generate result] start"); + auto gen_result_f = std::async([&] { + MultiKeySort( + intersection_indices_writer_->path(), sorted_intersection_indices_path, + std::vector<std::string>{kIdx}, true, need_intersection_deduplication_); + + if (role_ == v2::ROLE_RECEIVER || + config_.protocol_config().broadcast_result()) { + report_.set_intersection_count(GenerateResult( + config_.input_config().path(), config_.output_config().path(), + selected_keys_, sorted_intersection_indices_path, sort_output, + digest_equal_, !inner_join_config_ && config_.output_difference())); + } + }); + + SyncWait(lctx_, &gen_result_f); + SPDLOG_INFO("[AbstractPSIParty::Finalize][Generate result] end"); + + if (inner_join_config_ && !config_.output_difference()) { + SPDLOG_INFO("[AbstractPSIParty::Finalize][Inner join post-precess] start"); + auto sync_intersection_f = std::async([&] { + return InnerJoinSyncIntersectionCnt(lctx_, *inner_join_config_); + }); + + SyncWait(lctx_, &sync_intersection_f); + SPDLOG_INFO("[AbstractPSIParty::Finalize][Inner join post-precess] end"); + } + + lctx_->WaitLinkTaskFinish(); + + if (inner_join_config_) { + SPDLOG_INFO("[AbstractPSIParty::Finalize][Generate difference] start"); + if (config_.output_difference()) { + InnerJoinGenerateDifference(*inner_join_config_); + } else { + InnerJoinGenerateIntersection(*inner_join_config_); + } + SPDLOG_INFO("[AbstractPSIParty::Finalize][Generate difference] end"); + } + + if (role_ == v2::ROLE_SENDER && + !config_.protocol_config().broadcast_result()) { + report_.set_intersection_count(-1); + } + + SPDLOG_INFO("[AbstractPSIParty::Finalize] end"); + + return report_; +} + +void AbstractPSIParty::CheckSelfConfig() { + if (config_.protocol_config().protocol() == v2::PROTOCOL_ECDH) { + if (config_.protocol_config().ecdh_config().curve() == CURVE_INVALID_TYPE) { + YACL_THROW("Curve type is not specified."); + } + } + + if (config_.protocol_config().role() != role_) { + YACL_THROW("Role doesn't match."); + } + + if (config_.input_config().type() != v2::IO_TYPE_FILE_CSV) { + YACL_THROW("Input type only supports IO_TYPE_FILE_CSV at this moment."); + } + + if (config_.output_config().type() != v2::IO_TYPE_FILE_CSV) { + YACL_THROW("Output type only supports IO_TYPE_FILE_CSV at this moment."); + } + + if (config_.keys().empty()) { + YACL_THROW("keys are not specified."); + } + + if (config_.advanced_join_type() == + v2::PsiConfig::ADVANCED_JOIN_TYPE_LEFT_JOIN) { + YACL_THROW("left join is unsupported."); + } + + if (!config_.protocol_config().broadcast_result() && + config_.advanced_join_type() == + v2::PsiConfig::ADVANCED_JOIN_TYPE_INNER_JOIN) { + SPDLOG_WARN( + "broadcast_result turns off while inner_join is selected. " + "broadcast_result is modified to true since intersection has to be " + "sent to both parties."); + + YACL_ENFORCE(!config_.output_config().path().empty(), + "You have to provide path of output."); + + config_.mutable_protocol_config()->set_broadcast_result(true); + } + + if (config_.check_duplicates() && + config_.advanced_join_type() == + v2::PsiConfig::ADVANCED_JOIN_TYPE_INNER_JOIN) { + SPDLOG_WARN( + "check_duplicates turns on while inner_join is selected. " + "check_duplicates is ignored."); + + config_.set_check_duplicates(false); + } + + if (!config_.check_hash_digest() && config_.recovery_config().enabled()) { + SPDLOG_WARN( + "check_hash_digest turns off while recovery is enabled. " + "check_hash_digest is modified to true for robustness."); + + config_.set_check_hash_digest(true); + } +} + +void AbstractPSIParty::CheckPeerConfig() { + v2::PsiConfig config = config_; + + // The fields below don't need to verify. + config.mutable_input_config()->Clear(); + config.mutable_output_config()->Clear(); + config.mutable_link_config()->Clear(); + config.set_self_link_party(""); + config.mutable_keys()->Clear(); + config.mutable_debug_options()->Clear(); + config.set_check_duplicates(false); + config.set_output_difference(false); + config.set_sort_output(false); + + // Recovery must be enabled by all parties at the same time. + config.mutable_recovery_config()->set_folder(""); + + std::string serialized; + YACL_ENFORCE(config.SerializeToString(&serialized)); + + std::vector<yacl::Buffer> serialized_list = + yacl::link::AllGather(lctx_, serialized, "PSI:VERIFY_CONFIG"); + + YACL_ENFORCE_EQ(serialized_list.size(), 2UL); + + if (!(serialized_list[0] == serialized_list[1])) { + const std::string rank0_serialized(serialized_list[0].data<char>(), + serialized_list[0].size()); + + const std::string rank1_serialized(serialized_list[1].data<char>(), + serialized_list[1].size()); + + v2::PsiConfig rank0_config; + YACL_ENFORCE(rank0_config.ParseFromString(rank0_serialized)); + + v2::PsiConfig rank1_config; + YACL_ENFORCE(rank1_config.ParseFromString(rank1_serialized)); + + if (rank0_config.protocol_config().role() == + rank1_config.protocol_config().role()) { + YACL_THROW("The role of parties must be different."); + } + + rank0_config.mutable_protocol_config()->set_role(v2::ROLE_UNSPECIFIED); + rank1_config.mutable_protocol_config()->set_role(v2::ROLE_UNSPECIFIED); + + YACL_ENFORCE(::google::protobuf::util::MessageDifferencer::Equals( + rank0_config, rank1_config), + "PSI configs are not consistent between parties. Rank 0: {} " + "while Rank 1: {}", + rank0_config.ShortDebugString(), + rank1_config.ShortDebugString()); + } +} + +AbstractPSIReceiver::AbstractPSIReceiver( + const v2::PsiConfig &config, std::shared_ptr<yacl::link::Context> lctx) + : AbstractPSIParty(config, v2::Role::ROLE_RECEIVER, std::move(lctx)) {} + +AbstractPSISender::AbstractPSISender(const v2::PsiConfig &config, + std::shared_ptr<yacl::link::Context> lctx) + : AbstractPSIParty(config, v2::Role::ROLE_SENDER, std::move(lctx)) {} + +} // namespace psi::psi diff --git a/psi/psi/interface.h b/psi/psi/interface.h new file mode 100644 index 00000000..bf5b6f6c --- /dev/null +++ b/psi/psi/interface.h @@ -0,0 +1,132 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <cstddef> +#include <memory> +#include <string> + +#include "absl/status/status.h" +#include "yacl/base/exception.h" +#include "yacl/link/algorithm/barrier.h" +#include "yacl/link/link.h" + +#include "psi/psi/recovery.h" +#include "psi/psi/utils/index_store.h" + +#include "psi/proto/psi.pb.h" + +namespace psi::psi { + +class AbstractPSIParty { + public: + AbstractPSIParty() = delete; + + AbstractPSIParty(const AbstractPSIParty &copy) = delete; + + AbstractPSIParty(AbstractPSIParty &&source) = delete; + + AbstractPSIParty(const v2::PsiConfig &config, v2::Role role, + std::shared_ptr<yacl::link::Context> lctx = nullptr); + + // AbstractPSIParty(const v2::PsiConfig &config, v2::Role role); + + v2::PsiReport Run() { + Init(); + + if (!digest_equal_) { + PreProcess(); + + yacl::link::Barrier(lctx_, "psi_pre_process"); + + Online(); + + PostProcess(); + } + + return Finalize(); + } + + virtual ~AbstractPSIParty() = default; + + protected: + // Including tasks independent to protocol: + // - Config check + // - Input check + // - Network check + // - Extract keys + // - etc. + virtual void Init(); + + // Dependent to Protocol + virtual void PreProcess() = 0; + + // Dependent to Protocol + virtual void Online() = 0; + + // Dependent to Protocol + virtual void PostProcess() = 0; + + // Including tasks independent to protocol: + // - Compose output. + // - Sort output. + // - Write output. + virtual v2::PsiReport Finalize(); + + v2::PsiConfig config_; + + v2::Role role_; + + v2::PsiReport report_; + + std::vector<std::string> selected_keys_; + + std::shared_ptr<IndexWriter> intersection_indices_writer_; + + bool trunc_intersection_indices_ = false; + + std::shared_ptr<yacl::link::Context> lctx_; + + bool digest_equal_ = false; + + std::shared_ptr<RecoveryManager> recovery_manager_; + + std::string key_hash_digest_; + + std::shared_ptr<v2::InnerJoinConfig> inner_join_config_; + + bool need_intersection_deduplication_ = false; + + private: + void CheckPeerConfig(); + + void CheckSelfConfig(); +}; + +class AbstractPSIReceiver : public AbstractPSIParty { + public: + explicit AbstractPSIReceiver( + const v2::PsiConfig &config, + std::shared_ptr<yacl::link::Context> lctx = nullptr); +}; + +class AbstractPSISender : public AbstractPSIParty { + public: + explicit AbstractPSISender( + const v2::PsiConfig &config, + std::shared_ptr<yacl::link::Context> lctx = nullptr); +}; + +} // namespace psi::psi diff --git a/psi/psi/io/BUILD.bazel b/psi/psi/io/BUILD.bazel new file mode 100644 index 00000000..eae07b87 --- /dev/null +++ b/psi/psi/io/BUILD.bazel @@ -0,0 +1,27 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//bazel:psi.bzl", "psi_cc_library") + +psi_cc_library( + name = "io", + srcs = ["io.cc"], + hdrs = ["io.h"], + visibility = ["//visibility:public"], + deps = [ + "@yacl//yacl/base:exception", + "@yacl//yacl/io/rw", + "@yacl//yacl/io/stream", + ], +) diff --git a/psi/psi/io/io.cc b/psi/psi/io/io.cc new file mode 100644 index 00000000..4270895c --- /dev/null +++ b/psi/psi/io/io.cc @@ -0,0 +1,89 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/io/io.h" + +#include <memory> +#include <utility> + +#include "yacl/base/exception.h" +#include "yacl/io/rw/csv_reader.h" +#include "yacl/io/rw/csv_writer.h" +#include "yacl/io/stream/file_io.h" +#include "yacl/io/stream/mem_io.h" + +namespace psi::psi::io { + +std::unique_ptr<InputStream> BuildInputStream(const std::any& io_options) { + std::unique_ptr<InputStream> is; + if (io_options.type() == typeid(MemIoOptions)) { + auto op = std::any_cast<MemIoOptions>(io_options); + is = std::make_unique<yacl::io::MemInputStream>(*op.mem_io_buffer); + } else if (io_options.type() == typeid(FileIoOptions)) { + auto op = std::any_cast<FileIoOptions>(io_options); + is = std::make_unique<yacl::io::FileInputStream>(op.file_name); + } else { + YACL_THROW("unknow io_options type {}", io_options.type().name()); + } + + return is; +} + +std::unique_ptr<OutputStream> BuildOutputStream(const std::any& io_options) { + std::unique_ptr<OutputStream> os; + if (io_options.type() == typeid(MemIoOptions)) { + auto op = std::any_cast<MemIoOptions>(io_options); + os = std::make_unique<yacl::io::MemOutputStream>(op.mem_io_buffer); + } else if (io_options.type() == typeid(FileIoOptions)) { + auto op = std::any_cast<FileIoOptions>(io_options); + os = std::make_unique<yacl::io::FileOutputStream>( + op.file_name, op.trunc, op.exit_for_fail_in_destructor); + } else { + YACL_THROW("unknow io_options type {}", io_options.type().name()); + } + return os; +} + +std::unique_ptr<Reader> BuildReader(const std::any& io_options, + const std::any& format_options) { + auto is = BuildInputStream(io_options); + std::unique_ptr<Reader> ret; + if (format_options.type() == typeid(CsvOptions)) { + auto op = std::any_cast<CsvOptions>(format_options); + ret = std::make_unique<yacl::io::CsvReader>( + op.read_options, std::move(is), op.field_delimiter, op.line_delimiter); + } else { + YACL_THROW("unknow format_options type {}", format_options.type().name()); + } + ret->Init(); + return ret; +} + +std::unique_ptr<Writer> BuildWriter(const std::any& io_options, + const std::any& format_options) { + auto os = BuildOutputStream(io_options); + std::unique_ptr<Writer> ret; + if (format_options.type() == typeid(CsvOptions)) { + auto op = std::any_cast<CsvOptions>(format_options); + ret = std::make_unique<yacl::io::CsvWriter>( + op.writer_options, std::move(os), op.field_delimiter, + op.line_delimiter); + } else { + YACL_THROW("unknow format_options type {}", format_options.type().name()); + } + ret->Init(); + return ret; +} + +} // namespace psi::psi::io diff --git a/psi/psi/io/io.h b/psi/psi/io/io.h new file mode 100644 index 00000000..22f8455c --- /dev/null +++ b/psi/psi/io/io.h @@ -0,0 +1,105 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#include <any> +#include <memory> +#include <string> + +#include "yacl/io/rw/reader.h" +#include "yacl/io/rw/writer.h" +#include "yacl/io/stream/interface.h" +namespace psi::psi::io { + +using Schema = yacl::io::Schema; + +using InputStream = yacl::io::InputStream; +using OutputStream = yacl::io::OutputStream; + +using Reader = yacl::io::Reader; +using Writer = yacl::io::Writer; +using ReaderOptions = yacl::io::ReaderOptions; +using WriterOptions = yacl::io::WriterOptions; + +using ColumnVectorBatch = yacl::io::ColumnVectorBatch; +using FloatColumnVector = yacl::io::FloatColumnVector; +using DoubleColumnVector = yacl::io::DoubleColumnVector; +using StringColumnVector = yacl::io::StringColumnVector; + +template <class S> +using ColumnVector = yacl::io::ColumnVector<S>; + +struct MemIoOptions { + // IO buffer + // for reader, it's mock input data. + // for writer, it recv data when out stream close/. + std::string* mem_io_buffer; +}; + +struct FileIoOptions { + FileIoOptions() = default; + explicit FileIoOptions(const std::string& f, bool trunc_in = true, + bool exit_for_fail_in_destructor_in = true) + : file_name(f), + trunc(trunc_in), + exit_for_fail_in_destructor(exit_for_fail_in_destructor_in) {} + // filename for read / write. + // IF read: + // PLS make sure file exist. + // IF write: + // FileIo always trunc file if target file exist. + std::string file_name; + + bool trunc = true; + + // FileIo will try Close() in destructor if file not closed. + // IF false: + // FileIo action likes std::ofstream, ignores Close()'s io exception. + // output file may be damaged. + // Use for online service should not exit. + // IF true: + // FileIo will log & call _exit if Close() throw any io exception. + // + // SO, !!! PLS manually Close writer before release it !!! + bool exit_for_fail_in_destructor = true; +}; + +struct CsvOptions { + // for BuildReader + ReaderOptions read_options; + // for BuildWriter + WriterOptions writer_options; + char field_delimiter = ','; + char line_delimiter = '\n'; +}; + +/** + * Writer & OutputStream Always trunc file if target file exist. + * + * Please read io_test.cc as examples of how to use. + */ + +// Formatted IO +std::unique_ptr<Reader> BuildReader(const std::any& io_options, + const std::any& format_options); +// !!! PLS manually call Close before release Writer !!! +std::unique_ptr<Writer> BuildWriter(const std::any& io_options, + const std::any& format_options); + +// Raw IO +std::unique_ptr<InputStream> BuildInputStream(const std::any& io_options); +// !!! PLS manually call Close before release OutputStream !!! +std::unique_ptr<OutputStream> BuildOutputStream(const std::any& io_options); + +} // namespace psi::psi::io diff --git a/psi/psi/kkrt/BUILD.bazel b/psi/psi/kkrt/BUILD.bazel new file mode 100644 index 00000000..cff817b6 --- /dev/null +++ b/psi/psi/kkrt/BUILD.bazel @@ -0,0 +1,52 @@ +# Copyright 2023 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//bazel:psi.bzl", "psi_cc_library") + +package(default_visibility = ["//visibility:public"]) + +psi_cc_library( + name = "common", + srcs = ["common.cc"], + hdrs = ["common.h"], + deps = [ + "//psi/proto:psi_cc_proto", + "//psi/psi:bucket", + "//psi/psi:recovery", + ], +) + +psi_cc_library( + name = "receiver", + srcs = ["receiver.cc"], + hdrs = ["receiver.h"], + deps = [ + ":common", + "//psi/psi:interface", + "//psi/psi/core:kkrt_psi", + "//psi/psi/utils:arrow_csv_batch_provider", + ], +) + +psi_cc_library( + name = "sender", + srcs = ["sender.cc"], + hdrs = ["sender.h"], + deps = [ + ":common", + "//psi/psi:interface", + "//psi/psi/core:kkrt_psi", + "//psi/psi/utils:arrow_csv_batch_provider", + ], +) diff --git a/psi/psi/kkrt/common.cc b/psi/psi/kkrt/common.cc new file mode 100644 index 00000000..a845029a --- /dev/null +++ b/psi/psi/kkrt/common.cc @@ -0,0 +1,35 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/kkrt/common.h" + +#include "psi/psi/bucket.h" + +namespace psi::psi::kkrt { + +void CommonInit(const std::string& key_hash_digest, v2::PsiConfig* config, + RecoveryManager* recovery_manager, + bool* need_intersection_deduplication) { + if (config->protocol_config().kkrt_config().bucket_size() == 0) { + config->mutable_protocol_config()->mutable_kkrt_config()->set_bucket_size( + kDefaultBucketSize); + } + + if (recovery_manager) { + *need_intersection_deduplication = true; + recovery_manager->MarkInitEnd(*config, key_hash_digest); + } +} + +} // namespace psi::psi::kkrt diff --git a/psi/psi/kkrt/common.h b/psi/psi/kkrt/common.h new file mode 100644 index 00000000..f0f7722b --- /dev/null +++ b/psi/psi/kkrt/common.h @@ -0,0 +1,31 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include <cstdint> + +#include "psi/psi/recovery.h" + +#include "psi/proto/psi.pb.h" + +namespace psi::psi::kkrt { + +// For KkrtOt +constexpr size_t kDefaultNumOt = 512; + +void CommonInit(const std::string& key_hash_digest, v2::PsiConfig* config, + RecoveryManager* recovery_manager, + bool* need_intersection_deduplication); + +} // namespace psi::psi::kkrt diff --git a/psi/psi/kkrt/receiver.cc b/psi/psi/kkrt/receiver.cc new file mode 100644 index 00000000..e91ade7e --- /dev/null +++ b/psi/psi/kkrt/receiver.cc @@ -0,0 +1,180 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/kkrt/receiver.h" + +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/utils/parallel.h" + +#include "psi/psi/bucket.h" +#include "psi/psi/bucket_psi.h" +#include "psi/psi/core/kkrt_psi.h" +#include "psi/psi/kkrt/common.h" +#include "psi/psi/prelude.h" +#include "psi/psi/trace_categories.h" +#include "psi/psi/utils/serialize.h" +#include "psi/psi/utils/utils.h" + +namespace psi::psi::kkrt { + +KkrtPSIReceiver::KkrtPSIReceiver(const v2::PsiConfig& config, + std::shared_ptr<yacl::link::Context> lctx) + : AbstractPSIReceiver(config, std::move(lctx)) {} + +void KkrtPSIReceiver::Init() { + TRACE_EVENT("init", "KkrtPSIReceiver::Init"); + SPDLOG_INFO("[KkrtPSIReceiver::Init] start"); + + AbstractPSIReceiver::Init(); + + CommonInit(key_hash_digest_, &config_, recovery_manager_.get(), + &need_intersection_deduplication_); + SPDLOG_INFO("[KkrtPSIReceiver::Init] end"); +} + +void KkrtPSIReceiver::PreProcess() { + TRACE_EVENT("pre-process", "KkrtPSIReceiver::PreProcess"); + SPDLOG_INFO("[KkrtPSIReceiver::PreProcess] start"); + + if (digest_equal_) { + return; + } + + bucket_count_ = + NegotiateBucketNum(lctx_, report_.original_count(), + config_.protocol_config().kkrt_config().bucket_size(), + config_.protocol_config().protocol()); + + if (bucket_count_ > 0) { + std::vector<std::string> keys(config_.keys().begin(), config_.keys().end()); + + auto gen_input_bucket_f = std::async([&] { + if (recovery_manager_) { + input_bucket_store_ = CreateCacheFromCsv( + config_.input_config().path(), keys, + recovery_manager_->input_bucket_store_path(), bucket_count_); + } else { + input_bucket_store_ = CreateCacheFromCsv( + config_.input_config().path(), keys, + std::filesystem::path(config_.input_config().path()).parent_path(), + bucket_count_); + } + }); + + SyncWait(lctx_, &gen_input_bucket_f); + } + + if (bucket_count_ > 0) { + ot_send_ = std::make_unique<yacl::crypto::OtSendStore>( + GetKkrtOtReceiverOptions(lctx_, kDefaultNumOt)); + } + + if (recovery_manager_) { + recovery_manager_->MarkPreProcessEnd(); + } + + SPDLOG_INFO("[KkrtPSIReceiver::PreProcess] end"); +} + +void KkrtPSIReceiver::Online() { + TRACE_EVENT("online", "KkrtPSIReceiver::Online"); + SPDLOG_INFO("[KkrtPSIReceiver::Online] start"); + + if (digest_equal_) { + return; + } + + if (bucket_count_ == 0) { + return; + } + + bool online_stage_finished = + recovery_manager_ ? recovery_manager_->MarkOnlineStart(lctx_) : false; + + if (online_stage_finished) { + return; + } + + size_t bucket_idx = + recovery_manager_ + ? std::min(recovery_manager_->parsed_bucket_count_from_peer(), + recovery_manager_->checkpoint().parsed_bucket_count()) + : 0; + + for (; bucket_idx < input_bucket_store_->BucketNum(); bucket_idx++) { + auto bucket_items_list = + PrepareBucketData(config_.protocol_config().protocol(), bucket_idx, + lctx_, input_bucket_store_.get()); + + if (!bucket_items_list.has_value()) { + continue; + } + + auto run_f = std::async([&] { + std::vector<HashBucketCache::BucketItem> res; + + std::vector<uint128_t> items_hash(bucket_items_list->size()); + yacl::parallel_for(0, bucket_items_list->size(), 1, + [&](int64_t begin, int64_t end) { + for (int64_t i = begin; i < end; ++i) { + items_hash[i] = yacl::crypto::Blake3_128( + bucket_items_list->at(i).base64_data); + } + }); + + std::vector<size_t> kkrt_psi_result = + KkrtPsiRecv(lctx_, *ot_send_, items_hash); + res.reserve(kkrt_psi_result.size()); + + for (auto index : kkrt_psi_result) { + res.emplace_back(bucket_items_list->at(index)); + } + return res; + }); + + std::vector<HashBucketCache::BucketItem> result_list = + SyncWait(lctx_, &run_f); + + auto write_bucket_res_f = std::async([&] { + HandleBucketResultByReceiver(config_.protocol_config().broadcast_result(), + lctx_, result_list, + intersection_indices_writer_.get()); + }); + + SyncWait(lctx_, &write_bucket_res_f); + + if (recovery_manager_) { + recovery_manager_->UpdateParsedBucketCount(bucket_idx + 1); + } + } + + SPDLOG_INFO("[KkrtPSIReceiver::Online] end"); +} + +void KkrtPSIReceiver::PostProcess() { + TRACE_EVENT("post-process", "KkrtPSIReceiver::PostProcess"); + SPDLOG_INFO("[KkrtPSIReceiver::PostProcess] start"); + + if (digest_equal_) { + return; + } + + if (recovery_manager_) { + recovery_manager_->MarkPostProcessEnd(); + } + + SPDLOG_INFO("[KkrtPSIReceiver::PostProcess] end"); +} + +} // namespace psi::psi::kkrt diff --git a/psi/psi/kkrt/receiver.h b/psi/psi/kkrt/receiver.h new file mode 100644 index 00000000..9961c692 --- /dev/null +++ b/psi/psi/kkrt/receiver.h @@ -0,0 +1,48 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "yacl/crypto/primitives/ot/ot_store.h" + +#include "psi/psi/interface.h" +#include "psi/psi/utils/hash_bucket_cache.h" + +#include "psi/proto/psi.pb.h" + +namespace psi::psi::kkrt { + +class KkrtPSIReceiver final : public AbstractPSIReceiver { + public: + explicit KkrtPSIReceiver(const v2::PsiConfig &config, + std::shared_ptr<yacl::link::Context> lctx = nullptr); + + ~KkrtPSIReceiver() override = default; + + private: + void Init() override; + + void PreProcess() override; + + void Online() override; + + void PostProcess() override; + + uint64_t bucket_count_ = 0; + + std::unique_ptr<HashBucketCache> input_bucket_store_; + + std::unique_ptr<yacl::crypto::OtSendStore> ot_send_; +}; + +} // namespace psi::psi::kkrt diff --git a/psi/psi/kkrt/sender.cc b/psi/psi/kkrt/sender.cc new file mode 100644 index 00000000..5af041b7 --- /dev/null +++ b/psi/psi/kkrt/sender.cc @@ -0,0 +1,174 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/kkrt/sender.h" + +#include <cstddef> +#include <memory> +#include <utility> + +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/utils/parallel.h" + +#include "psi/psi/bucket.h" +#include "psi/psi/bucket_psi.h" +#include "psi/psi/core/kkrt_psi.h" +#include "psi/psi/kkrt/common.h" +#include "psi/psi/prelude.h" +#include "psi/psi/trace_categories.h" +#include "psi/psi/utils/serialize.h" +#include "psi/psi/utils/utils.h" + +namespace psi::psi::kkrt { + +KkrtPSISender::KkrtPSISender(const v2::PsiConfig& config, + std::shared_ptr<yacl::link::Context> lctx) + : AbstractPSISender(config, std::move(lctx)) {} + +void KkrtPSISender::Init() { + TRACE_EVENT("init", "KkrtPSISender::Init"); + SPDLOG_INFO("[KkrtPSISender::Init] start"); + + AbstractPSISender::Init(); + + CommonInit(key_hash_digest_, &config_, recovery_manager_.get(), + &need_intersection_deduplication_); + SPDLOG_INFO("[KkrtPSISender::Init] end"); +} + +void KkrtPSISender::PreProcess() { + TRACE_EVENT("pre-process", "KkrtPSISender::PreProcess"); + SPDLOG_INFO("[KkrtPSISender::PreProcess] start"); + + if (digest_equal_) { + return; + } + + bucket_count_ = + NegotiateBucketNum(lctx_, report_.original_count(), + config_.protocol_config().kkrt_config().bucket_size(), + config_.protocol_config().protocol()); + + if (bucket_count_ > 0) { + std::vector<std::string> keys(config_.keys().begin(), config_.keys().end()); + + auto gen_input_bucket_f = std::async([&] { + if (recovery_manager_) { + input_bucket_store_ = CreateCacheFromCsv( + config_.input_config().path(), keys, + recovery_manager_->input_bucket_store_path(), bucket_count_); + } else { + input_bucket_store_ = CreateCacheFromCsv( + config_.input_config().path(), keys, + std::filesystem::path(config_.input_config().path()).parent_path(), + bucket_count_); + } + }); + + SyncWait(lctx_, &gen_input_bucket_f); + } + + if (bucket_count_ > 0) { + ot_recv_ = std::make_unique<yacl::crypto::OtRecvStore>( + GetKkrtOtSenderOptions(lctx_, kDefaultNumOt)); + } + + if (recovery_manager_) { + recovery_manager_->MarkPreProcessEnd(); + } + + SPDLOG_INFO("[KkrtPSISender::PreProcess] end"); +} + +void KkrtPSISender::Online() { + TRACE_EVENT("online", "KkrtPSISender::Online"); + SPDLOG_INFO("[KkrtPSISender::Online] start"); + + if (digest_equal_) { + return; + } + + if (bucket_count_ == 0) { + return; + } + + bool online_stage_finished = + recovery_manager_ ? recovery_manager_->MarkOnlineStart(lctx_) : false; + + if (online_stage_finished) { + return; + } + + size_t bucket_idx = + recovery_manager_ + ? std::min(recovery_manager_->parsed_bucket_count_from_peer(), + recovery_manager_->checkpoint().parsed_bucket_count()) + : 0; + + for (; bucket_idx < input_bucket_store_->BucketNum(); bucket_idx++) { + auto bucket_items_list = + PrepareBucketData(config_.protocol_config().protocol(), bucket_idx, + lctx_, input_bucket_store_.get()); + + if (!bucket_items_list.has_value()) { + continue; + } + + auto run_f = std::async([&] { + std::vector<uint128_t> items_hash(bucket_items_list->size()); + yacl::parallel_for(0, bucket_items_list->size(), 1, + [&](int64_t begin, int64_t end) { + for (int64_t i = begin; i < end; ++i) { + items_hash[i] = yacl::crypto::Blake3_128( + bucket_items_list->at(i).base64_data); + } + }); + + KkrtPsiSend(lctx_, *ot_recv_, items_hash); + }); + + SyncWait(lctx_, &run_f); + + auto write_bucket_res_f = std::async([&] { + HandleBucketResultBySender(config_.protocol_config().broadcast_result(), + lctx_, *bucket_items_list, + intersection_indices_writer_.get()); + }); + + SyncWait(lctx_, &write_bucket_res_f); + + if (recovery_manager_) { + recovery_manager_->UpdateParsedBucketCount(bucket_idx + 1); + } + } + + SPDLOG_INFO("[KkrtPSISender::Online] end"); +} + +void KkrtPSISender::PostProcess() { + TRACE_EVENT("post-process", "KkrtPSISender::PostProcess"); + SPDLOG_INFO("[KkrtPSISender::PostProcess] start"); + + if (digest_equal_) { + return; + } + + if (recovery_manager_) { + recovery_manager_->MarkPostProcessEnd(); + } + + SPDLOG_INFO("[KkrtPSISender::PostProcess] end"); +} + +} // namespace psi::psi::kkrt diff --git a/psi/psi/kkrt/sender.h b/psi/psi/kkrt/sender.h new file mode 100644 index 00000000..4a414804 --- /dev/null +++ b/psi/psi/kkrt/sender.h @@ -0,0 +1,48 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "yacl/crypto/primitives/ot/ot_store.h" + +#include "psi/psi/interface.h" +#include "psi/psi/utils/hash_bucket_cache.h" + +#include "psi/proto/psi.pb.h" + +namespace psi::psi::kkrt { + +class KkrtPSISender final : public AbstractPSISender { + public: + explicit KkrtPSISender(const v2::PsiConfig &config, + std::shared_ptr<yacl::link::Context> lctx = nullptr); + + ~KkrtPSISender() override = default; + + private: + void Init() override; + + void PreProcess() override; + + void Online() override; + + void PostProcess() override; + + uint64_t bucket_count_ = 0; + + std::unique_ptr<HashBucketCache> input_bucket_store_; + + std::unique_ptr<yacl::crypto::OtRecvStore> ot_recv_; +}; + +} // namespace psi::psi::kkrt diff --git a/psi/psi/kuscia_adapter.cc b/psi/psi/kuscia_adapter.cc new file mode 100644 index 00000000..f1286565 --- /dev/null +++ b/psi/psi/kuscia_adapter.cc @@ -0,0 +1,103 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/kuscia_adapter.h" + +#include <fmt/format.h> + +#include <cstddef> +#include <cstdint> +#include <fstream> +#include <map> + +#include "google/protobuf/util/json_util.h" +#include "rapidjson/document.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/writer.h" +#include "yacl/base/exception.h" + +#include "psi/proto/kuscia.pb.h" +#include "psi/proto/psi.pb.h" + +namespace psi::psi { + +KusciaTask FromKusciaConfig(const std::string& json_str) { + rapidjson::Document d; + d.Parse(json_str.c_str()); + + KusciaTask kuscia_task; + + kuscia_task.task_id = d["task_id"].GetString(); + + std::string cluster_def_str = {d["task_cluster_def"].GetString(), + d["task_cluster_def"].GetStringLength()}; + + kuscia::ClusterDefine task_cluster_def; + YACL_ENFORCE(google::protobuf::util::JsonStringToMessage(cluster_def_str, + &task_cluster_def) + .ok()); + + std::string self_party = + task_cluster_def.parties(task_cluster_def.self_party_idx()).name(); + + kuscia::TaskInputConfig task_input_config; + + std::string task_input_config_str = { + d["task_input_config"].GetString(), + d["task_input_config"].GetStringLength()}; + + YACL_ENFORCE(google::protobuf::util::JsonStringToMessage( + task_input_config_str, &task_input_config) + .ok()); + + kuscia_task.psi_config = task_input_config.sf_psi_config_map().at(self_party); + + kuscia::AllocatedPorts allocated_ports; + std::string allocated_ports_str = {d["allocated_ports"].GetString(), + d["allocated_ports"].GetStringLength()}; + YACL_ENFORCE(google::protobuf::util::JsonStringToMessage(allocated_ports_str, + &allocated_ports) + .ok()); + + kuscia_task.psi_config.mutable_link_config()->mutable_parties()->Clear(); + + size_t port = allocated_ports.ports(0).port(); + std::map<std::string, std::string> id_host_map; + + for (int i = 0; i < task_cluster_def.parties().size(); i++) { + const auto& party = task_cluster_def.parties(i); + + id_host_map[party.name()] = + i == task_cluster_def.self_party_idx() + ? fmt::format("0.0.0.0:{}", port) + : fmt::format("http://{}", party.services(0).endpoints(0)); + } + + for (auto const& [id, host] : id_host_map) { + auto* new_party = + kuscia_task.psi_config.mutable_link_config()->add_parties(); + new_party->set_host(host); + new_party->set_id(id); + } + + kuscia_task.psi_config.mutable_link_config() + ->set_brpc_channel_connection_type("pooled"); + kuscia_task.psi_config.mutable_link_config()->set_brpc_channel_protocol( + "http"); + kuscia_task.psi_config.set_self_link_party(self_party); + + return kuscia_task; +} + +} // namespace psi::psi \ No newline at end of file diff --git a/psi/psi/kuscia_adapter.h b/psi/psi/kuscia_adapter.h new file mode 100644 index 00000000..3c5d264c --- /dev/null +++ b/psi/psi/kuscia_adapter.h @@ -0,0 +1,27 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "psi/proto/psi.pb.h" + +namespace psi::psi { + +struct KusciaTask { + v2::PsiConfig psi_config; + std::string task_id; +}; + +KusciaTask FromKusciaConfig(const std::string& json_str); + +} // namespace psi::psi diff --git a/psi/psi/kuscia_adapter_test.cc b/psi/psi/kuscia_adapter_test.cc new file mode 100644 index 00000000..7d0212d2 --- /dev/null +++ b/psi/psi/kuscia_adapter_test.cc @@ -0,0 +1,95 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/kuscia_adapter.h" + +#include <gtest/gtest.h> + +#include "google/protobuf/util/json_util.h" +#include "google/protobuf/util/message_differencer.h" +#include "gtest/gtest.h" + +#include "psi/proto/psi.pb.h" + +namespace psi::psi { + +TEST(KusicaAdapterTest, works) { + constexpr auto kuscia_json = R"json( +{ + "task_id": "task_id_1234", + "task_input_config": "{\n \"sf_psi_config_map\": {\n \"alice\": {\n \"protocol_config\": {\n \"protocol\": \"PROTOCOL_ECDH\",\n \"ecdh_config\": {\n \"curve\": \"CURVE_25519\"\n },\n \"role\": \"ROLE_RECEIVER\",\n \"broadcast_result\": true\n },\n \"input_config\": {\n \"type\": \"IO_TYPE_FILE_CSV\",\n \"path\": \"/tmp/receiver_input.csv\"\n },\n \"output_config\": {\n \"type\": \"IO_TYPE_FILE_CSV\",\n \"path\": \"/tmp/receiver_output.csv\"\n },\n \"keys\": [\n \"id0\",\n \"id1\"\n ],\n \"debug_options\": {\n \"trace_path\": \"/tmp/receiver.trace\"\n },\n \"check_duplicates\": false,\n \"sort_output\": false,\n \"recovery_config\": {\n \"enabled\": true,\n \"folder\": \"/tmp/receiver_cache\"\n },\n \"link_config\": {\n \"recv_timeout_ms\": 180000,\n \"http_timeout_ms\": 120000\n }\n },\n \"bob\": {\n \"protocol_config\": {\n \"protocol\": \"PROTOCOL_ECDH\",\n \"ecdh_config\": {\n \"curve\": \"CURVE_25519\"\n },\n \"role\": \"ROLE_SENDER\",\n \"broadcast_result\": true\n },\n \"input_config\": {\n \"type\": \"IO_TYPE_FILE_CSV\",\n \"path\": \"/tmp/sender_input.csv\"\n },\n \"output_config\": {\n \"type\": \"IO_TYPE_FILE_CSV\",\n \"path\": \"/tmp/sender_output.csv\"\n },\n \"keys\": [\n \"id2\",\n \"id3\"\n ],\n \"debug_options\": {\n \"trace_path\": \"/tmp/sender.trace\"\n },\n \"check_duplicates\": false,\n \"sort_output\": false,\n \"recovery_config\": {\n \"enabled\": true,\n \"folder\": \"/tmp/sender_cache\"\n },\n \"link_config\": {\n \"recv_timeout_ms\": 180000,\n \"http_timeout_ms\": 120000\n }\n }\n }\n}", + "task_cluster_def": "{\n \"parties\": [\n {\n \"name\": \"alice\",\n \"services\": [\n {\n \"portName\": \"psi\",\n \"endpoints\": [\n \"1.2.3.4:1234\"\n ]\n }\n ]\n },\n {\n \"name\": \"bob\",\n \"services\": [\n {\n \"portName\": \"psi\",\n \"endpoints\": [\n \"1.2.3.5:1235\"\n ]\n }\n ]\n }\n ],\n \"self_party_idx\": 0\n }", + "allocated_ports": "{\n \"ports\": [\n {\n \"name\": \"psi\",\n \"port\": 1234\n }\n ]\n }" +})json"; + + KusciaTask task = FromKusciaConfig(kuscia_json); + + constexpr auto expected_psi_json = R"json( + { + "protocolConfig": { + "protocol": "PROTOCOL_ECDH", + "ecdhConfig": { + "curve": "CURVE_25519" + }, + "role": "ROLE_RECEIVER", + "broadcastResult": true + }, + "inputConfig": { + "type": "IO_TYPE_FILE_CSV", + "path": "/tmp/receiver_input.csv" + }, + "outputConfig": { + "type": "IO_TYPE_FILE_CSV", + "path": "/tmp/receiver_output.csv" + }, + "linkConfig": { + "parties": [ + { + "id": "alice", + "host": "0.0.0.0:1234" + }, + { + "id": "bob", + "host": "http://1.2.3.5:1235" + } + ], + "brpc_channel_protocol": "http", + "brpc_channel_connection_type": "pooled", + "recv_timeout_ms": 180000, + "http_timeout_ms": 120000 + }, + "selfLinkParty": "alice", + "keys": [ + "id0", + "id1" + ], + "debugOptions": { + "tracePath": "/tmp/receiver.trace" + }, + "recoveryConfig": { + "enabled": true, + "folder": "/tmp/receiver_cache" + } + })json"; + + v2::PsiConfig expected_psi_config; + EXPECT_TRUE(google::protobuf::util::JsonStringToMessage(expected_psi_json, + &expected_psi_config) + .ok()); + + EXPECT_TRUE(::google::protobuf::util::MessageDifferencer::Equals( + expected_psi_config, task.psi_config)); +} + +} // namespace psi::psi \ No newline at end of file diff --git a/psi/psi/main.cc b/psi/psi/main.cc new file mode 100644 index 00000000..3f5e7cf3 --- /dev/null +++ b/psi/psi/main.cc @@ -0,0 +1,174 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// perfetto usage is adapted from +// https://github.com/google/perfetto/blob/master/examples/sdk/example.cc + +#include <spdlog/spdlog.h> + +#include <cctype> +#include <fstream> +#include <iostream> + +#include "boost/algorithm/string.hpp" +#include "gflags/gflags.h" +#include "google/protobuf/util/json_util.h" +#include "perfetto.h" +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" + +#include "psi/psi/factory.h" +#include "psi/psi/kuscia_adapter.h" +#include "psi/psi/trace_categories.h" +#include "psi/version.h" + +#include "psi/proto/psi.pb.h" + +DEFINE_string(config, "", "file path of psi config in JSON format."); +DEFINE_string(kuscia, "", "file path of kuscia task config in JSON format."); + +void InitializePerfetto() { + perfetto::TracingInitArgs args; + + // TODO(junfeng): include system backend as well. + args.backends = perfetto::kInProcessBackend; + + perfetto::Tracing::Initialize(args); + perfetto::TrackEvent::Register(); +} + +std::unique_ptr<perfetto::TracingSession> StartTracing() { + perfetto::TraceConfig cfg; + cfg.add_buffers()->set_size_kb(1024); + auto* ds_cfg = cfg.add_data_sources()->mutable_config(); + ds_cfg->set_name("track_event"); + + auto tracing_session = perfetto::Tracing::NewTrace(); + tracing_session->Setup(cfg); + tracing_session->StartBlocking(); + return tracing_session; +} + +void StopTracing(std::unique_ptr<perfetto::TracingSession> tracing_session, + const std::string& path) { + perfetto::TrackEvent::Flush(); + + // Stop tracing and read the trace data. + tracing_session->StopBlocking(); + std::vector<char> trace_data(tracing_session->ReadTraceBlocking()); + + // Write the result into a file. + std::ofstream output; + output.open(path, std::ios::out | std::ios::binary); + output.write(&trace_data[0], std::streamsize(trace_data.size())); + output.close(); + SPDLOG_INFO("Trace has been written to {}.", path); +} + +void SetLogLevel(const std::string& level) { + std::string normalized_level = boost::algorithm::to_lower_copy(level); + + if (normalized_level == "trace") { + spdlog::set_level(spdlog::level::trace); + } else if (normalized_level == "debug") { + spdlog::set_level(spdlog::level::debug); + } else if (normalized_level == "info" || normalized_level.empty()) { + spdlog::set_level(spdlog::level::info); + } else if (normalized_level == "warn") { + spdlog::set_level(spdlog::level::warn); + } else if (normalized_level == "err") { + spdlog::set_level(spdlog::level::err); + } else if (normalized_level == "critical") { + spdlog::set_level(spdlog::level::critical); + } else if (normalized_level == "off") { + spdlog::set_level(spdlog::level::off); + } else { + YACL_THROW("unsupported logging level: {}", level); + } +} + +std::string GenerateVersion() { + return fmt::format("v{}.{}.{}{}", PSI_VERSION_MAJOR, PSI_VERSION_MINOR, + PSI_VERSION_PATCH, PSI_DEV_IDENTIFIER); +} + +int main(int argc, char* argv[]) { + gflags::SetVersionString(GenerateVersion()); + gflags::AllowCommandLineReparsing(); + gflags::ParseCommandLineFlags(&argc, &argv, true); + + psi::psi::v2::PsiConfig psi_config; + + if (!FLAGS_kuscia.empty()) { + std::fstream json_config_file(FLAGS_kuscia, std::ios::in); + psi_config = + psi::psi::FromKusciaConfig( + std::string((std::istreambuf_iterator<char>(json_config_file)), + std::istreambuf_iterator<char>())) + .psi_config; + } else { + google::protobuf::util::JsonParseOptions json_parse_options; + json_parse_options.ignore_unknown_fields = false; // optional + std::fstream json_config_file(FLAGS_config, std::ios::in); + + auto status = google::protobuf::util::JsonStringToMessage( + std::string((std::istreambuf_iterator<char>(json_config_file)), + std::istreambuf_iterator<char>()), + &psi_config, json_parse_options); + + YACL_ENFORCE(status.ok(), "file {} couldn't be parsed as PsiConfig: {}.", + FLAGS_config, status.ToString()); + } + + SetLogLevel(psi_config.debug_options().logging_level()); + + SPDLOG_INFO("SecretFlow PSI Library {} Copyright 2023 Ant Group Co., Ltd.", + GenerateVersion()); + + InitializePerfetto(); + auto tracing_session = StartTracing(); + // Give a custom name for the traced process. + perfetto::ProcessTrack process_track = perfetto::ProcessTrack::Current(); + perfetto::protos::gen::TrackDescriptor desc = process_track.Serialize(); + desc.mutable_process()->set_process_name("psi"); + perfetto::TrackEvent::SetTrackDescriptor(process_track, desc); + + google::protobuf::util::JsonPrintOptions json_print_options; + json_print_options.preserve_proto_field_names = true; + + std::string config_json; + YACL_ENFORCE(google::protobuf::util::MessageToJsonString( + psi_config, &config_json, json_print_options) + .ok()); + SPDLOG_INFO("PSI config: {}", config_json); + + std::unique_ptr<psi::psi::AbstractPSIParty> psi_party = + psi::psi::createPSIParty(psi_config); + psi::psi::v2::PsiReport report = psi_party->Run(); + + StopTracing(std::move(tracing_session), + psi_config.debug_options().trace_path().empty() + ? fmt::format("/tmp/psi_{}.trace", + std::to_string(absl::ToUnixNanos(absl::Now()))) + : psi_config.debug_options().trace_path()); + + std::string report_json; + YACL_ENFORCE(google::protobuf::util::MessageToJsonString(report, &report_json, + json_print_options) + .ok()); + SPDLOG_INFO("PSI report: {}", report_json); + SPDLOG_INFO("Thank you for trusting SecretFlow PSI Library."); + + return 0; +} diff --git a/psi/psi/memory_psi.cc b/psi/psi/memory_psi.cc new file mode 100644 index 00000000..6888e702 --- /dev/null +++ b/psi/psi/memory_psi.cc @@ -0,0 +1,105 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/memory_psi.h" + +#include "spdlog/spdlog.h" + +#include "psi/psi/core/ecdh_psi.h" +#include "psi/psi/operator/factory.h" +#include "psi/psi/prelude.h" +#include "psi/psi/utils/utils.h" + +namespace psi::psi { + +MemoryPsi::MemoryPsi(MemoryPsiConfig config, + std::shared_ptr<yacl::link::Context> lctx) + : config_(std::move(config)), lctx_(std::move(lctx)) { + CheckOptions(); +} + +void MemoryPsi::CheckOptions() const { + // options sanity check. + YACL_ENFORCE(config_.psi_type() != PsiType::INVALID_PSI_TYPE, + "unsupported psi proto:{}", config_.psi_type()); + + YACL_ENFORCE( + static_cast<size_t>(config_.receiver_rank()) < lctx_->WorldSize(), + "invalid receiver_rank:{}, world_size:{}", config_.receiver_rank(), + lctx_->WorldSize()); + + // check world size + if (config_.psi_type() == PsiType::ECDH_PSI_2PC || + config_.psi_type() == PsiType::KKRT_PSI_2PC || + config_.psi_type() == PsiType::BC22_PSI_2PC) { + YACL_ENFORCE(lctx_->WorldSize() == 2, + "psi_type:{}, only two parties supported, got " + "{}", + config_.psi_type(), lctx_->WorldSize()); + } + if (config_.psi_type() == PsiType::ECDH_PSI_3PC) { + if (lctx_->WorldSize() != 3) { + YACL_ENFORCE(lctx_->WorldSize() == 3, + "psi_type:{}, only three parties supported, got " + "{}", + config_.psi_type(), lctx_->WorldSize()); + } + } +} + +std::vector<std::string> MemoryPsi::Run( + const std::vector<std::string>& inputs) { + std::vector<std::string> res; + size_t min_inputs_size = inputs.size(); + std::vector<size_t> inputs_size_list = + AllGatherItemsSize(lctx_, inputs.size()); + for (size_t idx = 0; idx < inputs_size_list.size(); idx++) { + SPDLOG_INFO("psi protocol={}, rank={}, inputs_size={}", config_.psi_type(), + idx, inputs_size_list[idx]); + min_inputs_size = std::min(min_inputs_size, inputs_size_list[idx]); + } + if (min_inputs_size == 0) { + SPDLOG_INFO( + "psi protocol={}, min_inputs_size=0, " + "no need do intersection", + config_.psi_type()); + return res; + } + + if (config_.psi_type() == PsiType::ECDH_PSI_2PC) { + auto run_f = std::async([&] { return EcdhPsi(inputs); }); + res = SyncWait(lctx_, &run_f); + } else { + res = OperatorFactory::GetInstance() + ->Create(config_, lctx_) + ->Run(inputs, config_.broadcast_result()); + } + + return res; +} + +std::vector<std::string> MemoryPsi::EcdhPsi( + const std::vector<std::string>& inputs) { + size_t target_rank = config_.receiver_rank(); + if (config_.broadcast_result()) { + target_rank = yacl::link::kAllRank; + } + + if (config_.curve_type() != CurveType::CURVE_INVALID_TYPE) { + return RunEcdhPsi(lctx_, inputs, target_rank, config_.curve_type()); + } + return RunEcdhPsi(lctx_, inputs, target_rank); +} + +} // namespace psi::psi diff --git a/psi/psi/memory_psi.h b/psi/psi/memory_psi.h new file mode 100644 index 00000000..8d2a008a --- /dev/null +++ b/psi/psi/memory_psi.h @@ -0,0 +1,47 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <memory> +#include <string> +#include <vector> + +#include "yacl/base/exception.h" +#include "yacl/link/link.h" + +#include "psi/psi/psi.pb.h" + +namespace psi::psi { + +class MemoryPsi { + public: + explicit MemoryPsi(MemoryPsiConfig config, + std::shared_ptr<yacl::link::Context> lctx); + ~MemoryPsi() = default; + + std::vector<std::string> Run(const std::vector<std::string>& inputs); + + private: + void CheckOptions() const; + + std::vector<std::string> EcdhPsi(const std::vector<std::string>& inputs); + + private: + MemoryPsiConfig config_; + + std::shared_ptr<yacl::link::Context> lctx_; +}; + +} // namespace psi::psi diff --git a/psi/psi/memory_psi_test.cc b/psi/psi/memory_psi_test.cc new file mode 100644 index 00000000..e5166e0d --- /dev/null +++ b/psi/psi/memory_psi_test.cc @@ -0,0 +1,209 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/memory_psi.h" + +#include <iostream> +#include <random> +#include <set> +#include <vector> + +#include "absl/strings/str_split.h" +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/link/test_util.h" + +#include "psi/psi/utils/test_utils.h" + +namespace psi::psi { + +namespace { +struct MemoryTaskTestParams { + std::vector<size_t> item_size; + size_t intersection_size; + PsiType psi_protocol; +}; + +std::vector<std::vector<std::string>> CreateMemoryTaskItems( + const MemoryTaskTestParams& params) { + std::vector<std::vector<std::string>> ret(params.item_size.size() + 1); + ret[params.item_size.size()] = + test::CreateRangeItems(1, params.intersection_size); + + for (size_t idx = 0; idx < params.item_size.size(); ++idx) { + ret[idx] = + test::CreateRangeItems((idx + 1) * 1000000, params.item_size[idx]); + } + + for (size_t idx = 0; idx < params.item_size.size(); ++idx) { + std::set<size_t> idx_set; + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(0, params.item_size[idx] - 1); + + while (idx_set.size() < params.intersection_size) { + idx_set.insert(dis(gen)); + } + size_t j = 0; + for (const auto& iter : idx_set) { + ret[idx][iter] = ret[params.item_size.size()][j++]; + } + } + return ret; +} + +} // namespace + +class MemoryTaskPsiTest : public testing::TestWithParam<MemoryTaskTestParams> { +}; + +TEST_P(MemoryTaskPsiTest, Works) { + std::vector<std::vector<std::string>> items; + + auto params = GetParam(); + items = CreateMemoryTaskItems(params); + + auto lctxs = yacl::link::test::SetupWorld(params.item_size.size()); + + auto proc = [&](int idx) -> std::vector<std::string> { + MemoryPsiConfig config; + config.set_psi_type(params.psi_protocol); + config.set_broadcast_result(true); + + MemoryPsi ctx(config, lctxs[idx]); + + return ctx.Run(items[idx]); + }; + + size_t world_size = lctxs.size(); + std::vector<std::future<std::vector<std::string>>> f_links(world_size); + for (size_t i = 0; i < world_size; i++) { + f_links[i] = std::async(proc, i); + } + + std::vector<std::string> intersection = + test::GetIntersection(items[params.item_size.size()], items[0]); + std::sort(intersection.begin(), intersection.end()); + + std::vector<std::vector<std::string>> results(world_size); + for (size_t i = 0; i < world_size; i++) { + results[i] = f_links[i].get(); + + EXPECT_EQ(results[i].size(), intersection.size()); + } +} + +TEST_P(MemoryTaskPsiTest, BroadcastFalse) { + std::vector<std::vector<std::string>> items; + + auto params = GetParam(); + items = CreateMemoryTaskItems(params); + size_t receiver_rank = 0; + + auto lctxs = yacl::link::test::SetupWorld(params.item_size.size()); + + auto proc = [&](int idx) -> std::vector<std::string> { + MemoryPsiConfig config; + config.set_psi_type(params.psi_protocol); + config.set_receiver_rank(receiver_rank); + config.set_broadcast_result(false); + + MemoryPsi ctx(config, lctxs[idx]); + + return ctx.Run(items[idx]); + }; + + size_t world_size = lctxs.size(); + std::vector<std::future<std::vector<std::string>>> f_links(world_size); + for (size_t i = 0; i < world_size; i++) { + f_links[i] = std::async(proc, i); + } + + std::vector<std::string> intersection = + test::GetIntersection(items[params.item_size.size()], items[0]); + std::sort(intersection.begin(), intersection.end()); + + std::vector<std::vector<std::string>> results(world_size); + for (size_t i = 0; i < world_size; i++) { + results[i] = f_links[i].get(); + + if (i == receiver_rank) { + EXPECT_EQ(results[i].size(), intersection.size()); + } else { + EXPECT_EQ(results[i].size(), 0); + } + } +} + +INSTANTIATE_TEST_SUITE_P( + Works_Instances, MemoryTaskPsiTest, + testing::Values( + MemoryTaskTestParams{{0, 3}, 0, PsiType::ECDH_PSI_2PC}, // + MemoryTaskTestParams{{3, 0}, 0, PsiType::KKRT_PSI_2PC}, // + MemoryTaskTestParams{{0, 0}, 0, PsiType::KKRT_PSI_2PC}, // + MemoryTaskTestParams{{3, 0}, 0, PsiType::BC22_PSI_2PC}, // + MemoryTaskTestParams{{0, 0}, 0, PsiType::BC22_PSI_2PC}, // + MemoryTaskTestParams{{4, 3, 0}, 0, PsiType::ECDH_PSI_3PC}, // + MemoryTaskTestParams{{4, 3, 0, 6}, 0, PsiType::ECDH_PSI_NPC}, // + + // + MemoryTaskTestParams{{20, 20}, 10, PsiType::KKRT_PSI_2PC}, // + MemoryTaskTestParams{{20, 17}, 10, PsiType::KKRT_PSI_2PC}, // + MemoryTaskTestParams{{17, 20}, 10, PsiType::KKRT_PSI_2PC}, // + MemoryTaskTestParams{{33, 45}, 20, PsiType::ECDH_PSI_2PC}, // + MemoryTaskTestParams{{100, 100}, 30, PsiType::BC22_PSI_2PC}, // + MemoryTaskTestParams{{200, 100}, 60, PsiType::BC22_PSI_2PC}, // + MemoryTaskTestParams{{100, 200}, 50, PsiType::BC22_PSI_2PC}, // + + MemoryTaskTestParams{{20, 17, 14}, 10, PsiType::ECDH_PSI_3PC}, // + MemoryTaskTestParams{{20, 17, 14, 30}, 10, PsiType::ECDH_PSI_NPC}, // + MemoryTaskTestParams{{20, 17, 14, 30, 35}, 11, PsiType::KKRT_PSI_NPC})); + +struct FailedTestParams { + size_t party_num; + size_t receiver_rank; + PsiType psi_protocol; +}; + +class MemoryTaskPsiTestFailedTest + : public testing::TestWithParam<FailedTestParams> {}; + +TEST_P(MemoryTaskPsiTestFailedTest, FailedWorks) { + auto params = GetParam(); + + auto lctxs = yacl::link::test::SetupWorld(params.party_num); + + MemoryPsiConfig config; + config.set_psi_type(params.psi_protocol); + config.set_receiver_rank(params.receiver_rank); + config.set_broadcast_result(true); + + ASSERT_ANY_THROW(MemoryPsi ctx(config, lctxs[0])); +} + +INSTANTIATE_TEST_SUITE_P(FailedWorks_Instances, MemoryTaskPsiTestFailedTest, + testing::Values( + // invalid link world size + FailedTestParams{3, 0, PsiType::KKRT_PSI_2PC}, + FailedTestParams{4, 0, PsiType::ECDH_PSI_2PC}, + FailedTestParams{1, 0, PsiType::BC22_PSI_2PC}, + FailedTestParams{2, 0, PsiType::ECDH_PSI_3PC}, + // invalid receiver_rank + FailedTestParams{3, 4, PsiType::ECDH_PSI_3PC}, + FailedTestParams{2, 5, PsiType::KKRT_PSI_2PC}, + // invalid psi_type + FailedTestParams{3, 4, + PsiType::INVALID_PSI_TYPE})); + +} // namespace psi::psi \ No newline at end of file diff --git a/psi/psi/operator/BUILD.bazel b/psi/psi/operator/BUILD.bazel new file mode 100644 index 00000000..616810b9 --- /dev/null +++ b/psi/psi/operator/BUILD.bazel @@ -0,0 +1,136 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//bazel:psi.bzl", "psi_cc_library", "psi_cc_test") + +package(default_visibility = ["//visibility:public"]) + +psi_cc_library( + name = "base_operator", + srcs = ["base_operator.cc"], + hdrs = ["base_operator.h"], + deps = [ + "//psi/psi:psi_cc_proto", + "//psi/psi/utils", + "@yacl//yacl/link", + ], +) + +psi_cc_library( + name = "ecdh_3party_psi", + srcs = ["ecdh_3party_psi.cc"], + hdrs = ["ecdh_3party_psi.h"], + deps = [ + ":base_operator", + ":factory", + "//psi/psi/core:ecdh_3pc_psi", + ], + alwayslink = True, +) + +psi_cc_library( + name = "kkrt_2party_psi", + srcs = ["kkrt_2party_psi.cc"], + hdrs = ["kkrt_2party_psi.h"], + deps = [ + ":base_operator", + ":factory", + "//psi/psi/core:kkrt_psi", + "@yacl//yacl/utils:parallel", + ], + alwayslink = True, +) + +psi_cc_library( + name = "nparty_psi", + srcs = ["nparty_psi.cc"], + hdrs = ["nparty_psi.h"], + deps = [ + ":base_operator", + ":factory", + ":kkrt_2party_psi", + "//psi/psi/core:ecdh_psi", + "@yacl//yacl/utils:parallel", + ], + alwayslink = True, +) + +psi_cc_test( + name = "nparty_psi_test", + srcs = ["nparty_psi_test.cc"], + deps = [ + ":nparty_psi", + "//psi/psi/utils:test_utils", + ], +) + +psi_cc_library( + name = "factory", + hdrs = ["factory.h"], + deps = [ + ":base_operator", + "//psi/psi:psi_cc_proto", + "@yacl//yacl/base:exception", + "@yacl//yacl/link", + ], +) + +psi_cc_library( + name = "bc22_2party_psi", + srcs = ["bc22_2party_psi.cc"], + hdrs = ["bc22_2party_psi.h"], + deps = [ + ":base_operator", + ":factory", + "//psi/psi/core/bc22_psi", + ], + alwayslink = True, +) + +psi_cc_library( + name = "dp_2party_psi", + srcs = ["dp_2party_psi.cc"], + hdrs = ["dp_2party_psi.h"], + deps = [ + ":base_operator", + ":factory", + "//psi/psi/core/dp_psi", + ], + alwayslink = True, +) + +psi_cc_library( + name = "rr22_2party_psi", + srcs = ["rr22_2party_psi.cc"], + hdrs = ["rr22_2party_psi.h"], + deps = [ + ":base_operator", + ":factory", + "//psi/psi/core/vole_psi:rr22_psi", + "@yacl//yacl/utils:parallel", + ], + alwayslink = True, +) + +psi_cc_library( + name = "operator", + deps = [ + ":bc22_2party_psi", + ":dp_2party_psi", + ":ecdh_3party_psi", + ":kkrt_2party_psi", + ":nparty_psi", + ":rr22_2party_psi", + ], +) diff --git a/psi/psi/operator/base_operator.cc b/psi/psi/operator/base_operator.cc new file mode 100644 index 00000000..f464cca9 --- /dev/null +++ b/psi/psi/operator/base_operator.cc @@ -0,0 +1,42 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/operator/base_operator.h" + +#include <utility> + +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" + +#include "psi/psi/utils/serialize.h" +#include "psi/psi/utils/utils.h" + +namespace psi::psi { + +PsiBaseOperator::PsiBaseOperator(std::shared_ptr<yacl::link::Context> link_ctx) + : link_ctx_(std::move(link_ctx)) {} + +std::vector<std::string> PsiBaseOperator::Run( + const std::vector<std::string>& inputs, bool broadcast_result) { + auto run_f = std::async([&] { return OnRun(inputs); }); + auto res = SyncWait(link_ctx_, &run_f); + + if (broadcast_result) { + BroadcastResult(link_ctx_, &res); + } + + return res; +} + +} // namespace psi::psi diff --git a/psi/psi/operator/base_operator.h b/psi/psi/operator/base_operator.h new file mode 100644 index 00000000..eae9d1eb --- /dev/null +++ b/psi/psi/operator/base_operator.h @@ -0,0 +1,43 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <string> +#include <vector> + +#include "yacl/link/link.h" + +#include "psi/psi/psi.pb.h" + +namespace psi::psi { + +class PsiBaseOperator { + public: + explicit PsiBaseOperator(std::shared_ptr<yacl::link::Context> link_ctx); + virtual ~PsiBaseOperator() = default; + + // after call OnRun, it decides whether to broadcast result or not based on + // param `broadcast_result` + std::vector<std::string> Run(const std::vector<std::string>& inputs, + bool broadcast_result); + + virtual std::vector<std::string> OnRun( + const std::vector<std::string>& inputs) = 0; + + protected: + std::shared_ptr<yacl::link::Context> link_ctx_; +}; + +} // namespace psi::psi diff --git a/psi/psi/operator/bc22_2party_psi.cc b/psi/psi/operator/bc22_2party_psi.cc new file mode 100644 index 00000000..a45f4fdd --- /dev/null +++ b/psi/psi/operator/bc22_2party_psi.cc @@ -0,0 +1,59 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "psi/psi/operator/bc22_2party_psi.h" + +#include <memory> + +#include "psi/psi/core/bc22_psi/bc22_psi.h" +#include "psi/psi/operator/factory.h" + +namespace psi::psi { + +Bc22PcgPsiOperator::Options Bc22PcgPsiOperator::ParseConfig( + const MemoryPsiConfig& config, + const std::shared_ptr<yacl::link::Context>& lctx) { + return {lctx, config.receiver_rank()}; +} + +Bc22PcgPsiOperator::Bc22PcgPsiOperator(const Options& options) + : PsiBaseOperator(options.lctx), options_(options) {} + +std::vector<std::string> Bc22PcgPsiOperator::OnRun( + const std::vector<std::string>& inputs) { + auto role = link_ctx_->Rank() == options_.receiver_rank + ? PsiRoleType::Receiver + : PsiRoleType::Sender; + Bc22PcgPsi pcg_psi(link_ctx_, role); + pcg_psi.RunPsi(inputs); + if (role == PsiRoleType::Receiver) { + return pcg_psi.GetIntersection(); + } else { + return {}; + } +} + +namespace { + +std::unique_ptr<PsiBaseOperator> CreateOperator( + const MemoryPsiConfig& config, + const std::shared_ptr<yacl::link::Context>& lctx) { + auto options = Bc22PcgPsiOperator::ParseConfig(config, lctx); + return std::make_unique<Bc22PcgPsiOperator>(options); +} + +REGISTER_OPERATOR(BC22_PSI_2PC, CreateOperator); + +} // namespace + +} // namespace psi::psi \ No newline at end of file diff --git a/psi/psi/operator/bc22_2party_psi.h b/psi/psi/operator/bc22_2party_psi.h new file mode 100644 index 00000000..15034686 --- /dev/null +++ b/psi/psi/operator/bc22_2party_psi.h @@ -0,0 +1,43 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "psi/psi/operator/base_operator.h" + +namespace psi::psi { + +class Bc22PcgPsiOperator : public PsiBaseOperator { + public: + struct Options { + std::shared_ptr<yacl::link::Context> lctx; + + size_t receiver_rank; + }; + + static Options ParseConfig(const MemoryPsiConfig& config, + const std::shared_ptr<yacl::link::Context>& lctx); + + public: + explicit Bc22PcgPsiOperator(const Options& options); + ~Bc22PcgPsiOperator() = default; + + std::vector<std::string> OnRun( + const std::vector<std::string>& inputs) override final; + + private: + Options options_; +}; + +} // namespace psi::psi \ No newline at end of file diff --git a/psi/psi/operator/dp_2party_psi.cc b/psi/psi/operator/dp_2party_psi.cc new file mode 100644 index 00000000..2c594718 --- /dev/null +++ b/psi/psi/operator/dp_2party_psi.cc @@ -0,0 +1,89 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/operator/dp_2party_psi.h" + +#include <future> +#include <memory> +#include <random> + +#include "spdlog/spdlog.h" +#include "yacl/link/context.h" +#include "yacl/link/link.h" +#include "yacl/utils/serialize.h" + +#include "psi/psi/cryptor/cryptor_selector.h" +#include "psi/psi/operator/factory.h" + +namespace psi::psi { + +DpPsiOperator::DpPsiOperator(const std::shared_ptr<yacl::link::Context>& lctx, + const DpPsiOptions& options, size_t receiver_rank, + CurveType curve_type) + : PsiBaseOperator(lctx), + dp_options_(options), + receiver_rank_(receiver_rank), + curve_type_(curve_type) {} + +std::vector<std::string> DpPsiOperator::OnRun( + const std::vector<std::string>& inputs) { + std::vector<std::string> res; + + size_t alice_sub_sample_size = 0; + size_t alice_up_sample_size = 0; + size_t bob_sub_sample_size = 0; + + if (receiver_rank_ == link_ctx_->Rank()) { + std::vector<size_t> dp_psi_result = RunDpEcdhPsiBob( + dp_options_, link_ctx_, inputs, &bob_sub_sample_size, curve_type_); + + for (auto index : dp_psi_result) { + res.emplace_back(inputs[index]); + } + } else { + RunDpEcdhPsiAlice(dp_options_, link_ctx_, inputs, &alice_sub_sample_size, + &alice_up_sample_size, curve_type_); + } + + return res; +} + +namespace { + +std::unique_ptr<PsiBaseOperator> CreateOperator( + const MemoryPsiConfig& config, + const std::shared_ptr<yacl::link::Context>& lctx) { + double bob_sub_sampling = 0.9; + double epsilon = 3; + if (config.has_dppsi_params()) { + bob_sub_sampling = config.dppsi_params().bob_sub_sampling(); + epsilon = config.dppsi_params().epsilon(); + } + + DpPsiOptions dp_options(bob_sub_sampling, epsilon); + + if (config.curve_type() != CurveType::CURVE_INVALID_TYPE) { + return std::make_unique<DpPsiOperator>( + lctx, dp_options, config.receiver_rank(), config.curve_type()); + } else { + return std::make_unique<DpPsiOperator>(lctx, dp_options, + config.receiver_rank()); + } +} + +REGISTER_OPERATOR(DP_PSI_2PC, CreateOperator); + +} // namespace + +} // namespace psi::psi diff --git a/psi/psi/operator/dp_2party_psi.h b/psi/psi/operator/dp_2party_psi.h new file mode 100644 index 00000000..f475784b --- /dev/null +++ b/psi/psi/operator/dp_2party_psi.h @@ -0,0 +1,40 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <string> +#include <vector> + +#include "psi/psi/core/dp_psi/dp_psi.h" +#include "psi/psi/operator/base_operator.h" + +namespace psi::psi { + +class DpPsiOperator : public PsiBaseOperator { + public: + DpPsiOperator(const std::shared_ptr<yacl::link::Context>& lctx, + const DpPsiOptions& options, size_t receiver_rank, + CurveType curve_type = CurveType::CURVE_25519); + + std::vector<std::string> OnRun( + const std::vector<std::string>& inputs) override final; + + private: + DpPsiOptions dp_options_; + size_t receiver_rank_; + CurveType curve_type_ = CurveType::CURVE_25519; +}; + +} // namespace psi::psi diff --git a/psi/psi/operator/ecdh_3party_psi.cc b/psi/psi/operator/ecdh_3party_psi.cc new file mode 100644 index 00000000..0251c5b3 --- /dev/null +++ b/psi/psi/operator/ecdh_3party_psi.cc @@ -0,0 +1,97 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/operator/ecdh_3party_psi.h" + +#include <future> +#include <memory> +#include <random> + +#include "fmt/format.h" +#include "openssl/crypto.h" +#include "openssl/rand.h" +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" +#include "yacl/link/context.h" +#include "yacl/link/link.h" +#include "yacl/utils/serialize.h" + +#include "psi/psi/cryptor/cryptor_selector.h" +#include "psi/psi/operator/factory.h" + +namespace { +constexpr uint32_t kLinkRecvTimeout = 30 * 60 * 1000; +} // namespace + +namespace psi::psi { + +Ecdh3PartyPsiOperator::Options Ecdh3PartyPsiOperator::ParseConfig( + const MemoryPsiConfig& config, + const std::shared_ptr<yacl::link::Context>& lctx) { + Ecdh3PartyPsiOperator::Options opts; + opts.link_ctx = lctx; + opts.master_rank = config.receiver_rank(); + if (config.curve_type() != CurveType::CURVE_INVALID_TYPE) { + opts.curve_type = config.curve_type(); + } + return opts; +} + +Ecdh3PartyPsiOperator::Ecdh3PartyPsiOperator(const Options& options) + : PsiBaseOperator(options.link_ctx), options_(options), handler_(nullptr) { + options_.link_ctx->SetRecvTimeout(kLinkRecvTimeout); + + ShuffleEcdh3PcPsi::Options opts; + opts.link_ctx = options_.link_ctx; + opts.master_rank = options_.master_rank; + opts.batch_size = options_.batch_size; + opts.dual_mask_size = options_.dual_mask_size; + opts.curve_type = options_.curve_type; + + handler_ = std::make_shared<ShuffleEcdh3PcPsi>(opts); +} + +std::vector<std::string> Ecdh3PartyPsiOperator::OnRun( + const std::vector<std::string>& inputs) { + std::vector<std::string> results; + std::vector<std::string> masked_master_items; + std::vector<std::string> partner_psi_items; + + auto mask_master = std::async( + [&] { return handler_->MaskMaster(inputs, &masked_master_items); }); + auto partner_psi = std::async( + [&] { return handler_->PartnersPsi(inputs, &partner_psi_items); }); + + mask_master.get(); + partner_psi.get(); + + handler_->FinalPsi(inputs, masked_master_items, partner_psi_items, &results); + + return results; +} + +namespace { + +std::unique_ptr<PsiBaseOperator> CreateOperator( + const MemoryPsiConfig& config, + const std::shared_ptr<yacl::link::Context>& lctx) { + auto options = Ecdh3PartyPsiOperator::ParseConfig(config, lctx); + return std::make_unique<Ecdh3PartyPsiOperator>(options); +} + +REGISTER_OPERATOR(ECDH_PSI_3PC, CreateOperator); + +} // namespace + +} // namespace psi::psi diff --git a/psi/psi/operator/ecdh_3party_psi.h b/psi/psi/operator/ecdh_3party_psi.h new file mode 100644 index 00000000..f137b7ba --- /dev/null +++ b/psi/psi/operator/ecdh_3party_psi.h @@ -0,0 +1,115 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <functional> +#include <memory> +#include <string> +#include <vector> + +#include "psi/psi/core/ecdh_3pc_psi.h" +#include "psi/psi/operator/base_operator.h" + +namespace psi::psi { + +// +// 3party ecdh psi algorithm. +// +// master get the final intersection +// calcuator role is master-link->NextRank() +// partner role is master-link->PrevRank() +// +// (calcuator) (partner) (master) +// alice bob candy +// partners psi ============================================= +// | | shuffle b items | +// x^a | x^a | | +// | --------> | x^a^b | +// | | | +// | y^b | y^b | +// | <-------- | | +// y^b^a | | | +// | | shuffle x^a^b | +// | x^a^b | | +// | <-------- | | +// calc intersection_ab | | +// | intersection_ab | +// | ---------------------------------> | {intersection_ab}^c +// mask master ============================================== +// | z^c | +// | <--------------------------------- | +// z^c^a | | | +// | z^c^a | | +// | --------> | | +// | | calc {z^c^a}^b | +// | | send z^c^a^b | +// | | ------------------> | +// calc result ============================================== +// | | | +// calc intersection_abc +class Ecdh3PartyPsiOperator : public PsiBaseOperator { + public: + struct Options { + // Provides the link for the rank world. + std::shared_ptr<yacl::link::Context> link_ctx; + + size_t master_rank; + + // batch_size + // batch compute dh mask + // batch send and read + size_t batch_size = kEcdhPsiBatchSize; + + size_t dual_mask_size = kFinalCompareBytes; + + // curve_type + CurveType curve_type = CurveType::CURVE_25519; + }; + + static Options ParseConfig(const MemoryPsiConfig& config, + const std::shared_ptr<yacl::link::Context>& lctx); + + public: + explicit Ecdh3PartyPsiOperator(const Options& options); + + public: + std::vector<std::string> OnRun( + const std::vector<std::string>& inputs) override final; + + private: + Options options_; + + std::shared_ptr<ShuffleEcdh3PcPsi> handler_; +}; + +// +// +/** + * @brief Simple wrapper for a common in memory shuffle based-psi case. + * + * @param link network communication link context + * @param master_rank rank of master role the 3party psi protocol + * @param items input data for psi + * @param batch_size mask/send size every batch, default kEcdhPsiBatchSize + * @return std::vector<std::string> master_rank get intersection result, + * other rank return empty vector + */ +std::vector<std::string> RunShuffleEcdh3PartyPsi( + const std::shared_ptr<yacl::link::Context>& link, size_t master_rank, + std::vector<std::string>& items, + CurveType curve_type = CurveType::CURVE_25519, + size_t batch_size = kEcdhPsiBatchSize); + +} // namespace psi::psi diff --git a/psi/psi/operator/factory.h b/psi/psi/operator/factory.h new file mode 100644 index 00000000..428e630a --- /dev/null +++ b/psi/psi/operator/factory.h @@ -0,0 +1,80 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <functional> +#include <memory> +#include <mutex> +#include <unordered_map> + +#include "yacl/base/exception.h" + +#include "psi/psi/operator/base_operator.h" + +#include "psi/psi/psi.pb.h" + +namespace psi::psi { + +using OperatorCreator = std::function<std::unique_ptr<PsiBaseOperator>( + const MemoryPsiConfig& config, + const std::shared_ptr<yacl::link::Context>& lctx)>; + +class OperatorFactory { + public: + static OperatorFactory* GetInstance() { + static OperatorFactory factory; + return &factory; + } + + void Register(const std::string& type, OperatorCreator creator) { + std::lock_guard<std::mutex> lock(mutex_); + YACL_ENFORCE(creators_.find(type) == creators_.end(), + "duplicated creator registered for {}", type); + creators_[type] = std::move(creator); + } + + std::unique_ptr<PsiBaseOperator> Create( + const MemoryPsiConfig& config, + const std::shared_ptr<yacl::link::Context>& lctx) { + std::string type = PsiType_Name(config.psi_type()); + auto creator = creators_[type]; + YACL_ENFORCE(creator, "no creator registered for operator type: {}", type); + return creator(config, lctx); + } + + protected: + OperatorFactory() {} + virtual ~OperatorFactory() {} + OperatorFactory(const OperatorFactory&) = delete; + OperatorFactory& operator=(const OperatorFactory&) = delete; + OperatorFactory(OperatorFactory&&) = delete; + OperatorFactory& operator=(OperatorFactory&&) = delete; + + private: + std::unordered_map<std::string, OperatorCreator> creators_; + std::mutex mutex_; +}; + +class OperatorRegistrar { + public: + explicit OperatorRegistrar(const std::string& type, OperatorCreator creator) { + OperatorFactory::GetInstance()->Register(type, std::move(creator)); + } +}; + +#define REGISTER_OPERATOR(type, creator) \ + static OperatorRegistrar registrar__##type##__object(#type, creator); + +} // namespace psi::psi diff --git a/psi/psi/operator/kkrt_2party_psi.cc b/psi/psi/operator/kkrt_2party_psi.cc new file mode 100644 index 00000000..7f37ba19 --- /dev/null +++ b/psi/psi/operator/kkrt_2party_psi.cc @@ -0,0 +1,73 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/operator/kkrt_2party_psi.h" + +#include <memory> + +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/utils/parallel.h" + +#include "psi/psi/operator/factory.h" + +namespace psi::psi { + +KkrtPsiOperator::Options KkrtPsiOperator::ParseConfig( + const MemoryPsiConfig& config, + const std::shared_ptr<yacl::link::Context>& lctx) { + return {lctx, config.receiver_rank()}; +} + +std::vector<std::string> KkrtPsiOperator::OnRun( + const std::vector<std::string>& inputs) { + std::vector<std::string> res; + + // hash items to uint128_t + std::vector<uint128_t> items_hash(inputs.size()); + yacl::parallel_for(0, inputs.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + items_hash[idx] = yacl::crypto::Blake3_128(inputs[idx]); + } + }); + + if (options_.receiver_rank == link_ctx_->Rank()) { + auto ot_send = GetKkrtOtReceiverOptions(options_.link_ctx, options_.num_ot); + std::vector<size_t> kkrt_psi_result = + KkrtPsiRecv(options_.link_ctx, ot_send, items_hash); + + for (auto index : kkrt_psi_result) { + res.emplace_back(inputs[index]); + } + } else { + auto ot_recv = GetKkrtOtSenderOptions(options_.link_ctx, options_.num_ot); + KkrtPsiSend(options_.link_ctx, ot_recv, items_hash); + } + + return res; +} + +namespace { + +std::unique_ptr<PsiBaseOperator> CreateOperator( + const MemoryPsiConfig& config, + const std::shared_ptr<yacl::link::Context>& lctx) { + auto options = KkrtPsiOperator::ParseConfig(config, lctx); + return std::make_unique<KkrtPsiOperator>(options); +} + +REGISTER_OPERATOR(KKRT_PSI_2PC, CreateOperator); + +} // namespace + +} // namespace psi::psi diff --git a/psi/psi/operator/kkrt_2party_psi.h b/psi/psi/operator/kkrt_2party_psi.h new file mode 100644 index 00000000..17b024fd --- /dev/null +++ b/psi/psi/operator/kkrt_2party_psi.h @@ -0,0 +1,47 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <functional> +#include <memory> +#include <string> +#include <vector> + +#include "psi/psi/core/kkrt_psi.h" +#include "psi/psi/operator/base_operator.h" + +namespace psi::psi { + +class KkrtPsiOperator : public PsiBaseOperator { + public: + struct Options { + std::shared_ptr<yacl::link::Context> link_ctx; + size_t receiver_rank = 0; + size_t num_ot = 512; + }; + + static Options ParseConfig(const MemoryPsiConfig& config, + const std::shared_ptr<yacl::link::Context>& lctx); + + explicit KkrtPsiOperator(const Options& options) + : PsiBaseOperator(options.link_ctx), options_(options) {} + + std::vector<std::string> OnRun(const std::vector<std::string>& inputs) final; + + private: + Options options_; +}; + +} // namespace psi::psi diff --git a/psi/psi/operator/nparty_psi.cc b/psi/psi/operator/nparty_psi.cc new file mode 100644 index 00000000..30fdcb75 --- /dev/null +++ b/psi/psi/operator/nparty_psi.cc @@ -0,0 +1,262 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/operator/nparty_psi.h" + +#include <algorithm> +#include <cstddef> +#include <future> +#include <memory> +#include <utility> + +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/utils/parallel.h" + +#include "psi/psi/core/communication.h" +#include "psi/psi/operator/factory.h" +#include "psi/psi/operator/kkrt_2party_psi.h" +#include "psi/psi/utils/serialize.h" + +namespace { +constexpr size_t kSyncRecvWaitTimeoutMs = 60L * 60 * 1000; +} // namespace + +namespace psi::psi { + +NpartyPsiOperator::Options NpartyPsiOperator::ParseConfig( + const MemoryPsiConfig& config, + const std::shared_ptr<yacl::link::Context>& lctx) { + NpartyPsiOperator::Options opts; + opts.link_ctx = lctx; + opts.master_rank = config.receiver_rank(); + opts.psi_proto = NpartyPsiOperator::PsiProtocol::Ecdh; + if (config.psi_type() == PsiType::KKRT_PSI_NPC) { + opts.psi_proto = NpartyPsiOperator::PsiProtocol::Kkrt; + } + if (config.curve_type() != CurveType::CURVE_INVALID_TYPE) { + opts.curve_type = config.curve_type(); + } + return opts; +} + +NpartyPsiOperator::NpartyPsiOperator(const Options& options) + : PsiBaseOperator(options.link_ctx), options_(options) { + YACL_ENFORCE(options_.link_ctx->WorldSize() >= 2); +} + +std::vector<std::string> NpartyPsiOperator::OnRun( + const std::vector<std::string>& inputs) { + std::vector<std::pair<size_t, size_t>> party_size_rank_vec = + GetAllPartyItemSizeVec(inputs.size()); + if ((party_size_rank_vec[0].first == 0) || + (party_size_rank_vec[1].first == 0)) { + // check master and min item size whether is zero + return {}; + } + + // std::log2(absl::bit_ceil( 00000000 )) = 0 + // std::log2(absl::bit_ceil( 00000001 )) = 0 + // std::log2(absl::bit_ceil( 00000010 )) = 1 + // std::log2(absl::bit_ceil( 00000011 )) = 2 + // std::log2(absl::bit_ceil( 00000100 )) = 2 + // std::log2(absl::bit_ceil( 00000101 )) = 3 + // std::log2(absl::bit_ceil( 00000110 )) = 3 + // std::log2(absl::bit_ceil( 00000111 )) = 3 + // std::log2(absl::bit_ceil( 00001000 )) = 3 + // std::log2(absl::bit_ceil( 00001001 )) = 4 + size_t level_num = std::log2(absl::bit_ceil(party_size_rank_vec.size())); + + std::vector<std::string> intersection; + for (size_t li = 0; li < level_num; ++li) { + size_t peer_rank; + size_t target_rank; + GetPsiRank(party_size_rank_vec, &peer_rank, &target_rank); + if (li == 0) { + intersection = Run2PartyPsi(inputs, peer_rank, target_rank); + } else { + intersection = Run2PartyPsi(intersection, peer_rank, target_rank); + } + + SPDLOG_INFO("rank:{}, level_idx:{}, level_num:{}, intersection:{}", + options_.link_ctx->Rank(), li, level_num, intersection.size()); + + // erase non-target rank + size_t erase_pos = (party_size_rank_vec.size() + 1) / 2; + for (size_t idx = erase_pos; idx < party_size_rank_vec.size(); ++idx) { + if (options_.link_ctx->Rank() == party_size_rank_vec[idx].second) { + yacl::link::RecvTimeoutGuard guard(options_.link_ctx, + kSyncRecvWaitTimeoutMs); + auto recv_intersection_buf = yacl::link::Broadcast( + options_.link_ctx, {}, options_.master_rank, "recv finish message"); + return {}; + } + } + party_size_rank_vec.erase(party_size_rank_vec.begin() + erase_pos, + party_size_rank_vec.end()); + + // gather all intersection size + std::vector<std::string> sub_party_ids(party_size_rank_vec.size()); + for (size_t idx = 0; idx < party_size_rank_vec.size(); ++idx) { + sub_party_ids[idx] = + options_.link_ctx->PartyIdByRank(party_size_rank_vec[idx].second); + } + std::string sub_id = + fmt::format("subid-level:{}-{}", li, party_size_rank_vec.size()); + std::shared_ptr<yacl::link::Context> sub_link_ctx = + options_.link_ctx->SubWorld(sub_id, sub_party_ids); + std::vector<yacl::Buffer> gather_size_bufs = yacl::link::AllGather( + sub_link_ctx, utils::SerializeSize(intersection.size()), + fmt::format("round:{}, {} gather item size", li, sub_link_ctx->Rank())); + + size_t min_intersection_size = inputs.size(); + for (auto& gather_size_buf : gather_size_bufs) { + size_t current_idx_size = utils::DeserializeSize(gather_size_buf); + min_intersection_size = std::min(min_intersection_size, current_idx_size); + if (min_intersection_size == 0) { + break; + } + } + + // check current loop has zero size intersection, + if (min_intersection_size == 0) { + intersection.resize(0); + if (options_.link_ctx->Rank() == options_.master_rank) { + yacl::link::Broadcast(options_.link_ctx, "finish", options_.master_rank, + "send finish message"); + } else { + yacl::link::Broadcast(options_.link_ctx, {}, options_.master_rank, + "recv finish message"); + } + return intersection; + } + + // broadcast intersection + if (party_size_rank_vec.size() == 1) { + yacl::link::Broadcast(options_.link_ctx, "finish", options_.master_rank, + "send finish message"); + std::sort(intersection.begin(), intersection.end()); + return intersection; + } + } + + return {}; +} + +std::vector<std::string> NpartyPsiOperator::Run2PartyPsi( + const std::vector<std::string>& items, size_t peer_rank, + size_t target_rank) { + SPDLOG_INFO("Run2PartyPsi:{}, peer_rank:{}, target_rank:{}, item_size:{}", + options_.link_ctx->Rank(), peer_rank, target_rank, items.size()); + if (peer_rank == options_.link_ctx->Rank()) { + return items; + } + + auto link_ctx = CreateP2PLinkCtx("2partypsi", options_.link_ctx, peer_rank); + if (options_.psi_proto == PsiProtocol::Ecdh) { + return RunEcdhPsi(link_ctx, items, + target_rank == options_.link_ctx->Rank() + ? link_ctx->Rank() + : link_ctx->NextRank(), + options_.curve_type, options_.batch_size); + } else if (options_.psi_proto == PsiProtocol::Kkrt) { + KkrtPsiOperator::Options opts; + opts.link_ctx = link_ctx; + opts.receiver_rank = target_rank == options_.link_ctx->Rank() + ? opts.link_ctx->Rank() + : opts.link_ctx->NextRank(); + KkrtPsiOperator kkrt_op(opts); + + return kkrt_op.Run(items, false); + } else { + YACL_THROW("not support psi type: {}", + static_cast<int>(options_.psi_proto)); + } +} + +std::vector<std::pair<size_t, size_t>> +NpartyPsiOperator::GetAllPartyItemSizeVec(size_t item_size) { + // get all party's item size + std::vector<std::pair<size_t, size_t>> party_size_rank_vec; + + std::vector<yacl::Buffer> gather_size = yacl::link::AllGather( + options_.link_ctx, utils::SerializeSize(item_size), + fmt::format("{} send item size", options_.link_ctx->Rank())); + YACL_ENFORCE(gather_size.size() == options_.link_ctx->WorldSize()); + + for (size_t idx = 0; idx < options_.link_ctx->WorldSize(); ++idx) { + size_t idx_item_size = utils::DeserializeSize(gather_size[idx]); + + party_size_rank_vec.emplace_back(idx_item_size, idx); + } + if (options_.master_rank != 0) { + // place master to first + std::swap(party_size_rank_vec[0], + party_size_rank_vec[options_.master_rank]); + } + // ascending sort other rank by items size, + std::sort(party_size_rank_vec.begin() + 1, party_size_rank_vec.end()); + + return party_size_rank_vec; +} + +void NpartyPsiOperator::GetPsiRank( + const std::vector<std::pair<size_t, size_t>>& party_size_rank_vec, + size_t* peer_rank, size_t* target_rank) { + if ((party_size_rank_vec.size() % 2 != 0) && + (party_size_rank_vec[party_size_rank_vec.size() / 2].second == + options_.link_ctx->Rank())) { + // no peer + *peer_rank = options_.link_ctx->Rank(); + *target_rank = options_.link_ctx->Rank(); + return; + } + + for (size_t idx = 0; idx < party_size_rank_vec.size() / 2; ++idx) { + // peer_idx begin from bottom + size_t peer_idx = party_size_rank_vec.size() - 1 - idx; + if (party_size_rank_vec[idx].second == options_.link_ctx->Rank()) { + *peer_rank = party_size_rank_vec[peer_idx].second; + *target_rank = party_size_rank_vec[idx].second; + + return; + } else if (party_size_rank_vec[peer_idx].second == + options_.link_ctx->Rank()) { + *peer_rank = party_size_rank_vec[idx].second; + *target_rank = party_size_rank_vec[idx].second; + + return; + } + } + + YACL_THROW("can not find self rank({}) in party_size_rank_vec", + options_.link_ctx->Rank()); +} + +namespace { + +std::unique_ptr<PsiBaseOperator> CreateOperator( + const MemoryPsiConfig& config, + const std::shared_ptr<yacl::link::Context>& lctx) { + auto options = NpartyPsiOperator::ParseConfig(config, lctx); + return std::make_unique<NpartyPsiOperator>(options); +} + +REGISTER_OPERATOR(ECDH_PSI_NPC, CreateOperator); +REGISTER_OPERATOR(KKRT_PSI_NPC, CreateOperator); + +} // namespace + +} // namespace psi::psi diff --git a/psi/psi/operator/nparty_psi.h b/psi/psi/operator/nparty_psi.h new file mode 100644 index 00000000..859159fc --- /dev/null +++ b/psi/psi/operator/nparty_psi.h @@ -0,0 +1,84 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <string> +#include <vector> + +#include "yacl/link/link.h" + +#include "psi/psi/core/ecdh_psi.h" +#include "psi/psi/operator/base_operator.h" + +namespace psi::psi { +// use 2-party psi to get n-party PSI +// put master rank at 0 position +// ascending sort other rank by items size, +// execute ceil( log(n) ) round, get final psi result +// for example 5 party, execute 3 round +// || 0 1 2 3 4 5 +// 1st round || <---------------------> +// || <-------------> +// || <----> +// ====================== +// || 0 1 2 +// 2nd round || <---------> +// ======= +// || 0 1 +// 3rd round || <----> +class NpartyPsiOperator : public PsiBaseOperator { + public: + enum class PsiProtocol { + Ecdh, + Kkrt, + }; + struct Options { + std::shared_ptr<yacl::link::Context> link_ctx; + + PsiProtocol psi_proto; + CurveType curve_type = CurveType::CURVE_25519; + size_t master_rank = 0; + + // for ecdh + size_t batch_size = kEcdhPsiBatchSize; + }; + + static Options ParseConfig(const MemoryPsiConfig& config, + const std::shared_ptr<yacl::link::Context>& lctx); + + public: + explicit NpartyPsiOperator(const Options& options); + + std::vector<std::string> OnRun( + const std::vector<std::string>& inputs) override final; + + private: + std::vector<std::string> Run2PartyPsi(const std::vector<std::string>& items, + size_t peer_rank, size_t target_rank); + + void GetPsiRank( + const std::vector<std::pair<size_t, size_t>>& party_size_rank_vec, + size_t* peer_rank, size_t* target_rank); + + // return item_size-rank pair vector, first element is master, others + // ascending sort by items size, + std::vector<std::pair<size_t, size_t>> GetAllPartyItemSizeVec( + size_t item_size); + + private: + Options options_; +}; + +} // namespace psi::psi \ No newline at end of file diff --git a/psi/psi/operator/nparty_psi_test.cc b/psi/psi/operator/nparty_psi_test.cc new file mode 100644 index 00000000..20d1b51e --- /dev/null +++ b/psi/psi/operator/nparty_psi_test.cc @@ -0,0 +1,149 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/operator/nparty_psi.h" + +#include <future> +#include <iostream> +#include <random> +#include <set> +#include <vector> + +#include "absl/strings/str_split.h" +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" +#include "yacl/link/test_util.h" + +#include "psi/psi/utils/test_utils.h" + +namespace psi::psi { + +namespace { +struct NPartyTestParams { + std::vector<size_t> item_size; + size_t intersection_size; + NpartyPsiOperator::PsiProtocol psi_type = + NpartyPsiOperator::PsiProtocol::Ecdh; +}; + +std::vector<std::vector<std::string>> CreateNPartyItems( + const NPartyTestParams& params) { + std::vector<std::vector<std::string>> ret(params.item_size.size() + 1); + ret[params.item_size.size()] = + test::CreateRangeItems(1, params.intersection_size); + + for (size_t idx = 0; idx < params.item_size.size(); ++idx) { + ret[idx] = + test::CreateRangeItems((idx + 1) * 1000000, params.item_size[idx]); + } + + for (size_t idx = 0; idx < params.item_size.size(); ++idx) { + std::set<size_t> idx_set; + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(0, params.item_size[idx] - 1); + + while (idx_set.size() < params.intersection_size) { + idx_set.insert(dis(gen)); + } + size_t j = 0; + for (const auto& iter : idx_set) { + ret[idx][iter] = ret[params.item_size.size()][j++]; + } + } + return ret; +} + +} // namespace + +class NPartyPsiTest : public testing::TestWithParam<NPartyTestParams> {}; + +TEST_P(NPartyPsiTest, Works) { + std::vector<std::vector<std::string>> items; + + auto params = GetParam(); + items = CreateNPartyItems(params); + size_t master_rank = 0; + + auto ctxs = yacl::link::test::SetupWorld(params.item_size.size()); + + auto proc = [&](int idx) -> std::vector<std::string> { + NpartyPsiOperator::Options opts; + opts.psi_proto = params.psi_type; + opts.link_ctx = ctxs[idx]; + opts.master_rank = master_rank; + + NpartyPsiOperator op(opts); + + return op.OnRun(items[idx]); + }; + + size_t world_size = ctxs.size(); + std::vector<std::future<std::vector<std::string>>> f_links(world_size); + for (size_t i = 0; i < world_size; i++) { + f_links[i] = std::async(proc, i); + } + + std::vector<std::string> intersection = + test::GetIntersection(items[params.item_size.size()], items[0]); + std::sort(intersection.begin(), intersection.end()); + + std::vector<std::vector<std::string>> results(world_size); + for (size_t i = 0; i < world_size; i++) { + results[i] = f_links[i].get(); + + if (i == master_rank) { + EXPECT_EQ(results[i].size(), intersection.size()); + EXPECT_EQ(results[i], intersection); + } else { + EXPECT_EQ(results[i].size(), 0); + } + } +} + +INSTANTIATE_TEST_SUITE_P( + Works_Instances, NPartyPsiTest, + testing::Values( + // ecdh + NPartyTestParams{{0, 3}, 0}, // + NPartyTestParams{{3, 0}, 0}, // + NPartyTestParams{{0, 0}, 0}, // + NPartyTestParams{{4, 3}, 2}, // + // + NPartyTestParams{{20, 17, 14}, 10}, // + NPartyTestParams{{20, 17, 14, 30}, 10}, // + NPartyTestParams{{20, 17, 14, 30, 35}, 11}, // + NPartyTestParams{{20, 17, 14, 30, 35}, 0}, // + // + NPartyTestParams{{0, 0}, 0}, + // kkrt + NPartyTestParams{{0, 3}, 0, NpartyPsiOperator::PsiProtocol::Kkrt}, // + NPartyTestParams{{3, 0}, 0, NpartyPsiOperator::PsiProtocol::Kkrt}, // + NPartyTestParams{{0, 0}, 0, NpartyPsiOperator::PsiProtocol::Kkrt}, // + NPartyTestParams{{4, 3}, 2, NpartyPsiOperator::PsiProtocol::Kkrt}, // + // + NPartyTestParams{ + {20, 17, 14}, 10, NpartyPsiOperator::PsiProtocol::Kkrt}, // + NPartyTestParams{ + {20, 17, 14, 30}, 10, NpartyPsiOperator::PsiProtocol::Kkrt}, // + NPartyTestParams{ + {20, 17, 14, 30, 35}, 11, NpartyPsiOperator::PsiProtocol::Kkrt}, // + NPartyTestParams{ + {20, 17, 14, 30, 35}, 0, NpartyPsiOperator::PsiProtocol::Kkrt}, // + + // + NPartyTestParams{{0, 0}, 0, NpartyPsiOperator::PsiProtocol::Kkrt})); + +} // namespace psi::psi \ No newline at end of file diff --git a/psi/psi/operator/rr22_2party_psi.cc b/psi/psi/operator/rr22_2party_psi.cc new file mode 100644 index 00000000..64230b0c --- /dev/null +++ b/psi/psi/operator/rr22_2party_psi.cc @@ -0,0 +1,113 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/operator/rr22_2party_psi.h" + +#include <omp.h> + +#include <chrono> + +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/utils/parallel.h" + +#include "psi/psi/operator/factory.h" + +using DurationMillis = std::chrono::duration<double, std::milli>; + +namespace psi::psi { + +Rr22PsiOperator::Options Rr22PsiOperator::ParseConfig( + const MemoryPsiConfig& config, + const std::shared_ptr<yacl::link::Context>& lctx) { + Options options; + options.link_ctx = lctx; + options.receiver_rank = config.receiver_rank(); + + size_t thread_num = omp_get_num_procs(); + + options.rr22_options.ssp = 40; + options.rr22_options.num_threads = thread_num; + options.rr22_options.compress = true; + + return options; +} + +std::vector<std::string> Rr22PsiOperator::OnRun( + const std::vector<std::string>& inputs) { + std::vector<std::string> result; + + // hash items to uint128_t + std::vector<uint128_t> items_hash(inputs.size()); + SPDLOG_INFO("begin items hash"); + yacl::parallel_for(0, inputs.size(), 1, [&](int64_t begin, int64_t end) { + for (int64_t idx = begin; idx < end; ++idx) { + items_hash[idx] = yacl::crypto::Blake3_128(inputs[idx]); + } + }); + SPDLOG_INFO("end items hash"); + + const auto psi_core_start = std::chrono::system_clock::now(); + + if (options_.receiver_rank == link_ctx_->Rank()) { + std::vector<size_t> rr22_psi_result = + Rr22PsiReceiver(options_.rr22_options, options_.link_ctx, items_hash); + + const auto psi_core_end = std::chrono::system_clock::now(); + const DurationMillis psi_core_duration = psi_core_end - psi_core_start; + SPDLOG_INFO("rank: {}, psi_core_duration:{}", options_.link_ctx->Rank(), + (psi_core_duration.count() / 1000)); + + result.reserve(rr22_psi_result.size()); + + for (auto index : rr22_psi_result) { + result.push_back(inputs[index]); + } + } else { + Rr22PsiSender(options_.rr22_options, options_.link_ctx, items_hash); + + const auto psi_core_end = std::chrono::system_clock::now(); + const DurationMillis psi_core_duration = psi_core_end - psi_core_start; + SPDLOG_INFO("rank: {}, psi_core_duration:{}", options_.link_ctx->Rank(), + (psi_core_duration.count() / 1000)); + } + + return result; +} + +namespace { + +std::unique_ptr<PsiBaseOperator> CreateFastOperator( + const MemoryPsiConfig& config, + const std::shared_ptr<yacl::link::Context>& lctx) { + auto options = Rr22PsiOperator::ParseConfig(config, lctx); + + return std::make_unique<Rr22PsiOperator>(options); +} + +std::unique_ptr<PsiBaseOperator> CreateLowCommOperator( + const MemoryPsiConfig& config, + const std::shared_ptr<yacl::link::Context>& lctx) { + auto options = Rr22PsiOperator::ParseConfig(config, lctx); + + options.rr22_options.mode = Rr22PsiMode::LowCommMode; + + return std::make_unique<Rr22PsiOperator>(options); +} + +REGISTER_OPERATOR(RR22_FAST_PSI_2PC, CreateFastOperator); +REGISTER_OPERATOR(RR22_LOWCOMM_PSI_2PC, CreateLowCommOperator); + +} // namespace + +} // namespace psi::psi diff --git a/psi/psi/operator/rr22_2party_psi.h b/psi/psi/operator/rr22_2party_psi.h new file mode 100644 index 00000000..8210caf0 --- /dev/null +++ b/psi/psi/operator/rr22_2party_psi.h @@ -0,0 +1,46 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <memory> +#include <string> +#include <vector> + +#include "psi/psi/core/vole_psi/rr22_psi.h" +#include "psi/psi/operator/base_operator.h" + +namespace psi::psi { + +class Rr22PsiOperator : public PsiBaseOperator { + public: + struct Options { + std::shared_ptr<yacl::link::Context> link_ctx; + size_t receiver_rank = 0; + Rr22PsiOptions rr22_options = Rr22PsiOptions(40, 0, true); + }; + + static Options ParseConfig(const MemoryPsiConfig& config, + const std::shared_ptr<yacl::link::Context>& lctx); + + explicit Rr22PsiOperator(const Options& options) + : PsiBaseOperator(options.link_ctx), options_(options) {} + + std::vector<std::string> OnRun(const std::vector<std::string>& inputs) final; + + private: + Options options_; +}; + +} // namespace psi::psi diff --git a/psi/psi/prelude.h b/psi/psi/prelude.h new file mode 100644 index 00000000..1086f5e7 --- /dev/null +++ b/psi/psi/prelude.h @@ -0,0 +1,47 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "fmt/ostream.h" + +#include "psi/proto/psi.pb.h" +#include "psi/psi/psi.pb.h" + +namespace fmt { + +template <> +struct formatter<psi::psi::CurveType> : ostream_formatter {}; + +template <> +struct formatter<psi::psi::PsiType> : ostream_formatter {}; + +template <> +struct formatter<psi::psi::v2::Role> : ostream_formatter {}; + +template <> +struct formatter<psi::psi::v2::Protocol> : ostream_formatter {}; + +template <> +struct formatter<psi::psi::v2::IoType> : ostream_formatter {}; + +template <> +struct formatter<psi::psi::v2::PsiConfig::AdvancedJoinType> + : ostream_formatter {}; + +template <> +struct formatter<psi::psi::v2::RecoveryCheckpoint::Stage> : ostream_formatter { +}; + +} // namespace fmt diff --git a/psi/psi/psi.proto b/psi/psi/psi.proto new file mode 100644 index 00000000..ee63efb8 --- /dev/null +++ b/psi/psi/psi.proto @@ -0,0 +1,217 @@ +// +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +package psi.psi; + +// The algorithm type of psi. +enum PsiType { + INVALID_PSI_TYPE = 0; + + // DDH based PSI + ECDH_PSI_2PC = 1; + // Efficient Batched Oblivious PRF with Applications to Private Set + // Intersection https://eprint.iacr.org/2016/799.pdf + KKRT_PSI_2PC = 2; + // PSI from Pseudorandom Correlation Generators + // https://eprint.iacr.org/2022/334 + BC22_PSI_2PC = 3; + // Multi-party PSI based on ECDH (Say A, B, C (receiver)) + // notice: two-party intersection cardinarlity leak (|A intersect B|) + ECDH_PSI_3PC = 4; + + // Iterative running 2-party ecdh psi to get n-party PSI. + // Notice: two-party intersection leak + ECDH_PSI_NPC = 5; + // Iterative running 2-party kkrt psi to get n-party PSI. + // Notice: two-party intersection leak + KKRT_PSI_NPC = 6; + + // ecdh-oprf 2-party Unbalanced-PSI Generate CACHE. + ECDH_OPRF_UB_PSI_2PC_GEN_CACHE = 7; + + // ecdh-oprf 2-party Unbalanced-PSI transfer CACHE. + ECDH_OPRF_UB_PSI_2PC_TRANSFER_CACHE = 8; + + // ecdh-oprf 2-party Unbalanced-PSI offline phase. + ECDH_OPRF_UB_PSI_2PC_OFFLINE = 9; + + // ecdh-oprf 2-party Unbalanced-PSI online phase. + ECDH_OPRF_UB_PSI_2PC_ONLINE = 10; + + // ecdh-oprf 2-party Unbalanced-PSI with shuffling online phase. + // large set party get intersection result + ECDH_OPRF_UB_PSI_2PC_SHUFFLE_ONLINE = 11; + + // Differentially-Private PSI https://arxiv.org/pdf/2208.13249.pdf + // bases on ECDH-PSI, and provides: Differentially private PSI results. + DP_PSI_2PC = 12; + + // Blazing Fast PSI https://eprint.iacr.org/2022/320.pdf + // two mode: fast mode or low communication mode + RR22_FAST_PSI_2PC = 13; + RR22_LOWCOMM_PSI_2PC = 14; +} + +// The specified elliptic curve cryptography used in psi. +enum CurveType { + CURVE_INVALID_TYPE = 0; + + // Daniel J. Bernstein. Curve25519: new diffie-hellman speed records + CURVE_25519 = 1; + // FourQ: four-dimensional decompositions on a Q-curve over the Mersenne prime + CURVE_FOURQ = 2; + // SM2 is an elliptic curve based cryptosystem (ECC) + // published as a Chinese National Standard as GBT.32918.1-2016 + // and published in ISO/IEC 14888-3:2018 + CURVE_SM2 = 3; + // parameters of the elliptic curve defined in Standards for Efficient + // Cryptography (SEC) http://www.secg.org/sec2-v2.pdf + CURVE_SECP256K1 = 4; + + // TODO: @changjun.zl support ristretto255 + // Ristretto255 implements abstract prime-order group interface of Curve25519 + // CURVE_RISTRETTO255 = 5; +} + +// The input parameters of psi. +message InputParams { + // The path of input csv file. + string path = 1; + // The select fields of input data. + repeated string select_fields = 2; + // Whether to check select fields duplicate. + bool precheck = 3; +} + +// The output parameters of psi. +message OutputParams { + // The path of output csv file. + string path = 1; + // Whether to sort output file by select fields. + bool need_sort = 2; +} + +// The report of psi result. +message PsiResultReport { + // The data count of input. + int64 original_count = 1; + // The count of intersection. Get `-1` when self party can not get result. + int64 intersection_count = 2; +} + +// The input parameters of dp-psi. +message DpPsiParams { + // bob sub-sampling bernoulli_distribution probability. + double bob_sub_sampling = 1; + // dp epsilon + double epsilon = 2; +} + +// The Bucket-psi configuration. +// +// ```python +// config = psi.BucketPsiConfig( # prepare config +// psi_type=PsiType.ECDH_PSI_2PC, +// broadcast_result=True, +// receiver_rank=0, +// input_params=psi.InputParams(path='/xxx/ccc.csv', select_fields=['c1', +// 'c2']), output_params=psi.OutputParams(path='/yyyy/oooo.csv', +// need_sort=True), +// ) +// report = psi.bucket_psi(lctx, config) # run psi and get report +// ``` +message BucketPsiConfig { + // + + /////////////////////////////////////// + // Basic + /////////////////////////////////////// + + // The psi type. + PsiType psi_type = 1; + + // Specified the receiver rank. Receiver can get psi result. + uint32 receiver_rank = 2; + + // Whether to broadcast psi result to all parties. + bool broadcast_result = 3; + + // The input parameters of psi. + InputParams input_params = 4; + + // The output parameters of psi. + OutputParams output_params = 5; + + /////////////////////////////////////// + // Advanced + /////////////////////////////////////// + + // Optional, specified elliptic curve cryptography used in psi when needed. + CurveType curve_type = 6; + + // Optional, specified the hash bucket size used in psi. + uint32 bucket_size = 7; + + // Optional,The path of offline preprocess file. + string preprocess_path = 8; + + // Optional,secret key path of ecdh_oprf, 256bit/32bytes binary file. + string ecdh_secret_key_path = 9; + + // Optional,Params for dp-psi + DpPsiParams dppsi_params = 10; +} + +// The In-memory psi configuration. +// +// ```python +// config = psi.MemoryPsiConfig( # prepare config +// psi_type=PsiType.ECDH_PSI_2PC, +// broadcast_result=True, +// receiver_rank=0, +// ) +// joined_list = psi.mem_psi( +// lctx, config, ['a1', 'v2', 'b3', 'v4'] +// ) # run psi and get joined list +// ``` +message MemoryPsiConfig { + // + + /////////////////////////////////////// + // Basic + /////////////////////////////////////// + + // The psi type. + PsiType psi_type = 1; + + // Specified the receiver rank. Receiver can get psi result. + uint32 receiver_rank = 2; + + // Whether to broadcast psi result to all parties. + bool broadcast_result = 3; + + /////////////////////////////////////// + // Advanced + /////////////////////////////////////// + + // Optional, specified elliptic curve cryptography used in psi when needed. + CurveType curve_type = 4; + + // Optional,Params for dp-psi + DpPsiParams dppsi_params = 5; +} \ No newline at end of file diff --git a/psi/psi/psi_test.cc b/psi/psi/psi_test.cc new file mode 100644 index 00000000..f10341b9 --- /dev/null +++ b/psi/psi/psi_test.cc @@ -0,0 +1,741 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <fmt/format.h> +#include <gtest/gtest.h> + +#include <cstddef> +#include <filesystem> +#include <iostream> +#include <iterator> +#include <memory> +#include <sstream> +#include <string> +#include <vector> + +#include "arrow/api.h" +#include "arrow/csv/api.h" +#include "arrow/io/api.h" +#include "arrow/ipc/api.h" +#include "gtest/gtest.h" +#include "spdlog/spdlog.h" +#include "yacl/link/test_util.h" + +#include "psi/psi/factory.h" +#include "psi/psi/io/io.h" +#include "psi/psi/prelude.h" +#include "psi/psi/utils/utils.h" + +#include "psi/proto/psi.pb.h" + +namespace psi::psi { +namespace { + +struct TestTable { + std::vector<std::string> headers; + + std::vector<std::vector<std::string>> rows; +}; + +struct TestParams { + std::string title; + std::vector<TestTable> inputs; + std::vector<TestTable> outputs; + std::vector<std::vector<std::string>> keys; + bool sort_output = false; + bool broadcast_result = false; + bool output_difference = false; + bool inner_join = false; +}; + +void SaveTableAsFile(const TestTable& data, const std::string& path) { + io::FileIoOptions io_opt(path); + + yacl::io::Schema schema; + schema.feature_names = data.headers; + schema.feature_types = std::vector<yacl::io::Schema::Type>( + data.headers.size(), yacl::io::Schema::STRING); + + io::CsvOptions csv_opt; + csv_opt.writer_options.file_schema = schema; + + std::unique_ptr<io::Writer> writer = io::BuildWriter(io_opt, csv_opt); + yacl::io::ColumnVectorBatch batch; + for (size_t i = 0; i < data.headers.size(); i++) { + std::vector<std::string> col; + col.reserve(data.rows.size()); + for (const auto& row : data.rows) { + col.emplace_back(row[i]); + } + batch.AppendCol(col); + } + + writer->Add(batch); + writer->Flush(); + writer->Close(); +} + +TestTable LoadTableFromFile(const std::string& path, + const std::vector<std::string>& headers) { + EXPECT_TRUE(std::filesystem::exists(path)); + + arrow::io::IOContext io_context = arrow::io::default_io_context(); + std::shared_ptr<arrow::io::ReadableFile> infile; + infile = arrow::io::ReadableFile::Open(path, arrow::default_memory_pool()) + .ValueOrDie(); + + auto read_options = arrow::csv::ReadOptions::Defaults(); + auto parse_options = arrow::csv::ParseOptions::Defaults(); + + auto convert_options = arrow::csv::ConvertOptions::Defaults(); + for (const auto& header : headers) { + convert_options.column_types[header] = arrow::utf8(); + } + + auto reader = + arrow::csv::StreamingReader::Make(io_context, infile, read_options, + parse_options, convert_options) + .ValueOrDie(); + + const std::shared_ptr<arrow::Schema>& input_schema = reader->schema(); + + EXPECT_EQ(headers.size(), input_schema->num_fields()); + + TestTable res; + res.headers = headers; + std::shared_ptr<arrow::RecordBatch> batch; + + while (true) { + arrow::Status status = reader->ReadNext(&batch); + + if (!status.ok()) { + YACL_THROW("Read csv error."); + } + + if (batch == NULL) { + break; + } + + for (int i = 0; i < batch->num_rows(); i++) { + std::vector<std::string> row; + for (const auto& header : headers) { + auto a = std::static_pointer_cast<arrow::StringArray>( + batch->GetColumnByName(header)); + row.emplace_back(a->Value(i)); + } + + res.rows.emplace_back(row); + } + } + + return res; +} + +class PsiTest + : public testing::TestWithParam<std::tuple<v2::Protocol, TestParams>> { + protected: + void SetUp() override { tmp_dir_ = std::filesystem::temp_directory_path(); } + void TearDown() override { + std::error_code ec; + + for (const auto& p : tmp_paths_) { + if (std::filesystem::exists(p)) { + if (std::filesystem::is_directory(p)) { + std::filesystem::remove_all(p, ec); + if (ec.value() != 0) { + SPDLOG_WARN("can not remove temp dir: {}, msg: {}", p.string(), + ec.message()); + } + } else { + std::filesystem::remove(p, ec); + if (ec.value() != 0) { + SPDLOG_WARN("can not remove temp file: {}, msg: {}", p.string(), + ec.message()); + } + } + } + } + } + + std::vector<std::filesystem::path> GenTempPaths( + const std::string& name_prefix, int cnt) { + std::vector<std::filesystem::path> res; + res.reserve(cnt); + for (int i = 0; i < cnt; ++i) { + res.emplace_back(tmp_dir_ / fmt::format("{}-{}", name_prefix, i)); + } + tmp_paths_.insert(tmp_paths_.end(), res.begin(), res.end()); + + return res; + } + + std::filesystem::path tmp_dir_; + std::vector<std::filesystem::path> tmp_paths_; +}; + +TEST_P(PsiTest, Works) { + auto protocol = std::get<0>(GetParam()); + auto params = std::get<1>(GetParam()); + + std::cout << "Test title: " << params.title << std::endl; + std::cout << "Protocol: " << protocol << std::endl; + + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + + std::string test_suite_name = test_info->test_suite_name(); + std::string test_case_name = test_info->test_case_name(); + std::replace(test_suite_name.begin(), test_suite_name.end(), '/', '_'); + std::replace(test_case_name.begin(), test_case_name.end(), '/', '_'); + + std::string test_name = test_suite_name + "-" + test_case_name; + + std::vector<std::filesystem::path> input_paths = + GenTempPaths(test_name + "-input", 2); + + std::vector<std::filesystem::path> output_paths = + GenTempPaths(test_name + "-output", 2); + + auto lctxs = yacl::link::test::SetupWorld(2); + + auto proc = [&](int idx) -> v2::PsiReport { + v2::PsiConfig config; + config.mutable_input_config()->set_path(input_paths[idx]); + config.mutable_input_config()->set_type(v2::IO_TYPE_FILE_CSV); + config.mutable_keys()->Add(params.keys[idx].begin(), + params.keys[idx].end()); + config.set_check_duplicates(true); + config.mutable_output_config()->set_path(output_paths[idx]); + config.mutable_output_config()->set_type(v2::IO_TYPE_FILE_CSV); + config.set_output_difference(params.output_difference); + config.set_sort_output(params.sort_output); + config.mutable_protocol_config()->set_protocol(protocol); + if (protocol == v2::PROTOCOL_ECDH) { + config.mutable_protocol_config()->mutable_ecdh_config()->set_curve( + CurveType::CURVE_25519); + } + config.mutable_protocol_config()->set_broadcast_result( + params.broadcast_result); + if (params.inner_join) { + config.set_advanced_join_type( + v2::PsiConfig::ADVANCED_JOIN_TYPE_INNER_JOIN); + } + + std::unique_ptr<AbstractPSIParty> party; + if (idx == 0) { + config.mutable_protocol_config()->set_role(v2::Role::ROLE_RECEIVER); + party = createPSIParty(config, lctxs[idx]); + } else { + config.mutable_protocol_config()->set_role(v2::Role::ROLE_SENDER); + party = createPSIParty(config, lctxs[idx]); + } + + return party->Run(); + }; + + size_t world_size = lctxs.size(); + std::vector<std::future<v2::PsiReport>> f_links(world_size); + for (size_t i = 0; i < world_size; i++) { + SaveTableAsFile(params.inputs[i], input_paths[i].string()); + f_links[i] = std::async(proc, i); + } + + for (size_t i = 0; i < world_size; i++) { + std::exception_ptr exptr = nullptr; + + v2::PsiReport report; + try { + report = f_links[i].get(); + } catch (const std::exception& e) { + exptr = std::current_exception(); + SPDLOG_ERROR("Error in mask self: {}", e.what()); + } + + if (exptr) { + std::rethrow_exception(exptr); + } + + if (i == 0 || params.broadcast_result || params.inner_join) { + TestTable output_hat = LoadTableFromFile(output_paths[i].string(), + params.outputs[i].headers); + + EXPECT_EQ(params.outputs[i].rows, output_hat.rows); + } + } +} + +INSTANTIATE_TEST_SUITE_P( + Works_Instances, PsiTest, + testing::Combine( + testing::Values(v2::PROTOCOL_ECDH, v2::PROTOCOL_KKRT, + v2::PROTOCOL_RR22), + testing::Values( + TestParams{"testcase 1: sort_output false", + // inputs + {TestTable{// header + + {"id1"}, + {// row + {"3"}, + // row + {"1"}, + // row + {"5"}}}, + TestTable{// header + {"id2"}, + {// row + {"3"}, + // row + {"6"}, + // row + {"1"}}}}, + // outputs + {TestTable{// header + {"id1"}, + {// row + {"3"}, + // row + {"1"}}}, + TestTable{// header + {"id2"}, + {// row + {"3"}, + // row + {"1"}}}}, + // keys + {{"id1"}, {"id2"}}, + /*sort_output = */ false, + /*broadcast_result = */ true, + /*output_difference = */ false}, + TestParams{"testcase 2: sort_output true", + // inputs + {TestTable{// header + + {"id1"}, + {// row + {"3"}, + // row + {"1"}, + // row + {"5"}}}, + TestTable{// header + {"id2"}, + {// row + {"3"}, + // row + {"6"}, + // row + {"1"}}}}, + // outputs + {TestTable{// header + {"id1"}, + {// row + {"1"}, + // row + {"3"}}}, + TestTable{// header + {"id2"}, + {// row + {"1"}, + // row + {"3"}}}}, + // keys + {{"id1"}, {"id2"}}, + /*sort_output = */ true, + /*broadcast_result = */ true, + /*output_difference = */ false}, + + TestParams{"testcase 3: broadcast_result false", + // inputs + {TestTable{// header + {"id1"}, + {// row + {"3"}, + // row + {"1"}, + // row + {"5"}}}, + TestTable{// header + {"id2"}, + {// row + {"3"}, + // row + {"6"}, + // row + {"1"}}}}, + // outputs + {TestTable{// header + {"id1"}, + {// row + {"1"}, + // row + {"3"}}}}, + // keys + {{"id1"}, {"id2"}}, + /*sort_output = */ true, + /*broadcast_result = */ false, + /*output_difference = */ false}, + TestParams{"testcase 4: w/ payload", + // inputs + {TestTable{// header + {"id1", "payload1"}, + {// row + {"3", "third1"}, + // row + {"1", "first1"}, + // row + {"5", "fifth1"}}}, + TestTable{// header + {"id2", "payload2"}, + {// row + {"3", "third2"}, + // row + {"6", "sixth6"}, + // row + {"1", "first2"}}}}, + // outputs + {TestTable{// header + {"id1", "payload1"}, + {// row + {"1", "first1"}, + // row + {"3", "third1"}}}}, + // keys + {{"id1"}, {"id2"}}, + /*sort_output = */ true, + /*broadcast_result = */ false, + /*output_difference = */ false}, + TestParams{"testcase 5: chinese characters", + // inputs + {TestTable{// header + {"测试id1", "测试payload1"}, + {// row + {"测试3", "测试third1"}, + // row + {"测试1", "测试first1"}, + // row + {"测试5", "测试fifth1"}}}, + TestTable{// header + {"测试id2", "测试payload2"}, + {// row + {"测试3", "测试third2"}, + // row + {"测试6", "测试sixth6"}, + // row + {"测试1", "测试first2"}}}}, + // outputs + {TestTable{// header + {"测试id1", "测试payload1"}, + {// row + {"测试1", "测试first1"}, + // row + {"测试3", "测试third1"}}}}, + // keys + {{"测试id1"}, {"测试id2"}}, + /*sort_output = */ true, + /*broadcast_result = */ false, + /*output_difference = */ false}, + TestParams{"testcase 6: multikey", + // inputs + {TestTable{// header + {"测试id1", "测试id2", "测试payload1"}, + {// row + {"测试3", "测试3", "测试third1"}, + // row + {"测试1", "测试1", "测试first1"}, + // row + {"测试3", "测试1", "测试first1"}, + // row + {"测试5", "测试5", "测试fifth1"}}}, + TestTable{// header + {"测试id3", "测试id4", "测试payload2"}, + {// row + {"测试3", "测试3", "测试third2"}, + // row + {"测试3", "测试1", "测试third2"}, + // row + {"测试6", "测试6", "测试sixth6"}, + // row + {"测试1", "测试1", "测试first1"}}}}, + // outputs + {TestTable{// header + {"测试id1", "测试id2", "测试payload1"}, + {// row + {"测试1", "测试1", "测试first1"}, + // row + {"测试3", "测试1", "测试first1"}, + // row + {"测试3", "测试3", "测试third1"}}}}, + // keys + {{"测试id1", "测试id2"}, {"测试id3", "测试id4"}}, + /*sort_output = */ true, + /*broadcast_result = */ false, + /*output_difference = */ false}, + TestParams{"testcase 7: same input", + // inputs + {TestTable{// header + {"id1"}, + {// row + {"3"}, + // row + {"1"}, + // row + {"5"}}}, + TestTable{// header + {"id2"}, + {// row + {"3"}, + // row + {"1"}, + // row + {"5"}}}}, + // outputs + {TestTable{// header + {"id1"}, + {// row + {"3"}, + // row + {"1"}, + // row + {"5"}}}, + TestTable{// header + {"id2"}, + {// row + {"3"}, + // row + {"1"}, + // row + {"5"}}}}, + // keys + {{"id1"}, {"id2"}}, + /*sort_output = */ false, + /*broadcast_result = */ true, + /*output_difference = */ false}, + TestParams{"testcase 8: output_difference", + // inputs + {TestTable{// header + {"id1"}, + {// row + {"3"}, + // row + {"2"}, + // row + {"1"}, + // row + {"7"}, + // row + {"5"}}}, + TestTable{// header + {"id2"}, + {// row + {"8"}, + // row + {"9"}, + // row + {"3"}, + // row + {"6"}, + // row + {"1"}}}}, + // outputs + {TestTable{// header + {"id1"}, + {// row + {"2"}, + // row + {"7"}, + // row + {"5"}}}, + TestTable{// header + {"id2"}, + {// row + {"8"}, + // row + {"9"}, + // row + {"6"}}}}, + // keys + {{"id1"}, {"id2"}}, + /*sort_output = */ false, + /*broadcast_result = */ true, + /*output_difference = */ true}, + TestParams{"testcase 9: output_difference with no intersection", + // inputs + {TestTable{// header + {"id1"}, + {// row + {"3"}, + // row + {"1"}, + // row + {"5"}}}, + TestTable{// header + {"id2"}, + {// row + {"2"}, + // row + {"6"}, + // row + {"4"}}}}, + // outputs + {TestTable{// header + {"id1"}, + {// row + {"3"}, + // row + {"1"}, + // row + {"5"}}}, + TestTable{// header + {"id2"}, + {// row + {"2"}, + // row + {"6"}, + // row + {"4"}}}}, + // keys + {{"id1"}, {"id2"}}, + /*sort_output = */ false, + /*broadcast_result = */ true, + /*output_difference = */ true}, + TestParams{"testcase 10: inner_join", + // inputs + {TestTable{// header + {"id1"}, + {// row + {"3"}, + // row + {"1"}, + // row + {"3"}, + // row + {"3"}, + // row + {"5"}}}, + TestTable{// header + {"id2"}, + {// row + {"3"}, + // row + {"1"}, + // row + {"6"}, + // row + {"1"}, + // row + {"3"}}}}, + // outputs + {TestTable{// header + {"id1"}, + {// row + {"1"}, + // row + {"1"}, + // row + {"3"}, + // row + {"3"}, + // row + {"3"}, + // row + {"3"}, + // row + {"3"}, + // row + {"3"}}}, + TestTable{// header + {"id2"}, + {// row + {"1"}, + // row + {"1"}, + // row + {"3"}, + // row + {"3"}, + // row + {"3"}, + // row + {"3"}, + // row + {"3"}, + // row + {"3"}}}}, + // keys + {{"id1"}, {"id2"}}, + /*sort_output = */ false, + /*broadcast_result = */ true, + /*output_difference = */ false, + /*inner_join = */ true}, + TestParams{"testcase 11: inner_join and output_difference", + // inputs + {TestTable{// header + {"id1"}, + {// row + {"3"}, + // row + {"1"}, + // row + {"0"}, + // row + {"2"}, + // row + {"2"}, + // row + {"3"}, + // row + {"3"}, + // row + {"5"}}}, + TestTable{// header + {"id2"}, + {// row + {"6"}, + // row + {"3"}, + // row + {"1"}, + // row + {"6"}, + // row + {"1"}, + // row + {"3"}}}}, + // outputs + {TestTable{// header + {"id1"}, + {// row + {"0"}, + // row + {"2"}, + // row + {"2"}, + // row + {"5"}}}, + TestTable{// header + {"id2"}, + {// row + {"6"}, + // row + {"6"}}}}, + // keys + {{"id1"}, {"id2"}}, + /*sort_output = */ false, + /*broadcast_result = */ true, + /*output_difference = */ true, + /*inner_join = */ true}))); + +} // namespace +} // namespace psi::psi diff --git a/psi/psi/recovery.cc b/psi/psi/recovery.cc new file mode 100644 index 00000000..d01a9954 --- /dev/null +++ b/psi/psi/recovery.cc @@ -0,0 +1,252 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/recovery.h" + +#include <spdlog/spdlog.h> + +#include <filesystem> +#include <fstream> + +#include "google/protobuf/util/json_util.h" +#include "google/protobuf/util/message_differencer.h" +#include "yacl/base/exception.h" + +#include "psi/psi/io/io.h" + +#include "psi/proto/psi.pb.h" + +namespace psi::psi { + +v2::RecoveryCheckpoint LoadRecoveryCheckpointFromFile( + const std::filesystem::path& path) { + v2::RecoveryCheckpoint pb; + google::protobuf::util::JsonParseOptions json_parse_options; + json_parse_options.ignore_unknown_fields = true; // optional + std::fstream json_config_file(path, std::ios::in); + + auto status = google::protobuf::util::JsonStringToMessage( + std::string((std::istreambuf_iterator<char>(json_config_file)), + std::istreambuf_iterator<char>()), + &pb, json_parse_options); + + YACL_ENFORCE(status.ok(), + "file {} couldn't be parsed as RecoveryCheckpoint: {}.", + path.string(), status.ToString()); + + return pb; +} + +RecoveryManager::RecoveryManager(const std::string& folder_path) + : folder_path_(folder_path) { + YACL_ENFORCE(std::filesystem::exists(folder_path_)); + + checkpoint_file_path_ = folder_path_ / "checkpoint.json"; + private_key_file_path_ = folder_path_ / "private_key"; + + ecdh_dual_masked_self_cache_path_ = + folder_path_ / "ecdh_dual_masked_self_cache"; + if (!std::filesystem::exists(ecdh_dual_masked_self_cache_path_)) { + std::filesystem::create_directory(ecdh_dual_masked_self_cache_path_); + } + + ecdh_dual_masked_peer_cache_path_ = + folder_path_ / "ecdh_dual_masked_peer_cache"; + if (!std::filesystem::exists(ecdh_dual_masked_peer_cache_path_)) { + std::filesystem::create_directory(ecdh_dual_masked_peer_cache_path_); + } + + input_bucket_store_path_ = folder_path_ / "input_bucket_store"; + if (!std::filesystem::exists(input_bucket_store_path_)) { + std::filesystem::create_directory(input_bucket_store_path_); + } + + // If checkpoint_file_path_ exists, load previous record for recovery. + if (std::filesystem::exists(checkpoint_file_path_)) { + checkpoint_ = LoadRecoveryCheckpointFromFile(checkpoint_file_path_); + } +} + +void RecoveryManager::SaveCheckpointFile() { + std::unique_lock<std::mutex> lock(mutex_); + + std::string checkpoint_json; + google::protobuf::util::JsonPrintOptions json_print_options; + json_print_options.preserve_proto_field_names = true; + YACL_ENFORCE(google::protobuf::util::MessageToJsonString( + checkpoint_, &checkpoint_json, json_print_options) + .ok()); + + std::ofstream file; + file.open(checkpoint_file_path_); + file << checkpoint_json; + file.close(); +} + +void RecoveryManager::MarkInitEnd(const v2::PsiConfig& config, + const std::string& input_hash_digest) { + if (checkpoint_.stage() != v2::RecoveryCheckpoint::STAGE_UNSPECIFIED) { + // Check whether config and input_hash_digest match the previous record. + YACL_ENFORCE(::google::protobuf::util::MessageDifferencer::Equals( + config, checkpoint_.config()), + "PSI Config doesn't match previous record."); + + YACL_ENFORCE(input_hash_digest == checkpoint_.input_hash_digest(), + "input_hash_digest doesn't match previous record."); + } else { + checkpoint_.set_stage(v2::RecoveryCheckpoint::STAGE_INIT_END); + *checkpoint_.mutable_config() = config; + checkpoint_.set_input_hash_digest(input_hash_digest); + + SaveCheckpointFile(); + } +} + +void RecoveryManager::MarkPreProcessEnd( + const std::shared_ptr<IEccCryptor>& cryptor) { + if (cryptor) { + if (checkpoint_.stage() < v2::RecoveryCheckpoint::STAGE_PRE_PROCESS_END) { + checkpoint_.set_stage(v2::RecoveryCheckpoint::STAGE_PRE_PROCESS_END); + + // save private key + auto private_key_output = + io::BuildOutputStream(io::FileIoOptions(private_key_file_path_)); + private_key_output->Write(cryptor->GetPrivateKey(), kEccKeySize); + private_key_output->Flush(); + + // save checkpoint file + SaveCheckpointFile(); + } else { + // Load private key. + std::ifstream instream(private_key_file_path_, + std::ios::in | std::ios::binary); + std::vector<uint8_t> private_key( + (std::istreambuf_iterator<char>(instream)), + std::istreambuf_iterator<char>()); + cryptor->SetPrivateKey(private_key); + } + } +} + +bool RecoveryManager::MarkOnlineStart( + const std::shared_ptr<yacl::link::Context>& lctx) { + bool online_stage_finished = false; + if (lctx) { + YACL_ENFORCE_EQ(lctx->WorldSize(), 2U); + + v2::InternalRecoveryRecord record; + + record.set_stage(checkpoint_.stage()); + record.set_ecdh_dual_masked_item_peer_count( + checkpoint_.ecdh_dual_masked_item_peer_count()); + record.set_parsed_bucket_count(checkpoint_.parsed_bucket_count()); + std::string record_str; + YACL_ENFORCE(record.SerializeToString(&record_str)); + + std::vector<yacl::Buffer> record_buffer_list = yacl::link::AllGather( + lctx, record_str, "RecoveryManager::MarkOnlineStart"); + + const std::string rank0_serialized(record_buffer_list[0].data<char>(), + record_buffer_list[0].size()); + + const std::string rank1_serialized(record_buffer_list[1].data<char>(), + record_buffer_list[1].size()); + + v2::InternalRecoveryRecord rank0; + YACL_ENFORCE(rank0.ParseFromString(rank0_serialized)); + + v2::InternalRecoveryRecord rank1; + YACL_ENFORCE(rank1.ParseFromString(rank1_serialized)); + + if (rank0.stage() >= v2::RecoveryCheckpoint::STAGE_ONLINE_END && + rank1.stage() >= v2::RecoveryCheckpoint::STAGE_ONLINE_END) { + online_stage_finished = true; + } + + if (lctx->Rank() == 0) { + ecdh_dual_masked_cnt_from_peer_ = + rank1.ecdh_dual_masked_item_peer_count(); + parsed_bucket_count_from_peer_ = rank1.parsed_bucket_count(); + } else { + ecdh_dual_masked_cnt_from_peer_ = + rank0.ecdh_dual_masked_item_peer_count(); + parsed_bucket_count_from_peer_ = rank0.parsed_bucket_count(); + } + } + + SPDLOG_INFO( + "RecoveryManager::MarkOnlineStart ecdh_dual_masked_cnt_from_peer_ = {}", + ecdh_dual_masked_cnt_from_peer_); + + SPDLOG_INFO( + "RecoveryManager::MarkOnlineStart parsed_bucket_count_from_peer_ = " + "{}", + parsed_bucket_count_from_peer_); + + if (checkpoint_.stage() < v2::RecoveryCheckpoint::STAGE_ONLINE_START) { + checkpoint_.set_stage(v2::RecoveryCheckpoint::STAGE_ONLINE_START); + + // save checkpoint file + SaveCheckpointFile(); + } + + return online_stage_finished; +} + +void RecoveryManager::UpdateEcdhDualMaskedItemSelfCount(uint64_t cnt) { + checkpoint_.set_stage(v2::RecoveryCheckpoint::STAGE_ONLINE_START); + checkpoint_.set_ecdh_dual_masked_item_self_count(cnt); + + // save checkpoint file + SaveCheckpointFile(); +} + +void RecoveryManager::UpdateEcdhDualMaskedItemPeerCount(uint64_t cnt) { + SPDLOG_INFO("RecoveryManager::UpdateEcdhDualMaskedItemPeerCount, cnt = {}", + cnt); + checkpoint_.set_stage(v2::RecoveryCheckpoint::STAGE_ONLINE_START); + checkpoint_.set_ecdh_dual_masked_item_peer_count(cnt); + + // save checkpoint file + SaveCheckpointFile(); +} + +void RecoveryManager::UpdateParsedBucketCount(uint64_t cnt) { + SPDLOG_INFO("RecoveryManager::UpdateParsedBucketCount, cnt = {}", cnt); + checkpoint_.set_stage(v2::RecoveryCheckpoint::STAGE_ONLINE_START); + checkpoint_.set_parsed_bucket_count(cnt); + + // save checkpoint file + SaveCheckpointFile(); +} + +void RecoveryManager::MarkOnlineEnd() { + if (checkpoint_.stage() < v2::RecoveryCheckpoint::STAGE_ONLINE_END) { + checkpoint_.set_stage(v2::RecoveryCheckpoint::STAGE_ONLINE_END); + + // save checkpoint file + SaveCheckpointFile(); + } +} + +void RecoveryManager::MarkPostProcessEnd() { + if (checkpoint_.stage() < v2::RecoveryCheckpoint::STAGE_POST_PROCESS_END) { + checkpoint_.set_stage(v2::RecoveryCheckpoint::STAGE_POST_PROCESS_END); + + // save checkpoint file + SaveCheckpointFile(); + } +} + +} // namespace psi::psi \ No newline at end of file diff --git a/psi/psi/recovery.h b/psi/psi/recovery.h new file mode 100644 index 00000000..4b148fb2 --- /dev/null +++ b/psi/psi/recovery.h @@ -0,0 +1,128 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <cstddef> +#include <cstdint> +#include <filesystem> +#include <string> + +#include "yacl/link/link.h" + +#include "psi/psi/cryptor/ecc_cryptor.h" + +#include "psi/proto/psi.pb.h" + +namespace psi::psi { + +v2::RecoveryCheckpoint LoadRecoveryCheckpointFromFile( + const std::filesystem::path& path); + +// TODO(junfeng): There are some remaining work to do: +// 1. Check the integrity of cache. +// 2. Handle the situation when cache is malformed. +// Utils for recovery. +class RecoveryManager { + public: + // folder_path must exist. + explicit RecoveryManager(const std::string& folder_path); + + void MarkInitEnd(const v2::PsiConfig& config, + const std::string& input_hash_digest); + + void MarkPreProcessEnd(const std::shared_ptr<IEccCryptor>& cryptor = nullptr); + + // Returns whether online stage is finished at this moment. + bool MarkOnlineStart( + const std::shared_ptr<yacl::link::Context>& lctx = nullptr); + + void UpdateEcdhDualMaskedItemSelfCount(uint64_t cnt); + + void UpdateEcdhDualMaskedItemPeerCount(uint64_t cnt); + + void UpdateParsedBucketCount(uint64_t cnt); + + void MarkOnlineEnd(); + + void MarkPostProcessEnd(); + + // The path of recovery file root. + std::filesystem::path folder_path() const { return folder_path_; } + + // The path of checkpoint file which stores RecoveryCheckpoint. + std::filesystem::path checkpoint_file_path() const { + return checkpoint_file_path_; + } + + // The path of private key file. + std::filesystem::path private_key_file_path() const { + return private_key_file_path_; + } + + // The folder to save ECDH dual masked item from self. + std::filesystem::path ecdh_dual_masked_self_cache_path() const { + return ecdh_dual_masked_self_cache_path_; + } + + // The folder to save ECDH dual masked item from peer. + std::filesystem::path ecdh_dual_masked_peer_cache_path() const { + return ecdh_dual_masked_peer_cache_path_; + } + + // The folder to store KKRT and RR22 input bucket store. + std::filesystem::path input_bucket_store_path() const { + return input_bucket_store_path_; + } + + bool post_precess_stage_finished() const { + return checkpoint_.stage() == + v2::RecoveryCheckpoint::STAGE_POST_PROCESS_END; + } + + uint64_t ecdh_dual_masked_cnt_from_peer() const { + return ecdh_dual_masked_cnt_from_peer_; + } + + uint64_t parsed_bucket_count_from_peer() const { + return parsed_bucket_count_from_peer_; + } + + v2::RecoveryCheckpoint checkpoint() const { return checkpoint_; } + + private: + void SaveCheckpointFile(); + + std::mutex mutex_; + + std::filesystem::path folder_path_; + + std::filesystem::path checkpoint_file_path_; + + std::filesystem::path private_key_file_path_; + + std::filesystem::path ecdh_dual_masked_self_cache_path_; + + std::filesystem::path ecdh_dual_masked_peer_cache_path_; + + std::filesystem::path input_bucket_store_path_; + + v2::RecoveryCheckpoint checkpoint_; + + uint64_t ecdh_dual_masked_cnt_from_peer_ = 0; + + uint64_t parsed_bucket_count_from_peer_ = 0; +}; + +} // namespace psi::psi diff --git a/psi/psi/recovery_test.cc b/psi/psi/recovery_test.cc new file mode 100644 index 00000000..a7c11d08 --- /dev/null +++ b/psi/psi/recovery_test.cc @@ -0,0 +1,136 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/recovery.h" + +#include <gtest/gtest.h> + +#include <filesystem> +#include <memory> + +#include "gtest/gtest.h" + +#include "psi/psi/cryptor/cryptor_selector.h" + +#include "psi/proto/psi.pb.h" + +namespace psi::psi { + +class RecoveryTest : public ::testing::Test { + protected: + void SetUp() override { + tmp_dir_ = "./tmp"; + std::filesystem::create_directory(tmp_dir_); + } + void TearDown() override { + if (!tmp_dir_.empty()) { + std::error_code ec; + std::filesystem::remove_all(tmp_dir_, ec); + // Leave error as it is, do nothing + } + } + + std::string tmp_dir_; +}; + +TEST_F(RecoveryTest, Mark) { + RecoveryManager mgr(tmp_dir_); + + v2::PsiConfig config; + config.set_self_link_party("alice"); + mgr.MarkInitEnd(config, "input_hash_digest"); + + { + v2::RecoveryCheckpoint pb = + LoadRecoveryCheckpointFromFile(mgr.checkpoint_file_path()); + + EXPECT_EQ(pb.stage(), v2::RecoveryCheckpoint::STAGE_INIT_END); + EXPECT_EQ(pb.config().self_link_party(), "alice"); + EXPECT_EQ(pb.input_hash_digest(), "input_hash_digest"); + } + + std::shared_ptr<IEccCryptor> ecc_cryptor = + CreateEccCryptor(CurveType::CURVE_25519); + mgr.MarkPreProcessEnd(ecc_cryptor); + { + v2::RecoveryCheckpoint pb = + LoadRecoveryCheckpointFromFile(mgr.checkpoint_file_path()); + + EXPECT_EQ(pb.stage(), v2::RecoveryCheckpoint::STAGE_PRE_PROCESS_END); + EXPECT_EQ(pb.config().self_link_party(), "alice"); + EXPECT_EQ(pb.input_hash_digest(), "input_hash_digest"); + + EXPECT_TRUE(std::filesystem::exists(mgr.private_key_file_path())); + } + + mgr.MarkOnlineStart(); + { + v2::RecoveryCheckpoint pb = + LoadRecoveryCheckpointFromFile(mgr.checkpoint_file_path()); + + EXPECT_EQ(pb.stage(), v2::RecoveryCheckpoint::STAGE_ONLINE_START); + EXPECT_EQ(pb.config().self_link_party(), "alice"); + EXPECT_EQ(pb.input_hash_digest(), "input_hash_digest"); + } + + mgr.UpdateEcdhDualMaskedItemSelfCount(100); + { + v2::RecoveryCheckpoint pb = + LoadRecoveryCheckpointFromFile(mgr.checkpoint_file_path()); + + EXPECT_EQ(pb.stage(), v2::RecoveryCheckpoint::STAGE_ONLINE_START); + EXPECT_EQ(pb.ecdh_dual_masked_item_self_count(), 100); + EXPECT_EQ(pb.ecdh_dual_masked_item_peer_count(), 0); + EXPECT_EQ(pb.config().self_link_party(), "alice"); + EXPECT_EQ(pb.input_hash_digest(), "input_hash_digest"); + } + + mgr.UpdateEcdhDualMaskedItemPeerCount(150); + { + v2::RecoveryCheckpoint pb = + LoadRecoveryCheckpointFromFile(mgr.checkpoint_file_path()); + + EXPECT_EQ(pb.stage(), v2::RecoveryCheckpoint::STAGE_ONLINE_START); + EXPECT_EQ(pb.ecdh_dual_masked_item_self_count(), 100); + EXPECT_EQ(pb.ecdh_dual_masked_item_peer_count(), 150); + EXPECT_EQ(pb.config().self_link_party(), "alice"); + EXPECT_EQ(pb.input_hash_digest(), "input_hash_digest"); + } + + mgr.MarkOnlineEnd(); + { + v2::RecoveryCheckpoint pb = + LoadRecoveryCheckpointFromFile(mgr.checkpoint_file_path()); + + EXPECT_EQ(pb.stage(), v2::RecoveryCheckpoint::STAGE_ONLINE_END); + EXPECT_EQ(pb.ecdh_dual_masked_item_self_count(), 100); + EXPECT_EQ(pb.ecdh_dual_masked_item_peer_count(), 150); + EXPECT_EQ(pb.config().self_link_party(), "alice"); + EXPECT_EQ(pb.input_hash_digest(), "input_hash_digest"); + } + + mgr.MarkPostProcessEnd(); + { + v2::RecoveryCheckpoint pb = + LoadRecoveryCheckpointFromFile(mgr.checkpoint_file_path()); + + EXPECT_EQ(pb.stage(), v2::RecoveryCheckpoint::STAGE_POST_PROCESS_END); + EXPECT_EQ(pb.ecdh_dual_masked_item_self_count(), 100); + EXPECT_EQ(pb.ecdh_dual_masked_item_peer_count(), 150); + EXPECT_EQ(pb.config().self_link_party(), "alice"); + EXPECT_EQ(pb.input_hash_digest(), "input_hash_digest"); + } +} + +} // namespace psi::psi diff --git a/psi/psi/rr22/BUILD.bazel b/psi/psi/rr22/BUILD.bazel new file mode 100644 index 00000000..2d09ef07 --- /dev/null +++ b/psi/psi/rr22/BUILD.bazel @@ -0,0 +1,51 @@ +# Copyright 2023 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//bazel:psi.bzl", "psi_cc_library") + +package(default_visibility = ["//visibility:public"]) + +psi_cc_library( + name = "common", + srcs = ["common.cc"], + hdrs = ["common.h"], + deps = [ + "//psi/proto:psi_cc_proto", + "//psi/psi:bucket", + "//psi/psi:recovery", + "//psi/psi/core/vole_psi:rr22_psi", + ], +) + +psi_cc_library( + name = "receiver", + srcs = ["receiver.cc"], + hdrs = ["receiver.h"], + deps = [ + ":common", + "//psi/psi:interface", + "//psi/psi/utils:arrow_csv_batch_provider", + ], +) + +psi_cc_library( + name = "sender", + srcs = ["sender.cc"], + hdrs = ["sender.h"], + deps = [ + ":common", + "//psi/psi:interface", + "//psi/psi/utils:arrow_csv_batch_provider", + ], +) diff --git a/psi/psi/rr22/common.cc b/psi/psi/rr22/common.cc new file mode 100644 index 00000000..f5554a15 --- /dev/null +++ b/psi/psi/rr22/common.cc @@ -0,0 +1,45 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/rr22/common.h" + +#include <omp.h> + +#include "psi/psi/bucket.h" + +namespace psi::psi::rr22 { + +Rr22PsiOptions GenerateRr22PsiOptions(bool low_comm_mode) { + Rr22PsiOptions options(kDefaultSSP, omp_get_num_procs(), kDefaultCompress); + options.mode = + low_comm_mode ? Rr22PsiMode::LowCommMode : Rr22PsiMode::FastMode; + + return options; +} + +void CommonInit(const std::string& key_hash_digest, v2::PsiConfig* config, + RecoveryManager* recovery_manager, + bool* need_intersection_deduplication) { + if (config->protocol_config().rr22_config().bucket_size() == 0) { + config->mutable_protocol_config()->mutable_rr22_config()->set_bucket_size( + kDefaultBucketSize); + } + + if (recovery_manager) { + *need_intersection_deduplication = true; + recovery_manager->MarkInitEnd(*config, key_hash_digest); + } +} + +} // namespace psi::psi::rr22 diff --git a/psi/psi/rr22/common.h b/psi/psi/rr22/common.h new file mode 100644 index 00000000..5861b13d --- /dev/null +++ b/psi/psi/rr22/common.h @@ -0,0 +1,38 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include <cstdint> + +#include "psi/psi/core/vole_psi/rr22_oprf.h" +#include "psi/psi/core/vole_psi/rr22_psi.h" +#include "psi/psi/recovery.h" + +#include "psi/proto/psi.pb.h" + +namespace psi::psi::rr22 { + +// Statistical security parameter +constexpr size_t kDefaultSSP = 40; + +// Whether compress the OPRF outputs +constexpr bool kDefaultCompress = true; + +Rr22PsiOptions GenerateRr22PsiOptions(bool low_comm_mode); + +void CommonInit(const std::string& key_hash_digest, v2::PsiConfig* config, + RecoveryManager* recovery_manager, + bool* need_intersection_deduplication); + +} // namespace psi::psi::rr22 diff --git a/psi/psi/rr22/receiver.cc b/psi/psi/rr22/receiver.cc new file mode 100644 index 00000000..47554d08 --- /dev/null +++ b/psi/psi/rr22/receiver.cc @@ -0,0 +1,177 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/rr22/receiver.h" + +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/utils/parallel.h" + +#include "psi/psi/bucket.h" +#include "psi/psi/bucket_psi.h" +#include "psi/psi/prelude.h" +#include "psi/psi/rr22/common.h" +#include "psi/psi/trace_categories.h" +#include "psi/psi/utils/serialize.h" +#include "psi/psi/utils/utils.h" + +namespace psi::psi::rr22 { + +Rr22PSIReceiver::Rr22PSIReceiver(const v2::PsiConfig &config, + std::shared_ptr<yacl::link::Context> lctx) + : AbstractPSIReceiver(config, std::move(lctx)) {} + +void Rr22PSIReceiver::Init() { + TRACE_EVENT("init", "Rr22PSIReceiver::Init"); + SPDLOG_INFO("[Rr22PSIReceiver::Init] start"); + + AbstractPSIReceiver::Init(); + + CommonInit(key_hash_digest_, &config_, recovery_manager_.get(), + &need_intersection_deduplication_); + SPDLOG_INFO("[Rr22PSIReceiver::Init] end"); +} + +void Rr22PSIReceiver::PreProcess() { + TRACE_EVENT("pre-process", "Rr22PSIReceiver::PreProcess"); + SPDLOG_INFO("[Rr22PSIReceiver::PreProcess] start"); + + if (digest_equal_) { + return; + } + + bucket_count_ = + NegotiateBucketNum(lctx_, report_.original_count(), + config_.protocol_config().rr22_config().bucket_size(), + config_.protocol_config().protocol()); + + if (bucket_count_ > 0) { + std::vector<std::string> keys(config_.keys().begin(), config_.keys().end()); + + auto gen_input_bucket_f = std::async([&] { + if (recovery_manager_) { + input_bucket_store_ = CreateCacheFromCsv( + config_.input_config().path(), keys, + recovery_manager_->input_bucket_store_path(), bucket_count_); + } else { + input_bucket_store_ = CreateCacheFromCsv( + config_.input_config().path(), keys, + std::filesystem::path(config_.input_config().path()).parent_path(), + bucket_count_); + } + }); + + SyncWait(lctx_, &gen_input_bucket_f); + } + + if (recovery_manager_) { + recovery_manager_->MarkPreProcessEnd(); + } + + SPDLOG_INFO("[Rr22PSIReceiver::PreProcess] end"); +} + +void Rr22PSIReceiver::Online() { + TRACE_EVENT("online", "Rr22PSIReceiver::Online"); + SPDLOG_INFO("[Rr22PSIReceiver::Online] start"); + + if (digest_equal_) { + return; + } + + if (bucket_count_ == 0) { + return; + } + + bool online_stage_finished = + recovery_manager_ ? recovery_manager_->MarkOnlineStart(lctx_) : false; + + if (online_stage_finished) { + return; + } + + size_t bucket_idx = + recovery_manager_ + ? std::min(recovery_manager_->parsed_bucket_count_from_peer(), + recovery_manager_->checkpoint().parsed_bucket_count()) + : 0; + + Rr22PsiOptions rr22_options = GenerateRr22PsiOptions( + config_.protocol_config().rr22_config().low_comm_mode()); + + for (; bucket_idx < input_bucket_store_->BucketNum(); bucket_idx++) { + auto bucket_items_list = + PrepareBucketData(config_.protocol_config().protocol(), bucket_idx, + lctx_, input_bucket_store_.get()); + + if (!bucket_items_list.has_value()) { + continue; + } + + auto run_f = std::async([&] { + std::vector<HashBucketCache::BucketItem> res; + + std::vector<uint128_t> items_hash(bucket_items_list->size()); + yacl::parallel_for(0, bucket_items_list->size(), 1, + [&](int64_t begin, int64_t end) { + for (int64_t i = begin; i < end; ++i) { + items_hash[i] = yacl::crypto::Blake3_128( + bucket_items_list->at(i).base64_data); + } + }); + + std::vector<size_t> rr22_psi_result = + Rr22PsiReceiver(rr22_options, lctx_, items_hash); + res.reserve(rr22_psi_result.size()); + + for (auto index : rr22_psi_result) { + res.emplace_back(bucket_items_list->at(index)); + } + return res; + }); + + std::vector<HashBucketCache::BucketItem> result_list = + SyncWait(lctx_, &run_f); + + auto write_bucket_res_f = std::async([&] { + HandleBucketResultByReceiver(config_.protocol_config().broadcast_result(), + lctx_, result_list, + intersection_indices_writer_.get()); + }); + + SyncWait(lctx_, &write_bucket_res_f); + + if (recovery_manager_) { + recovery_manager_->UpdateParsedBucketCount(bucket_idx + 1); + } + } + + SPDLOG_INFO("[Rr22PSIReceiver::Online] end"); +} + +void Rr22PSIReceiver::PostProcess() { + TRACE_EVENT("post-process", "Rr22PSIReceiver::PostProcess"); + SPDLOG_INFO("[Rr22PSIReceiver::PostProcess] start"); + + if (digest_equal_) { + return; + } + + if (recovery_manager_) { + recovery_manager_->MarkPostProcessEnd(); + } + + SPDLOG_INFO("[Rr22PSIReceiver::PostProcess] end"); +} + +} // namespace psi::psi::rr22 diff --git a/psi/psi/rr22/receiver.h b/psi/psi/rr22/receiver.h new file mode 100644 index 00000000..d324b466 --- /dev/null +++ b/psi/psi/rr22/receiver.h @@ -0,0 +1,44 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "psi/psi/interface.h" +#include "psi/psi/utils/hash_bucket_cache.h" + +#include "psi/proto/psi.pb.h" + +namespace psi::psi::rr22 { + +class Rr22PSIReceiver final : public AbstractPSIReceiver { + public: + explicit Rr22PSIReceiver(const v2::PsiConfig &config, + std::shared_ptr<yacl::link::Context> lctx = nullptr); + + ~Rr22PSIReceiver() override = default; + + private: + void Init() override; + + void PreProcess() override; + + void Online() override; + + void PostProcess() override; + + uint64_t bucket_count_ = 0; + + std::unique_ptr<HashBucketCache> input_bucket_store_; +}; + +} // namespace psi::psi::rr22 diff --git a/psi/psi/rr22/sender.cc b/psi/psi/rr22/sender.cc new file mode 100644 index 00000000..0c6a54b1 --- /dev/null +++ b/psi/psi/rr22/sender.cc @@ -0,0 +1,165 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/rr22/sender.h" + +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/utils/parallel.h" + +#include "psi/psi/bucket.h" +#include "psi/psi/bucket_psi.h" +#include "psi/psi/core/vole_psi/rr22_psi.h" +#include "psi/psi/rr22/common.h" +#include "psi/psi/trace_categories.h" + +namespace psi::psi::rr22 { + +Rr22PSISender::Rr22PSISender(const v2::PsiConfig &config, + std::shared_ptr<yacl::link::Context> lctx) + : AbstractPSISender(config, std::move(lctx)) {} + +void Rr22PSISender::Init() { + TRACE_EVENT("init", "Rr22PSISender::Init"); + SPDLOG_INFO("[Rr22PSISender::Init] start"); + + AbstractPSISender::Init(); + + CommonInit(key_hash_digest_, &config_, recovery_manager_.get(), + &need_intersection_deduplication_); + SPDLOG_INFO("[Rr22PSISender::Init] end"); +} + +void Rr22PSISender::PreProcess() { + TRACE_EVENT("pre-process", "Rr22PSISender::PreProcess"); + SPDLOG_INFO("[Rr22PSISender::PreProcess] start"); + + if (digest_equal_) { + return; + } + + bucket_count_ = + NegotiateBucketNum(lctx_, report_.original_count(), + config_.protocol_config().rr22_config().bucket_size(), + config_.protocol_config().protocol()); + + if (bucket_count_ > 0) { + std::vector<std::string> keys(config_.keys().begin(), config_.keys().end()); + + auto gen_input_bucket_f = std::async([&] { + if (recovery_manager_) { + input_bucket_store_ = CreateCacheFromCsv( + config_.input_config().path(), keys, + recovery_manager_->input_bucket_store_path(), bucket_count_); + } else { + input_bucket_store_ = CreateCacheFromCsv( + config_.input_config().path(), keys, + std::filesystem::path(config_.input_config().path()).parent_path(), + bucket_count_); + } + }); + + SyncWait(lctx_, &gen_input_bucket_f); + } + + if (recovery_manager_) { + recovery_manager_->MarkPreProcessEnd(); + } + + SPDLOG_INFO("[Rr22PSISender::PreProcess] end"); +} + +void Rr22PSISender::Online() { + TRACE_EVENT("online", "Rr22PSISender::Online"); + SPDLOG_INFO("[Rr22PSISender::Online] start"); + + if (digest_equal_) { + return; + } + + if (bucket_count_ == 0) { + return; + } + + bool online_stage_finished = + recovery_manager_ ? recovery_manager_->MarkOnlineStart(lctx_) : false; + + if (online_stage_finished) { + return; + } + + size_t bucket_idx = + recovery_manager_ + ? std::min(recovery_manager_->parsed_bucket_count_from_peer(), + recovery_manager_->checkpoint().parsed_bucket_count()) + : 0; + + Rr22PsiOptions rr22_options = GenerateRr22PsiOptions( + config_.protocol_config().rr22_config().low_comm_mode()); + + for (; bucket_idx < input_bucket_store_->BucketNum(); bucket_idx++) { + auto bucket_items_list = + PrepareBucketData(config_.protocol_config().protocol(), bucket_idx, + lctx_, input_bucket_store_.get()); + + if (!bucket_items_list.has_value()) { + continue; + } + + auto run_f = std::async([&] { + std::vector<uint128_t> items_hash(bucket_items_list->size()); + yacl::parallel_for(0, bucket_items_list->size(), 1, + [&](int64_t begin, int64_t end) { + for (int64_t i = begin; i < end; ++i) { + items_hash[i] = yacl::crypto::Blake3_128( + bucket_items_list->at(i).base64_data); + } + }); + + Rr22PsiSender(rr22_options, lctx_, items_hash); + }); + + SyncWait(lctx_, &run_f); + + auto write_bucket_res_f = std::async([&] { + HandleBucketResultBySender(config_.protocol_config().broadcast_result(), + lctx_, *bucket_items_list, + intersection_indices_writer_.get()); + }); + + SyncWait(lctx_, &write_bucket_res_f); + + if (recovery_manager_) { + recovery_manager_->UpdateParsedBucketCount(bucket_idx + 1); + } + } + + SPDLOG_INFO("[Rr22PSISender::Online] end"); +} + +void Rr22PSISender::PostProcess() { + TRACE_EVENT("post-process", "Rr22PSISender::PostProcess"); + SPDLOG_INFO("[Rr22PSISender::PostProcess] start"); + + if (digest_equal_) { + return; + } + + if (recovery_manager_) { + recovery_manager_->MarkPostProcessEnd(); + } + + SPDLOG_INFO("[Rr22PSISender::PostProcess] end"); +} + +} // namespace psi::psi::rr22 diff --git a/psi/psi/rr22/sender.h b/psi/psi/rr22/sender.h new file mode 100644 index 00000000..ad142d50 --- /dev/null +++ b/psi/psi/rr22/sender.h @@ -0,0 +1,44 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "psi/psi/interface.h" +#include "psi/psi/utils/hash_bucket_cache.h" + +#include "psi/proto/psi.pb.h" + +namespace psi::psi::rr22 { + +class Rr22PSISender final : public AbstractPSISender { + public: + explicit Rr22PSISender(const v2::PsiConfig &config, + std::shared_ptr<yacl::link::Context> lctx = nullptr); + + ~Rr22PSISender() override = default; + + private: + void Init() override; + + void PreProcess() override; + + void Online() override; + + void PostProcess() override; + + uint64_t bucket_count_ = 0; + + std::unique_ptr<HashBucketCache> input_bucket_store_; +}; + +} // namespace psi::psi::rr22 diff --git a/psi/psi/trace_categories.cc b/psi/psi/trace_categories.cc new file mode 100644 index 00000000..4365e262 --- /dev/null +++ b/psi/psi/trace_categories.cc @@ -0,0 +1,18 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/trace_categories.h" + +// Reserves internal static storage for our tracing categories. +PERFETTO_TRACK_EVENT_STATIC_STORAGE(); diff --git a/psi/psi/trace_categories.h b/psi/psi/trace_categories.h new file mode 100644 index 00000000..2c66998b --- /dev/null +++ b/psi/psi/trace_categories.h @@ -0,0 +1,23 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +#include "perfetto.h" + +PERFETTO_DEFINE_CATEGORIES( + perfetto::Category("init").SetDescription("Init stage."), + perfetto::Category("pre-process").SetDescription("Pre-process stage."), + perfetto::Category("online").SetDescription("Online stage."), + perfetto::Category("post-process").SetDescription("Post-process stage."), + perfetto::Category("finalize").SetDescription("Finalize stage.")); diff --git a/psi/psi/utils/BUILD.bazel b/psi/psi/utils/BUILD.bazel new file mode 100644 index 00000000..c153795c --- /dev/null +++ b/psi/psi/utils/BUILD.bazel @@ -0,0 +1,314 @@ +# Copyright 2022 Ant Group Co., Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//bazel:psi.bzl", "psi_cc_library", "psi_cc_test") +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@rules_cc//cc:defs.bzl", "cc_proto_library") +load("@yacl//bazel:yacl.bzl", "AES_COPT_FLAGS") + +package(default_visibility = ["//visibility:public"]) + +psi_cc_library( + name = "multiplex_disk_cache", + srcs = ["multiplex_disk_cache.cc"], + hdrs = ["multiplex_disk_cache.h"], + deps = [ + "//psi/psi/io", + "@yacl//yacl/base:exception", + ], +) + +psi_cc_test( + name = "multiplex_disk_cache_test", + srcs = ["multiplex_disk_cache_test.cc"], + deps = [ + ":multiplex_disk_cache", + ], +) + +psi_cc_library( + name = "hash_bucket_cache", + srcs = ["hash_bucket_cache.cc"], + hdrs = ["hash_bucket_cache.h"], + deps = [ + ":multiplex_disk_cache", + "//psi/psi/utils:batch_provider", + "@com_google_absl//absl/strings", + ], +) + +psi_cc_library( + name = "csv_checker", + srcs = ["csv_checker.cc"], + hdrs = ["csv_checker.h"], + deps = [ + "//psi/psi/io", + "//psi/psi/utils", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", + "@org_apache_arrow//:arrow", + "@yacl//yacl/base:exception", + "@yacl//yacl/crypto/base/hash:hash_utils", + "@yacl//yacl/utils:scope_guard", + ], +) + +psi_cc_library( + name = "serialize", + hdrs = ["serialize.h"], + deps = [ + ":serializable_cc_proto", + "@yacl//yacl/base:buffer", + ], +) + +proto_library( + name = "serializable_proto", + srcs = ["serializable.proto"], +) + +cc_proto_library( + name = "serializable_cc_proto", + deps = [":serializable_proto"], +) + +psi_cc_library( + name = "csv_header_analyzer", + hdrs = ["csv_header_analyzer.h"], + deps = [ + "@yacl//yacl/base:exception", + ], +) + +psi_cc_test( + name = "csv_checker_test", + srcs = ["csv_checker_test.cc"], + deps = [ + ":csv_checker", + ], +) + +psi_cc_library( + name = "ec_point_store", + srcs = ["ec_point_store.cc"], + hdrs = ["ec_point_store.h"], + linkopts = select({ + "@bazel_tools//src/conditions:darwin": [], + "//conditions:default": ["-fopenmp"], + }), + deps = [ + ":hash_bucket_cache", + ":index_store", + ] + select({ + "@bazel_tools//src/conditions:darwin_x86_64": ["@local_homebrew_x64//:openmp"], + "@bazel_tools//src/conditions:darwin_arm64": ["@local_homebrew_arm64//:openmp"], + "//conditions:default": [], + }), +) + +psi_cc_library( + name = "batch_provider", + srcs = ["batch_provider.cc"], + hdrs = ["batch_provider.h"], + deps = [ + ":csv_header_analyzer", + ":utils", + "//psi/psi/io", + "@com_google_absl//absl/strings", + "@yacl//yacl/base:exception", + "@yacl//yacl/crypto/utils:rand", + ], +) + +psi_cc_library( + name = "resource", + srcs = ["resource.cc"], + hdrs = ["resource.h"], + deps = [ + ":hash_bucket_cache", + "@com_google_absl//absl/strings", + "@yacl//yacl/base:exception", + ], +) + +psi_cc_library( + name = "test_utils", + hdrs = [ + "test_utils.h", + ], + deps = [ + "//psi/psi:psi_cc_proto", + "@yacl//yacl/crypto/base/hash:hash_utils", + ], +) + +psi_cc_library( + name = "utils", + srcs = ["utils.cc"], + hdrs = ["utils.h"], + deps = [ + ":csv_header_parser", + ":index_store", + ":serialize", + "//psi/psi/io", + "@yacl//yacl/base:exception", + "@yacl//yacl/crypto/utils:rand", + "@yacl//yacl/link", + ], +) + +psi_cc_library( + name = "emp_io_adapter", + srcs = ["emp_io_adapter.cc"], + hdrs = [ + "emp_io_adapter.h", + ], + deps = [ + "@com_github_emptoolkit_emp_tool//:emp-tool", + "@yacl//yacl/link", + ], +) + +psi_cc_test( + name = "emp_io_adapter_test", + srcs = ["emp_io_adapter_test.cc"], + copts = AES_COPT_FLAGS, + deps = [ + "emp_io_adapter", + ], +) + +psi_cc_library( + name = "ub_psi_cache", + srcs = ["ub_psi_cache.cc"], + hdrs = ["ub_psi_cache.h"], + deps = [ + ":batch_provider", + "//psi/psi/io", + "@yacl//yacl/base:exception", + ], +) + +psi_cc_test( + name = "ub_psi_cache_test", + srcs = ["ub_psi_cache_test.cc"], + deps = [ + ":ub_psi_cache", + "@com_google_absl//absl/time", + "@yacl//yacl/crypto/utils:rand", + "@yacl//yacl/utils:scope_guard", + ], +) + +psi_cc_library( + name = "progress", + srcs = ["progress.cc"], + hdrs = ["progress.h"], + deps = [ + "@com_github_fmtlib_fmt//:fmtlib", + ], +) + +psi_cc_test( + name = "progress_test", + srcs = ["progress_test.cc"], + deps = [ + ":progress", + "@com_google_googletest//:gtest", + ], +) + +psi_cc_library( + name = "file", + srcs = ["file.cc"], + hdrs = ["file.h"], + deps = [ + "@yacl//yacl/base:exception", + ], +) + +psi_cc_library( + name = "arrow_csv_batch_provider", + srcs = ["arrow_csv_batch_provider.cc"], + hdrs = ["arrow_csv_batch_provider.h"], + deps = [ + ":batch_provider", + "@org_apache_arrow//:arrow", + ], +) + +psi_cc_test( + name = "arrow_csv_batch_provider_test", + srcs = ["arrow_csv_batch_provider_test.cc"], + deps = [ + ":arrow_csv_batch_provider", + ], +) + +psi_cc_library( + name = "inner_join", + srcs = ["inner_join.cc"], + hdrs = ["inner_join.h"], + deps = [ + "//psi/proto:psi_cc_proto", + "//psi/psi/utils", + "@org_apache_arrow//:arrow", + "@yacl//yacl/base:exception", + "@yacl//yacl/link", + ], +) + +psi_cc_test( + name = "inner_join_test", + srcs = ["inner_join_test.cc"], + deps = [ + ":inner_join", + ], +) + +psi_cc_library( + name = "index_store", + srcs = ["index_store.cc"], + hdrs = ["index_store.h"], + deps = [ + "@org_apache_arrow//:arrow", + "@yacl//yacl/base:exception", + ], +) + +psi_cc_test( + name = "index_store_test", + srcs = ["index_store_test.cc"], + deps = [ + ":index_store", + ], +) + +psi_cc_library( + name = "csv_header_parser", + srcs = ["csv_header_parser.cc"], + hdrs = ["csv_header_parser.h"], + deps = [ + "@org_apache_arrow//:arrow", + "@yacl//yacl/base:exception", + ], +) + +psi_cc_test( + name = "csv_header_parser_test", + srcs = ["csv_header_parser_test.cc"], + deps = [ + ":csv_header_parser", + ], +) diff --git a/psi/psi/utils/arrow_csv_batch_provider.cc b/psi/psi/utils/arrow_csv_batch_provider.cc new file mode 100644 index 00000000..87d791e9 --- /dev/null +++ b/psi/psi/utils/arrow_csv_batch_provider.cc @@ -0,0 +1,100 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/arrow_csv_batch_provider.h" + +#include <filesystem> + +#include "arrow/array.h" +#include "arrow/compute/api.h" +#include "arrow/datum.h" +#include "spdlog/spdlog.h" + +namespace psi::psi { + +ArrowCsvBatchProvider::ArrowCsvBatchProvider( + const std::string& file_path, const std::vector<std::string>& keys, + const std::string& separator, size_t block_size) + : block_size_(block_size), + file_path_(file_path), + keys_(keys), + separator_(separator) { + Init(); +} + +std::vector<std::string> ArrowCsvBatchProvider::ReadNextBatch() { + std::shared_ptr<arrow::RecordBatch> batch; + arrow::Status status = reader_->ReadNext(&batch); + if (!status.ok()) { + YACL_THROW("Read csv error."); + } + + if (!batch) { + SPDLOG_INFO("Reach the end of csv file {}.", file_path_); + return {}; + } + + std::vector<arrow::Datum> join_cols; + + arrow::compute::CastOptions cast_options; + + for (const auto& col : batch->columns()) { + join_cols.emplace_back( + arrow::compute::Cast(arrow::Datum(*col), arrow::utf8(), cast_options) + .ValueOrDie()); + } + + join_cols.emplace_back(arrow::MakeScalar(separator_)); + + arrow::Datum join_datum = + arrow::compute::CallFunction("binary_join_element_wise", join_cols) + .ValueOrDie(); + + std::shared_ptr<arrow::Array> join_array = std::move(join_datum).make_array(); + auto str_array = std::dynamic_pointer_cast<arrow::StringArray>(join_array); + + std::vector<std::string> res; + for (int i = 0; i < batch->num_rows(); ++i) { + res.emplace_back(str_array->GetString(i)); + } + + row_cnt_ += batch->num_rows(); + + return res; +} + +void ArrowCsvBatchProvider::Init() { + YACL_ENFORCE(std::filesystem::exists(file_path_), + "Input file {} doesn't exist.", file_path_); + + arrow::io::IOContext io_context = arrow::io::default_io_context(); + infile_ = + arrow::io::ReadableFile::Open(file_path_, arrow::default_memory_pool()) + .ValueOrDie(); + + auto read_options = arrow::csv::ReadOptions::Defaults(); + read_options.block_size = block_size_; + auto parse_options = arrow::csv::ParseOptions::Defaults(); + auto convert_options = arrow::csv::ConvertOptions::Defaults(); + + if (!keys_.empty()) { + convert_options.include_columns = keys_; + } + + reader_ = arrow::csv::StreamingReader::Make(io_context, infile_, read_options, + parse_options, convert_options) + .ValueOrDie(); +} + +} // namespace psi::psi diff --git a/psi/psi/utils/arrow_csv_batch_provider.h b/psi/psi/utils/arrow_csv_batch_provider.h new file mode 100644 index 00000000..24d7c68a --- /dev/null +++ b/psi/psi/utils/arrow_csv_batch_provider.h @@ -0,0 +1,60 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <cstddef> +#include <string> + +#include "arrow/csv/api.h" +#include "arrow/io/api.h" + +#include "psi/psi/utils/batch_provider.h" + +namespace psi::psi { + +class ArrowCsvBatchProvider : public IBasicBatchProvider { + public: + // NOTE(junfeng): block_size is not col num of each batch, which by default is + // 1 << 20 (1 Mb). + explicit ArrowCsvBatchProvider(const std::string& file_path, + const std::vector<std::string>& keys = {}, + const std::string& separator = ",", + size_t block_size = 1 << 20); + + std::vector<std::string> ReadNextBatch() override; + + [[nodiscard]] size_t row_cnt() const { return row_cnt_; } + + [[nodiscard]] size_t batch_size() const { return block_size_; } + + private: + void Init(); + + const size_t block_size_; + + const std::string file_path_; + + const std::vector<std::string> keys_; + + const std::string separator_; + + size_t row_cnt_ = 0; + + std::shared_ptr<arrow::io::ReadableFile> infile_; + + std::shared_ptr<arrow::csv::StreamingReader> reader_; +}; + +} // namespace psi::psi diff --git a/psi/psi/utils/arrow_csv_batch_provider_test.cc b/psi/psi/utils/arrow_csv_batch_provider_test.cc new file mode 100644 index 00000000..17a48147 --- /dev/null +++ b/psi/psi/utils/arrow_csv_batch_provider_test.cc @@ -0,0 +1,76 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/arrow_csv_batch_provider.h" + +#include <filesystem> +#include <memory> + +#include "gtest/gtest.h" + +namespace psi::psi { +namespace { + +constexpr auto content = R"csv(id1,id2,id3 +1,"one","first" +2,"two","second" +3,"three","third" +)csv"; + +TEST(ArrowCsvBatchProvider, works) { + std::filesystem::path file_path = std::filesystem::temp_directory_path() / + "arrow_csv_batch_provider_test.csv"; + + std::ofstream file; + file.open(file_path); + file << content; + file.close(); + + { + ArrowCsvBatchProvider provider(file_path); + EXPECT_EQ(provider.ReadNextBatch(), + std::vector<std::string>( + {"1,one,first", "2,two,second", "3,three,third"})); + EXPECT_EQ(provider.row_cnt(), 3); + EXPECT_TRUE(provider.ReadNextBatch().empty()); + EXPECT_TRUE(provider.ReadNextBatch().empty()); + EXPECT_EQ(provider.row_cnt(), 3); + } + + { + ArrowCsvBatchProvider provider(file_path, {}, "#"); + EXPECT_EQ(provider.ReadNextBatch(), + std::vector<std::string>( + {"1#one#first", "2#two#second", "3#three#third"})); + } + + { + ArrowCsvBatchProvider provider(file_path, {"id2", "id1"}); + EXPECT_EQ(provider.ReadNextBatch(), + std::vector<std::string>({"one,1", "two,2", "three,3"})); + } + + { + ArrowCsvBatchProvider provider(file_path, {"id3"}); + EXPECT_EQ(provider.ReadNextBatch(), + std::vector<std::string>({"first", "second", "third"})); + } + + std::error_code ec; + std::filesystem::remove(file_path, ec); + EXPECT_EQ(ec.value(), 0); +} + +} // namespace +} // namespace psi::psi diff --git a/psi/psi/utils/batch_provider.cc b/psi/psi/utils/batch_provider.cc new file mode 100644 index 00000000..de9d2ae4 --- /dev/null +++ b/psi/psi/utils/batch_provider.cc @@ -0,0 +1,337 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/batch_provider.h" + +#include <algorithm> +#include <future> +#include <memory> +#include <random> + +#include "absl/strings/escaping.h" +#include "absl/strings/str_join.h" +#include "absl/strings/string_view.h" +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" +#include "yacl/crypto/utils/rand.h" + +#include "psi/psi/utils/utils.h" + +namespace psi::psi { + +MemoryBatchProvider::MemoryBatchProvider(const std::vector<std::string>& items, + size_t batch_size, + const std::vector<std::string>& labels, + bool enable_shuffle) + : batch_size_(batch_size), items_(items), labels_(labels) { + if (enable_shuffle) { + shuffled_indices_.resize(items.size()); + std::iota(shuffled_indices_.begin(), shuffled_indices_.end(), 0); + std::mt19937 rng(yacl::crypto::SecureRandU64()); + std::shuffle(shuffled_indices_.begin(), shuffled_indices_.end(), rng); + } +} + +std::vector<std::string> MemoryBatchProvider::ReadNextBatch() { + std::vector<std::string> batch; + YACL_ENFORCE(cursor_index_ <= items_.size()); + size_t n_items = std::min(batch_size_, items_.size() - cursor_index_); + batch.insert(batch.end(), items_.begin() + cursor_index_, + items_.begin() + cursor_index_ + n_items); + cursor_index_ += n_items; + return batch; +} + +std::pair<std::vector<std::string>, std::vector<std::string>> +MemoryBatchProvider::ReadNextLabeledBatch() { + if (labels_.empty()) { + YACL_THROW("unsupported."); + } + + std::vector<std::string> batch_items; + std::vector<std::string> batch_labels; + + YACL_ENFORCE(cursor_index_ <= items_.size()); + size_t n_items = std::min(batch_size_, items_.size() - cursor_index_); + + batch_items.insert(batch_items.end(), items_.begin() + cursor_index_, + items_.begin() + cursor_index_ + n_items); + + batch_labels.insert(batch_labels.end(), labels_.begin() + cursor_index_, + labels_.begin() + cursor_index_ + n_items); + + cursor_index_ += n_items; + return std::make_pair(batch_items, batch_labels); +} + +std::tuple<std::vector<std::string>, std::vector<size_t>, std::vector<size_t>> +MemoryBatchProvider::ReadNextShuffledBatch() { + if (shuffled_indices_.empty()) { + YACL_THROW("unsupported."); + } + + std::vector<std::string> batch_data; + std::vector<size_t> batch_indices; + std::vector<size_t> shuffle_indices; + + YACL_ENFORCE(cursor_index_ <= items_.size()); + size_t n_items = std::min(batch_size_, items_.size() - cursor_index_); + for (size_t i = 0; i < n_items; ++i) { + size_t shuffled_index = shuffled_indices_[cursor_index_ + i]; + batch_data.push_back(items_[shuffled_index]); + batch_indices.push_back(cursor_index_ + i); + shuffle_indices.push_back(shuffled_index); + } + + cursor_index_ += n_items; + + return std::make_tuple(batch_data, batch_indices, shuffle_indices); +} + +const std::vector<std::string>& MemoryBatchProvider::items() const { + return items_; +} + +const std::vector<std::string>& MemoryBatchProvider::labels() const { + if (labels_.empty()) { + YACL_THROW("unsupported."); + } else { + return labels_; + } +} + +const std::vector<size_t>& MemoryBatchProvider::shuffled_indices() const { + if (shuffled_indices_.empty()) { + YACL_THROW("unsupported."); + } else { + return shuffled_indices_; + } +} + +CsvBatchProvider::CsvBatchProvider(const std::string& path, + const std::vector<std::string>& item_fields, + size_t batch_size, + const std::vector<std::string>& label_fields) + : batch_size_(batch_size), path_(path), item_analyzer_(path, item_fields) { + in_ = io::BuildInputStream(io::FileIoOptions(path_)); + // skip header + std::string line; + in_->GetLine(&line); + + if (!label_fields.empty()) { + label_analyzer_ = std::make_unique<CsvHeaderAnalyzer>(path, label_fields); + } +} + +std::vector<std::string> CsvBatchProvider::ReadNextBatch() { + std::vector<std::string> ret; + std::string line; + while (in_->GetLine(&line)) { + std::vector<absl::string_view> tokens = absl::StrSplit(line, ','); + std::vector<absl::string_view> targets; + for (size_t fidx : item_analyzer_.target_indices()) { + YACL_ENFORCE(fidx < tokens.size(), + "Illegal line due to no field at index={}, line={}", fidx, + line); + targets.push_back(absl::StripAsciiWhitespace(tokens[fidx])); + } + ret.push_back(KeysJoin(targets)); + if (ret.size() == batch_size_) { + break; + } + } + return ret; +} + +std::pair<std::vector<std::string>, std::vector<std::string>> +CsvBatchProvider::ReadNextLabeledBatch() { + if (!label_analyzer_) { + YACL_THROW("unsupported."); + } + + std::pair<std::vector<std::string>, std::vector<std::string>> ret; + std::string line; + + while (in_->GetLine(&line)) { + std::vector<absl::string_view> tokens = absl::StrSplit(line, ','); + std::vector<absl::string_view> items; + std::vector<absl::string_view> labels; + for (size_t fidx : item_analyzer_.target_indices()) { + YACL_ENFORCE(fidx < tokens.size(), + "Illegal line due to no field at index={}, line={}", fidx, + line); + items.push_back(absl::StripAsciiWhitespace(tokens[fidx])); + } + for (size_t fidx : label_analyzer_->target_indices()) { + YACL_ENFORCE(fidx < tokens.size(), + "Illegal line due to no field at index={}, line={}", fidx, + line); + labels.push_back(absl::StripAsciiWhitespace(tokens[fidx])); + } + + ret.first.push_back(KeysJoin(items)); + ret.second.push_back(KeysJoin(labels)); + + if (ret.first.size() == batch_size_) { + break; + } + } + + return ret; +} + +CachedCsvBatchProvider::CachedCsvBatchProvider( + const std::string& path, const std::vector<std::string>& target_fields, + size_t batch_size, size_t bucket_size, bool shuffle) + : batch_size_(batch_size), bucket_size_(bucket_size), shuffle_(shuffle) { + provider_ = + std::make_shared<CsvBatchProvider>(path, target_fields, bucket_size); + + ReadAndShuffle(0, false); + if (!file_end_flag_) { + ReadAndShuffle(1, true); + } +} + +std::tuple<std::vector<std::string>, std::vector<size_t>, std::vector<size_t>> +CachedCsvBatchProvider::ReadNextShuffledBatch() { + std::unique_lock lk(read_mutex_); + + std::vector<std::string> batch_data; + std::vector<size_t> batch_indices; + std::vector<size_t> shuffle_indices; + + if (file_end_flag_ && bucket_items_[bucket_index_].empty()) { + SPDLOG_INFO("bucket_index_:{}, {}-{}", bucket_index_, + bucket_items_[0].size(), bucket_items_[1].size()); + return std::make_tuple(batch_data, batch_indices, shuffle_indices); + } + YACL_ENFORCE(cursor_index_ <= bucket_items_[bucket_index_].size()); + + size_t n_items = std::min( + batch_size_, bucket_items_[bucket_index_].size() - cursor_index_); + + for (size_t i = 0; i < n_items; ++i) { + size_t shuffled_index = shuffled_indices_[bucket_index_][cursor_index_ + i]; + batch_data.push_back(bucket_items_[bucket_index_][shuffled_index]); + batch_indices.push_back(bucket_count_ * bucket_size_ + cursor_index_ + i); + shuffle_indices.push_back(bucket_count_ * bucket_size_ + shuffled_index); + } + + cursor_index_ += n_items; + + if (cursor_index_ == bucket_items_[bucket_index_].size()) { + SPDLOG_INFO("cursor_index_:{} n_items:{} batch_size:{}", cursor_index_, + n_items, batch_size_); + std::unique_lock lk(bucket_mutex_[bucket_index_]); + bucket_items_[bucket_index_].resize(0); + cursor_index_ = 0; + } + + if (n_items == batch_size_) { + return std::make_tuple(batch_data, batch_indices, shuffle_indices); + } + + size_t next_index = 1 - bucket_index_; + { + // get next_index lock + std::unique_lock lk(bucket_mutex_[next_index]); + + if (!bucket_items_[next_index].empty()) { + SPDLOG_INFO("lock idx:{}", next_index); + + size_t left_size = batch_size_ - n_items; + + cursor_index_ = 0; + bucket_count_++; + size_t m_items = std::min(left_size, bucket_items_[next_index].size()); + + for (size_t i = 0; i < m_items; ++i) { + size_t shuffled_index = + shuffled_indices_[next_index][cursor_index_ + i]; + batch_data.push_back(bucket_items_[next_index][shuffled_index]); + batch_indices.push_back(bucket_count_ * bucket_size_ + cursor_index_ + + i); + shuffle_indices.push_back(bucket_count_ * bucket_size_ + + shuffled_index); + } + + if (m_items == bucket_items_[next_index].size()) { + cursor_index_ = 0; + bucket_items_[next_index].resize(0); + } else { + cursor_index_ += m_items; + } + n_items += m_items; + + // read + SPDLOG_INFO("read next bucket, n_items:{} m_items:{}", n_items, m_items); + if (!file_end_flag_) { + ReadAndShuffle(bucket_index_, true); + } + + bucket_index_ = next_index; + + SPDLOG_INFO("unlock idx:{}", next_index); + } + } + + return std::make_tuple(batch_data, batch_indices, shuffle_indices); +} + +void CachedCsvBatchProvider::ReadAndShuffle(size_t read_index, + bool thread_model) { + SPDLOG_INFO("begin func for ReadAndShuffle read_index:{}", read_index); + + std::unique_lock<std::mutex> lk(bucket_mutex_[read_index]); + + auto read_proc = [&](int idx) -> void { + SPDLOG_INFO( + "Begin thread ReadAndShuffle next bucket, read_index:{} " + "bucket_size_:{}", + idx, bucket_size_); + + SPDLOG_INFO("lock idx:{}", idx); + { + std::unique_lock<std::mutex> file_lk(file_mutex_); + bucket_items_[idx] = provider_->ReadNextBatch(); + if (bucket_items_[idx].empty() || + (bucket_items_[idx].size() < bucket_size_)) { + file_end_flag_ = true; + } + + shuffled_indices_[idx].resize(bucket_items_[idx].size()); + std::iota(shuffled_indices_[idx].begin(), shuffled_indices_[idx].end(), + 0); + } + + if (shuffle_ && !bucket_items_[idx].empty()) { + std::mt19937 rng(yacl::crypto::SecureRandU64()); + std::shuffle(shuffled_indices_[idx].begin(), shuffled_indices_[idx].end(), + rng); + } + SPDLOG_INFO("unlock idx:{}", idx); + + SPDLOG_INFO("End thread ReadAndShuffle next bucket[{}] {}", idx, + bucket_items_[idx].size()); + }; + + f_read_[read_index] = std::async(std::launch::async, read_proc, read_index); + if (!thread_model) { + f_read_[read_index].get(); + } + SPDLOG_INFO("end func ReadAndShuffle read_index:{}", read_index); +} + +} // namespace psi::psi diff --git a/psi/psi/utils/batch_provider.h b/psi/psi/utils/batch_provider.h new file mode 100644 index 00000000..2fe81ff1 --- /dev/null +++ b/psi/psi/utils/batch_provider.h @@ -0,0 +1,165 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <cstddef> +#include <future> +#include <memory> +#include <mutex> +#include <string> +#include <tuple> +#include <utility> +#include <vector> + +#include "psi/psi/io/io.h" +#include "psi/psi/utils/csv_header_analyzer.h" + +namespace psi::psi { + +/// Interface which produce batch of strings. +class IBatchProvider { + public: + virtual ~IBatchProvider() = default; + + [[nodiscard]] virtual size_t batch_size() const = 0; +}; + +class IBasicBatchProvider : virtual public IBatchProvider { + public: + explicit IBasicBatchProvider() : IBatchProvider() {} + + virtual ~IBasicBatchProvider() = default; + + // Read at most `batch_size` items and return them. An empty returned vector + // is treated as the end of stream. + virtual std::vector<std::string> ReadNextBatch() = 0; +}; + +class ILabeledBatchProvider : virtual public IBatchProvider { + public: + explicit ILabeledBatchProvider() : IBatchProvider() {} + + virtual ~ILabeledBatchProvider() = default; + + // Read at most `batch_size` items&labels and return them. An empty returned + // vector is treated as the end of stream. + virtual std::pair<std::vector<std::string>, std::vector<std::string>> + ReadNextLabeledBatch() = 0; +}; + +class IShuffledBatchProvider : virtual public IBatchProvider { + public: + explicit IShuffledBatchProvider() : IBatchProvider() {} + + virtual ~IShuffledBatchProvider() = default; + + // Read at most `batch_size` items and return data and shuffle index. + // An empty returned vector is treated as the end of stream. + virtual std::tuple<std::vector<std::string>, std::vector<size_t>, + std::vector<size_t>> + ReadNextShuffledBatch() = 0; +}; + +class MemoryBatchProvider : public IBasicBatchProvider, + public ILabeledBatchProvider, + public IShuffledBatchProvider { + public: + MemoryBatchProvider(const std::vector<std::string>& items, size_t batch_size, + const std::vector<std::string>& labels = {}, + bool enable_shuffle = false); + + std::vector<std::string> ReadNextBatch() override; + + std::pair<std::vector<std::string>, std::vector<std::string>> + ReadNextLabeledBatch() override; + + std::tuple<std::vector<std::string>, std::vector<size_t>, std::vector<size_t>> + ReadNextShuffledBatch() override; + + [[nodiscard]] size_t batch_size() const override { return batch_size_; } + + [[nodiscard]] const std::vector<std::string>& items() const; + + [[nodiscard]] const std::vector<std::string>& labels() const; + + [[nodiscard]] const std::vector<size_t>& shuffled_indices() const; + + private: + const size_t batch_size_; + const std::vector<std::string>& items_; + const std::vector<std::string>& labels_; + std::vector<size_t> shuffled_indices_; + size_t cursor_index_ = 0; +}; + +class CsvBatchProvider : public IBasicBatchProvider, + public ILabeledBatchProvider { + public: + CsvBatchProvider(const std::string& path, + const std::vector<std::string>& item_fields, + size_t batch_size, + const std::vector<std::string>& label_fields = {}); + + std::vector<std::string> ReadNextBatch() override; + + std::pair<std::vector<std::string>, std::vector<std::string>> + ReadNextLabeledBatch() override; + + [[nodiscard]] size_t batch_size() const override { return batch_size_; } + + private: + const size_t batch_size_; + const std::string path_; + std::unique_ptr<io::InputStream> in_; + CsvHeaderAnalyzer item_analyzer_; + std::unique_ptr<CsvHeaderAnalyzer> label_analyzer_; +}; + +class CachedCsvBatchProvider : public IShuffledBatchProvider { + public: + explicit CachedCsvBatchProvider(const std::string& path, + const std::vector<std::string>& target_fields, + size_t batch_size, + size_t bucket_size = 100000000, + bool shuffle = false); + + std::tuple<std::vector<std::string>, std::vector<size_t>, std::vector<size_t>> + ReadNextShuffledBatch() override; + + [[nodiscard]] size_t batch_size() const override { return batch_size_; } + + private: + void ReadAndShuffle(size_t read_index, bool thread_model = false); + + const size_t batch_size_; + std::shared_ptr<CsvBatchProvider> provider_; + size_t bucket_size_; + bool shuffle_; + + std::array<std::vector<std::string>, 2> bucket_items_; + std::array<std::vector<size_t>, 2> shuffled_indices_; + size_t cursor_index_ = 0; + size_t bucket_index_ = 0; + size_t bucket_count_ = 0; + + std::array<std::future<void>, 2> f_read_; + + std::array<std::mutex, 2> bucket_mutex_; + std::mutex read_mutex_; + std::mutex file_mutex_; + bool file_end_flag_ = false; +}; + +} // namespace psi::psi diff --git a/psi/psi/utils/csv_checker.cc b/psi/psi/utils/csv_checker.cc new file mode 100644 index 00000000..4ec56b33 --- /dev/null +++ b/psi/psi/utils/csv_checker.cc @@ -0,0 +1,281 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/csv_checker.h" + +#include <fmt/core.h> + +#include <cstddef> +#include <filesystem> +#include <string> +#include <unordered_set> + +#include "absl/strings/escaping.h" +#include "absl/strings/str_join.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "arrow/api.h" +#include "arrow/csv/api.h" +#include "arrow/io/api.h" +#include "arrow/ipc/api.h" +#include "arrow/pretty_print.h" +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" +#include "yacl/crypto/base/hash/hash_utils.h" +#include "yacl/utils/scope_guard.h" + +#include "psi/psi/io/io.h" +#include "psi/psi/utils/utils.h" + +namespace psi::psi { +namespace { +// Check if the first line starts with BOM(Byte Order Mark). +bool CheckIfBOMExists(const std::string& file_path) { + std::string first_line; + { + io::FileIoOptions file_opts(file_path); + auto file_is = io::BuildInputStream(file_opts); + file_is->GetLine(&first_line, '\n'); + file_is->Close(); + } + + // Only detect UTF-8 BOM right now. + return first_line.length() >= 3 && first_line[0] == '\xEF' && + first_line[1] == '\xBB' && first_line[2] == '\xBF'; +} +} // namespace + +CsvChecker::CsvChecker(const std::string& csv_path, + const std::vector<std::string>& schema_names, + const std::string& tmp_cache_dir, bool skip_check) { + YACL_ENFORCE(!CheckIfBOMExists(csv_path), + "the file {} starts with BOM(Byte Order Mark).", csv_path); + + size_t duplicated_size = 0; + std::vector<std::string> duplicated_keys; + yacl::crypto::SslHash hash_obj(yacl::crypto::HashAlgorithm::SHA256); + + io::FileIoOptions file_opts(csv_path); + io::CsvOptions csv_opts; + csv_opts.read_options.file_schema.feature_names = schema_names; + csv_opts.read_options.file_schema.feature_types.resize(schema_names.size(), + io::Schema::STRING); + auto csv_reader = io::BuildReader(file_opts, csv_opts); + + auto timestamp_str = std::to_string(absl::ToUnixNanos(absl::Now())); + std::string keys_file = fmt::format("selected-keys.{}", timestamp_str); + + io::FileIoOptions tmp_file_ops(keys_file); + auto keys_os = io::BuildOutputStream(tmp_file_ops); + ON_SCOPE_EXIT([&] { + std::error_code ec; + std::filesystem::remove(tmp_file_ops.file_name, ec); + if (ec.value() != 0) { + SPDLOG_WARN("can not remove tmp file: {}, msg: {}", + tmp_file_ops.file_name, ec.message()); + } + }); + + // read csv file by row + io::ColumnVectorBatch batch; + while (csv_reader->Next(&batch)) { + for (size_t row = 0; row < batch.Shape().rows; row++) { + std::vector<absl::string_view> chosen; + for (size_t col = 0; col < batch.Shape().cols; col++) { + const auto& token = batch.At<std::string>(row, col); + YACL_ENFORCE(token.size(), "empty token in row={} field={}", + data_count_ + row, schema_names[col]); + chosen.push_back(token); + } + // if combined_id is about 128bytes + // .keys file tasks almost 12GB for 10^8 samples. + std::string combined_id = KeysJoin(chosen); + hash_obj.Update(combined_id); + if (!skip_check) { + keys_os->Write(combined_id.data(), combined_id.size()); + keys_os->Write("\n", 1); + } + } + data_count_ += batch.Shape().rows; + } + keys_os->Close(); + + if (!skip_check) { + std::string duplicated_keys_file = + fmt::format("duplicate-keys.{}", timestamp_str); + ON_SCOPE_EXIT([&] { + std::error_code ec; + std::filesystem::remove(duplicated_keys_file, ec); + if (ec.value() != 0) { + SPDLOG_WARN("can not remove tmp file: {}, msg: {}", + duplicated_keys_file, ec.message()); + } + }); + + std::string cmd = fmt::format( + "LC_ALL=C sort --buffer-size=1G --temporary-directory={} " + "--stable {} | LC_ALL=C uniq -d > {}", + tmp_cache_dir, keys_file, duplicated_keys_file); + SPDLOG_INFO("Executing duplicated scripts: {}", cmd); + int ret = system(cmd.c_str()); + YACL_ENFORCE(ret == 0, "failed to execute cmd={}, ret={}", cmd, ret); + io::FileIoOptions dup_keys_file_opts(duplicated_keys_file); + auto duplicated_is = io::BuildInputStream(dup_keys_file_opts); + std::string duplicated_key; + while (duplicated_is->GetLine(&duplicated_key)) { + if (duplicated_size++ < 10) { + duplicated_keys.push_back(duplicated_key); + } + } + // not precise size if some key repeat more than 2 times. + YACL_ENFORCE(duplicated_size == 0, "found duplicated keys: {}", + fmt::join(duplicated_keys, ",")); + } + + std::vector<uint8_t> digest = hash_obj.CumulativeHash(); + hash_digest_ = absl::BytesToHexString(absl::string_view( + reinterpret_cast<const char*>(digest.data()), digest.size())); +} + +CheckCsvReport CheckCsv(const std::string& input_file_path, + const std::vector<std::string>& keys, + bool check_duplicates, bool generate_key_hash_digest) { + CheckCsvReport report; + + auto timestamp_str = std::to_string(absl::ToUnixNanos(absl::Now())); + + std::string output_file_path = std::filesystem::temp_directory_path() / + fmt::format("{}.psi_checked", timestamp_str); + + YACL_ENFORCE(std::filesystem::exists(input_file_path), + "Input file {} doesn't exist.", input_file_path); + + // Read input file and generate output file by filtering unselected keys. + arrow::io::IOContext io_context = arrow::io::default_io_context(); + std::shared_ptr<arrow::io::ReadableFile> infile; + + infile = arrow::io::ReadableFile::Open(input_file_path, + arrow::default_memory_pool()) + .ValueOrDie(); + auto read_options = arrow::csv::ReadOptions::Defaults(); + auto parse_options = arrow::csv::ParseOptions::Defaults(); + auto convert_options = arrow::csv::ConvertOptions::Defaults(); + convert_options.include_columns = keys; + + auto reader = + arrow::csv::StreamingReader::Make(io_context, infile, read_options, + parse_options, convert_options) + .ValueOrDie(); + + const std::shared_ptr<arrow::Schema>& schema = reader->schema(); + + std::shared_ptr<arrow::io::FileOutputStream> outfile = + arrow::io::FileOutputStream::Open(output_file_path).ValueOrDie(); + + auto write_options = arrow::csv::WriteOptions::Defaults(); + write_options.include_header = true; + + auto writer = + arrow::csv::MakeCSVWriter(outfile, schema, write_options).ValueOrDie(); + + std::shared_ptr<arrow::RecordBatch> batch; + + while (true) { + arrow::Status status = reader->ReadNext(&batch); + + if (!status.ok()) { + YACL_THROW("Read csv error."); + } + + if (batch == NULL) { + // Handle end of file + break; + } + + report.num_rows += batch->num_rows(); + + if (!writer->WriteRecordBatch(*batch).ok()) { + YACL_THROW("writer WriteRecordBatch failed."); + } + } + + if (!writer->Close().ok()) { + YACL_THROW("writer Close failed."); + } + if (!outfile->Close().ok()) { + YACL_THROW("outfile Close failed."); + } + if (!infile->Close().ok()) { + YACL_THROW("infile Close failed."); + } + + // Check duplicates. + if (check_duplicates) { + std::string duplicated_output_file_path = + std::filesystem::temp_directory_path() / + fmt::format("{}.psi_checked_duplicates", timestamp_str); + + std::string cmd = fmt::format( + "LC_ALL=C tail -n +2 {} | " // skip header + "LC_ALL=C sort --buffer-size=1G " + "--stable | LC_ALL=C uniq -d > {}", + output_file_path, duplicated_output_file_path); + SPDLOG_INFO("Executing script to get duplicates: {}", cmd); + int ret = system(cmd.c_str()); + YACL_ENFORCE(ret == 0, "Failed to execute cmd={}, ret={}", cmd, ret); + + infile = arrow::io::ReadableFile::Open(duplicated_output_file_path, + arrow::default_memory_pool()) + .ValueOrDie(); + size_t file_size = infile->GetSize().ValueOrDie(); + if (file_size != 0) { + report.contains_duplicates = true; + report.duplicates_keys_file_path = duplicated_output_file_path; + } else { + report.contains_duplicates = false; + } + + if (!infile->Close().ok()) { + YACL_THROW("infile Close failed."); + } + } + + // Get hash digest. + if (generate_key_hash_digest) { + std::string command(fmt::format("sha256sum {}", output_file_path)); + std::array<char, 128> buffer; + std::string result; + FILE* pipe = popen(command.c_str(), "r"); + if (!pipe) { + YACL_ENFORCE("Couldn't execute cmd: {}", command); + } + while (fgets(buffer.data(), 128, pipe) != NULL) { + result += buffer.data(); + } + int return_code = pclose(pipe); + + if (return_code != 0) { + YACL_ENFORCE("Execute cmd {} failed, return code is {}", command, + return_code); + } + + std::string hash_digest = result.substr(0, 64); + + report.key_hash_digest = hash_digest; + } + + return report; +} + +} // namespace psi::psi diff --git a/psi/psi/utils/csv_checker.h b/psi/psi/utils/csv_checker.h new file mode 100644 index 00000000..252aed99 --- /dev/null +++ b/psi/psi/utils/csv_checker.h @@ -0,0 +1,53 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <string> +#include <vector> + +namespace psi::psi { + +class CsvChecker { + public: + explicit CsvChecker(const std::string& csv_path, + const std::vector<std::string>& schema_names, + const std::string& tmp_cache_dir, + bool skip_check = false); + + uint32_t data_count() const { return data_count_; } + + std::string hash_digest() const { return hash_digest_; } + + private: + uint32_t data_count_ = 0; + + std::string hash_digest_; +}; + +struct CheckCsvReport { + uint64_t num_rows = 0; + + std::string key_hash_digest; + + bool contains_duplicates = false; + + std::string duplicates_keys_file_path; +}; + +CheckCsvReport CheckCsv(const std::string& input_file_path, + const std::vector<std::string>& keys, + bool check_duplicates, bool generate_key_hash_digest); + +} // namespace psi::psi diff --git a/psi/psi/utils/csv_checker_test.cc b/psi/psi/utils/csv_checker_test.cc new file mode 100644 index 00000000..47d5fba7 --- /dev/null +++ b/psi/psi/utils/csv_checker_test.cc @@ -0,0 +1,287 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/csv_checker.h" + +#include <gtest/gtest.h> + +#include <filesystem> +#include <fstream> + +#include "gtest/gtest.h" + +#include "psi/psi/io/io.h" + +namespace psi::psi { + +namespace { +struct TestParams { + std::string content_a; + std::string content_b; + std::vector<std::string> ids_a; + std::vector<std::string> ids_b; + uint32_t item_size_a; + uint32_t item_size_b; + bool skip_check = false; +}; + +struct FailedTestParams { + std::string content; + std::vector<std::string> ids; +}; +} // namespace + +class CsvCheckerTest : public testing::TestWithParam<TestParams> { + protected: + void SetUp() override { + tmp_file_path_a_ = "csv_checker_test_file_a"; + tmp_file_path_b_ = "csv_checker_test_file_b"; + } + void TearDown() override { + if (!tmp_file_path_a_.empty()) { + std::error_code ec; + std::filesystem::remove(tmp_file_path_a_, ec); + // Leave error as it is, do nothing + } + if (!tmp_file_path_b_.empty()) { + std::error_code ec; + std::filesystem::remove(tmp_file_path_b_, ec); + // Leave error as it is, do nothing + } + } + + std::string tmp_file_path_a_; + std::string tmp_file_path_b_; +}; + +TEST_P(CsvCheckerTest, Works) { + auto params = GetParam(); + + auto a_os = io::BuildOutputStream(io::FileIoOptions(tmp_file_path_a_)); + a_os->Write(params.content_a); + a_os->Close(); + + auto b_os = io::BuildOutputStream(io::FileIoOptions(tmp_file_path_b_)); + b_os->Write(params.content_b); + b_os->Close(); + + CsvChecker checker_a(tmp_file_path_a_, params.ids_a, "./", params.skip_check); + CsvChecker checker_b(tmp_file_path_b_, params.ids_b, "./", params.skip_check); + + ASSERT_EQ(params.item_size_a, checker_a.data_count()); + ASSERT_EQ(params.item_size_b, checker_b.data_count()); +} + +class CsvCheckerDigestEqualTest : public testing::TestWithParam<TestParams> { + protected: + void SetUp() override { + tmp_file_path_a_ = "csv_checker_test_file_a"; + tmp_file_path_b_ = "csv_checker_test_file_b"; + } + void TearDown() override { + if (!tmp_file_path_a_.empty()) { + std::error_code ec; + std::filesystem::remove(tmp_file_path_a_, ec); + // Leave error as it is, do nothing + } + if (!tmp_file_path_b_.empty()) { + std::error_code ec; + std::filesystem::remove(tmp_file_path_b_, ec); + // Leave error as it is, do nothing + } + } + + std::string tmp_file_path_a_; + std::string tmp_file_path_b_; +}; + +TEST_P(CsvCheckerDigestEqualTest, HashDigestEqual) { + auto params = GetParam(); + + auto a_os = io::BuildOutputStream(io::FileIoOptions(tmp_file_path_a_)); + std::cout << "content_a:" << params.content_a << std::endl; + a_os->Write(params.content_a); + a_os->Close(); + + auto b_os = io::BuildOutputStream(io::FileIoOptions(tmp_file_path_b_)); + std::cout << "content_b:" << params.content_b << std::endl; + b_os->Write(params.content_b); + b_os->Close(); + + CsvChecker checker_a(tmp_file_path_a_, params.ids_a, "./", params.skip_check); + CsvChecker checker_b(tmp_file_path_b_, params.ids_b, "./", params.skip_check); + + ASSERT_EQ(params.item_size_a, checker_a.data_count()); + ASSERT_EQ(params.item_size_b, checker_b.data_count()); + ASSERT_EQ(checker_a.hash_digest(), checker_b.hash_digest()); +} + +INSTANTIATE_TEST_SUITE_P(Works_Instances, CsvCheckerTest, + testing::Values(TestParams{"id\nc\nb\na\n", + "x1,x2,id\n,,a\n,,b\n,,c\n", + {"id"}, + {"id"}, + 3, + 3}, + TestParams{"x1,id\n1,a\n2,b\n3,c\n", + "x1,id\n2,b\n1,a\n3,c\n", + {"id", "x1"}, + {"id", "x1"}, + 3, + 3}, + TestParams{"x1,id\n1,a\n2,b\n3,c\n", + "x1,id\n2,b\n1,a\n3,c\n", + {"id", "x1"}, + {"id", "x1"}, + 3, + 3, + true})); + +INSTANTIATE_TEST_SUITE_P( + HashDigestEqual_Instances, CsvCheckerDigestEqualTest, + testing::Values( + TestParams{"id\nc\nb\na\n", "id\nc\nb\na\n", {"id"}, {"id"}, 3, 3}, + TestParams{"x1,id\n1,a\n2,b\n3,c\n", + "x1,id\n1,a\n2,b\n3,c\n", + {"id", "x1"}, + {"id", "x1"}, + 3, + 3})); + +class CsvCheckerFailedTest : public testing::TestWithParam<FailedTestParams> { + protected: + void SetUp() override { tmp_file_path_ = "csv_checker_failed_test_file"; } + void TearDown() override { + if (!tmp_file_path_.empty()) { + std::error_code ec; + std::filesystem::remove(tmp_file_path_, ec); + // Leave error as it is, do nothing + } + } + + std::string tmp_file_path_; +}; + +TEST_P(CsvCheckerFailedTest, FailedWorks) { + auto params = GetParam(); + + auto os = io::BuildOutputStream(io::FileIoOptions(tmp_file_path_)); + os->Write(params.content); + os->Close(); + + ASSERT_ANY_THROW(CsvChecker checker(tmp_file_path_, params.ids, "./", false)); +} + +INSTANTIATE_TEST_SUITE_P( + FailedWorks_Instances, CsvCheckerFailedTest, + testing::Values( + // ecdh + FailedTestParams{"id\nc\nb\na\n", {"i"}}, + FailedTestParams{"x1,x2,id\n1,,a\n,2,b\n,,c\n", {"x1"}}, + FailedTestParams{"id\nc\nb\na\nc\n", {"id"}}, + FailedTestParams{"x1,id\n1,a\n2,b\n3,c\n1,a\n", {"x1, id"}})); + +TEST(CheckCsvTest, works) { + { + constexpr auto content = R"csv(id1,id2,id3 +1,"one","first" +1,"two","second" +3,"three","third" +)csv"; + + std::filesystem::path file_path = std::filesystem::temp_directory_path() / + "check_csv_test_check_duplicates.csv"; + + std::ofstream file; + file.open(file_path); + file << content; + file.close(); + + auto report = CheckCsv(file_path, {"id1"}, true, false); + EXPECT_EQ(report.num_rows, 3); + EXPECT_TRUE(report.contains_duplicates); + EXPECT_TRUE(report.key_hash_digest.empty()); + + std::error_code ec; + std::filesystem::remove(report.duplicates_keys_file_path, ec); + EXPECT_EQ(ec.value(), 0); + + std::filesystem::remove(file_path, ec); + EXPECT_EQ(ec.value(), 0); + } + + { + constexpr auto content = R"csv(id1,id2,id3 +1,"one","first" + +1,"one_hat","second" + +3,"three","third" + +)csv"; + + std::filesystem::path file_path = std::filesystem::temp_directory_path() / + "check_csv_test_check_duplicates.csv"; + + std::ofstream file; + file.open(file_path); + file << content; + file.close(); + + auto report = CheckCsv(file_path, {"id1"}, true, false); + EXPECT_EQ(report.num_rows, 3); + EXPECT_TRUE(report.contains_duplicates); + EXPECT_TRUE(report.key_hash_digest.empty()); + + std::error_code ec; + std::filesystem::remove(report.duplicates_keys_file_path, ec); + EXPECT_EQ(ec.value(), 0); + + std::filesystem::remove(file_path, ec); + EXPECT_EQ(ec.value(), 0); + } + + { + constexpr auto content = R"csv(id1,id2,id3 +1,"one","first" + +1,"one","second" + +3,"three","third" + +)csv"; + + std::filesystem::path file_path = std::filesystem::temp_directory_path() / + "check_csv_test_check_duplicates.csv"; + + std::ofstream file; + file.open(file_path); + file << content; + file.close(); + + auto report = CheckCsv(file_path, {"id1"}, true, true); + EXPECT_EQ(report.num_rows, 3); + EXPECT_TRUE(report.contains_duplicates); + EXPECT_FALSE(report.key_hash_digest.empty()); + + std::error_code ec; + std::filesystem::remove(report.duplicates_keys_file_path, ec); + EXPECT_EQ(ec.value(), 0); + + std::filesystem::remove(file_path, ec); + EXPECT_EQ(ec.value(), 0); + } +} + +} // namespace psi::psi diff --git a/psi/psi/utils/csv_header_analyzer.h b/psi/psi/utils/csv_header_analyzer.h new file mode 100644 index 00000000..3df7b1af --- /dev/null +++ b/psi/psi/utils/csv_header_analyzer.h @@ -0,0 +1,129 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <algorithm> +#include <fstream> +#include <map> +#include <set> +#include <vector> + +#include "absl/strings/ascii.h" +#include "absl/strings/str_split.h" +#include "yacl/base/exception.h" + +namespace psi::psi { + +// TODO(junfeng): rm this class and replace usage with CsvHeaderParser. +class CsvHeaderAnalyzer { + public: + CsvHeaderAnalyzer(const std::string& path, + const std::vector<std::string>& target_fields) { + std::set<std::string> target_set = CheckAndNormalizeTokens(target_fields); + + std::ifstream in(path); + YACL_ENFORCE(in.is_open(), "Cannot open {}", path); + std::string line; + YACL_ENFORCE(std::getline(in, line), "Cannot read header line in {}", path); + YACL_ENFORCE(!CheckIfBOMExists(line), + "The file {} starts with BOM(Byte Order Mark).", path); + + std::vector<std::string> headers = GetCsvTokens(line); + std::map<std::string, size_t> col_index_map; + size_t idx = 0; + for (const std::string& header : headers) { + headers_.push_back(header); + col_index_map[header] = idx; + idx++; + } + // Iterate by sorted order. + for (const auto& target : target_set) { + YACL_ENFORCE(col_index_map.find(target) != col_index_map.end(), + "Cannot find feature name='{}' in CSV file header='{}'", + target, line); + target_indices_sorted_.push_back(col_index_map[target]); + } + // Iterate by target_fields sequence. + for (std::string target : target_fields) { + absl::StripAsciiWhitespace(&target); + YACL_ENFORCE(col_index_map.find(target) != col_index_map.end(), + "Cannot find feature name='{}' in CSV file header='{}'", + target, line); + target_indices_.push_back(col_index_map[target]); + } + headers_set_ = CheckAndNormalizeTokens(headers_); + header_line_ = line; + } + + // Return fields list in this csv. The sequences are same as the file header. + const std::vector<std::string>& headers() const { return headers_; } + + // Return sorted fields set in this csv. + const std::set<std::string>& headers_set() const { return headers_set_; } + + // Return interested fields indices. The indices are stored by sorted target + // field names order. + const std::vector<size_t>& target_indices_sorted() const { + return target_indices_sorted_; + } + + // Return interested fields indices. + const std::vector<size_t>& target_indices() const { return target_indices_; } + + const std::string& header_line() const { return header_line_; } + + static std::set<std::string> CheckAndNormalizeTokens( + const std::vector<std::string>& inputs) { + std::set<std::string> ret; + for (std::string input : inputs) { + absl::StripAsciiWhitespace(&input); + YACL_ENFORCE(!input.empty(), + "Found empty feature name, input feature names='{}'", + fmt::join(inputs, ",")); + ret.insert(input); + } + YACL_ENFORCE(ret.size() == inputs.size(), "Repeated feature name in ='{}'", + fmt::join(inputs, ",")); + return ret; + } + + static std::vector<std::string> GetCsvTokens(const std::string& line) { + std::vector<std::string> headers = absl::StrSplit(line, ','); + std::for_each(headers.begin(), headers.end(), + [](auto& header) { absl::StripAsciiWhitespace(&header); }); + return headers; + } + + // Check if the first line starts with BOM(Byte Order Mark). + static bool CheckIfBOMExists(const std::string& first_line) { + // Only detect UTF-8 BOM right now. + if (first_line.length() >= 3 && first_line[0] == '\xEF' && + first_line[1] == '\xBB' && first_line[2] == '\xBF') { + return true; + } else { + return false; + } + } + + private: + std::set<std::string> headers_set_; + std::vector<std::string> headers_; + // The indices are stored by sorted target field names. + std::vector<size_t> target_indices_sorted_; + std::vector<size_t> target_indices_; + std::string header_line_; +}; + +} // namespace psi::psi \ No newline at end of file diff --git a/psi/psi/utils/csv_header_parser.cc b/psi/psi/utils/csv_header_parser.cc new file mode 100644 index 00000000..1419c7b0 --- /dev/null +++ b/psi/psi/utils/csv_header_parser.cc @@ -0,0 +1,69 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/csv_header_parser.h" + +#include <cstddef> +#include <filesystem> + +#include "arrow/api.h" +#include "arrow/csv/api.h" +#include "arrow/io/api.h" +#include "yacl/base/exception.h" + +namespace psi::psi { + +CsvHeaderParser::CsvHeaderParser(const std::string& path) : path_(path) { + YACL_ENFORCE(std::filesystem::exists(path_), "Input file {} doesn't exist.", + path_); + + arrow::io::IOContext io_context = arrow::io::default_io_context(); + std::shared_ptr<arrow::io::ReadableFile> infile; + infile = arrow::io::ReadableFile::Open(path_, arrow::default_memory_pool()) + .ValueOrDie(); + auto read_options = arrow::csv::ReadOptions::Defaults(); + auto parse_options = arrow::csv::ParseOptions::Defaults(); + auto convert_options = arrow::csv::ConvertOptions::Defaults(); + auto reader = + arrow::csv::StreamingReader::Make(io_context, infile, read_options, + parse_options, convert_options) + .ValueOrDie(); + + const std::shared_ptr<arrow::Schema>& schema = reader->schema(); + + for (int i = 0; i < schema->num_fields(); i++) { + key_index_map_[schema->field(i)->name()] = i; + } + + if (!infile->Close().ok()) { + YACL_THROW("Infile {} close failed.", path_); + } +} + +std::vector<size_t> CsvHeaderParser::target_indices( + const std::vector<std::string>& target_fields, size_t offset) const { + std::vector<size_t> indices; + + for (const std::string& key : target_fields) { + if (key_index_map_.find(key) == key_index_map_.end()) { + YACL_THROW("key {} is not found in {}", key, path_); + } + + indices.emplace_back(key_index_map_.at(key) + offset); + } + + return indices; +} + +} // namespace psi::psi diff --git a/psi/psi/utils/csv_header_parser.h b/psi/psi/utils/csv_header_parser.h new file mode 100644 index 00000000..5d552bae --- /dev/null +++ b/psi/psi/utils/csv_header_parser.h @@ -0,0 +1,40 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <cstddef> +#include <set> +#include <string> +#include <unordered_map> +#include <vector> + +namespace psi::psi { + +// Just another version of CsvHeaderAnalyzer based on Apache Arrow. +class CsvHeaderParser { + public: + explicit CsvHeaderParser(const std::string& path); + + // Return interested fields indices. + [[nodiscard]] std::vector<size_t> target_indices( + const std::vector<std::string>& target_fields, size_t offset = 0) const; + + private: + std::string path_; + + std::unordered_map<std::string, size_t> key_index_map_; +}; + +} // namespace psi::psi diff --git a/psi/psi/utils/csv_header_parser_test.cc b/psi/psi/utils/csv_header_parser_test.cc new file mode 100644 index 00000000..be03e3e2 --- /dev/null +++ b/psi/psi/utils/csv_header_parser_test.cc @@ -0,0 +1,57 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/csv_header_parser.h" + +#include <cstddef> +#include <filesystem> +#include <fstream> +#include <vector> + +#include "gtest/gtest.h" + +namespace psi::psi { + +constexpr auto csv_content = R"csv(id1,id2,y1 +1,"b","y1_1" +1,"b","y1_2" +3,"c","y1_3" +4,"b","y1_4" +2,"a","y1_5" +2,"a","y1_6" +)csv"; + +TEST(CsvHeaderParserTest, Works) { + std::filesystem::path csv_path = + std::filesystem::temp_directory_path() / "csv_header_parser_test.csv"; + + { + std::ofstream file; + file.open(csv_path); + file << csv_content; + file.close(); + } + + CsvHeaderParser parser(csv_path); + EXPECT_EQ(parser.target_indices(std::vector<std::string>{"id2", "id1", "y1"}), + (std::vector<size_t>{1, 0, 2})); + + EXPECT_EQ(parser.target_indices( + std::vector<std::string>{"y1", "id2", "id2", "id1"}, 1), + (std::vector<size_t>{3, 2, 2, 1})); + + { std::filesystem::remove(csv_path); } +} + +} // namespace psi::psi diff --git a/psi/psi/utils/ec_point_store.cc b/psi/psi/utils/ec_point_store.cc new file mode 100644 index 00000000..db1698c4 --- /dev/null +++ b/psi/psi/utils/ec_point_store.cc @@ -0,0 +1,297 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/ec_point_store.h" + +#include <omp.h> + +#include <algorithm> +#include <future> +#include <optional> +#include <unordered_set> +#include <utility> + +#include "absl/strings/escaping.h" +#include "spdlog/spdlog.h" + +#include "psi/psi/utils/batch_provider.h" + +namespace psi::psi { + +void MemoryEcPointStore::Save(std::string ciphertext) { + store_.push_back(std::move(ciphertext)); + item_cnt_++; +} + +HashBucketEcPointStore::HashBucketEcPointStore(const std::string& cache_dir, + size_t num_bins, + bool use_scoped_tmp_dir) + : num_bins_(num_bins) { + cache_ = std::make_unique<HashBucketCache>(cache_dir, num_bins, + use_scoped_tmp_dir); +} + +void HashBucketEcPointStore::Save(std::string ciphertext) { + cache_->WriteItem(ciphertext); +} + +uint64_t HashBucketEcPointStore::ItemCount() { return cache_->ItemCount(); } + +HashBucketEcPointStore::~HashBucketEcPointStore() { Flush(); } + +std::vector<uint64_t> FinalizeAndComputeIndices( + const std::shared_ptr<HashBucketEcPointStore>& self, + const std::shared_ptr<HashBucketEcPointStore>& peer) { + YACL_ENFORCE_EQ(self->num_bins(), peer->num_bins()); + self->Flush(); + peer->Flush(); + + // Compute indices + std::vector<uint64_t> indices; + for (size_t bin_idx = 0; bin_idx < self->num_bins(); ++bin_idx) { + std::vector<HashBucketCache::BucketItem> self_results = + self->cache_->LoadBucketItems(bin_idx); + std::vector<HashBucketCache::BucketItem> peer_results = + peer->cache_->LoadBucketItems(bin_idx); + std::unordered_set<std::string> peer_set; + peer_set.reserve(peer_results.size()); + std::for_each(peer_results.begin(), peer_results.end(), + [&](HashBucketCache::BucketItem& item) { + peer_set.insert(std::move(item.base64_data)); + }); + for (const auto& item : self_results) { + if (peer_set.find(item.base64_data) != peer_set.end()) { + indices.push_back(item.index); + } + } + } + // Sort to make `FilterFileByIndices` happy. + std::sort(indices.begin(), indices.end()); + return indices; +} + +void FinalizeAndComputeIndices( + const std::shared_ptr<HashBucketEcPointStore>& self, + const std::shared_ptr<HashBucketEcPointStore>& peer, + IndexWriter* index_writer) { + YACL_ENFORCE_EQ(self->num_bins(), peer->num_bins()); + self->Flush(); + peer->Flush(); + + // Compute indices + for (size_t bin_idx = 0; bin_idx < self->num_bins(); ++bin_idx) { + std::vector<HashBucketCache::BucketItem> self_results = + self->cache_->LoadBucketItems(bin_idx); + std::vector<HashBucketCache::BucketItem> peer_results = + peer->cache_->LoadBucketItems(bin_idx); + std::unordered_set<std::string> peer_set; + peer_set.reserve(peer_results.size()); + std::for_each(peer_results.begin(), peer_results.end(), + [&](HashBucketCache::BucketItem& item) { + peer_set.insert(std::move(item.base64_data)); + }); + for (const auto& item : self_results) { + if (peer_set.find(item.base64_data) != peer_set.end()) { + index_writer->WriteCache(item.index); + } + } + + index_writer->Commit(); + } +} + +const std::string CachedCsvEcPointStore::cipher_id = "id"; + +CachedCsvEcPointStore::CachedCsvEcPointStore(const std::string& path, + bool enable_cache, + const std::string& party, + bool read_only) + : path_(path), + enable_cache_(enable_cache), + party_(party), + read_only_(read_only) { + if (!read_only) { + output_stream_ = io::BuildOutputStream(io::FileIoOptions(path)); + output_stream_->Write(fmt::format("{}\n", cipher_id)); + } +} + +CachedCsvEcPointStore::~CachedCsvEcPointStore() { + if (!read_only_) { + output_stream_->Close(); + } +} + +void CachedCsvEcPointStore::Save(std::string ciphertext) { + output_stream_->Write(fmt::format("{}\n", ciphertext)); + if (enable_cache_) { + cache_.insert({ciphertext, item_cnt_}); + } + + item_cnt_++; + if (item_cnt_ % 10000000 == 0) { + SPDLOG_INFO("{} item_cnt_={}", party_, item_cnt_); + } +} + +void CachedCsvEcPointStore::Save(const std::vector<std::string>& ciphertext) { + for (const auto& text : ciphertext) { + output_stream_->Write(fmt::format("{}\n", text)); + if (enable_cache_) { + cache_.insert({text, item_cnt_}); + } + + item_cnt_++; + if (item_cnt_ % 10000000 == 0) { + SPDLOG_INFO("{} item_cnt_={}", party_, item_cnt_); + } + } +} + +std::pair<std::vector<uint64_t>, std::vector<std::string>> +FinalizeAndComputeIndices(const std::shared_ptr<CachedCsvEcPointStore>& self, + const std::shared_ptr<CachedCsvEcPointStore>& peer, + size_t batch_size) { + self->Flush(); + peer->Flush(); + + SPDLOG_INFO("Begin FinalizeAndComputeIndices"); + + std::vector<uint64_t> indices; + std::vector<std::string> masked_items; + + std::vector<std::string> ids = {CachedCsvEcPointStore::cipher_id}; + CsvBatchProvider peer_provider(peer->path_, ids, batch_size); + size_t batch_count = 0; + size_t compare_thread_num = omp_get_num_procs(); + + while (true) { + SPDLOG_INFO("begin read compare batch {}", batch_count); + std::vector<std::string> batch_peer_data = peer_provider.ReadNextBatch(); + SPDLOG_INFO("end read compare batch {}", batch_count); + + if (batch_peer_data.empty()) { + break; + } + + size_t compare_size = + (batch_peer_data.size() + compare_thread_num - 1) / compare_thread_num; + + std::vector<std::vector<uint64_t>> batch_indices(compare_thread_num); + std::vector<std::vector<std::string>> batch_masked_items( + compare_thread_num); + + auto compare_proc = [&](int idx) -> void { + uint64_t begin = idx * compare_size; + uint64_t end = + std::min<uint64_t>(batch_peer_data.size(), begin + compare_size); + + for (size_t i = begin; i < end; i++) { + auto search_ret = self->cache_.find(batch_peer_data[i]); + if (search_ret != self->cache_.end()) { + batch_indices[idx].push_back(search_ret->second); + batch_masked_items[idx].push_back(batch_peer_data[i]); + } + } + }; + + std::vector<std::future<void>> f_compare(compare_thread_num); + for (size_t i = 0; i < compare_thread_num; i++) { + f_compare[i] = std::async(compare_proc, i); + } + + for (size_t i = 0; i < compare_thread_num; i++) { + f_compare[i].get(); + } + + batch_count++; + + for (const auto& r : batch_indices) { + indices.insert(indices.end(), r.begin(), r.end()); + } + for (const auto& r : batch_masked_items) { + masked_items.insert(masked_items.end(), r.begin(), r.end()); + } + SPDLOG_INFO("FinalizeAndComputeIndices, batch_count:{}", batch_count); + } + + SPDLOG_INFO("End FinalizeAndComputeIndices, batch_count:{}", batch_count); + return std::make_pair(indices, masked_items); +} + +std::vector<uint64_t> GetIndicesByItems( + const std::string& input_path, + const std::vector<std::string>& selected_fields, + const std::vector<std::string>& items, size_t batch_size) { + std::vector<uint64_t> indices; + + std::unordered_set<std::string> items_set; + + items_set.insert(items.begin(), items.end()); + + auto batch_provider = std::make_shared<CsvBatchProvider>( + input_path, selected_fields, batch_size); + + size_t compare_thread_num = omp_get_num_procs(); + + size_t item_index = 0; + size_t batch_count = 0; + while (true) { + auto batch_items = batch_provider->ReadNextBatch(); + if (batch_items.empty()) { + break; + } + + size_t compare_size = + (batch_items.size() + compare_thread_num - 1) / compare_thread_num; + + std::vector<std::vector<uint64_t>> result(compare_thread_num); + + auto compare_proc = [&](int idx) -> void { + uint64_t begin = idx * compare_size; + uint64_t end = std::min<size_t>(batch_items.size(), begin + compare_size); + + for (uint64_t i = begin; i < end; i++) { + auto search_ret = items_set.find(batch_items[i]); + if (search_ret != items_set.end()) { + result[idx].push_back(item_index + i); + } + } + }; + + std::vector<std::future<void>> f_compare(compare_thread_num); + for (size_t i = 0; i < compare_thread_num; i++) { + f_compare[i] = std::async(compare_proc, i); + } + + for (size_t i = 0; i < compare_thread_num; i++) { + f_compare[i].get(); + } + + for (const auto& r : result) { + indices.insert(indices.end(), r.begin(), r.end()); + } + + batch_count++; + + item_index += batch_items.size(); + SPDLOG_INFO("GetIndices batch count:{}, item_index:{}", batch_count, + item_index); + } + SPDLOG_INFO("Finish GetIndices, indices size:{}", indices.size()); + + return indices; +} + +} // namespace psi::psi diff --git a/psi/psi/utils/ec_point_store.h b/psi/psi/utils/ec_point_store.h new file mode 100644 index 00000000..d678591c --- /dev/null +++ b/psi/psi/utils/ec_point_store.h @@ -0,0 +1,143 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <cstddef> +#include <memory> +#include <optional> +#include <string> +#include <unordered_map> +#include <utility> +#include <vector> + +#include "yacl/link/link.h" + +#include "psi/psi/utils/hash_bucket_cache.h" +#include "psi/psi/utils/index_store.h" + +namespace psi::psi { + +class IEcPointStore { + public: + virtual ~IEcPointStore() = default; + + virtual void Save(std::string ciphertext) = 0; + + virtual void Flush() = 0; + + virtual void Save(const std::vector<std::string>& ciphertext) { + for (const auto& ct : ciphertext) { + Save(ct); + } + } + + virtual uint64_t ItemCount() = 0; +}; + +class MemoryEcPointStore : public IEcPointStore { + public: + void Save(std::string ciphertext) override; + + std::vector<std::string>& content() { return store_; } + + void Flush() override {} + + uint64_t ItemCount() override { return item_cnt_; } + + private: + std::vector<std::string> store_; + + uint64_t item_cnt_ = 0; +}; + +class HashBucketEcPointStore : public IEcPointStore { + public: + HashBucketEcPointStore(const std::string& cache_dir, size_t num_bins, + bool use_scoped_tmp_dir = true); + + ~HashBucketEcPointStore() override; + + void Save(std::string ciphertext) override; + + [[nodiscard]] size_t num_bins() const { return num_bins_; } + + void Flush() override { cache_->Flush(); } + + uint64_t ItemCount() override; + + friend std::vector<uint64_t> FinalizeAndComputeIndices( + const std::shared_ptr<HashBucketEcPointStore>& self, + const std::shared_ptr<HashBucketEcPointStore>& peer); + + friend void FinalizeAndComputeIndices( + const std::shared_ptr<HashBucketEcPointStore>& self, + const std::shared_ptr<HashBucketEcPointStore>& peer, + IndexWriter* index_writer); + + protected: + std::unique_ptr<HashBucketCache> cache_; + + const size_t num_bins_; +}; + +class CachedCsvEcPointStore : public IEcPointStore { + public: + CachedCsvEcPointStore(const std::string& path, bool enable_cache, + const std::string& party, bool read_only); + + ~CachedCsvEcPointStore() override; + + void Save(std::string ciphertext) override; + + void Save(const std::vector<std::string>& ciphertext) override; + + void Flush() override { + if (!read_only_) { + output_stream_->Flush(); + } + } + + uint64_t ItemCount() override { return item_cnt_; } + + const static std::string cipher_id; + + friend std::pair<std::vector<uint64_t>, std::vector<std::string>> + FinalizeAndComputeIndices(const std::shared_ptr<CachedCsvEcPointStore>& self, + const std::shared_ptr<CachedCsvEcPointStore>& peer, + size_t batch_size); + + protected: + const std::string path_; + + const bool enable_cache_; + + const std::string party_; + + const bool read_only_; + + std::unique_ptr<io::OutputStream> output_stream_; + + std::unordered_map<std::string, size_t> cache_; + + size_t item_cnt_ = 0; +}; + +// Get data Indices in csv file +std::vector<uint64_t> GetIndicesByItems( + const std::string& input_path, + const std::vector<std::string>& selected_fields, + const std::vector<std::string>& items, size_t batch_size); + +} // namespace psi::psi diff --git a/psi/psi/utils/emp_io_adapter.cc b/psi/psi/utils/emp_io_adapter.cc new file mode 100644 index 00000000..b0f5df27 --- /dev/null +++ b/psi/psi/utils/emp_io_adapter.cc @@ -0,0 +1,156 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/emp_io_adapter.h" + +#include <utility> + +#include "emp-tool/utils/block.h" +#include "emp-tool/utils/group.h" +#include "spdlog/spdlog.h" +#include "yacl/base/byte_container_view.h" +#include "yacl/base/int128.h" +#include "yacl/link/link.h" + +namespace psi { + +EmpIoAdapter::EmpIoAdapter(std::shared_ptr<yacl::link::Context> ctx) + : ctx_(std::move(ctx)), + send_op_(0), + recv_op_(0), + send_buffer_used_(0), + recv_buffer_used_(0) { + send_buffer_.resize(SEND_BUFFER_SIZE); +} + +EmpIoAdapter::~EmpIoAdapter() { + try { + flush(); + } catch (const std::exception &e) { + SPDLOG_ERROR("Error in flush: {}", e.what()); + } +} + +void EmpIoAdapter::flush() { + if (send_buffer_used_ == 0) { + return; + } + + ctx_->SendAsyncThrottled( + ctx_->NextRank(), + yacl::ByteContainerView(send_buffer_.data(), send_buffer_used_), + fmt::format("Cheetah send:{}", send_op_++)); + + memset(send_buffer_.data(), 0, SEND_BUFFER_SIZE); + send_buffer_used_ = 0; +} + +void EmpIoAdapter::fill_recv() { + recv_buffer_ = + ctx_->Recv(ctx_->NextRank(), fmt::format("Cheetah recv:{}", recv_op_++)); + recv_buffer_used_ = 0; +} + +void EmpIoAdapter::send_data_internal(const void *data, int len) { + size_t send_buffer_left = SEND_BUFFER_SIZE - send_buffer_used_; + if (send_buffer_left <= static_cast<size_t>(len)) { + memcpy(send_buffer_.data() + send_buffer_used_, data, send_buffer_left); + send_buffer_used_ += send_buffer_left; + flush(); + + send_data_internal(static_cast<const char *>(data) + send_buffer_left, + len - send_buffer_left); + } else { + memcpy(send_buffer_.data() + send_buffer_used_, data, len); + send_buffer_used_ += len; + } +} + +void EmpIoAdapter::recv_data_internal(void *data, int len) { + if (send_buffer_used_ > 0) { + flush(); + } + + size_t recv_buffer_left = recv_buffer_.size() - recv_buffer_used_; + if (recv_buffer_left >= static_cast<size_t>(len)) { + memcpy(data, recv_buffer_.data<uint8_t>() + recv_buffer_used_, len); + recv_buffer_used_ += len; + } else { + if (recv_buffer_.size() != 0) { + memcpy(data, recv_buffer_.data<uint8_t>() + recv_buffer_used_, + recv_buffer_left); + } + fill_recv(); + + recv_data_internal(static_cast<char *>(data) + recv_buffer_left, + len - recv_buffer_left); + } +} + +template <typename T> +void EmpIoAdapter::send_data_partial(const T *data, int len, int bitlength) { + if (bitlength == sizeof(T) * 8) { + send_data_internal(static_cast<const void *>(data), len * sizeof(T)); + return; + } + + int compact_len = (bitlength + 7) / 8; + std::vector<uint8_t> bytes(len); + for (int i = 0; i < compact_len; i++) { + for (int j = 0; j < len; j++) { + bytes[j] = static_cast<uint8_t>(data[j] >> (i * 8)); + } + send_data_internal(bytes.data(), len); + } +} + +template <typename T> +void EmpIoAdapter::recv_data_partial(T *data, int len, int bitlength) { + if (bitlength == sizeof(T) * 8) { + recv_data_internal(static_cast<void *>(data), len * sizeof(T)); + return; + } + memset(data, 0, len * sizeof(T)); + + int compact_len = (bitlength + 7) / 8; + std::vector<uint8_t> bytes(len); + for (int i = 0; i < compact_len; i++) { + recv_data_internal(bytes.data(), len); + for (int j = 0; j < len; j++) { + data[j] |= static_cast<T>(bytes[j]) << (i * 8); + } + } + T mask = (static_cast<T>(1) << bitlength) - 1; + for (int i = 0; i < len; i++) { + data[i] &= mask; + } +} + +template void EmpIoAdapter::send_data_partial<uint32_t>(const uint32_t *data, + int len, int bitlength); +template void EmpIoAdapter::send_data_partial<uint64_t>(const uint64_t *data, + int len, int bitlength); +template void EmpIoAdapter::send_data_partial<uint128_t>(const uint128_t *data, + int len, + int bitlength); + +template void EmpIoAdapter::recv_data_partial<uint32_t>(uint32_t *data, int len, + int bitlength); +template void EmpIoAdapter::recv_data_partial<uint64_t>(uint64_t *data, int len, + int bitlength); +template void EmpIoAdapter::recv_data_partial<uint128_t>(uint128_t *data, + int len, + int bitlength); + +} // namespace psi diff --git a/psi/psi/utils/emp_io_adapter.h b/psi/psi/utils/emp_io_adapter.h new file mode 100644 index 00000000..dd81a304 --- /dev/null +++ b/psi/psi/utils/emp_io_adapter.h @@ -0,0 +1,56 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "emp-tool/io/io_channel.h" +#include "yacl/base/buffer.h" +#include "yacl/link/link.h" + +namespace psi { + +class EmpIoAdapter : public emp::IOChannel<EmpIoAdapter> { + public: + std::shared_ptr<yacl::link::Context> ctx_; + + const static uint64_t SEND_BUFFER_SIZE = 1024 * 1024; + uint32_t send_op_; + uint32_t recv_op_; + + std::vector<uint8_t> send_buffer_; + uint64_t send_buffer_used_; + + yacl::Buffer recv_buffer_; + uint64_t recv_buffer_used_; + + explicit EmpIoAdapter(std::shared_ptr<yacl::link::Context> ctx); + + ~EmpIoAdapter(); + + void flush(); + + void fill_recv(); + + void send_data_internal(const void* data, int len); + + void recv_data_internal(void* data, int len); + + template <typename T> + void send_data_partial(const T* data, int len, int bitlength); + + template <typename T> + void recv_data_partial(T* data, int len, int bitlength); +}; + +} // namespace psi diff --git a/psi/psi/utils/emp_io_adapter_test.cc b/psi/psi/utils/emp_io_adapter_test.cc new file mode 100644 index 00000000..e10897b5 --- /dev/null +++ b/psi/psi/utils/emp_io_adapter_test.cc @@ -0,0 +1,79 @@ +// Copyright 2021 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/emp_io_adapter.h" + +#include <future> +#include <thread> + +#include "gtest/gtest.h" +#include "yacl/link/test_util.h" + +namespace psi { + +TEST(EmpIoAdapterTest, Test) { + const int kWorldSize = 2; + auto contexts = yacl::link::test::SetupWorld(kWorldSize); + + std::future<void> player1 = std::async([&] { + char msg[100]; + EmpIoAdapter io(contexts[0]); + io.send_data("hello", 5); + io.send_data(" world", 6); + + io.recv_data(msg, 7); + + std::cout << "player1 receive: " << msg << std::endl; + }); + + std::future<void> player2 = std::async([&] { + char msg[100]; + EmpIoAdapter io(contexts[1]); + io.recv_data(msg, 11); + + io.send_data("goodbye", 7); + + std::cout << "player2 receive: " << msg << std::endl; + }); + + player1.get(); + player2.get(); +} + +TEST(EmpIoAdapterTest, TestPartial) { + const int kWorldSize = 2; + auto contexts = yacl::link::test::SetupWorld(kWorldSize); + + std::future<void> player1 = std::async([&] { + uint64_t a[4] = {0x284, 0xf3a, 0x97e4, 0x8fa}; + EmpIoAdapter io(contexts[0]); + io.send_data_partial(a, 4, 12); + + std::cout << "player1 sends: " << a[0] << ", " << a[1] << ", " + << (a[2] & ((1 << 12) - 1)) << ", " << a[3] << std::endl; + }); + + std::future<void> player2 = std::async([&] { + uint64_t a[4]; + EmpIoAdapter io(contexts[1]); + io.recv_data_partial(a, 4, 12); + std::cout << "player2 receives: " << a[0] << ", " << a[1] << ", " << a[2] + << ", " << a[3] << std::endl; + }); + + player1.get(); + player2.get(); +} + +} // namespace psi diff --git a/psi/psi/utils/file.cc b/psi/psi/utils/file.cc new file mode 100644 index 00000000..1d90c0c1 --- /dev/null +++ b/psi/psi/utils/file.cc @@ -0,0 +1,37 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/file.h" + +#include <filesystem> + +#include "yacl/base/exception.h" + +namespace psi::psi { + +void CreateOutputFolder(const std::string& path) { + // create output folder. + auto out_dir_path = std::filesystem::path(path).parent_path(); + if (out_dir_path.empty()) { + return; // create file under CWD, no need to create parent dir + } + + std::error_code ec; + std::filesystem::create_directory(out_dir_path, ec); + YACL_ENFORCE(ec.value() == 0, + "failed to create output dir={} for path={}, reason = {}", + out_dir_path.string(), path, ec.message()); +} + +} // namespace psi::psi diff --git a/psi/psi/utils/file.h b/psi/psi/utils/file.h new file mode 100644 index 00000000..ea9bb243 --- /dev/null +++ b/psi/psi/utils/file.h @@ -0,0 +1,21 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <string> + +namespace psi::psi { + +void CreateOutputFolder(const std::string& path); + +} // namespace psi::psi diff --git a/psi/psi/utils/hash_bucket_cache.cc b/psi/psi/utils/hash_bucket_cache.cc new file mode 100644 index 00000000..dd2deb19 --- /dev/null +++ b/psi/psi/utils/hash_bucket_cache.cc @@ -0,0 +1,99 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/hash_bucket_cache.h" + +#include <filesystem> +#include <memory> +#include <utility> + +#include "absl/strings/escaping.h" +#include "absl/strings/str_split.h" +#include "spdlog/spdlog.h" + +#include "psi/psi/utils/batch_provider.h" + +namespace psi::psi { + +HashBucketCache::HashBucketCache(std::string target_dir, uint32_t bucket_num, + bool use_scoped_tmp_dir) + : bucket_num_(bucket_num), item_index_(0) { + YACL_ENFORCE(bucket_num_ > 0); + disk_cache_ = std::make_unique<MultiplexDiskCache>( + std::filesystem::path(target_dir), use_scoped_tmp_dir); + YACL_ENFORCE(disk_cache_, "cannot create disk cache from dir={}", target_dir); + disk_cache_->CreateOutputStreams(bucket_num_, &bucket_os_vec_); +} + +HashBucketCache::~HashBucketCache() { + bucket_os_vec_.clear(); + disk_cache_ = nullptr; +} + +void HashBucketCache::WriteItem(const std::string& data) { + BucketItem bucket_item; + bucket_item.index = item_index_; + bucket_item.base64_data = absl::Base64Escape(data); + + auto& out = bucket_os_vec_[std::hash<std::string>()(bucket_item.base64_data) % + bucket_os_vec_.size()]; + out->Write(bucket_item.Serialize()); + out->Write("\n"); + item_index_++; +} + +void HashBucketCache::Flush() { + // Flush files. + for (const auto& out : bucket_os_vec_) { + out->Flush(); + } +} + +std::vector<HashBucketCache::BucketItem> HashBucketCache::LoadBucketItems( + uint32_t index) { + std::vector<BucketItem> ret; + auto in = disk_cache_->CreateInputStream(index); + + std::string line; + while (in->GetLine(&line)) { + auto item = BucketItem::Deserialize(line); + ret.push_back(std::move(item)); + } + return ret; +} + +std::unique_ptr<HashBucketCache> CreateCacheFromCsv( + const std::string& csv_path, const std::vector<std::string>& schema_names, + const std::string& cache_dir, uint32_t bucket_num, uint32_t read_batch_size, + bool use_scoped_tmp_dir) { + auto bucket_cache = std::make_unique<HashBucketCache>(cache_dir, bucket_num, + use_scoped_tmp_dir); + + auto batch_provider = std::make_unique<CsvBatchProvider>( + csv_path, schema_names, read_batch_size); + while (true) { + auto items = batch_provider->ReadNextBatch(); + if (items.empty()) { + break; + } + for (const auto& it : items) { + bucket_cache->WriteItem(it); + } + } + bucket_cache->Flush(); + + return bucket_cache; +} + +} // namespace psi::psi diff --git a/psi/psi/utils/hash_bucket_cache.h b/psi/psi/utils/hash_bucket_cache.h new file mode 100644 index 00000000..fb21225d --- /dev/null +++ b/psi/psi/utils/hash_bucket_cache.h @@ -0,0 +1,83 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <memory> +#include <string> +#include <string_view> +#include <vector> + +#include "absl/strings/str_split.h" +#include "fmt/format.h" +#include "yacl/base/exception.h" + +#include "psi/psi/io/io.h" +#include "psi/psi/utils/multiplex_disk_cache.h" + +namespace psi::psi { + +class HashBucketCache { + public: + struct BucketItem { + uint64_t index; + std::string base64_data; + + std::string Serialize() { return fmt::format("{},{}", index, base64_data); } + + static BucketItem Deserialize(std::string_view data_str) { + BucketItem item; + std::vector<absl::string_view> tokens = absl::StrSplit(data_str, ','); + YACL_ENFORCE(tokens.size() == 2, "should have two tokens, actual: {}", + tokens.size()); + YACL_ENFORCE(absl::SimpleAtoi(tokens[0], &item.index), + "cannot convert {} to idx", + std::string(tokens[0].data(), tokens[0].size())); + item.base64_data = std::string(tokens[1].data(), tokens[1].size()); + + return item; + } + }; + + HashBucketCache(std::string target_dir, uint32_t bucket_num, + bool use_scoped_tmp_dir = true); + + ~HashBucketCache(); + + void WriteItem(const std::string& data); + + void Flush(); + + std::vector<BucketItem> LoadBucketItems(uint32_t index); + + uint32_t BucketNum() const { return bucket_num_; } + + uint64_t ItemCount() const { return item_index_; } + + private: + std::unique_ptr<MultiplexDiskCache> disk_cache_; + + std::vector<std::unique_ptr<io::OutputStream>> bucket_os_vec_; + + uint32_t bucket_num_; + + uint64_t item_index_; +}; + +std::unique_ptr<HashBucketCache> CreateCacheFromCsv( + const std::string& csv_path, const std::vector<std::string>& schema_names, + const std::string& cache_dir, uint32_t bucket_num, + uint32_t read_batch_size = 4096, bool use_scoped_tmp_dir = true); + +} // namespace psi::psi diff --git a/psi/psi/utils/index_store.cc b/psi/psi/utils/index_store.cc new file mode 100644 index 00000000..5855a1f9 --- /dev/null +++ b/psi/psi/utils/index_store.cc @@ -0,0 +1,160 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/index_store.h" + +#include <cstddef> +#include <cstdint> +#include <fstream> +#include <iostream> + +#include "arrow/api.h" +#include "arrow/csv/api.h" +#include "arrow/csv/options.h" +#include "spdlog/spdlog.h" + +namespace psi::psi { + +IndexWriter::IndexWriter(const std::filesystem::path& path, size_t cache_size, + bool trunc) + : path_(path), cache_size_(cache_size) { + auto write_options = arrow::csv::WriteOptions::Defaults(); + write_options.include_header = false; + if (trunc || !std::filesystem::exists(path_)) { + // NOTE(junfeng): This is a hack to write the header without quotation masks + // to make YACL csv utils happy. + // Should be fixed as soon as possible. + { + std::ofstream file(path_); + file << kIdx << "\n"; + file.close(); + } + } + outfile_ = arrow::io::FileOutputStream::Open(path_, true).ValueOrDie(); + + schema_ = arrow::schema({arrow::field(kIdx, arrow::uint64())}); + writer_ = + arrow::csv::MakeCSVWriter(outfile_, schema_, write_options).ValueOrDie(); + + builder_ = arrow::MakeBuilder(arrow::uint64()).ValueOrDie(); + YACL_ENFORCE(builder_->Resize(cache_size_ * sizeof(uint64_t)).ok()); +} + +size_t IndexWriter::WriteCache(const std::vector<uint64_t>& indexes) { + YACL_ENFORCE(!outfile_->closed()); + + for (auto i : indexes) { + WriteCache(i); + } + + return write_cnt_; +} + +size_t IndexWriter::WriteCache(uint64_t index) { + YACL_ENFORCE(!outfile_->closed()); + + YACL_ENFORCE(builder_->AppendScalar(arrow::UInt64Scalar(index)).ok()); + cache_cnt_++; + write_cnt_++; + + return write_cnt_; +} + +void IndexWriter::Commit() { + YACL_ENFORCE(!outfile_->closed()); + + if (cache_cnt_ == 0) { + return; + } + + std::vector<std::shared_ptr<arrow::Array>> output_arrays; + output_arrays.emplace_back(builder_->Finish().ValueOrDie()); + + std::shared_ptr<arrow::RecordBatch> output_batch = arrow::RecordBatch::Make( + schema_, output_arrays[0]->length(), output_arrays); + if (!writer_->WriteRecordBatch(*output_batch).ok()) { + YACL_THROW("writer WriteRecordBatch failed."); + } + YACL_ENFORCE(outfile_->Flush().ok()); + builder_->Reset(); + cache_cnt_ = 0; +} + +void IndexWriter::Close() { + if (outfile_->closed()) { + return; + } + + Commit(); + + if (!outfile_->Close().ok()) { + SPDLOG_ERROR("outfile_ close failed."); + } +} + +IndexWriter::~IndexWriter() { Close(); } + +IndexReader::IndexReader(const std::filesystem::path& path) { + YACL_ENFORCE(std::filesystem::exists(path), "Input file {} doesn't exist.", + path.string()); + + arrow::io::IOContext io_context = arrow::io::default_io_context(); + infile_ = arrow::io::ReadableFile::Open(path, arrow::default_memory_pool()) + .ValueOrDie(); + auto read_options = arrow::csv::ReadOptions::Defaults(); + auto parse_options = arrow::csv::ParseOptions::Defaults(); + auto convert_options = arrow::csv::ConvertOptions::Defaults(); + convert_options.include_columns = std::vector<std::string>{kIdx}; + reader_ = arrow::csv::StreamingReader::Make(io_context, infile_, read_options, + parse_options, convert_options) + .ValueOrDie(); +} + +bool IndexReader::HasNext() { + bool new_batch = false; + if (!batch_ || idx_in_batch_ >= static_cast<size_t>(batch_->num_rows())) { + arrow::Status status = reader_->ReadNext(&batch_); + + if (!status.ok()) { + YACL_THROW("Read csv error."); + } + + new_batch = true; + } + + if (batch_ == NULL) { + return false; + } + + if (new_batch) { + array_ = std::static_pointer_cast<arrow::UInt64Array>(batch_->column(0)); + + idx_in_batch_ = 0; + } + + return idx_in_batch_ < static_cast<size_t>(batch_->num_rows()); +} + +std::optional<uint64_t> IndexReader::GetNext() { + if (!HasNext()) { + return {}; + } else { + uint64_t v = array_->Value(idx_in_batch_); + idx_in_batch_++; + read_cnt_++; + return v; + } +} + +} // namespace psi::psi diff --git a/psi/psi/utils/index_store.h b/psi/psi/utils/index_store.h new file mode 100644 index 00000000..d8a773c4 --- /dev/null +++ b/psi/psi/utils/index_store.h @@ -0,0 +1,97 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <cstddef> +#include <cstdint> +#include <filesystem> +#include <memory> +#include <optional> +#include <vector> + +#include "arrow/io/api.h" +#include "arrow/ipc/api.h" +#include "yacl/base/exception.h" + +namespace psi::psi { + +constexpr char kIdx[] = "psi_index"; + +class IndexWriter { + public: + explicit IndexWriter(const std::filesystem::path& path, + size_t batch_size = 10000, bool trunc = false); + + ~IndexWriter(); + + size_t WriteCache(const std::vector<uint64_t>& indexes); + + size_t WriteCache(uint64_t index); + + void Close(); + + void Commit(); + + [[nodiscard]] size_t cache_cnt() const { return cache_cnt_; } + + [[nodiscard]] size_t cache_size() const { return cache_size_; } + + [[nodiscard]] size_t write_cnt() const { return write_cnt_; } + + [[nodiscard]] std::filesystem::path path() const { return path_; } + + private: + std::filesystem::path path_; + + size_t cache_cnt_ = 0; + + size_t write_cnt_ = 0; + + size_t cache_size_ = 0; + + std::shared_ptr<arrow::ArrayBuilder> builder_; + + std::shared_ptr<arrow::io::FileOutputStream> outfile_; + + std::shared_ptr<arrow::ipc::RecordBatchWriter> writer_; + + std::shared_ptr<arrow::Schema> schema_; +}; + +class IndexReader { + public: + explicit IndexReader(const std::filesystem::path& path); + + bool HasNext(); + + std::optional<uint64_t> GetNext(); + + [[nodiscard]] size_t read_cnt() const { return read_cnt_; } + + private: + std::shared_ptr<arrow::io::ReadableFile> infile_; + + std::shared_ptr<arrow::ipc::RecordBatchReader> reader_; + + std::shared_ptr<arrow::RecordBatch> batch_; + + size_t idx_in_batch_ = 0; + + size_t read_cnt_ = 0; + + std::shared_ptr<arrow::UInt64Array> array_; +}; + +} // namespace psi::psi diff --git a/psi/psi/utils/index_store_test.cc b/psi/psi/utils/index_store_test.cc new file mode 100644 index 00000000..eb26614a --- /dev/null +++ b/psi/psi/utils/index_store_test.cc @@ -0,0 +1,129 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/index_store.h" + +#include <cstddef> +#include <cstdint> +#include <filesystem> +#include <numeric> + +#include "gtest/gtest.h" + +namespace psi::psi { + +class IndexStoreTest : public ::testing::Test { + protected: + void TearDown() override { std::filesystem::remove(index_store_path_); } + + std::filesystem::path index_store_path_ = + std::filesystem::temp_directory_path() / "index_store_test.csv"; +}; + +TEST_F(IndexStoreTest, Works) { + { + IndexWriter writer(index_store_path_); + + for (uint64_t i = 0; i < 5000; i++) { + EXPECT_EQ(writer.WriteCache(i), i + 1); + } + + EXPECT_EQ(writer.cache_cnt(), 5000); + EXPECT_EQ(writer.write_cnt(), 5000); + } + { + IndexWriter writer(index_store_path_); + + std::vector<uint64_t> indexes; + indexes.resize(5000); + + std::iota(indexes.begin(), indexes.end(), 5000); + + EXPECT_EQ(writer.WriteCache(indexes), 5000); + + EXPECT_EQ(writer.cache_cnt(), 5000); + EXPECT_EQ(writer.write_cnt(), 5000); + + writer.Commit(); + + EXPECT_EQ(writer.cache_cnt(), 0); + EXPECT_EQ(writer.write_cnt(), 5000); + } + { + IndexWriter writer(index_store_path_); + + for (uint64_t i = 10000; i < 15000; i++) { + writer.WriteCache(i); + + EXPECT_EQ(writer.cache_cnt(), 1); + EXPECT_EQ(writer.write_cnt(), i - 9999); + + writer.Commit(); + + EXPECT_EQ(writer.cache_cnt(), 0); + EXPECT_EQ(writer.write_cnt(), i - 9999); + } + } + { + IndexWriter writer(index_store_path_); + + std::vector<uint64_t> indexes; + indexes.resize(5000); + + std::iota(indexes.begin(), indexes.end(), 15000); + + writer.WriteCache(indexes); + + EXPECT_EQ(writer.cache_cnt(), 5000); + + writer.Commit(); + + EXPECT_EQ(writer.cache_cnt(), 0); + } + + { + IndexReader reader(index_store_path_); + + uint64_t idx = 0; + + while (reader.HasNext()) { + EXPECT_EQ(reader.GetNext().value(), idx); + idx++; + EXPECT_EQ(reader.read_cnt(), idx); + } + + EXPECT_EQ(idx, 20000); + EXPECT_EQ(reader.read_cnt(), 20000); + + EXPECT_FALSE(reader.HasNext()); + EXPECT_FALSE(reader.GetNext().has_value()); + + EXPECT_FALSE(reader.HasNext()); + EXPECT_FALSE(reader.GetNext().has_value()); + } +} + +TEST_F(IndexStoreTest, Empty) { + { IndexWriter writer(index_store_path_); } + + { + IndexReader reader(index_store_path_); + EXPECT_FALSE(reader.HasNext()); + EXPECT_FALSE(reader.HasNext()); + EXPECT_FALSE(reader.GetNext().has_value()); + EXPECT_FALSE(reader.HasNext()); + } +} + +} // namespace psi::psi diff --git a/psi/psi/utils/inner_join.cc b/psi/psi/utils/inner_join.cc new file mode 100644 index 00000000..8c36d3e0 --- /dev/null +++ b/psi/psi/utils/inner_join.cc @@ -0,0 +1,904 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/inner_join.h" + +#include <cstddef> +#include <cstdint> +#include <fstream> +#include <future> +#include <string> +#include <unordered_map> +#include <vector> + +#include "arrow/api.h" +#include "arrow/csv/api.h" +#include "arrow/io/api.h" +#include "arrow/ipc/api.h" +#include "external/com_github_gabime_spdlog/spdlog/include/spdlog/spdlog.h" +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" + +#include "psi/psi/utils/inner_join.h" +#include "psi/psi/utils/utils.h" + +#include "psi/proto/psi.pb.h" + +namespace psi::psi { + +constexpr char kInnerJoinKeyCount[] = "psi_inner_join_cnt"; +constexpr char kInnerJoinFirstIndex[] = "psi_innner_join_first_index"; +constexpr char kLinkTag[] = "PSI:INNER_JOIN_SYNC_CNT"; + +v2::InnerJoinConfig BuildInnerJoinConfig(const v2::PsiConfig& psi_config, + const std::filesystem::path& root) { + v2::InnerJoinConfig inner_join_config; + + inner_join_config.set_role(psi_config.protocol_config().role()); + + for (const auto& key : psi_config.keys()) { + *inner_join_config.add_keys() = key; + } + + std::string prefix = psi_config.protocol_config().role() == v2::ROLE_RECEIVER + ? "receiver_" + : "sender_"; + + std::filesystem::path sorted_input_path = + root / (prefix + "sorted_input.csv"); + std::filesystem::path unique_input_keys_cnt_path = + root / (prefix + "unique_input_keys_cnt.csv"); + std::filesystem::path self_intersection_cnt_path = + root / (prefix + "self_intersection_cnt.csv"); + std::filesystem::path peer_intersection_cnt_path = + root / (prefix + "peer_intersection_cnt.csv"); + + inner_join_config.set_input_path(psi_config.input_config().path()); + inner_join_config.set_output_path(psi_config.output_config().path()); + + inner_join_config.set_sorted_input_path(sorted_input_path.string()); + inner_join_config.set_unique_input_keys_cnt_path( + unique_input_keys_cnt_path.string()); + inner_join_config.set_self_intersection_cnt_path( + self_intersection_cnt_path.string()); + inner_join_config.set_peer_intersection_cnt_path( + peer_intersection_cnt_path.string()); + + return inner_join_config; +} + +void InnerJoinGenerateSortedInput(const v2::InnerJoinConfig& config) { + YACL_ENFORCE(std::filesystem::exists(config.input_path()), + "Input file {} doesn't exist.", config.input_path()); + std::vector<std::string> keys(config.keys().begin(), config.keys().end()); + MultiKeySort(config.input_path(), config.sorted_input_path(), keys); +} + +void InnerJoinGenerateUniqueInputKeysCnt(const v2::InnerJoinConfig& config) { + YACL_ENFORCE(std::filesystem::exists(config.sorted_input_path()), + "Sorted input file {} doesn't exist.", + config.sorted_input_path()); + + arrow::io::IOContext io_context = arrow::io::default_io_context(); + std::shared_ptr<arrow::io::ReadableFile> infile; + infile = arrow::io::ReadableFile::Open(config.sorted_input_path(), + arrow::default_memory_pool()) + .ValueOrDie(); + auto read_options = arrow::csv::ReadOptions::Defaults(); + auto parse_options = arrow::csv::ParseOptions::Defaults(); + auto convert_options = arrow::csv::ConvertOptions::Defaults(); + + std::vector<std::string> keys(config.keys().begin(), config.keys().end()); + + convert_options.include_columns = keys; + + auto reader = + arrow::csv::StreamingReader::Make(io_context, infile, read_options, + parse_options, convert_options) + .ValueOrDie(); + + const std::shared_ptr<arrow::Schema>& input_schema = reader->schema(); + + std::vector<std::shared_ptr<arrow::Field>> output_fields; + for (int i = 0; i < input_schema->num_fields(); i++) { + output_fields.emplace_back(input_schema->field(i)); + } + output_fields.emplace_back(arrow::field(kInnerJoinKeyCount, arrow::int64())); + output_fields.emplace_back( + arrow::field(kInnerJoinFirstIndex, arrow::int64())); + + std::shared_ptr<arrow::Schema> output_schema = arrow::schema(output_fields); + + // NOTE(junfeng): This is a hack to write the header without quotation masks + // to make YACL csv utils happy. + // Should be fixed as soon as possible. + { + std::ofstream file(config.unique_input_keys_cnt_path()); + + for (int i = 0; i < output_schema->num_fields(); i++) { + file << output_schema->field(i)->name(); + if (i != output_schema->num_fields() - 1) { + file << ","; + } + } + + file << "\n"; + + file.close(); + } + std::shared_ptr<arrow::io::FileOutputStream> outfile = + arrow::io::FileOutputStream::Open(config.unique_input_keys_cnt_path(), + true) + .ValueOrDie(); + auto write_options = arrow::csv::WriteOptions::Defaults(); + write_options.include_header = false; + + auto writer = arrow::csv::MakeCSVWriter(outfile, output_schema, write_options) + .ValueOrDie(); + + std::shared_ptr<arrow::RecordBatch> batch; + size_t cnt = 0; + std::vector<std::shared_ptr<arrow::Scalar>> previous_keys; + int64_t idx = 0; + int64_t first_appearance = 0; + + while (true) { + arrow::Status status = reader->ReadNext(&batch); + + if (!status.ok()) { + YACL_THROW("Read csv error."); + } + + if (batch == NULL) { + break; + } + + std::vector<std::unique_ptr<arrow::ArrayBuilder>> output_array_builders; + output_array_builders.reserve(output_fields.size()); + + for (auto field : output_fields) { + output_array_builders.emplace_back( + arrow::MakeBuilder(field->type()).ValueOrDie()); + } + + std::vector<std::shared_ptr<arrow::Array>> input_data = batch->columns(); + int num_rows = batch->num_rows(); + int num_cols = input_data.size(); + + for (int i = 0; i < num_rows; i++) { + std::vector<std::shared_ptr<arrow::Scalar>> current_keys; + current_keys.reserve(input_data.size()); + for (int j = 0; j < num_cols; j++) { + current_keys.emplace_back(input_data[j]->GetScalar(i).ValueOrDie()); + } + + if (previous_keys.empty()) { + previous_keys = current_keys; + cnt += 1; + } else { + bool is_same = true; + + for (int k = 0; k < num_cols; k++) { + if (!previous_keys[k]->Equals(current_keys[k])) { + is_same = false; + break; + } + } + + if (is_same) { + cnt += 1; + } else { + for (int p = 0; p < num_cols; p++) { + YACL_ENFORCE(output_array_builders[p] + ->AppendScalar(*(previous_keys[p])) + .ok()); + } + YACL_ENFORCE(output_array_builders[num_cols] + ->AppendScalar(arrow::Int64Scalar(cnt)) + .ok()); + YACL_ENFORCE(output_array_builders[num_cols + 1] + ->AppendScalar(arrow::Int64Scalar(first_appearance)) + .ok()); + + previous_keys = current_keys; + cnt = 1; + first_appearance = idx; + } + } + idx++; + } + + std::vector<std::shared_ptr<arrow::Array>> output_arrays; + output_arrays.reserve(output_fields.size()); + for (auto& builder : output_array_builders) { + output_arrays.emplace_back(builder->Finish().ValueOrDie()); + } + + if (output_arrays[0]->length() > 0) { + std::shared_ptr<arrow::RecordBatch> output_batch = + arrow::RecordBatch::Make(output_schema, output_arrays[0]->length(), + output_arrays); + if (!writer->WriteRecordBatch(*output_batch).ok()) { + YACL_THROW("writer WriteRecordBatch failed."); + } + } + } + + // last cnt + std::vector<std::unique_ptr<arrow::ArrayBuilder>> output_array_builders; + output_array_builders.reserve(output_fields.size()); + + for (auto field : output_fields) { + output_array_builders.emplace_back( + arrow::MakeBuilder(field->type()).ValueOrDie()); + } + for (int p = 0; p < input_schema->num_fields(); p++) { + YACL_ENFORCE( + output_array_builders[p]->AppendScalar(*(previous_keys[p])).ok()); + } + YACL_ENFORCE(output_array_builders[input_schema->num_fields()] + ->AppendScalar(arrow::Int64Scalar(cnt)) + .ok()); + YACL_ENFORCE(output_array_builders[input_schema->num_fields() + 1] + ->AppendScalar(arrow::Int64Scalar(first_appearance)) + .ok()); + + std::vector<std::shared_ptr<arrow::Array>> output_arrays; + output_arrays.reserve(output_fields.size()); + for (auto& builder : output_array_builders) { + output_arrays.emplace_back(builder->Finish().ValueOrDie()); + } + + if (output_arrays[0]->length() > 0) { + std::shared_ptr<arrow::RecordBatch> output_batch = arrow::RecordBatch::Make( + output_schema, output_arrays[0]->length(), output_arrays); + if (!writer->WriteRecordBatch(*output_batch).ok()) { + YACL_THROW("writer WriteRecordBatch failed."); + } + } + + if (!writer->Close().ok()) { + YACL_THROW("writer Close failed."); + } + if (!outfile->Close().ok()) { + YACL_THROW("outfile Close failed."); + } + if (!infile->Close().ok()) { + YACL_THROW("infile Close failed."); + } +} + +void SendSelfCnt(const std::shared_ptr<yacl::link::Context>& link_ctx, + const v2::InnerJoinConfig& config) { + YACL_ENFORCE(link_ctx->WorldSize() == 2); + YACL_ENFORCE(std::filesystem::exists(config.self_intersection_cnt_path()), + "Self intersection cnt file {} doesn't exist.", + config.self_intersection_cnt_path()); + + std::shared_ptr<arrow::io::ReadableFile> infile; + infile = arrow::io::ReadableFile::Open(config.self_intersection_cnt_path(), + arrow::default_memory_pool()) + .ValueOrDie(); + + auto read_options = arrow::csv::ReadOptions::Defaults(); + auto parse_options = arrow::csv::ParseOptions::Defaults(); + auto convert_options = arrow::csv::ConvertOptions::Defaults(); + convert_options.include_columns = + std::vector<std::string>{kInnerJoinKeyCount}; + + arrow::io::IOContext io_context = arrow::io::default_io_context(); + + auto reader = + arrow::csv::StreamingReader::Make(io_context, infile, read_options, + parse_options, convert_options) + .ValueOrDie(); + + std::shared_ptr<arrow::RecordBatch> batch; + + while (true) { + arrow::Status status = reader->ReadNext(&batch); + + if (!status.ok()) { + YACL_THROW("Read csv error."); + } + + if (batch == NULL) { + // send an empty record to indicate the end of sending. + link_ctx->SendAsync(link_ctx->NextRank(), "", kLinkTag); + break; + } else { + auto cnt_array = + std::static_pointer_cast<arrow::Int64Array>(batch->column(0)); + + std::shared_ptr<arrow::Buffer> buffer = cnt_array->values(); + link_ctx->SendAsync( + link_ctx->NextRank(), + yacl::ByteContainerView(buffer->data(), buffer->size()), kLinkTag); + } + } + + if (!infile->Close().ok()) { + YACL_THROW("infile Close failed."); + } +} + +void RecvPeerCnt(const std::shared_ptr<yacl::link::Context>& link_ctx, + const v2::InnerJoinConfig& config) { + YACL_ENFORCE(link_ctx->WorldSize() == 2); + + std::shared_ptr<arrow::io::FileOutputStream> outfile = + arrow::io::FileOutputStream::Open(config.peer_intersection_cnt_path()) + .ValueOrDie(); + auto write_options = arrow::csv::WriteOptions::Defaults(); + write_options.include_header = true; + + std::shared_ptr<arrow::Schema> schema = + arrow::schema({arrow::field(kInnerJoinKeyCount, arrow::int64())}); + + auto writer = + arrow::csv::MakeCSVWriter(outfile, schema, write_options).ValueOrDie(); + + while (true) { + yacl::Buffer buffer = link_ctx->Recv(link_ctx->NextRank(), kLinkTag); + + int buffer_size = buffer.size(); + + if (buffer_size == 0) { + // Receiving empty record which indicates the end of peer sending. + break; + } + + int64_t* buffer_data = buffer.data<int64_t>(); + + std::shared_ptr<arrow::ArrayBuilder> builder = + arrow::MakeBuilder(arrow::int64()).ValueOrDie(); + + for (size_t i = 0; i < buffer_size / sizeof(int64_t); i++) { + YACL_ENFORCE( + builder->AppendScalar(arrow::Int64Scalar(buffer_data[i])).ok()); + } + + std::vector<std::shared_ptr<arrow::Array>> output_arrays; + output_arrays.emplace_back(builder->Finish().ValueOrDie()); + + if (output_arrays[0]->length() > 0) { + std::shared_ptr<arrow::RecordBatch> output_batch = + arrow::RecordBatch::Make(schema, output_arrays[0]->length(), + output_arrays); + if (!writer->WriteRecordBatch(*output_batch).ok()) { + YACL_THROW("writer WriteRecordBatch failed."); + } + } + } + + if (!outfile->Close().ok()) { + YACL_THROW("outfile Close failed."); + } +} + +void InnerJoinSyncIntersectionCnt( + const std::shared_ptr<yacl::link::Context>& link_ctx, + const v2::InnerJoinConfig& config) { + YACL_ENFORCE(link_ctx->WorldSize() == 2); + + std::future<void> f_send_self_cnt = + std::async([&] { return SendSelfCnt(link_ctx, config); }); + std::future<void> f_recv_peer_cnt = + std::async([&] { return RecvPeerCnt(link_ctx, config); }); + + std::exception_ptr send_self_cnt_exptr = nullptr; + std::exception_ptr recv_peer_cnt_exptr = nullptr; + + try { + f_send_self_cnt.get(); + } catch (const std::exception& e) { + send_self_cnt_exptr = std::current_exception(); + SPDLOG_ERROR("Error in send self cnt: {}", e.what()); + } + + try { + f_recv_peer_cnt.get(); + } catch (const std::exception& e) { + recv_peer_cnt_exptr = std::current_exception(); + SPDLOG_ERROR("Error in recv peer cnt: {}", e.what()); + } + + if (send_self_cnt_exptr) { + std::rethrow_exception(send_self_cnt_exptr); + } + if (recv_peer_cnt_exptr) { + std::rethrow_exception(recv_peer_cnt_exptr); + } +} + +// Read next record. +std::vector<std::shared_ptr<arrow::Scalar>> ReadNextRecord( + const std::shared_ptr<arrow::ipc::RecordBatchReader>& reader, + std::shared_ptr<arrow::RecordBatch>* batch, int64_t* read_batch_index, + const std::vector<std::string>& cols) { + std::vector<std::shared_ptr<arrow::Scalar>> res; + + if (*batch == NULL || *read_batch_index >= (*batch)->num_rows()) { + arrow::Status status = reader->ReadNext(batch); + + if (!status.ok()) { + YACL_THROW("Read csv error."); + } + + if (*batch == NULL) { + SPDLOG_WARN("Reach the end of csv."); + return res; + } + + *read_batch_index = 0; + } + + int64_t previous_read_batch_index = *read_batch_index; + *read_batch_index = previous_read_batch_index + 1; + + for (const auto& col : cols) { + res.emplace_back((*batch) + ->GetColumnByName(col) + ->GetScalar(previous_read_batch_index) + .ValueOrDie()); + } + + return res; +} + +void GenerateOutputForSingle( + const std::shared_ptr<arrow::ipc::RecordBatchWriter>& writer, + const std::shared_ptr<arrow::csv::StreamingReader>& sorted_input_reader, + const std::vector<std::string>& key_col_names, + const std::vector<std::shared_ptr<arrow::Scalar>>& target_keys, + int64_t self_cnt, int64_t peer_cnt, v2::Role role, + std::shared_ptr<arrow::RecordBatch>* sorted_input_batch, + int64_t* read_batch_index) { + bool found = false; + + // found target keys; + while (!found) { + if (*sorted_input_batch == NULL || + *read_batch_index >= (*sorted_input_batch)->num_rows()) { + arrow::Status status = sorted_input_reader->ReadNext(sorted_input_batch); + + if (!status.ok()) { + YACL_THROW("Read csv error."); + } + + if (*sorted_input_batch == NULL) { + YACL_THROW("sorted_input_reader reach the end."); + } + + *read_batch_index = 0; + } + + for (; *read_batch_index < (*sorted_input_batch)->num_rows(); + *read_batch_index = *read_batch_index + 1) { + std::vector<std::shared_ptr<arrow::Scalar>> current_keys; + current_keys.reserve(key_col_names.size()); + for (const std::string& col_name : key_col_names) { + current_keys.emplace_back((*sorted_input_batch) + ->GetColumnByName(col_name) + ->GetScalar(*read_batch_index) + .ValueOrDie()); + } + + bool equal = true; + for (size_t i = 0; i < key_col_names.size(); i++) { + if (!current_keys[i]->Equals(target_keys[i])) { + equal = false; + break; + } + } + + if (equal) { + found = true; + break; + } + } + } + + // collect all rows with the same keys. + std::vector<std::vector<std::shared_ptr<arrow::Scalar>>> output_scalars; + output_scalars.reserve(sorted_input_reader->schema()->num_fields()); + + for (int i = 0; i < sorted_input_reader->schema()->num_fields(); i++) { + output_scalars.emplace_back(std::vector<std::shared_ptr<arrow::Scalar>>()); + } + + bool finished = false; + while (!finished) { + if (*sorted_input_batch == NULL || + *read_batch_index >= (*sorted_input_batch)->num_rows()) { + arrow::Status status = sorted_input_reader->ReadNext(sorted_input_batch); + + if (!status.ok()) { + YACL_THROW("Read csv error."); + } + + if (*sorted_input_batch == NULL) { + break; + } + + *read_batch_index = 0; + } + + for (; *read_batch_index < (*sorted_input_batch)->num_rows(); + *read_batch_index = *read_batch_index + 1) { + std::vector<std::shared_ptr<arrow::Scalar>> current_keys; + current_keys.reserve(key_col_names.size()); + for (const std::string& col_name : key_col_names) { + current_keys.emplace_back((*sorted_input_batch) + ->GetColumnByName(col_name) + ->GetScalar(*read_batch_index) + .ValueOrDie()); + } + + bool equal = true; + for (size_t i = 0; i < key_col_names.size(); i++) { + if (!current_keys[i]->Equals(target_keys[i])) { + equal = false; + break; + } + } + + if (equal) { + for (int i = 0; i < sorted_input_reader->schema()->num_fields(); i++) { + output_scalars[i].emplace_back((*sorted_input_batch) + ->column(i) + ->GetScalar(*read_batch_index) + .ValueOrDie()); + } + } else { + finished = true; + break; + } + } + } + + YACL_ENFORCE_EQ(static_cast<int64_t>(output_scalars[0].size()), self_cnt, + "self_cnt doesn't match the actual sorted input."); + + // Write to output + std::vector<std::unique_ptr<arrow::ArrayBuilder>> output_array_builders; + output_array_builders.reserve(sorted_input_reader->schema()->num_fields()); + + for (int i = 0; i < sorted_input_reader->schema()->num_fields(); i++) { + output_array_builders.emplace_back( + arrow::MakeBuilder(sorted_input_reader->schema()->field(i)->type()) + .ValueOrDie()); + } + + if (role == v2::ROLE_RECEIVER) { + for (int i = 0; i < self_cnt; i++) { + for (int j = 0; j < peer_cnt; j++) { + for (int k = 0; k < sorted_input_reader->schema()->num_fields(); k++) { + YACL_ENFORCE(output_array_builders[k] + ->AppendScalar(*(output_scalars[k][i])) + .ok()); + } + } + } + } else { + for (int i = 0; i < peer_cnt; i++) { + for (int j = 0; j < self_cnt; j++) { + for (int k = 0; k < sorted_input_reader->schema()->num_fields(); k++) { + YACL_ENFORCE(output_array_builders[k] + ->AppendScalar(*(output_scalars[k][j])) + .ok()); + } + } + } + } + + std::vector<std::shared_ptr<arrow::Array>> output_arrays; + output_arrays.reserve(sorted_input_reader->schema()->num_fields()); + + for (auto& builder : output_array_builders) { + output_arrays.emplace_back(builder->Finish().ValueOrDie()); + } + + if (output_arrays[0]->length() > 0) { + std::shared_ptr<arrow::RecordBatch> output_batch = + arrow::RecordBatch::Make(sorted_input_reader->schema(), + output_arrays[0]->length(), output_arrays); + if (!writer->WriteRecordBatch(*output_batch).ok()) { + YACL_THROW("writer WriteRecordBatch failed."); + } + } +} + +void InnerJoinGenerateIntersection(const v2::InnerJoinConfig& config) { + YACL_ENFORCE(std::filesystem::exists(config.sorted_input_path()), + "Sorted input file {} doesn't exist.", + config.sorted_input_path()); + + YACL_ENFORCE(std::filesystem::exists(config.self_intersection_cnt_path()), + "Sorted input file {} doesn't exist.", + config.self_intersection_cnt_path()); + + YACL_ENFORCE(std::filesystem::exists(config.peer_intersection_cnt_path()), + "Sorted input file {} doesn't exist.", + config.peer_intersection_cnt_path()); + + std::shared_ptr<arrow::io::ReadableFile> self_intersection_cnt_infile; + self_intersection_cnt_infile = + arrow::io::ReadableFile::Open(config.self_intersection_cnt_path(), + arrow::default_memory_pool()) + .ValueOrDie(); + std::shared_ptr<arrow::io::ReadableFile> peer_intersection_cnt_infile; + peer_intersection_cnt_infile = + arrow::io::ReadableFile::Open(config.peer_intersection_cnt_path(), + arrow::default_memory_pool()) + .ValueOrDie(); + std::shared_ptr<arrow::io::ReadableFile> sorted_input_infile; + sorted_input_infile = + arrow::io::ReadableFile::Open(config.sorted_input_path(), + arrow::default_memory_pool()) + .ValueOrDie(); + + auto read_options = arrow::csv::ReadOptions::Defaults(); + auto parse_options = arrow::csv::ParseOptions::Defaults(); + auto self_intersection_cnt_convert_options = + arrow::csv::ConvertOptions::Defaults(); + + std::vector<std::string> keys(config.keys().begin(), config.keys().end()); + keys.emplace_back(kInnerJoinKeyCount); + self_intersection_cnt_convert_options.include_columns = keys; + auto peer_intersection_cnt_convert_options = + arrow::csv::ConvertOptions::Defaults(); + peer_intersection_cnt_convert_options.include_columns = + std::vector<std::string>{kInnerJoinKeyCount}; + auto sorted_input_convert_options = arrow::csv::ConvertOptions::Defaults(); + + arrow::io::IOContext self_intersection_cnt_io_context = + arrow::io::default_io_context(); + arrow::io::IOContext peer_intersection_cnt_io_context = + arrow::io::default_io_context(); + arrow::io::IOContext sorted_input_io_context = + arrow::io::default_io_context(); + + auto self_intersection_cnt_reader = + arrow::csv::StreamingReader::Make( + self_intersection_cnt_io_context, self_intersection_cnt_infile, + read_options, parse_options, self_intersection_cnt_convert_options) + .ValueOrDie(); + auto peer_intersection_cnt_reader = + arrow::csv::StreamingReader::Make( + peer_intersection_cnt_io_context, peer_intersection_cnt_infile, + read_options, parse_options, peer_intersection_cnt_convert_options) + .ValueOrDie(); + auto sorted_input_reader = + arrow::csv::StreamingReader::Make( + sorted_input_io_context, sorted_input_infile, read_options, + parse_options, sorted_input_convert_options) + .ValueOrDie(); + + std::shared_ptr<arrow::RecordBatch> self_intersection_cnt_batch; + std::shared_ptr<arrow::RecordBatch> peer_intersection_cnt_batch; + std::shared_ptr<arrow::RecordBatch> sorted_input_batch; + + std::shared_ptr<arrow::io::FileOutputStream> outfile = + arrow::io::FileOutputStream::Open(config.output_path()).ValueOrDie(); + auto write_options = arrow::csv::WriteOptions::Defaults(); + write_options.include_header = true; + + auto writer = arrow::csv::MakeCSVWriter( + outfile, sorted_input_reader->schema(), write_options) + .ValueOrDie(); + + int64_t input_batch_index = 0; + int64_t peer_cnt_batch_index = 0; + + // Iterate self_intersection_cnt; + while (true) { + arrow::Status status = + self_intersection_cnt_reader->ReadNext(&self_intersection_cnt_batch); + + if (!status.ok()) { + YACL_THROW("Read csv error."); + } + + if (self_intersection_cnt_batch == NULL) { + break; + } + + std::vector<std::shared_ptr<arrow::Array>> self_intersection_cnt_data = + self_intersection_cnt_batch->columns(); + + auto self_count_array = std::static_pointer_cast<arrow::Int64Array>( + self_intersection_cnt_data[config.keys().size()]); + + for (int i = 0; i < self_intersection_cnt_batch->num_rows(); i++) { + std::vector<std::shared_ptr<arrow::Scalar>> current_keys; + current_keys.reserve(config.keys().size()); + for (int j = 0; j < config.keys().size(); j++) { + current_keys.emplace_back( + self_intersection_cnt_data[j]->GetScalar(i).ValueOrDie()); + } + + int64_t current_self_count = self_count_array->Value(i); + + std::vector<std::shared_ptr<arrow::Scalar>> record = ReadNextRecord( + peer_intersection_cnt_reader, &peer_intersection_cnt_batch, + &peer_cnt_batch_index, std::vector<std::string>{kInnerJoinKeyCount}); + + int64_t peer_count = + std::dynamic_pointer_cast<arrow::Int64Scalar>(record[0])->value; + + YACL_ENFORCE_NE(peer_count, -1); + + std::vector<std::string> psi_keys(config.keys().begin(), + config.keys().end()); + + GenerateOutputForSingle(writer, sorted_input_reader, psi_keys, + current_keys, current_self_count, peer_count, + config.role(), &sorted_input_batch, + &input_batch_index); + } + } + + YACL_ENFORCE(ReadNextRecord(peer_intersection_cnt_reader, + &peer_intersection_cnt_batch, + &peer_cnt_batch_index, + std::vector<std::string>{kInnerJoinKeyCount}) + .empty()); + + if (!self_intersection_cnt_infile->Close().ok()) { + YACL_THROW("self_intersection_cnt_infile Close failed."); + } + if (!peer_intersection_cnt_infile->Close().ok()) { + YACL_THROW("peer_intersection_cnt_infile Close failed."); + } + if (!sorted_input_infile->Close().ok()) { + YACL_THROW("sorted_input_infile Close failed."); + } + if (!outfile->Close().ok()) { + YACL_THROW("outfile Close failed."); + } +} + +void InnerJoinGenerateDifference(const v2::InnerJoinConfig& config) { + YACL_ENFORCE(std::filesystem::exists(config.sorted_input_path()), + "Sorted input file {} doesn't exist.", + config.sorted_input_path()); + + YACL_ENFORCE(std::filesystem::exists(config.self_intersection_cnt_path()), + "Sorted input file {} doesn't exist.", + config.self_intersection_cnt_path()); + + std::shared_ptr<arrow::io::ReadableFile> self_intersection_cnt_infile; + self_intersection_cnt_infile = + arrow::io::ReadableFile::Open(config.self_intersection_cnt_path(), + arrow::default_memory_pool()) + .ValueOrDie(); + std::shared_ptr<arrow::io::ReadableFile> sorted_input_infile; + sorted_input_infile = + arrow::io::ReadableFile::Open(config.sorted_input_path(), + arrow::default_memory_pool()) + .ValueOrDie(); + + auto read_options = arrow::csv::ReadOptions::Defaults(); + auto parse_options = arrow::csv::ParseOptions::Defaults(); + auto self_intersection_cnt_convert_options = + arrow::csv::ConvertOptions::Defaults(); + auto sorted_input_convert_options = arrow::csv::ConvertOptions::Defaults(); + + arrow::io::IOContext self_intersection_cnt_io_context = + arrow::io::default_io_context(); + arrow::io::IOContext sorted_input_io_context = + arrow::io::default_io_context(); + + auto self_intersection_cnt_reader = + arrow::csv::StreamingReader::Make( + self_intersection_cnt_io_context, self_intersection_cnt_infile, + read_options, parse_options, self_intersection_cnt_convert_options) + .ValueOrDie(); + auto sorted_input_reader = + arrow::csv::StreamingReader::Make( + sorted_input_io_context, sorted_input_infile, read_options, + parse_options, sorted_input_convert_options) + .ValueOrDie(); + + std::shared_ptr<arrow::RecordBatch> self_intersection_cnt_batch; + std::shared_ptr<arrow::RecordBatch> sorted_input_batch; + + std::shared_ptr<arrow::io::FileOutputStream> outfile = + arrow::io::FileOutputStream::Open(config.output_path()).ValueOrDie(); + auto write_options = arrow::csv::WriteOptions::Defaults(); + write_options.include_header = true; + + auto writer = arrow::csv::MakeCSVWriter( + outfile, sorted_input_reader->schema(), write_options) + .ValueOrDie(); + + int64_t self_intersection_cnt_batch_index = 0; + + std::vector<std::shared_ptr<arrow::Scalar>> record = ReadNextRecord( + self_intersection_cnt_reader, &self_intersection_cnt_batch, + &self_intersection_cnt_batch_index, + std::vector<std::string>{kInnerJoinFirstIndex, kInnerJoinKeyCount}); + + int64_t idx = 0; + + while (true) { + arrow::Status status = sorted_input_reader->ReadNext(&sorted_input_batch); + + if (!status.ok()) { + YACL_THROW("Read csv error."); + } + + if (sorted_input_batch == NULL) { + break; + } + + std::vector<std::shared_ptr<arrow::Array>> sorted_input_data = + sorted_input_batch->columns(); + + std::vector<std::unique_ptr<arrow::ArrayBuilder>> output_array_builders; + output_array_builders.reserve(sorted_input_reader->schema()->num_fields()); + + for (int i = 0; i < sorted_input_reader->schema()->num_fields(); i++) { + output_array_builders.emplace_back( + arrow::MakeBuilder(sorted_input_reader->schema()->field(i)->type()) + .ValueOrDie()); + } + + for (int i = 0; i < sorted_input_batch->num_rows(); i++) { + if (record.empty() || + idx < + std::dynamic_pointer_cast<arrow::Int64Scalar>(record[0])->value) { + for (int j = 0; j < sorted_input_reader->schema()->num_fields(); j++) { + YACL_ENFORCE( + output_array_builders[j] + ->AppendScalar(*( + sorted_input_batch->column(j)->GetScalar(i).ValueOrDie())) + .ok()); + } + } + + if (!record.empty() && + idx == + (std::dynamic_pointer_cast<arrow::Int64Scalar>(record[0])->value + + std::dynamic_pointer_cast<arrow::Int64Scalar>(record[1])->value - + 1)) { + record = ReadNextRecord( + self_intersection_cnt_reader, &self_intersection_cnt_batch, + &self_intersection_cnt_batch_index, + std::vector<std::string>{kInnerJoinFirstIndex, kInnerJoinKeyCount}); + } + + idx += 1; + } + + std::vector<std::shared_ptr<arrow::Array>> output_arrays; + output_arrays.reserve(sorted_input_reader->schema()->num_fields()); + + for (auto& builder : output_array_builders) { + output_arrays.emplace_back(builder->Finish().ValueOrDie()); + } + + if (output_arrays[0]->length() > 0) { + std::shared_ptr<arrow::RecordBatch> output_batch = + arrow::RecordBatch::Make(sorted_input_reader->schema(), + output_arrays[0]->length(), output_arrays); + if (!writer->WriteRecordBatch(*output_batch).ok()) { + YACL_THROW("writer WriteRecordBatch failed."); + } + } + } +} + +} // namespace psi::psi diff --git a/psi/psi/utils/inner_join.h b/psi/psi/utils/inner_join.h new file mode 100644 index 00000000..0644be2e --- /dev/null +++ b/psi/psi/utils/inner_join.h @@ -0,0 +1,52 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <filesystem> +#include <string> +#include <vector> + +#include "yacl/link/link.h" + +#include "psi/proto/psi.pb.h" + +namespace psi::psi { + +// Generate InnerJoinConfig from PsiConfig. +v2::InnerJoinConfig BuildInnerJoinConfig( + const v2::PsiConfig& psi_config, + const std::filesystem::path& root = std::filesystem::temp_directory_path()); + +// Generate sorted input at context.sorted_input_path +void InnerJoinGenerateSortedInput(const v2::InnerJoinConfig& config); + +// Generate unique keys and their cnt at context.unique_input_keys_cnt_path. +void InnerJoinGenerateUniqueInputKeysCnt(const v2::InnerJoinConfig& config); + +// After PSI with config.unique_input_keys_cnt_path as input and +// config.self_intersection_cnt_path as output. +// Run SyncIntersectionCnt to sync and save count at +// config.peer_intersection_cnt_path +void InnerJoinSyncIntersectionCnt( + const std::shared_ptr<yacl::link::Context>& link_ctx, + const v2::InnerJoinConfig& config); + +// Generate inner join intersection at config.output_path_path +void InnerJoinGenerateIntersection(const v2::InnerJoinConfig& config); + +// Generate inner join intersection at config.output_path_path +void InnerJoinGenerateDifference(const v2::InnerJoinConfig& config); + +} // namespace psi::psi diff --git a/psi/psi/utils/inner_join_test.cc b/psi/psi/utils/inner_join_test.cc new file mode 100644 index 00000000..1ced2986 --- /dev/null +++ b/psi/psi/utils/inner_join_test.cc @@ -0,0 +1,309 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/inner_join.h" + +#include <filesystem> +#include <fstream> + +#include "gtest/gtest.h" +#include "yacl/link/test_util.h" + +#include "psi/proto/psi.pb.h" + +namespace psi::psi { + +TEST(InnerJoinTest, Works) { + constexpr auto input_content_alice = R"csv(id1,id2,y1 +1,"b","y1_1" +1,"b","y1_2" +3,"c","y1_3" +4,"b","y1_4" +2,"a","y1_5" +2,"a","y1_6" +)csv"; + + constexpr auto input_content_bob = + R"csv(id1,id2,y2 +1,"b","y2_1" +1,"b","y2_2" +1,"b","y2_3" +3,"c","y2_4" +3,"c","y2_5" +3,"c","y2_6" +2,"a","y2_7" +4,"b","y2_8" +4,"b","y2_9" +)csv"; + + constexpr auto expected_output_content_alice = + R"csv("id1","id2","y1" +1,"b","y1_1" +1,"b","y1_1" +1,"b","y1_1" +1,"b","y1_2" +1,"b","y1_2" +1,"b","y1_2" +2,"a","y1_5" +2,"a","y1_6" +3,"c","y1_3" +3,"c","y1_3" +3,"c","y1_3" +4,"b","y1_4" +4,"b","y1_4" +)csv"; + + constexpr auto expected_output_content_bob = + R"csv("id1","id2","y2" +1,"b","y2_1" +1,"b","y2_2" +1,"b","y2_3" +1,"b","y2_1" +1,"b","y2_2" +1,"b","y2_3" +2,"a","y2_7" +2,"a","y2_7" +3,"c","y2_4" +3,"c","y2_5" +3,"c","y2_6" +4,"b","y2_8" +4,"b","y2_9" +)csv"; + + v2::InnerJoinConfig alice_config; + + std::filesystem::path inner_join_test_input_alice = + std::filesystem::temp_directory_path() / + "inner_join_test_input_alice.csv"; + std::filesystem::path inner_join_test_sorted_input_alice = + std::filesystem::temp_directory_path() / + "inner_join_test_sorted_input_alice.csv"; + std::filesystem::path inner_join_test_unique_input_keys_cnt_alice = + std::filesystem::temp_directory_path() / + "inner_join_test_unique_input_keys_cnt_alice.csv"; + std::filesystem::path inner_join_test_peer_intersection_cnt_alice = + std::filesystem::temp_directory_path() / + "inner_join_test_peer_intersection_cnt_alice.csv"; + std::filesystem::path inner_join_test_output_path_alice = + std::filesystem::temp_directory_path() / + "inner_join_test_output_path_alice.csv"; + + alice_config.set_input_path(inner_join_test_input_alice.string()); + alice_config.set_sorted_input_path( + inner_join_test_sorted_input_alice.string()); + alice_config.set_unique_input_keys_cnt_path( + inner_join_test_unique_input_keys_cnt_alice.string()); + alice_config.set_self_intersection_cnt_path( + inner_join_test_unique_input_keys_cnt_alice.string()); + alice_config.set_peer_intersection_cnt_path( + inner_join_test_peer_intersection_cnt_alice.string()); + alice_config.set_output_path(inner_join_test_output_path_alice.string()); + + alice_config.set_role(v2::ROLE_RECEIVER); + *alice_config.add_keys() = "id1"; + *alice_config.add_keys() = "id2"; + + v2::InnerJoinConfig bob_config; + + std::filesystem::path inner_join_test_input_bob = + std::filesystem::temp_directory_path() / "inner_join_test_input_bob.csv"; + std::filesystem::path inner_join_test_sorted_input_bob = + std::filesystem::temp_directory_path() / + "inner_join_test_sorted_input_bob.csv"; + std::filesystem::path inner_join_test_unique_input_keys_cnt_bob = + std::filesystem::temp_directory_path() / + "inner_join_test_unique_input_keys_cnt_bob.csv"; + std::filesystem::path inner_join_test_peer_intersection_cnt_bob = + std::filesystem::temp_directory_path() / + "inner_join_test_peer_intersection_cnt_bob.csv"; + std::filesystem::path inner_join_test_output_path_bob = + std::filesystem::temp_directory_path() / + "inner_join_test_output_path_bob.csv"; + + bob_config.set_input_path(inner_join_test_input_bob.string()); + bob_config.set_sorted_input_path(inner_join_test_sorted_input_bob.string()); + bob_config.set_unique_input_keys_cnt_path( + inner_join_test_unique_input_keys_cnt_bob.string()); + bob_config.set_self_intersection_cnt_path( + inner_join_test_unique_input_keys_cnt_bob.string()); + bob_config.set_peer_intersection_cnt_path( + inner_join_test_peer_intersection_cnt_bob.string()); + bob_config.set_output_path(inner_join_test_output_path_bob.string()); + + bob_config.set_role(v2::ROLE_SENDER); + *bob_config.add_keys() = "id1"; + *bob_config.add_keys() = "id2"; + + { + std::ofstream file; + file.open(alice_config.input_path()); + file << input_content_alice; + file.close(); + } + + { + std::ofstream file; + file.open(bob_config.input_path()); + file << input_content_bob; + file.close(); + } + + InnerJoinGenerateSortedInput(alice_config); + InnerJoinGenerateUniqueInputKeysCnt(alice_config); + + InnerJoinGenerateSortedInput(bob_config); + InnerJoinGenerateUniqueInputKeysCnt(bob_config); + + auto lctxs = yacl::link::test::SetupWorld(2); + + auto proc = [&](int idx) { + if (idx == 1) { + InnerJoinSyncIntersectionCnt(lctxs[idx], alice_config); + } else { + InnerJoinSyncIntersectionCnt(lctxs[idx], bob_config); + } + }; + + std::vector<std::future<void>> f_links(2); + + for (size_t i = 0; i < 2; i++) { + f_links[i] = std::async(proc, i); + } + + for (size_t i = 0; i < 2; i++) { + f_links[i].get(); + } + + InnerJoinGenerateIntersection(alice_config); + InnerJoinGenerateIntersection(bob_config); + + { + std::ifstream t(alice_config.output_path()); + std::stringstream buffer; + buffer << t.rdbuf(); + EXPECT_EQ(buffer.str(), expected_output_content_alice); + } + { + std::ifstream t(bob_config.output_path()); + std::stringstream buffer; + buffer << t.rdbuf(); + EXPECT_EQ(buffer.str(), expected_output_content_bob); + } + + { + std::filesystem::remove(alice_config.input_path()); + std::filesystem::remove(alice_config.sorted_input_path()); + std::filesystem::remove(alice_config.unique_input_keys_cnt_path()); + std::filesystem::remove(alice_config.peer_intersection_cnt_path()); + std::filesystem::remove(alice_config.output_path()); + } + { + std::filesystem::remove(bob_config.input_path()); + std::filesystem::remove(bob_config.sorted_input_path()); + std::filesystem::remove(bob_config.unique_input_keys_cnt_path()); + std::filesystem::remove(bob_config.peer_intersection_cnt_path()); + std::filesystem::remove(bob_config.output_path()); + } +} + +TEST(InnerJoinTest, OutputDifference) { + constexpr auto sorted_input_content = R"csv(id1,id2,y1 +0,"a","y1_0" +0,"b","y1_0" +0,"b","y1_0" +0,"c","y1_0" +1,"b","y1_1" +1,"b","y1_1" +1,"c","y1_0" +1,"c","y1_0" +2,"a","y1_5" +2,"a","y1_6" +3,"c","y1_3" +3,"b","y1_0" +4,"b","y1_4" +5,"a","y1_0" +5,"b","y1_0" +5,"b","y1_0" +5,"c","y1_0" +)csv"; + + constexpr auto self_intersection_cnt_content = + R"csv(id1,id2,psi_inner_join_cnt,psi_innner_join_first_index +1,"b",2,4 +2,"a",2,8 +3,"c",1,10 +4,"b",1,12 +)csv"; + + constexpr auto expected_output_content = R"csv("id1","id2","y1" +0,"a","y1_0" +0,"b","y1_0" +0,"b","y1_0" +0,"c","y1_0" +1,"c","y1_0" +1,"c","y1_0" +3,"b","y1_0" +5,"a","y1_0" +5,"b","y1_0" +5,"b","y1_0" +5,"c","y1_0" +)csv"; + + v2::InnerJoinConfig config; + + std::filesystem::path inner_join_test_sorted_input_file = + std::filesystem::temp_directory_path() / + "inner_join_test_sorted_input.csv"; + std::filesystem::path inner_join_test_self_intersection_file = + std::filesystem::temp_directory_path() / + "inner_join_test_self_intersection.csv"; + std::filesystem::path inner_join_test_output_path_file = + std::filesystem::temp_directory_path() / "inner_join_test_output.csv"; + + config.set_sorted_input_path(inner_join_test_sorted_input_file.string()); + config.set_self_intersection_cnt_path( + inner_join_test_self_intersection_file.string()); + config.set_output_path(inner_join_test_output_path_file.string()); + + { + std::ofstream file; + file.open(config.sorted_input_path()); + file << sorted_input_content; + file.close(); + } + + { + std::ofstream file; + file.open(config.self_intersection_cnt_path()); + file << self_intersection_cnt_content; + file.close(); + } + + InnerJoinGenerateDifference(config); + + { + std::ifstream t(config.output_path()); + std::stringstream buffer; + buffer << t.rdbuf(); + EXPECT_EQ(buffer.str(), expected_output_content); + } + + { + std::filesystem::remove(config.sorted_input_path()); + std::filesystem::remove(config.self_intersection_cnt_path()); + // std::filesystem::remove(config.output_path()); + } +} + +} // namespace psi::psi diff --git a/psi/psi/utils/multiplex_disk_cache.cc b/psi/psi/utils/multiplex_disk_cache.cc new file mode 100644 index 00000000..7fcac995 --- /dev/null +++ b/psi/psi/utils/multiplex_disk_cache.cc @@ -0,0 +1,87 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/multiplex_disk_cache.h" + +#include <unistd.h> + +#include <chrono> +#include <ctime> +#include <memory> +#include <random> + +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" + +namespace psi::psi { + +bool ScopedTempDir::CreateUniqueTempDirUnderPath( + const std::filesystem::path& parent_path) { + int tries = 0; + + const int kMaxTries = 10; + + do { + std::random_device dev; + auto seed = dev() ^ getpid(); + std::mt19937 prng(seed); + + std::uniform_int_distribution<uint64_t> rand(0); + std::stringstream ss; + ss << "psi-disk-cache-" << std::hex << rand(prng); + dir_ = parent_path / ss.str(); + if (!std::filesystem::exists(dir_)) { + return std::filesystem::create_directory(dir_); + } + tries++; + + } while (tries < kMaxTries); + + return false; +} + +MultiplexDiskCache::MultiplexDiskCache(const std::filesystem::path& path, + bool use_scoped_tmp_dir) { + if (use_scoped_tmp_dir) { + scoped_temp_dir_ = std::make_unique<ScopedTempDir>(); + YACL_ENFORCE(scoped_temp_dir_->CreateUniqueTempDirUnderPath(path)); + cache_dir_ = scoped_temp_dir_->path(); + } else { + cache_dir_ = path; + } +} + +std::string MultiplexDiskCache::GetPath(size_t index) const { + std::filesystem::path path = cache_dir_ / std::to_string(index); + + return path.string(); +} + +void MultiplexDiskCache::CreateOutputStreams( + size_t num_bins, + std::vector<std::unique_ptr<io::OutputStream>>* bin_outs) const { + YACL_ENFORCE(num_bins != 0, "bad num_bins={}", num_bins); + for (size_t i = 0; i < num_bins; ++i) { + // trunc is off. + bin_outs->push_back( + io::BuildOutputStream(io::FileIoOptions(GetPath(i), false, true))); + } +} + +std::unique_ptr<io::InputStream> MultiplexDiskCache::CreateInputStream( + size_t index) const { + return io::BuildInputStream(io::FileIoOptions(GetPath(index))); +} + +} // namespace psi::psi diff --git a/psi/psi/utils/multiplex_disk_cache.h b/psi/psi/utils/multiplex_disk_cache.h new file mode 100644 index 00000000..ea19e0e9 --- /dev/null +++ b/psi/psi/utils/multiplex_disk_cache.h @@ -0,0 +1,71 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <filesystem> +#include <memory> +#include <string> +#include <vector> + +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" + +#include "psi/psi/io/io.h" + +namespace psi::psi { + +class ScopedTempDir { + public: + bool CreateUniqueTempDirUnderPath(const std::filesystem::path& parent_path); + + const std::filesystem::path& path() const { return dir_; } + + ~ScopedTempDir() { + if (!dir_.empty()) { + std::error_code ec; + std::filesystem::remove_all(dir_, ec); + // Leave error as it is, do nothing + } + } + + private: + std::filesystem::path dir_; +}; + +/// MultiplexDiskCache is a disk cache with multiple file storage. +class MultiplexDiskCache { + public: + explicit MultiplexDiskCache(const std::filesystem::path& path, + bool use_scoped_tmp_dir = true); + + MultiplexDiskCache(const MultiplexDiskCache&) = delete; + MultiplexDiskCache& operator=(const MultiplexDiskCache&) = delete; + + std::string GetPath(size_t index) const; + + std::filesystem::path cache_dir() const { return cache_dir_; } + + void CreateOutputStreams( + size_t num_bins, + std::vector<std::unique_ptr<io::OutputStream>>* bin_outs) const; + + std::unique_ptr<io::InputStream> CreateInputStream(size_t index) const; + + private: + std::filesystem::path cache_dir_; + std::unique_ptr<ScopedTempDir> scoped_temp_dir_; +}; + +} // namespace psi::psi diff --git a/psi/psi/utils/multiplex_disk_cache_test.cc b/psi/psi/utils/multiplex_disk_cache_test.cc new file mode 100644 index 00000000..7f14e5fa --- /dev/null +++ b/psi/psi/utils/multiplex_disk_cache_test.cc @@ -0,0 +1,85 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/multiplex_disk_cache.h" + +#include <filesystem> + +#include "gtest/gtest.h" + +#include "psi/psi/io/io.h" + +namespace psi::psi { + +std::size_t GetFileCntInDirectory(const std::filesystem::path& path) { + using std::filesystem::directory_iterator; + return std::distance(directory_iterator(path), directory_iterator{}); +} + +class MultiplexDiskCacheTest : public ::testing::Test { + protected: + void SetUp() override { + tmp_dir_ = "./tmp"; + std::filesystem::create_directory(tmp_dir_); + } + void TearDown() override { + if (!tmp_dir_.empty()) { + std::error_code ec; + std::filesystem::remove_all(tmp_dir_, ec); + // Leave error as it is, do nothing + } + } + + std::string tmp_dir_; +}; + +TEST_F(MultiplexDiskCacheTest, UseScopedTmpDirOn) { + std::filesystem::path cache_path; + { + MultiplexDiskCache disk_cache_(tmp_dir_); + + std::vector<std::unique_ptr<io::OutputStream>> output_streams; + disk_cache_.CreateOutputStreams(10, &output_streams); + for (int i = 0; i < 10; i++) { + output_streams[i]->Flush(); + } + + cache_path = disk_cache_.cache_dir(); + EXPECT_EQ(10, GetFileCntInDirectory(cache_path)); + } + + EXPECT_EQ(0, GetFileCntInDirectory(tmp_dir_)); + EXPECT_FALSE(std::filesystem::exists(cache_path)); +} + +TEST_F(MultiplexDiskCacheTest, UseScopedTmpDirOff) { + std::filesystem::path cache_path; + { + MultiplexDiskCache disk_cache_(tmp_dir_, false); + + std::vector<std::unique_ptr<io::OutputStream>> output_streams; + disk_cache_.CreateOutputStreams(10, &output_streams); + for (int i = 0; i < 10; i++) { + output_streams[i]->Flush(); + } + cache_path = disk_cache_.cache_dir(); + EXPECT_EQ(10, GetFileCntInDirectory(cache_path)); + } + + EXPECT_EQ(10, GetFileCntInDirectory(tmp_dir_)); + EXPECT_TRUE(std::filesystem::exists(cache_path)); + EXPECT_EQ(10, GetFileCntInDirectory(cache_path)); +} + +} // namespace psi::psi diff --git a/psi/psi/utils/progress.cc b/psi/psi/utils/progress.cc new file mode 100644 index 00000000..d830f469 --- /dev/null +++ b/psi/psi/utils/progress.cc @@ -0,0 +1,145 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/progress.h" + +#include <mutex> +#include <numeric> + +#include "fmt/format.h" + +namespace psi::psi { + +Progress::Progress(std::string description) + : description_(description), + percentage_(0), + mode_(Mode::kSingle), + done_(false) {} + +void Progress::Update(size_t percentage) { + percentage_.store(std::min<size_t>(percentage, 100)); +} + +void Progress::Done() { + if (IsDone()) { + return; + } + + if (mode_.load() != Mode::kSingle) { + std::unique_lock lock(rw_mutex_); + + for (size_t i = 0; i < sub_progresses_.size(); i++) { + sub_progresses_[i]->Done(); + } + } + + percentage_.store(100); + done_.store(true); +} + +bool Progress::IsDone() const { return done_.load(); } + +Progress::Data Progress::Get() { + if (mode_.load() == Mode::kSingle) { + Data out; + auto p = percentage_.load(); + out.total = 1; + out.running = p > 0 && p < 100 ? 1 : 0; + out.finished = p >= 100 ? 1 : 0; + out.percentage = p; + if (description_.empty()) { + out.description = fmt::format("{}%", p); + } else { + out.description = fmt::format("{}, {}%", description_, p); + } + return out; + } + + std::shared_lock r_lock(rw_mutex_); + + size_t total_weight = + std::accumulate(weights_.begin(), weights_.end(), size_t(0)); + size_t total_percent = 0; + Data out; + + std::string sub_desc; + out.total = weights_.size(); + for (size_t i = 0; i < sub_progresses_.size(); i++) { + auto sub_data = sub_progresses_[i]->Get(); + total_percent += weights_[i] * sub_data.percentage; + + out.total--; + out.total += sub_data.total; + out.finished += sub_data.finished; + out.running += sub_data.running; + + // get running job's or last job's description + if (sub_desc.empty() && + (!sub_progresses_[i]->IsDone() || i == sub_progresses_.size() - 1)) { + sub_desc = sub_data.description; + } + } + out.percentage = total_percent / (total_weight == 0 ? 1 : total_weight); + + if (mode_.load() == Mode::kParallel) { + sub_desc = fmt::format("{} jobs running ({} / {})", out.running, + out.finished, out.total); + } + + if (description_.empty()) { + out.description = sub_desc; + } else if (sub_desc.empty()) { + out.description = description_; + } else { + out.description = description_ + ", " + sub_desc; + } + + return out; +} + +void Progress::SetWeights(std::vector<size_t> weights, Mode mode) { + std::unique_lock lock(rw_mutex_); + mode_.store(mode); + weights_ = std::move(weights); +} + +void Progress::SetSubJobCount(size_t count, Mode mode) { + std::unique_lock lock(rw_mutex_); + mode_.store(mode); + weights_.resize(count, 1); +} + +std::shared_ptr<Progress> Progress::AddSubProgress( + const std::string& description) { + std::unique_lock lock(rw_mutex_); + + auto p = std::make_shared<Progress>(description); + sub_progresses_.push_back(p); + return p; +} + +std::shared_ptr<Progress> Progress::NextSubProgress( + const std::string& description) { + std::unique_lock lock(rw_mutex_); + + if (!sub_progresses_.empty()) { + auto current = sub_progresses_.back(); + current->Done(); + } + auto p = std::make_shared<Progress>(description); + sub_progresses_.push_back(p); + return p; +} + +} // namespace psi::psi \ No newline at end of file diff --git a/psi/psi/utils/progress.h b/psi/psi/utils/progress.h new file mode 100644 index 00000000..40dc5bb7 --- /dev/null +++ b/psi/psi/utils/progress.h @@ -0,0 +1,83 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <atomic> +#include <memory> +#include <shared_mutex> +#include <string> +#include <vector> + +namespace psi::psi { + +class Progress { + public: + enum class Mode { + kSingle, + kSerial, + kParallel, + }; + + struct Data { + size_t total; + size_t finished; + size_t running; + size_t percentage; + std::string description; + + Data() + : total(0), finished(0), running(0), percentage(0), description("") {} + }; + + public: + Progress(std::string description = ""); + + ~Progress() = default; + + void Update(size_t percentage); + + Data Get(); + + void Done(); + + bool IsDone() const; + + void SetWeights(std::vector<size_t> weights, Mode mode = Mode::kSerial); + + void SetSubJobCount(size_t count, Mode mode = Mode::kParallel); + + std::shared_ptr<Progress> AddSubProgress(const std::string& description = ""); + + // Mark current sub progress as Done and Add new sub progress + std::shared_ptr<Progress> NextSubProgress( + const std::string& description = ""); + + private: + std::shared_mutex rw_mutex_; + + std::vector<size_t> weights_; + + std::vector<std::shared_ptr<Progress>> sub_progresses_; + + const std::string description_; + + std::atomic_size_t percentage_; + + std::atomic<Mode> mode_; + + std::atomic_bool done_; +}; + +} // namespace psi::psi \ No newline at end of file diff --git a/psi/psi/utils/progress_test.cc b/psi/psi/utils/progress_test.cc new file mode 100644 index 00000000..fb1cdca5 --- /dev/null +++ b/psi/psi/utils/progress_test.cc @@ -0,0 +1,85 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/progress.h" + +#include "gtest/gtest.h" + +namespace psi::psi { + +class ProgressTest : public ::testing::Test {}; + +TEST_F(ProgressTest, ProgressSinglePhase_Normal) { + auto progress = std::make_shared<Progress>(); + + auto data = progress->Get(); + EXPECT_EQ(data.percentage, 0); + EXPECT_EQ(data.total, 1); + EXPECT_EQ(data.finished, 0); + EXPECT_EQ(data.running, 0); + EXPECT_EQ(data.description, "0%"); + + progress->Update(50); + data = progress->Get(); + EXPECT_EQ(data.percentage, 50); + EXPECT_EQ(data.description, "50%"); + + progress->Done(); + data = progress->Get(); + EXPECT_EQ(data.percentage, 100); + EXPECT_EQ(data.description, "100%"); +} + +TEST_F(ProgressTest, ProgressMultiPhase_Normal) { + auto progress = std::make_shared<Progress>(); + progress->SetWeights({10, 90}); + auto data = progress->Get(); + EXPECT_EQ(data.percentage, 0); + EXPECT_EQ(data.total, 2); + EXPECT_EQ(data.finished, 0); + EXPECT_EQ(data.running, 0); + EXPECT_EQ(data.description, ""); + + progress->Update(50); // do nothing + data = progress->Get(); + EXPECT_EQ(data.percentage, 0); + EXPECT_EQ(data.description, ""); + + auto step1 = progress->NextSubProgress("Step1"); + data = progress->Get(); + EXPECT_EQ(data.percentage, 0); + EXPECT_EQ(data.description, "Step1, 0%"); + + step1->Update(50); + data = progress->Get(); + EXPECT_EQ(data.percentage, 5); + EXPECT_EQ(data.description, "Step1, 50%"); + + auto step2 = progress->NextSubProgress("Step2"); + data = progress->Get(); + EXPECT_EQ(data.percentage, 10); + EXPECT_EQ(data.description, "Step2, 0%"); + + step2->Update(50); + data = progress->Get(); + EXPECT_EQ(data.percentage, 55); // 10 + 90 * 0.5 + EXPECT_EQ(data.description, "Step2, 50%"); + + step2->Done(); + data = progress->Get(); + EXPECT_EQ(data.percentage, 100); + EXPECT_EQ(data.description, "Step2, 100%"); +} + +} // namespace psi::psi diff --git a/psi/psi/utils/resource.cc b/psi/psi/utils/resource.cc new file mode 100644 index 00000000..6de1b0b8 --- /dev/null +++ b/psi/psi/utils/resource.cc @@ -0,0 +1,56 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/resource.h" + +#include <fstream> +#include <string> + +#include "absl/strings/ascii.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_split.h" + +namespace psi::psi { + +std::string ReadProcSelfStatusByKey(const std::string& key) { + std::string ret; + std::ifstream self_status("/proc/self/status"); + std::string line; + while (std::getline(self_status, line)) { + std::vector<absl::string_view> fields = + absl::StrSplit(line, absl::ByChar(':')); + if (fields.size() == 2 && key == absl::StripAsciiWhitespace(fields[0])) { + ret = absl::StripAsciiWhitespace(fields[1]); + } + } + return ret; +} + +// only work with VmXXX in /proc/self/status +size_t ReadVMxFromProcSelfStatus(const std::string& key) { + const std::string str_usage = ReadProcSelfStatusByKey(key); + std::vector<absl::string_view> fields = + absl::StrSplit(str_usage, absl::ByChar(' ')); + if (fields.size() == 2) { + size_t ret = 0; + YACL_ENFORCE(absl::SimpleAtoi(fields[0], &ret), + "Fail to get {} in self status, {}", key, str_usage); + return ret; + } + YACL_THROW("Fail to get {} in self status, {}", key, str_usage); +} + +size_t GetPeakKbMemUsage() { return ReadVMxFromProcSelfStatus("VmHWM"); } + +} // namespace psi::psi diff --git a/psi/psi/utils/resource.h b/psi/psi/utils/resource.h new file mode 100644 index 00000000..8dbf2b3c --- /dev/null +++ b/psi/psi/utils/resource.h @@ -0,0 +1,29 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <cstddef> + +#include "yacl/base/exception.h" + +namespace psi::psi { + +/* + * VmHWM from /proc/self/status + * see https://man7.org/linux/man-pages/man5/proc.5.html + */ +size_t GetPeakKbMemUsage(); + +} // namespace psi::psi diff --git a/psi/psi/utils/serializable.proto b/psi/psi/utils/serializable.proto new file mode 100644 index 00000000..46e4fc27 --- /dev/null +++ b/psi/psi/utils/serializable.proto @@ -0,0 +1,33 @@ +// +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +syntax = "proto3"; + +package psi.psi.proto; + +message SizeProto { + uint64 input_size = 1; +} + +message PsiDataBatchProto { + uint32 item_num = 1; + bytes flatten_bytes = 2; + bool is_last_batch = 3; +} + +message StrItemsProto { + repeated string items = 1; +} diff --git a/psi/psi/utils/serialize.h b/psi/psi/utils/serialize.h new file mode 100644 index 00000000..03befb95 --- /dev/null +++ b/psi/psi/utils/serialize.h @@ -0,0 +1,68 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <string> +#include <vector> + +#include "yacl/base/buffer.h" + +#include "psi/psi/utils/serializable.pb.h" + +namespace psi::psi::utils { + +inline yacl::Buffer SerializeSize(size_t size) { + proto::SizeProto proto; + proto.set_input_size(size); + yacl::Buffer buf(proto.ByteSizeLong()); + proto.SerializeToArray(buf.data(), buf.size()); + return buf; +} + +inline size_t DeserializeSize(const yacl::Buffer& buf) { + proto::SizeProto proto; + proto.ParseFromArray(buf.data(), buf.size()); + return proto.input_size(); +} + +inline yacl::Buffer SerializeStrItems(const std::vector<std::string>& items) { + proto::StrItemsProto proto; + for (const auto& item : items) { + proto.add_items(item); + } + yacl::Buffer buf(proto.ByteSizeLong()); + proto.SerializeToArray(buf.data(), buf.size()); + return buf; +} + +inline void DeserializeStrItems(const yacl::Buffer& buf, + std::vector<std::string>* items) { + proto::StrItemsProto proto; + proto.ParseFromArray(buf.data(), buf.size()); + items->reserve(proto.items_size()); + for (auto item : proto.items()) { + items->emplace_back(item); + } +} + +inline size_t GetCompareBytesLength(size_t size_a, size_t size_b, + size_t stats_params = 40) { + size_t compare_bits = std::ceil(std::log2(size_a)) + + std::ceil(std::log2(size_b)) + stats_params; + + return (compare_bits + 7) / 8; +} + +} // namespace psi::psi::utils diff --git a/psi/psi/utils/test_utils.h b/psi/psi/utils/test_utils.h new file mode 100644 index 00000000..18d20569 --- /dev/null +++ b/psi/psi/utils/test_utils.h @@ -0,0 +1,69 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <optional> +#include <set> +#include <string> +#include <vector> + +#include "yacl/crypto/base/hash/hash_utils.h" + +#include "psi/psi/psi.pb.h" + +namespace psi::psi::test { + +inline std::vector<std::string> CreateRangeItems(size_t begin, size_t size) { + std::vector<std::string> ret; + for (size_t i = 0; i < size; i++) { + ret.push_back(std::to_string(begin + i)); + } + return ret; +} + +inline std::vector<std::string> GetIntersection( + const std::vector<std::string> &items_a, + const std::vector<std::string> &items_b) { + std::set<std::string> set(items_a.begin(), items_a.end()); + std::vector<std::string> ret; + for (const auto &s : items_b) { + if (set.count(s) != 0) { + ret.push_back(s); + } + } + return ret; +} + +inline std::vector<uint128_t> CreateItemHashes(size_t begin, size_t size) { + std::vector<uint128_t> ret; + for (size_t i = 0; i < size; i++) { + ret.push_back(yacl::crypto::Blake3_128(std::to_string(begin + i))); + } + return ret; +} + +inline std::optional<CurveType> GetOverrideCurveType() { + if (const auto *env = std::getenv("OVERRIDE_CURVE")) { + if (std::strcmp(env, "25519") == 0) { + return CurveType::CURVE_25519; + } + if (std::strcmp(env, "FOURQ") == 0) { + return CurveType::CURVE_FOURQ; + } + } + return {}; +} + +} // namespace psi::psi::test diff --git a/psi/psi/utils/ub_psi_cache.cc b/psi/psi/utils/ub_psi_cache.cc new file mode 100644 index 00000000..96f020f5 --- /dev/null +++ b/psi/psi/utils/ub_psi_cache.cc @@ -0,0 +1,147 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/ub_psi_cache.h" + +#include <algorithm> +#include <tuple> + +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" + +#include "psi/psi/utils/serialize.h" + +namespace psi::psi { + +UbPsiCacheProvider::UbPsiCacheProvider(const std::string &file_path, + size_t batch_size, size_t data_len) + : batch_size_(batch_size), file_path_(file_path), data_len_(data_len) { + in_ = io::BuildInputStream(io::FileIoOptions(file_path)); + file_size_ = in_->GetLength(); + + data_index_len_ = data_len_ + 2 * sizeof(uint64_t); + + uint64_t ids_buffer_len; + in_->Read(&ids_buffer_len, sizeof(uint64_t)); + + file_cursor_ += sizeof(uint64_t); + + if (ids_buffer_len > 0) { + yacl::Buffer ids_buffer(ids_buffer_len); + in_->Read(ids_buffer.data(), ids_buffer_len); + utils::DeserializeStrItems(ids_buffer, &selected_fields_); + file_cursor_ += ids_buffer_len; + } +} + +std::vector<std::tuple<std::string, size_t, size_t>> +UbPsiCacheProvider::ReadData(size_t read_count) { + std::vector<uint8_t> read_data(read_count * data_index_len_); + std::string item(data_len_, '\0'); + size_t index, shuffle_index; + + std::vector<std::tuple<std::string, size_t, size_t>> ret; + + in_->Read(read_data.data(), read_count * data_index_len_); + for (size_t i = 0; i < read_count; ++i) { + size_t cur_pos = i * data_index_len_; + std::memcpy(item.data(), read_data.data() + cur_pos, data_len_); + std::memcpy(&index, read_data.data() + cur_pos + data_len_, sizeof(size_t)); + std::memcpy(&shuffle_index, + read_data.data() + cur_pos + data_len_ + sizeof(size_t), + sizeof(size_t)); + ret.push_back(std::make_tuple(item, index, shuffle_index)); + } + + return ret; +} + +std::vector<std::string> UbPsiCacheProvider::ReadNextBatch() { + std::vector<std::string> ret; + + size_t read_bytes = std::min<size_t>(batch_size_ * data_index_len_, + file_size_ - file_cursor_); + size_t read_count = read_bytes / data_index_len_; + + if (read_bytes > 0) { + std::vector<std::tuple<std::string, size_t, size_t>> data = + ReadData(read_count); + + for (const auto &d : data) { + ret.push_back(std::get<0>(d)); + } + file_cursor_ += read_bytes; + } + + return ret; +} + +std::tuple<std::vector<std::string>, std::vector<size_t>, std::vector<size_t>> +UbPsiCacheProvider::ReadNextShuffledBatch() { + std::vector<std::string> ret_data; + std::vector<size_t> ret_indices; + std::vector<size_t> shuffle_indices; + + size_t read_bytes = std::min<size_t>(batch_size_ * data_index_len_, + file_size_ - file_cursor_); + size_t read_count = read_bytes / data_index_len_; + + if (read_bytes > 0) { + std::vector<std::tuple<std::string, size_t, size_t>> data = + ReadData(read_count); + for (const auto &d : data) { + ret_data.push_back(std::get<0>(d)); + ret_indices.push_back(std::get<1>(d)); + shuffle_indices.push_back(std::get<2>(d)); + } + file_cursor_ += read_bytes; + } + + return std::make_tuple(ret_data, ret_indices, shuffle_indices); +} + +const std::vector<std::string> &UbPsiCacheProvider::GetSelectedFields() { + return selected_fields_; +} + +UbPsiCache::UbPsiCache(const std::string &file_path, size_t data_len, + const std::vector<std::string> &selected_fields) + : file_path_(file_path), data_len_(data_len) { + out_stream_ = io::BuildOutputStream(io::FileIoOptions(file_path)); + data_index_len_ = data_len_ + 2 * sizeof(uint64_t); + + yacl::Buffer ids_buffer = utils::SerializeStrItems(selected_fields); + uint64_t buffer_len = ids_buffer.size(); + + out_stream_->Write(&buffer_len, sizeof(uint64_t)); + if (buffer_len > 0) { + out_stream_->Write(ids_buffer.data(), ids_buffer.size()); + } +} + +void UbPsiCache::SaveData(yacl::ByteContainerView item, size_t index, + size_t shuffle_index) { + YACL_ENFORCE(item.size() == data_len_, "item size:{} data_len_:{}", + item.size(), data_len_); + std::string data_with_index(data_index_len_, '\0'); + + std::memcpy(data_with_index.data(), item.data(), data_len_); + std::memcpy(data_with_index.data() + data_len_, &index, sizeof(uint64_t)); + std::memcpy(data_with_index.data() + data_len_ + sizeof(uint64_t), + &shuffle_index, sizeof(uint64_t)); + + out_stream_->Write(data_with_index.data(), data_with_index.length()); +} + +} // namespace psi::psi diff --git a/psi/psi/utils/ub_psi_cache.h b/psi/psi/utils/ub_psi_cache.h new file mode 100644 index 00000000..d9d9df45 --- /dev/null +++ b/psi/psi/utils/ub_psi_cache.h @@ -0,0 +1,90 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <memory> +#include <string> +#include <tuple> +#include <utility> +#include <vector> + +#include "yacl/base/byte_container_view.h" + +#include "psi/psi/io/io.h" +#include "psi/psi/utils/batch_provider.h" + +namespace psi::psi { + +class UbPsiCacheProvider : public IBasicBatchProvider, + public IShuffledBatchProvider { + public: + UbPsiCacheProvider(const std::string &file_path, size_t batch_size, + size_t data_len); + ~UbPsiCacheProvider() override { in_->Close(); } + + std::vector<std::string> ReadNextBatch() override; + + std::tuple<std::vector<std::string>, std::vector<size_t>, std::vector<size_t>> + ReadNextShuffledBatch() override; + + const std::vector<std::string> &GetSelectedFields(); + + [[nodiscard]] size_t batch_size() const override { return batch_size_; } + + private: + std::vector<std::tuple<std::string, size_t, size_t>> ReadData( + size_t read_count); + + const size_t batch_size_; + std::string file_path_; + size_t file_size_; + size_t file_cursor_ = 0; + std::unique_ptr<io::InputStream> in_; + size_t data_len_; + size_t data_index_len_; + + std::vector<std::string> selected_fields_; +}; + +class IUbPsiCache { + public: + virtual ~IUbPsiCache() = default; + + virtual void SaveData(yacl::ByteContainerView item, size_t index, + size_t shuffle_index) = 0; + + virtual void Flush() { return; } +}; + +class UbPsiCache : public IUbPsiCache { + public: + UbPsiCache(const std::string &file_path, size_t data_len, + const std::vector<std::string> &ids); + + ~UbPsiCache() { out_stream_->Close(); } + + void SaveData(yacl::ByteContainerView item, size_t index, + size_t shuffle_index) override; + + void Flush() override { out_stream_->Flush(); } + + private: + std::string file_path_; + size_t data_len_; + size_t data_index_len_; + std::unique_ptr<io::OutputStream> out_stream_; +}; + +} // namespace psi::psi diff --git a/psi/psi/utils/ub_psi_cache_test.cc b/psi/psi/utils/ub_psi_cache_test.cc new file mode 100644 index 00000000..530d00af --- /dev/null +++ b/psi/psi/utils/ub_psi_cache_test.cc @@ -0,0 +1,82 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/ub_psi_cache.h" + +#include <filesystem> +#include <iostream> +#include <string> +#include <utility> +#include <vector> + +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "gtest/gtest.h" +#include "yacl/crypto/utils/rand.h" +#include "yacl/utils/scope_guard.h" + +namespace psi::psi { + +TEST(UbPsiCacheTest, Simple) { + size_t data_len = 12; + + auto timestamp_str = std::to_string(absl::ToUnixNanos(absl::Now())); + auto tmp_file_path = + std::filesystem::path(fmt::format("tmp-cache-{}", timestamp_str)); + + // register remove of temp file. + ON_SCOPE_EXIT([&] { + std::error_code ec; + std::filesystem::remove(tmp_file_path, ec); + if (ec.value() != 0) { + SPDLOG_WARN("can not remove tmp file: {}, msg: {}", tmp_file_path.c_str(), + ec.message()); + } + }); + + std::vector<std::vector<uint8_t>> items; + + std::vector<std::string> selected_fields = {"id1", "id2"}; + UbPsiCache cache(tmp_file_path.string(), data_len, selected_fields); + + std::vector<uint8_t> rand_bytes = yacl::crypto::RandBytes(data_len); + items.push_back(rand_bytes); + + cache.SaveData(rand_bytes, 0, 10); + rand_bytes = yacl::crypto::RandBytes(data_len); + items.push_back(rand_bytes); + cache.SaveData(rand_bytes, 1, 11); + cache.Flush(); + + UbPsiCacheProvider provider(tmp_file_path.string(), items.size() + 1, + data_len); + + const std::vector<std::string>& read_fields = provider.GetSelectedFields(); + EXPECT_EQ(read_fields.size(), selected_fields.size()); + + std::vector<std::string> batch_data; + std::vector<size_t> batch_indices; + std::vector<size_t> shuffle_indices; + std::tie(batch_data, batch_indices, shuffle_indices) = + provider.ReadNextShuffledBatch(); + + EXPECT_EQ(batch_data.size(), items.size()); + + for (size_t i = 0; i < batch_data.size(); i++) { + EXPECT_EQ(batch_indices[i], i); + EXPECT_EQ(std::memcmp(batch_data[i].data(), items[i].data(), data_len), 0); + } +} + +} // namespace psi::psi diff --git a/psi/psi/utils/utils.cc b/psi/psi/utils/utils.cc new file mode 100644 index 00000000..4926c7b8 --- /dev/null +++ b/psi/psi/utils/utils.cc @@ -0,0 +1,285 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "psi/psi/utils/utils.h" + +#include "spdlog/spdlog.h" +#include "yacl/base/exception.h" +#include "yacl/crypto/utils/rand.h" + +#include "psi/psi/io/io.h" +#include "psi/psi/utils/csv_header_parser.h" +#include "psi/psi/utils/index_store.h" +#include "psi/psi/utils/serialize.h" + +namespace psi::psi { + +// Multiple-Key out-of-core sort. +// Out-of-core support reference: +// http://vkundeti.blogspot.com/2008/03/tech-algorithmic-details-of-unix-sort.html +// Multiple-Key support reference: +// https://stackoverflow.com/questions/9471101/sort-csv-file-by-column-priority-using-the-sort-command +// use POSIX locale for sort +// https://unix.stackexchange.com/questions/43465/whats-the-default-order-of-linux-sort/43466 +// +// NOTE: +// This implementation requires `sort` command, which is guaranteed by our +// docker-way ship. +void MultiKeySort(const std::string& in_csv, const std::string& out_csv, + const std::vector<std::string>& keys, bool numeric_sort, + bool unique) { + CsvHeaderParser parser(in_csv); + + std::string line; + { + // Copy head line to out_csv + // Add scope to flush write here. + io::FileIoOptions in_opts(in_csv); + auto in = io::BuildInputStream(in_opts); + in->GetLine(&line); + in->Close(); + + io::FileIoOptions out_opts(out_csv); + auto out = io::BuildOutputStream(out_opts); + out->Write(line); + out->Write("\n"); + out->Close(); + } + + // Construct sort key indices. + // NOTE: `sort` cmd starts from index 1. + std::vector<std::string> sort_keys; + for (size_t index : parser.target_indices(keys, 1)) { + // About `sort --key=KEYDEF` + // + // KEYDEF is F[.C][OPTS][,F[.C][OPTS]] for start and stop position, where + // F is a field number and C a character position in the field; both are + // origin 1, and the stop position defaults to the line's end. If neither + // -t nor -b is in effect, characters in a field are counted from the + // beginning of the preceding whitespace. OPTS is one or more + // single-letter ordering options [bdfgiMhnRrV], which override global + // ordering options for that key. If no key is given, use the entire line + // as the key. + // + // I have already verified `sort --key=3,3 --key=1,1` will firstly sort by + // 3rd field and then 1st field. + sort_keys.push_back(fmt::format("--key={},{}", index, index)); + } + YACL_ENFORCE(sort_keys.size() == keys.size(), + "mismatched header, field_names={}, line={}", + fmt::join(keys, ","), line); + + // Sort the csv body and append to out csv. + std::string cmd = fmt::format( + "tail -n +2 {} | LC_ALL=C sort {} --buffer-size=3G --parallel=8 " + "--temporary-directory=./ --stable --field-separator=, {} {} >>{}", + in_csv, numeric_sort ? "-n" : "", fmt::join(sort_keys, " "), + unique ? "| LC_ALL=C uniq" : "", out_csv); + SPDLOG_INFO("Executing sort scripts: {}", cmd); + int ret = system(cmd.c_str()); + SPDLOG_INFO("Finished sort scripts: {}, ret={}", cmd, ret); + YACL_ENFORCE(ret == 0, "failed to execute cmd={}, ret={}", cmd, ret); +} + +size_t FilterFileByIndices(const std::string& input, const std::string& output, + const std::vector<uint64_t>& indices, + bool output_difference, size_t header_line_count) { + auto in = io::BuildInputStream(io::FileIoOptions(input)); + auto out = io::BuildOutputStream(io::FileIoOptions(output)); + + std::string line; + size_t idx = 0; + size_t actual_count = 0; + auto indices_iter = indices.begin(); + while (in->GetLine(&line)) { + if (idx < header_line_count) { + out->Write(line); + out->Write("\n"); + } else { + if (!output_difference) { + if (indices_iter == indices.end()) { + break; + } + } + + if ((indices_iter != indices.end() && + *indices_iter == idx - header_line_count) != output_difference) { + out->Write(line); + out->Write("\n"); + actual_count++; + } + + if (indices_iter != indices.end() && + *indices_iter == idx - header_line_count) { + indices_iter++; + } + } + idx++; + } + size_t target_count = + (output_difference ? (idx - header_line_count - indices.size()) + : indices.size()); + + YACL_ENFORCE_EQ(actual_count, target_count, + "logstic error, indices.size={}, actual_count={}, " + "target_count={}, output_difference={}, please be " + "sure the `indices` is sorted", + indices.size(), actual_count, target_count, + output_difference); + + out->Close(); + in->Close(); + + return indices.size(); +} + +size_t FilterFileByIndices(const std::string& input, const std::string& output, + const std::filesystem::path& indices, + bool output_difference, size_t header_line_count) { + auto in = io::BuildInputStream(io::FileIoOptions(input)); + auto out = io::BuildOutputStream(io::FileIoOptions(output)); + + std::string line; + size_t idx = 0; + size_t actual_count = 0; + IndexReader reader(indices); + + std::optional<uint64_t> intersection_index = reader.GetNext(); + + while (in->GetLine(&line)) { + if (idx < header_line_count) { + out->Write(line); + out->Write("\n"); + } else { + if (!output_difference) { + if (!intersection_index.has_value()) { + break; + } + } + + if ((intersection_index.has_value() && + intersection_index.value() == idx - header_line_count) != + output_difference) { + out->Write(line); + out->Write("\n"); + actual_count++; + } + + if (intersection_index.has_value() && + intersection_index.value() == idx - header_line_count) { + intersection_index = reader.GetNext(); + } + } + idx++; + } + + size_t target_count = + (output_difference ? (idx - header_line_count - reader.read_cnt()) + : reader.read_cnt()); + + YACL_ENFORCE_EQ(actual_count, target_count, + "logstic error, reader.read_cnt={}, actual_count={}, " + "target_count={}, output_difference={}, please be " + "sure the `indices` is sorted", + reader.read_cnt(), actual_count, target_count, + output_difference); + + out->Close(); + in->Close(); + + return reader.read_cnt(); +} + +std::string KeysJoin(const std::vector<absl::string_view>& keys) { + return absl::StrJoin(keys, ","); +} + +std::vector<size_t> AllGatherItemsSize( + const std::shared_ptr<yacl::link::Context>& link_ctx, size_t self_size) { + std::vector<size_t> items_size_list(link_ctx->WorldSize()); + + std::vector<yacl::Buffer> items_size_buf_list = yacl::link::AllGather( + link_ctx, utils::SerializeSize(self_size), "PSI:SYNC_SIZE"); + + for (size_t idx = 0; idx < items_size_buf_list.size(); idx++) { + items_size_list[idx] = utils::DeserializeSize(items_size_buf_list[idx]); + } + + return items_size_list; +} + +std::vector<size_t> GetShuffledIdx(size_t items_size) { + std::mt19937 rng(yacl::crypto::RandU64()); + + std::vector<size_t> shuffled_idx_vec(items_size); + std::iota(shuffled_idx_vec.begin(), shuffled_idx_vec.end(), 0); + std::shuffle(shuffled_idx_vec.begin(), shuffled_idx_vec.end(), rng); + + return shuffled_idx_vec; +} + +// pad data to max_len bytes +// format len(32bit)||data||00..00 +std::vector<uint8_t> PaddingData(yacl::ByteContainerView data, size_t max_len) { + YACL_ENFORCE((data.size() + 4) <= max_len, "data_size:{} max_len:{}", + data.size(), max_len); + + std::vector<uint8_t> data_with_padding(max_len); + uint32_t data_size = data.size(); + + std::memcpy(&data_with_padding[0], &data_size, sizeof(uint32_t)); + std::memcpy(&data_with_padding[4], data.data(), data.size()); + + return data_with_padding; +} + +std::string UnPaddingData(yacl::ByteContainerView data) { + uint32_t data_len; + std::memcpy(&data_len, data.data(), sizeof(data_len)); + + YACL_ENFORCE((data_len + sizeof(uint32_t)) <= data.size()); + + std::string ret(data_len, '\0'); + std::memcpy(ret.data(), data.data() + 4, data_len); + + return ret; +} + +void BroadcastResult(const std::shared_ptr<yacl::link::Context>& link_ctx, + std::vector<std::string>* res) { + size_t max_size = res->size(); + size_t broadcast_rank = 0; + std::vector<size_t> res_size_list = AllGatherItemsSize(link_ctx, res->size()); + for (size_t i = 0; i < res_size_list.size(); ++i) { + max_size = std::max(max_size, res_size_list[i]); + if (res_size_list[i] > 0) { + // in broadcast case, there should be only one party have results + YACL_ENFORCE(broadcast_rank == 0); + broadcast_rank = i; + } + } + if (max_size == 0) { + // no need broadcast + return; + } + auto recv_res_buf = + yacl::link::Broadcast(link_ctx, utils::SerializeStrItems(*res), + broadcast_rank, "broadcast psi result"); + if (res->empty()) { + // use broadcast result + utils::DeserializeStrItems(recv_res_buf, res); + } +} + +} // namespace psi::psi diff --git a/psi/psi/utils/utils.h b/psi/psi/utils/utils.h new file mode 100644 index 00000000..80d5a9b0 --- /dev/null +++ b/psi/psi/utils/utils.h @@ -0,0 +1,101 @@ +// Copyright 2022 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include <chrono> +#include <cstddef> +#include <filesystem> +#include <future> +#include <memory> +#include <string> +#include <string_view> +#include <vector> + +#include "yacl/link/link.h" + +#include "psi/psi/utils/serializable.pb.h" + +namespace psi::psi { + +namespace { + +static const std::string kFinishedFlag = "p_finished"; +static const std::string kUnFinishedFlag = "p_unfinished"; + +} // namespace + +// Multiple-Key out-of-core sort. +// Out-of-core support reference: +// http://vkundeti.blogspot.com/2008/03/tech-algorithmic-details-of-unix-sort.html +// Multiple-Key support reference: +// https://stackoverflow.com/questions/9471101/sort-csv-file-by-column-priority-using-the-sort-command +// use POSIX locale for sort +// https://unix.stackexchange.com/questions/43465/whats-the-default-order-of-linux-sort/43466 +// +// NOTE: +// This implementation requires `sort` command, which is guaranteed by our +// docker-way ship. +void MultiKeySort(const std::string& in_csv, const std::string& out_csv, + const std::vector<std::string>& keys, + bool numeric_sort = false, bool unique = false); + +// NOTE(junfeng): `indices` must be sorted +size_t FilterFileByIndices(const std::string& input, const std::string& output, + const std::vector<uint64_t>& indices, + bool output_difference, + size_t header_line_count = 1); + +size_t FilterFileByIndices(const std::string& input, const std::string& output, + const std::filesystem::path& indices, + bool output_difference, + size_t header_line_count = 1); + +// join keys with "," +std::string KeysJoin(const std::vector<absl::string_view>& keys); + +std::vector<size_t> AllGatherItemsSize( + const std::shared_ptr<yacl::link::Context>& link_ctx, size_t self_size); + +template <typename T> +T SyncWait(const std::shared_ptr<yacl::link::Context>& lctx, + std::future<T>* f) { + std::shared_ptr<yacl::link::Context> sync_lctx = lctx->Spawn(); + std::vector<yacl::Buffer> flag_list; + std::chrono::seconds span(5); + while (true) { + bool done = f->wait_for(span) == std::future_status::ready; + auto flag = done ? kFinishedFlag : kUnFinishedFlag; + flag_list = yacl::link::AllGather(sync_lctx, flag, "sync wait"); + if (std::find_if(flag_list.begin(), flag_list.end(), + [](const yacl::Buffer& b) { + return std::string_view(b.data<char>(), b.size()) == + kUnFinishedFlag; + }) == flag_list.end()) { + // all done + break; + } + } + return f->get(); +} + +void BroadcastResult(const std::shared_ptr<yacl::link::Context>& link_ctx, + std::vector<std::string>* res); + +std::vector<size_t> GetShuffledIdx(size_t items_size); + +std::vector<uint8_t> PaddingData(yacl::ByteContainerView data, size_t max_len); +std::string UnPaddingData(yacl::ByteContainerView data); + +} // namespace psi::psi diff --git a/psi/version.h b/psi/version.h new file mode 100644 index 00000000..5862eda4 --- /dev/null +++ b/psi/version.h @@ -0,0 +1,20 @@ +// Copyright 2023 Ant Group Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#define PSI_VERSION_MAJOR 0 +#define PSI_VERSION_MINOR 1 +#define PSI_VERSION_PATCH 0 +#define PSI_DEV_IDENTIFIER "beta" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..e69de29b