You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@teaclave.apache.org by ms...@apache.org on 2020/06/10 23:51:37 UTC

[incubator-teaclave] branch master updated: [function] Add Private Join And Compute Function (#345)

This is an automated email from the ASF dual-hosted git repository.

mssun pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-teaclave.git


The following commit(s) were added to refs/heads/master by this push:
     new 3f6a1a9  [function] Add Private Join And Compute Function (#345)
3f6a1a9 is described below

commit 3f6a1a921cd588f0f2cad2e25ea116e8161bcfb7
Author: Qinkun Bao <qi...@gmail.com>
AuthorDate: Wed Jun 10 19:51:12 2020 -0400

    [function] Add Private Join And Compute Function (#345)
---
 README.md                                          |   1 +
 cmake/scripts/test.sh                              |   1 +
 docs/README.md                                     |   9 +-
 docs/builtin-functions.md                          | 126 ++++++++++++
 .../python/builtin_private_join_and_compute.py     | 221 +++++++++++++++++++++
 executor/Cargo.toml                                |   2 +
 executor/src/builtin.rs                            |   5 +-
 function/README.md                                 |   2 +
 function/src/lib.rs                                |   3 +
 function/src/private_join_and_compute.rs           | 201 +++++++++++++++++++
 runtime/src/raw_io.rs                              |   1 +
 .../three_party_data/bank_a.enc                    | Bin 0 -> 4096 bytes
 .../three_party_data/bank_a.txt                    |   5 +
 .../three_party_data/bank_b.enc                    | Bin 0 -> 4096 bytes
 .../three_party_data/bank_b.txt                    |   5 +
 .../three_party_data/bank_c.enc                    | Bin 0 -> 4096 bytes
 .../three_party_data/bank_c.txt                    |   5 +
 17 files changed, 582 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md
index 2dbb2f7..907733d 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,7 @@ platform, making computation on privacy-sensitive data safe and simple.
 ### Try Teaclave
 
 - [My First Function](docs/my-first-function.md)
+- [How to Add Built-in Functions](docs/builtin-functions.md)
 
 ### Design
 
diff --git a/cmake/scripts/test.sh b/cmake/scripts/test.sh
index 64859a0..87e48a8 100755
--- a/cmake/scripts/test.sh
+++ b/cmake/scripts/test.sh
@@ -162,6 +162,7 @@ run_examples() {
   python3 mesapy_echo.py
   python3 builtin_gbdt_train.py
   python3 builtin_online_decrypt.py
+  python3 builtin_private_join_and_compute.py
   popd
 
   # kill all background services
diff --git a/docs/README.md b/docs/README.md
index f0b306c..c1b8789 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -4,9 +4,10 @@ permalink: /docs/
 
 # Teaclave Documentation
 
-- [My First Function](my-first-function.md)
-- [Threat Model](threat-model.md)
-- [Rust Development Guideline](rust-guideline.md)
-- [Mutual Attestation: Why and How](mutual-attestation.md)
 - [Access Control in Teaclave](access-control.md)
 - [Build System](build-system.md)
+- [How to Add Built-in Functions](builtin-functions.md)
+- [Mutual Attestation: Why and How](mutual-attestation.md)
+- [My First Function](my-first-function.md)
+- [Rust Development Guideline](rust-guideline.md)
+- [Threat Model](threat-model.md)
diff --git a/docs/builtin-functions.md b/docs/builtin-functions.md
new file mode 100644
index 0000000..cc7c36c
--- /dev/null
+++ b/docs/builtin-functions.md
@@ -0,0 +1,126 @@
+---
+permalink: /docs/builtin-functions
+---
+
+# How to Add Built-in Functions
+
+There are several ways to execute user-defined functions in the Teaclave
+platform. One simple way is to write Python scripts and register as functions,
+and the scripts will be executed by the *MesaPy executor*. Another way is to add native
+functions as built-in functions, and will they be managed by the *Built-in executor*.
+Compared to Python scripts, native built-in functions implemented in Rust are
+memory-safe, have better performance, support more third-party libraries and
+can be remotely attested as well. In this document, we will guide you through
+how to add a built-in function to Teaclave step by step with a "private join and
+compute" example.
+
+In this example, consider several banks have names and balance of their clients.
+These banks want to compute total balance of common clients in their private
+data set without leaking the raw sensitive client data to other parties. This is
+a perfect usage scenarios of the Teaclave platform, and we will provide a
+solution by implementing a built-in function in Teaclave.
+
+## Implement Built-in Functions in Rust
+
+All built-in functions are implemented in the `teaclave_function` crate and can
+be selectively compiled using feature gates. Basically, one built-in function
+needs two things: a name and a function implementation. Follow the convention of
+other built-in function implementations, we define our "private join and
+compute" function like this:
+
+```rust
+#[derive(Default)]
+pub struct PrivateJoinAndCompute;
+
+impl PrivateJoinAndCompute {
+    pub const NAME: &'static str = "builtin-private-join-and-compute";
+    pub fn new() -> Self {
+        Default::default()
+    }
+    pub fn run(
+        &self,
+        arguments: FunctionArguments,
+        runtime: FunctionRuntime,
+    ) -> Result<String> {
+        ...
+        Ok(summary)
+}
+```
+
+The `NAME` is the identifier of a function, which is used for creating tasks.
+Usually, the name of a built-in function starts with the `built-in` prefix. In
+addition, we need to define an entry point of the function, which is the `run`
+function. The `run` function can take arguments (in the `FunctionAruguments`
+type) and runtime (in the `FunctionRuntime` type) for interacting with external
+resources (e.g., reading/writing input/output files). Also, the `run` function
+can return a summary of the function execution.
+
+Since the function arguments is in the JSON object format and can be easily
+deserialized to a Rust struct with `serde_json`. Therefore, we define a struct
+`PrivateJoinAndComputeArguments` which derive the `serde::Deserialize` trait for
+the conversion. Then implement `TryFrom` trait for the struct to convert the
+`FunctionArguments` type to the actual `PrivateJoinAndComputeArguments` type.
+
+```rust
+#[derive(serde::Deserialize)]
+struct PrivateJoinAndComputeArguments {
+    num_user: usize, // Number of users in the multiple party computation
+}
+
+impl TryFrom<FunctionArguments> for PrivateJoinAndComputeArguments {
+    type Error = anyhow::Error;
+
+    fn try_from(arguments: FunctionArguments) -> Result<Self, Self::Error> {
+        use anyhow::Context;
+        serde_json::from_str(&arguments.into_string()).context("Cannot deserialize arguments")
+    }
+}
+
+```
+
+When executing the function, a `runtime` object will be passed to the function.
+We can read or write files with the `runtime` with the `open_input` and
+`create_output` functions.
+
+
+```rust
+// Read data from a file
+let mut input_io = runtime.open_input(&input_file_name)?;
+input_io.read_to_end(&mut data)?;
+...
+// Write data into a file
+let mut output = runtime.create_output(&output_file_name)?;
+output.write_all(&output_bytes)?;
+```
+
+## Register Functions in the Executor
+
+To use the function, we need to register it to the built-in executor. Please also
+put a `cfg` attribute to make sure developers can conditionally build functions
+into the executor.
+
+``` rust
+impl TeaclaveExecutor for BuiltinFunctionExecutor {
+    fn execute(
+        &self,
+        name: String,
+        arguments: FunctionArguments,
+        _payload: String,
+        runtime: FunctionRuntime,
+    ) -> Result<String> {
+        match name.as_str() {
+            ...
+            #[cfg(feature = "builtin_private_join_and_compute")]
+            PrivateJoinAndCompute::NAME => PrivateJoinAndCompute::new().run(arguments, runtime),
+            ...
+        }
+    }
+}
+```
+
+## Invoke Functions with the Client SDK
+
+Finally, we can invoke the function with the client SDK. In our example, we use
+the Python client SDK. Basically, this process includes registering input/output
+files, creating task, approving task, invoking task and getting execution
+results. You can see more details in the `examples/python` directory.
diff --git a/examples/python/builtin_private_join_and_compute.py b/examples/python/builtin_private_join_and_compute.py
new file mode 100644
index 0000000..1c17435
--- /dev/null
+++ b/examples/python/builtin_private_join_and_compute.py
@@ -0,0 +1,221 @@
+#!/usr/bin/env python3
+
+import sys
+
+from teaclave import (AuthenticationService, FrontendService,
+                      AuthenticationClient, FrontendClient, FunctionInput,
+                      FunctionOutput, OwnerList, DataMap)
+from utils import (AUTHENTICATION_SERVICE_ADDRESS, FRONTEND_SERVICE_ADDRESS,
+                   AS_ROOT_CA_CERT_PATH, ENCLAVE_INFO_PATH, USER_ID,
+                   USER_PASSWORD)
+
+# In the example, user 3 creates the task and user 0, 1, 2 upload their private data.
+# Then user 3 invokes the task and user 0, 1, 2 get the result.
+
+
+class UserData:
+    def __init__(self,
+                 user_id,
+                 password,
+                 input_url="",
+                 output_url="",
+                 input_cmac="",
+                 key=[]):
+        self.user_id = user_id
+        self.password = password
+        self.input_url = input_url
+        self.output_url = output_url
+        self.input_cmac = input_cmac
+        self.key = key
+
+
+INPUT_FILE_URL_PREFIX = "http://localhost:6789/fixtures/functions/private_join_and_compute/three_party_data/"
+OUTPUT_FILE_URL_PREFIX = "http://localhost:6789/fixtures/functions/private_join_and_compute/three_party_data/"
+
+USER_DATA_0 = UserData("user0", "password",
+                       INPUT_FILE_URL_PREFIX + "bank_a.enc",
+                       OUTPUT_FILE_URL_PREFIX + "user0_output.enc",
+                       "7884a62894e7be50b9795ba22ce5ee7f",
+                       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
+
+USER_DATA_1 = UserData("user1", "password",
+                       INPUT_FILE_URL_PREFIX + "bank_b.enc",
+                       OUTPUT_FILE_URL_PREFIX + "user1_output.enc",
+                       "75b8e931887bd57564d93df31c282bb9",
+                       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
+
+USER_DATA_2 = UserData("user2", "password",
+                       INPUT_FILE_URL_PREFIX + "bank_c.enc",
+                       OUTPUT_FILE_URL_PREFIX + "user2_output.enc",
+                       "35acf29139485067d1ae6212c0577b43",
+                       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
+
+USER_DATA_3 = UserData("user3", "password")
+
+
+class DataList:
+    def __init__(self, data_name, data_id):
+        self.data_name = data_name
+        self.data_id = data_id
+
+
+class ConfigClient:
+    def __init__(self, user_id, user_password):
+        self.user_id = user_id
+        self.user_password = user_password
+        self.client = AuthenticationService(
+            AUTHENTICATION_SERVICE_ADDRESS, AS_ROOT_CA_CERT_PATH,
+            ENCLAVE_INFO_PATH).connect().get_client()
+        print(f"[+] {self.user_id} registering user")
+        self.client.user_register(self.user_id, self.user_password)
+        print(f"[+] {self.user_id} login")
+        token = self.client.user_login(self.user_id, self.user_password)
+        self.client = FrontendService(
+            FRONTEND_SERVICE_ADDRESS, AS_ROOT_CA_CERT_PATH,
+            ENCLAVE_INFO_PATH).connect().get_client()
+        metadata = {"id": self.user_id, "token": token}
+        self.client.metadata = metadata
+
+    def set_task(self):
+        client = self.client
+
+        print(f"[+] {self.user_id} registering function")
+
+        function_id = client.register_function(
+            name="builtin-private-join-and-compute",
+            description="Native Private Join And Compute",
+            executor_type="builtin",
+            arguments=["num_user"],
+            inputs=[
+                FunctionInput("input_data0", "Bank A data file."),
+                FunctionInput("input_data1", "Bank B data file."),
+                FunctionInput("input_data2", "Bank C data file.")
+            ],
+            outputs=[
+                FunctionOutput("output_data0", "Output data."),
+                FunctionOutput("output_data1", "Output data."),
+                FunctionOutput("output_data2", "Output date.")
+            ])
+
+        print(f"[+] {self.user_id} creating task")
+        task_id = client.create_task(function_id=function_id,
+                                     function_arguments=({
+                                         "num_user": 3,
+                                     }),
+                                     executor="builtin",
+                                     inputs_ownership=[
+                                         OwnerList("input_data0",
+                                                   [USER_DATA_0.user_id]),
+                                         OwnerList("input_data1",
+                                                   [USER_DATA_1.user_id]),
+                                         OwnerList("input_data2",
+                                                   [USER_DATA_2.user_id])
+                                     ],
+                                     outputs_ownership=[
+                                         OwnerList("output_data0",
+                                                   [USER_DATA_0.user_id]),
+                                         OwnerList("output_data1",
+                                                   [USER_DATA_1.user_id]),
+                                         OwnerList("output_data2",
+                                                   [USER_DATA_2.user_id])
+                                     ])
+
+        return task_id
+
+    def run_task(self, task_id):
+        client = self.client
+        client.approve_task(task_id)
+        print(f"[+] {self.user_id} invoking task")
+        client.invoke_task(task_id)
+
+
+class DataClient:
+    def __init__(self, user_id, user_password):
+        self.user_id = user_id
+        self.user_password = user_password
+        self.client = AuthenticationService(
+            AUTHENTICATION_SERVICE_ADDRESS, AS_ROOT_CA_CERT_PATH,
+            ENCLAVE_INFO_PATH).connect().get_client()
+        print(f"[+] {self.user_id} registering user")
+        self.client.user_register(self.user_id, self.user_password)
+        print(f"[+] {self.user_id} login")
+        token = self.client.user_login(self.user_id, self.user_password)
+        self.client = FrontendService(
+            FRONTEND_SERVICE_ADDRESS, AS_ROOT_CA_CERT_PATH,
+            ENCLAVE_INFO_PATH).connect().get_client()
+        metadata = {"id": self.user_id, "token": token}
+        self.client.metadata = metadata
+
+    def register_data(self, task_id, input_url, input_cmac, output_url,
+                      file_key, input_label, output_label):
+        client = self.client
+
+        print(f"[+] {self.user_id} registering input file")
+        url = input_url
+        cmac = input_cmac
+        schema = "teaclave-file-128"
+        key = file_key
+        iv = []
+        input_id = client.register_input_file(url, schema, key, iv, cmac)
+        print(f"[+] {self.user_id} registering output file")
+        url = output_url
+        schema = "teaclave-file-128"
+        key = file_key
+        iv = []
+        output_id = client.register_output_file(url, schema, key, iv)
+
+        print(f"[+] {self.user_id} assigning data to task")
+        client.assign_data_to_task(task_id, [DataList(input_label, input_id)],
+                                   [DataList(output_label, output_id)])
+
+    def approve_task(self, task_id):
+        client = self.client
+        print(f"[+] {self.user_id} approving task")
+        client.approve_task(task_id)
+
+    def get_task_result(self, task_id):
+        client = self.client
+        print(f"[+] {self.user_id} getting task result")
+        return bytes(client.get_task_result(task_id))
+
+
+def main():
+    ## USER 3 creates the task
+    config_client = ConfigClient(USER_DATA_3.user_id, USER_DATA_3.password)
+    task_id = config_client.set_task()
+
+    ## USER 0, 1, 2 join the task and upload their data
+    user0 = DataClient(USER_DATA_0.user_id, USER_DATA_0.password)
+    user0.register_data(task_id, USER_DATA_0.input_url, USER_DATA_0.input_cmac,
+                        USER_DATA_0.output_url, USER_DATA_0.key, "input_data0",
+                        "output_data0")
+
+    user1 = DataClient(USER_DATA_1.user_id, USER_DATA_1.password)
+    user1.register_data(task_id, USER_DATA_1.input_url, USER_DATA_1.input_cmac,
+                        USER_DATA_1.output_url, USER_DATA_1.key, "input_data1",
+                        "output_data1")
+
+    user2 = DataClient(USER_DATA_2.user_id, USER_DATA_2.password)
+    user2.register_data(task_id, USER_DATA_2.input_url, USER_DATA_2.input_cmac,
+                        USER_DATA_2.output_url, USER_DATA_2.key, "input_data2",
+                        "output_data2")
+
+    user0.approve_task(task_id)
+    user1.approve_task(task_id)
+    user2.approve_task(task_id)
+
+    ## USER 3 start the computation
+    config_client.run_task(task_id)
+
+    ## USER 0, 1, 2 get the result
+    result_user0 = user0.get_task_result(task_id)
+    result_user1 = user1.get_task_result(task_id)
+    result_user2 = user2.get_task_result(task_id)
+
+    print("[+] User 0 result: " + result_user0.decode("utf-8"))
+    print("[+] User 1 result: " + result_user1.decode("utf-8"))
+    print("[+] User 2 result: " + result_user2.decode("utf-8"))
+
+
+if __name__ == '__main__':
+    main()
diff --git a/executor/Cargo.toml b/executor/Cargo.toml
index 19c0e7e..d150597 100644
--- a/executor/Cargo.toml
+++ b/executor/Cargo.toml
@@ -32,6 +32,7 @@ full_builtin_function = [
   "builtin_logistic_regression_predict",
   "builtin_logistic_regression_train",
   "builtin_online_decrypt",
+  "builtin_private_join_and_compute",
 ]
 
 builtin_echo = []
@@ -40,6 +41,7 @@ builtin_gbdt_train = []
 builtin_logistic_regression_predict = []
 builtin_logistic_regression_train = []
 builtin_online_decrypt = []
+builtin_private_join_and_compute = []
 
 [dependencies]
 log           = { version = "0.4.6" }
diff --git a/executor/src/builtin.rs b/executor/src/builtin.rs
index 990d014..ef4c413 100644
--- a/executor/src/builtin.rs
+++ b/executor/src/builtin.rs
@@ -19,7 +19,8 @@
 use std::prelude::v1::*;
 
 use teaclave_function::{
-    Echo, GbdtPredict, GbdtTrain, LogisticRegressionPredict, LogisticRegressionTrain, OnlineDecrypt,
+    Echo, GbdtPredict, GbdtTrain, LogisticRegressionPredict, LogisticRegressionTrain,
+    OnlineDecrypt, PrivateJoinAndCompute,
 };
 use teaclave_types::{FunctionArguments, FunctionRuntime, TeaclaveExecutor};
 
@@ -51,6 +52,8 @@ impl TeaclaveExecutor for BuiltinFunctionExecutor {
             }
             #[cfg(feature = "builtin_online_decrypt")]
             OnlineDecrypt::NAME => OnlineDecrypt::new().run(arguments, runtime),
+            #[cfg(feature = "builtin_private_join_and_compute")]
+            PrivateJoinAndCompute::NAME => PrivateJoinAndCompute::new().run(arguments, runtime),
             _ => bail!("Function not found."),
         }
     }
diff --git a/function/README.md b/function/README.md
index 10938ee..42df464 100644
--- a/function/README.md
+++ b/function/README.md
@@ -18,6 +18,8 @@ Currently, we have these built-in functions:
   - `builtin-gbdt-predict`: GBDT prediction with input model and input test data.
   - `bulitin-logistic-regression-train`: Use input data to train a LR model.
   - `builtin-logistic-regression-predict`: LR prediction with input model and input test data.
+  - `builtin-private-join-and-compute`: Find intersection of muti-parties' input
+    data and compute sum of the common items.
   
 The function arguments are in JSON format and can be serialized to a Rust struct
 very easily. You can learn more about supported arguments in the implementation
diff --git a/function/src/lib.rs b/function/src/lib.rs
index 43189b6..0e83885 100644
--- a/function/src/lib.rs
+++ b/function/src/lib.rs
@@ -28,6 +28,7 @@ mod gbdt_train;
 mod logistic_regression_predict;
 mod logistic_regression_train;
 mod online_decrypt;
+mod private_join_and_compute;
 
 pub use echo::Echo;
 pub use gbdt_predict::GbdtPredict;
@@ -35,6 +36,7 @@ pub use gbdt_train::GbdtTrain;
 pub use logistic_regression_predict::LogisticRegressionPredict;
 pub use logistic_regression_train::LogisticRegressionTrain;
 pub use online_decrypt::OnlineDecrypt;
+pub use private_join_and_compute::PrivateJoinAndCompute;
 
 #[cfg(feature = "enclave_unit_test")]
 pub mod tests {
@@ -49,6 +51,7 @@ pub mod tests {
             logistic_regression_train::tests::run_tests(),
             logistic_regression_predict::tests::run_tests(),
             online_decrypt::tests::run_tests(),
+            private_join_and_compute::tests::run_tests(),
         )
     }
 }
diff --git a/function/src/private_join_and_compute.rs b/function/src/private_join_and_compute.rs
new file mode 100644
index 0000000..0be5b94
--- /dev/null
+++ b/function/src/private_join_and_compute.rs
@@ -0,0 +1,201 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use anyhow::{bail, Result};
+use std::collections::HashMap;
+use std::convert::TryFrom;
+use std::fmt;
+use std::format;
+use std::io::Write;
+#[cfg(feature = "mesalock_sgx")]
+use std::prelude::v1::*;
+use teaclave_types::{FunctionArguments, FunctionRuntime};
+
+const IN_DATA: &str = "input_data";
+const OUT_RESULT: &str = "output_data";
+
+#[derive(Default)]
+pub struct PrivateJoinAndCompute;
+
+#[derive(serde::Deserialize)]
+struct PrivateJoinAndComputeArguments {
+    num_user: usize, // Number of users in the mutiple party computation
+}
+
+impl TryFrom<FunctionArguments> for PrivateJoinAndComputeArguments {
+    type Error = anyhow::Error;
+
+    fn try_from(arguments: FunctionArguments) -> Result<Self, Self::Error> {
+        use anyhow::Context;
+        serde_json::from_str(&arguments.into_string()).context("Cannot deserialize arguments")
+    }
+}
+
+impl PrivateJoinAndCompute {
+    pub const NAME: &'static str = "builtin-private-join-and-compute";
+
+    pub fn new() -> Self {
+        Default::default()
+    }
+
+    pub fn run(
+        &self,
+        arguments: FunctionArguments,
+        runtime: FunctionRuntime,
+    ) -> anyhow::Result<String> {
+        let args = PrivateJoinAndComputeArguments::try_from(arguments)?;
+        let num_user = args.num_user;
+        if num_user < 2 {
+            bail!("The demo requires at least two parties!");
+        }
+
+        let mut output = String::new();
+        let data_0 = get_data(0, &runtime)?;
+        let input_map_0 = parse_input(data_0)?;
+        let mut res_map: HashMap<String, u32> = input_map_0;
+
+        for i in 1..num_user {
+            let data = get_data(i, &runtime)?;
+            let input_map = parse_input(data)?;
+            res_map = get_intersection_sum(&input_map, &res_map);
+        }
+
+        for (identity, amount) in res_map {
+            fmt::write(&mut output, format_args!("{}, {}\n", identity, amount))?;
+        }
+
+        let output_bytes = output.as_bytes();
+
+        for i in 0..num_user {
+            let output_file_name = format!("{}{}", OUT_RESULT, i);
+            let mut output = runtime.create_output(&output_file_name)?;
+            output.write_all(&output_bytes)?;
+        }
+
+        let summary = format!("{} users join the task in total.", num_user);
+        Ok(summary)
+    }
+}
+
+fn get_data(user_id: usize, runtime: &FunctionRuntime) -> anyhow::Result<Vec<u8>> {
+    let mut data: Vec<u8> = Vec::new();
+    let input_file_name = format!("{}{}", IN_DATA, user_id);
+    let mut input_io = runtime.open_input(&input_file_name)?;
+    input_io.read_to_end(&mut data)?;
+    Ok(data)
+}
+
+fn get_intersection_sum(
+    map1: &HashMap<String, u32>,
+    map2: &HashMap<String, u32>,
+) -> HashMap<String, u32> {
+    let mut res_map: HashMap<String, u32> = HashMap::new();
+    for (identity, amount) in map1 {
+        if map2.contains_key(identity) {
+            let total = amount + map2[identity];
+            res_map.insert(identity.to_owned(), total);
+        }
+    }
+    res_map
+}
+
+fn parse_input(data: Vec<u8>) -> anyhow::Result<HashMap<String, u32>> {
+    let data_list = String::from_utf8(data)?;
+    let mut ret: HashMap<String, u32> = HashMap::new();
+    for data_item in data_list.split('\n') {
+        let pair = data_item.trim();
+        if pair.len() < 3 {
+            continue;
+        }
+        let kv_pair: Vec<&str> = pair.split(':').collect();
+        if kv_pair.len() != 2 {
+            continue;
+        }
+        let identity = kv_pair[0].trim().to_string();
+        let amount = match kv_pair[1].trim().parse::<u32>() {
+            Ok(amount) => amount,
+            Err(_) => continue,
+        };
+        ret.insert(identity, amount);
+    }
+    Ok(ret)
+}
+
+#[cfg(feature = "enclave_unit_test")]
+pub mod tests {
+    use super::*;
+    use serde_json::json;
+    use std::untrusted::fs;
+    use teaclave_crypto::*;
+    use teaclave_runtime::*;
+    use teaclave_test_utils::*;
+    use teaclave_types::*;
+
+    pub fn run_tests() -> bool {
+        run_tests!(test_private_join_and_compute)
+    }
+
+    fn test_private_join_and_compute() {
+        let arguments = FunctionArguments::from_json(json!({
+            "num_user": 3
+        }))
+        .unwrap();
+
+        let user0_input = "fixtures/functions/private_join_and_compute/three_party_data/bank_a.txt";
+        let user0_output =
+            "fixtures/functions/private_join_and_compute/three_party_data/user0_output.txt";
+
+        let user1_input = "fixtures/functions/private_join_and_compute/three_party_data/bank_b.txt";
+        let user1_output =
+            "fixtures/functions/private_join_and_compute/three_party_data/user1_output.txt";
+
+        let user2_input = "fixtures/functions/private_join_and_compute/three_party_data/bank_c.txt";
+        let user2_output =
+            "fixtures/functions/private_join_and_compute/three_party_data/user2_output.txt";
+
+        let input_files = StagedFiles::new(hashmap!(
+            "input_data0" =>
+            StagedFileInfo::new(user0_input, TeaclaveFile128Key::random(), FileAuthTag::mock()),
+            "input_data1" =>
+            StagedFileInfo::new(user1_input, TeaclaveFile128Key::random(), FileAuthTag::mock()),
+            "input_data2" =>
+            StagedFileInfo::new(user2_input, TeaclaveFile128Key::random(), FileAuthTag::mock())
+        ));
+
+        let output_files = StagedFiles::new(hashmap!(
+            "output_data0" =>
+            StagedFileInfo::new(user0_output, TeaclaveFile128Key::random(), FileAuthTag::mock()),
+            "output_data1" =>
+            StagedFileInfo::new(user1_output, TeaclaveFile128Key::random(), FileAuthTag::mock()),
+            "output_data2" =>
+            StagedFileInfo::new(user2_output, TeaclaveFile128Key::random(), FileAuthTag::mock())
+        ));
+
+        let runtime = Box::new(RawIoRuntime::new(input_files, output_files));
+
+        let summary = PrivateJoinAndCompute::new()
+            .run(arguments, runtime)
+            .unwrap();
+
+        let user0 = fs::read_to_string(&user0_output).unwrap();
+        let user1 = fs::read_to_string(&user1_output).unwrap();
+        let user2 = fs::read_to_string(&user2_output).unwrap();
+        assert_eq!(&user0[..], &user1[..]);
+        assert_eq!(&user1[..], &user2[..]);
+        assert_eq!(summary, "3 users join the task in total.")
+    }
+}
diff --git a/runtime/src/raw_io.rs b/runtime/src/raw_io.rs
index 9d88d85..d6a665c 100644
--- a/runtime/src/raw_io.rs
+++ b/runtime/src/raw_io.rs
@@ -54,6 +54,7 @@ impl TeaclaveRuntime for RawIoRuntime {
             .output_files
             .get(identifier)
             .ok_or_else(|| anyhow::anyhow!("Invalid output file identifier"))?;
+        log::debug!("create_output: {:?}", file_info.path);
         let f = File::create(&file_info.path)?;
         Ok(Box::new(f))
     }
diff --git a/tests/fixtures/functions/private_join_and_compute/three_party_data/bank_a.enc b/tests/fixtures/functions/private_join_and_compute/three_party_data/bank_a.enc
new file mode 100644
index 0000000..5800154
Binary files /dev/null and b/tests/fixtures/functions/private_join_and_compute/three_party_data/bank_a.enc differ
diff --git a/tests/fixtures/functions/private_join_and_compute/three_party_data/bank_a.txt b/tests/fixtures/functions/private_join_and_compute/three_party_data/bank_a.txt
new file mode 100644
index 0000000..1816421
--- /dev/null
+++ b/tests/fixtures/functions/private_join_and_compute/three_party_data/bank_a.txt
@@ -0,0 +1,5 @@
+b : 2000
+a : 100
+c : 30000
+e : 5000000
+d : 400000
diff --git a/tests/fixtures/functions/private_join_and_compute/three_party_data/bank_b.enc b/tests/fixtures/functions/private_join_and_compute/three_party_data/bank_b.enc
new file mode 100644
index 0000000..eb76a7d
Binary files /dev/null and b/tests/fixtures/functions/private_join_and_compute/three_party_data/bank_b.enc differ
diff --git a/tests/fixtures/functions/private_join_and_compute/three_party_data/bank_b.txt b/tests/fixtures/functions/private_join_and_compute/three_party_data/bank_b.txt
new file mode 100644
index 0000000..06b09c2
--- /dev/null
+++ b/tests/fixtures/functions/private_join_and_compute/three_party_data/bank_b.txt
@@ -0,0 +1,5 @@
+e : 3000
+x : 200
+c : 40000
+y : 10
+a : 5000000
\ No newline at end of file
diff --git a/tests/fixtures/functions/private_join_and_compute/three_party_data/bank_c.enc b/tests/fixtures/functions/private_join_and_compute/three_party_data/bank_c.enc
new file mode 100644
index 0000000..5fa5ec1
Binary files /dev/null and b/tests/fixtures/functions/private_join_and_compute/three_party_data/bank_c.enc differ
diff --git a/tests/fixtures/functions/private_join_and_compute/three_party_data/bank_c.txt b/tests/fixtures/functions/private_join_and_compute/three_party_data/bank_c.txt
new file mode 100644
index 0000000..7233cbc
--- /dev/null
+++ b/tests/fixtures/functions/private_join_and_compute/three_party_data/bank_c.txt
@@ -0,0 +1,5 @@
+e : 30000
+x : 200
+c : 400000
+y : 10
+d : 5000000


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@teaclave.apache.org
For additional commands, e-mail: commits-help@teaclave.apache.org