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