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 e232e51  [functions] Add ordered set intersection (#353)
e232e51 is described below

commit e232e51859c91adab93d2f1bcc4598f57210731b
Author: Qinkun Bao <[email protected]>
AuthorDate: Mon Jun 15 17:17:08 2020 -0400

    [functions] Add ordered set intersection (#353)
---
 cmake/scripts/test.sh                              |   1 +
 examples/python/builtin_ordered_set_intersect.py   | 175 +++++++++++++++++
 executor/Cargo.toml                                |   2 +
 executor/src/builtin.rs                            |   4 +-
 function/Cargo.toml                                |   1 +
 function/README.md                                 |   4 +
 function/src/lib.rs                                |   3 +
 function/src/ordered_set_intersect.rs              | 213 +++++++++++++++++++++
 .../functions/ordered_set_intersect/psi0.txt       |   7 +
 .../functions/ordered_set_intersect/psi0.txt.enc   | Bin 0 -> 4096 bytes
 .../functions/ordered_set_intersect/psi1.txt       |   5 +
 .../functions/ordered_set_intersect/psi1.txt.enc   | Bin 0 -> 4096 bytes
 12 files changed, 414 insertions(+), 1 deletion(-)

diff --git a/cmake/scripts/test.sh b/cmake/scripts/test.sh
index 316e612..e6c2c74 100755
--- a/cmake/scripts/test.sh
+++ b/cmake/scripts/test.sh
@@ -169,6 +169,7 @@ run_examples() {
   python3 builtin_gbdt_train.py
   python3 builtin_online_decrypt.py
   python3 builtin_private_join_and_compute.py
+  python3 builtin_ordered_set_intersect.py
   python3 builtin_rsa_sign.py
   popd
 
diff --git a/examples/python/builtin_ordered_set_intersect.py 
b/examples/python/builtin_ordered_set_intersect.py
new file mode 100644
index 0000000..5b0a1dc
--- /dev/null
+++ b/examples/python/builtin_ordered_set_intersect.py
@@ -0,0 +1,175 @@
+#!/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 0 creates the task and user 0, 1, upload their private 
data.
+# Then user 0 invokes the task and user 0, 1 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/ordered_set_intersect/";
+OUTPUT_FILE_URL_PREFIX = 
"http://localhost:6789/fixtures/functions/ordered_set_intersect/";
+
+USER_DATA_0 = UserData("user0", "password",
+                       INPUT_FILE_URL_PREFIX + "psi0.txt.enc",
+                       OUTPUT_FILE_URL_PREFIX + "output_psi0.enc",
+                       "e08adeb021e876ffe82234445e632121",
+                       [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 + "psi1.txt.enc",
+                       OUTPUT_FILE_URL_PREFIX + "output_psi1.enc",
+                       "538dafbf7802d962bb01e2389b4e943a",
+                       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
+
+
+class DataList:
+    def __init__(self, data_name, data_id):
+        self.data_name = data_name
+        self.data_id = data_id
+
+
+class Client:
+    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-ordered-set-intersect",
+            description="Native Private Set Intersection",
+            executor_type="builtin",
+            arguments=["order"],
+            inputs=[
+                FunctionInput("input_data1", "Client 0 data."),
+                FunctionInput("input_data2", "Client 1 data.")
+            ],
+            outputs=[
+                FunctionOutput("output_result1", "Output data."),
+                FunctionOutput("output_result2", "Output data.")
+            ])
+
+        print(f"[+] {self.user_id} creating task")
+        task_id = client.create_task(
+            function_id=function_id,
+            function_arguments=({
+                "order": "ascending",  # Order can be ascending or desending 
+            }),
+            executor="builtin",
+            inputs_ownership=[
+                OwnerList("input_data1", [USER_DATA_0.user_id]),
+                OwnerList("input_data2", [USER_DATA_1.user_id])
+            ],
+            outputs_ownership=[
+                OwnerList("output_result1", [USER_DATA_0.user_id]),
+                OwnerList("output_result2", [USER_DATA_1.user_id])
+            ])
+
+        return task_id
+
+    def run_task(self, task_id):
+        client = self.client
+        print(f"[+] {self.user_id} invoking task")
+        client.invoke_task(task_id)
+
+    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():
+    user0 = Client(USER_DATA_0.user_id, USER_DATA_0.password)
+    user1 = Client(USER_DATA_1.user_id, USER_DATA_1.password)
+
+    task_id = user0.set_task()
+
+    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_data1",
+                        "output_result1")
+
+    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_data2",
+                        "output_result2")
+
+    user0.approve_task(task_id)
+    user1.approve_task(task_id)
+
+    ## USER 0 start the computation
+    user0.run_task(task_id)
+
+    ## USER 0, 1 get the result
+    result_user0 = user0.get_task_result(task_id)
+    result_user1 = user1.get_task_result(task_id)
+
+    print("[+] User 0 result: " + result_user0.decode("utf-8"))
+    print("[+] User 1 result: " + result_user1.decode("utf-8"))
+
+
+if __name__ == '__main__':
+    main()
diff --git a/executor/Cargo.toml b/executor/Cargo.toml
index c102d44..ea03e45 100644
--- a/executor/Cargo.toml
+++ b/executor/Cargo.toml
@@ -33,6 +33,7 @@ full_builtin_function = [
   "builtin_logistic_regression_train",
   "builtin_online_decrypt",
   "builtin_private_join_and_compute",
+  "builtin_ordered_set_intersect",
   "builtin_rsa_sign",
 ]
 
@@ -43,6 +44,7 @@ builtin_logistic_regression_predict = []
 builtin_logistic_regression_train = []
 builtin_online_decrypt = []
 builtin_private_join_and_compute = []
+builtin_ordered_set_intersect = []
 builtin_rsa_sign = []
 
 [dependencies]
diff --git a/executor/src/builtin.rs b/executor/src/builtin.rs
index 5874d74..677e782 100644
--- a/executor/src/builtin.rs
+++ b/executor/src/builtin.rs
@@ -20,7 +20,7 @@ use std::prelude::v1::*;
 
 use teaclave_function::{
     Echo, GbdtPredict, GbdtTrain, LogisticRegressionPredict, 
LogisticRegressionTrain,
-    OnlineDecrypt, PrivateJoinAndCompute, RsaSign,
+    OnlineDecrypt, OrderedSetIntersect, PrivateJoinAndCompute, RsaSign,
 };
 use teaclave_types::{FunctionArguments, FunctionRuntime, TeaclaveExecutor};
 
@@ -54,6 +54,8 @@ impl TeaclaveExecutor for BuiltinFunctionExecutor {
             OnlineDecrypt::NAME => OnlineDecrypt::new().run(arguments, 
runtime),
             #[cfg(feature = "builtin_private_join_and_compute")]
             PrivateJoinAndCompute::NAME => 
PrivateJoinAndCompute::new().run(arguments, runtime),
+            #[cfg(feature = "builtin_ordered_set_intersect")]
+            OrderedSetIntersect::NAME => 
OrderedSetIntersect::new().run(arguments, runtime),
             #[cfg(feature = "builtin_rsa_sign")]
             RsaSign::NAME => RsaSign::new().run(arguments, runtime),
             _ => bail!("Function not found."),
diff --git a/function/Cargo.toml b/function/Cargo.toml
index 587c10b..638482e 100644
--- a/function/Cargo.toml
+++ b/function/Cargo.toml
@@ -33,6 +33,7 @@ rusty-machine = { version = "0.5.4" }
 itertools     = { version = "0.8.0", default-features = false }
 ring          = { version = "0.16.5" }
 base64        = { version = "0.10.1" }
+hex           = { version = "0.4.0"  }
 teaclave_types = { path = "../types" }
 teaclave_crypto = { path = "../crypto" }
 teaclave_runtime = { path = "../runtime", optional = true }
diff --git a/function/README.md b/function/README.md
index 91bed63..fbcfc92 100644
--- a/function/README.md
+++ b/function/README.md
@@ -20,6 +20,10 @@ Currently, we have these built-in functions:
   - `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.
+  - `builtin-ordered-set-intersect`: Allow two parties to compute the
+    intersection of their ordered sets without revealing anything except for 
the
+    elements in the intersection. Users should calculate hash values of each 
item
+    and upload them as a sorted list.
   - `builtin-rsa-sign`: Signing data with RSA key.
   
 The function arguments are in JSON format and can be serialized to a Rust 
struct
diff --git a/function/src/lib.rs b/function/src/lib.rs
index 2d43640..1ea110f 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 ordered_set_intersect;
 mod private_join_and_compute;
 mod rsa_sign;
 
@@ -37,6 +38,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 ordered_set_intersect::OrderedSetIntersect;
 pub use private_join_and_compute::PrivateJoinAndCompute;
 pub use rsa_sign::RsaSign;
 
@@ -54,6 +56,7 @@ pub mod tests {
             logistic_regression_predict::tests::run_tests(),
             online_decrypt::tests::run_tests(),
             private_join_and_compute::tests::run_tests(),
+            ordered_set_intersect::tests::run_tests(),
             rsa_sign::tests::run_tests(),
         )
     }
diff --git a/function/src/ordered_set_intersect.rs 
b/function/src/ordered_set_intersect.rs
new file mode 100644
index 0000000..ae1c086
--- /dev/null
+++ b/function/src/ordered_set_intersect.rs
@@ -0,0 +1,213 @@
+// 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;
+use std::cmp;
+use std::convert::TryFrom;
+use std::format;
+use std::io::{self, BufRead, BufReader, Write};
+#[cfg(feature = "mesalock_sgx")]
+use std::prelude::v1::*;
+use teaclave_types::{FunctionArguments, FunctionRuntime};
+
+extern crate hex;
+extern crate sgx_tstd as std;
+
+// Input data should be a list of sorted hash values.
+
+const IN_DATA1: &str = "input_data1";
+const IN_DATA2: &str = "input_data2";
+const OUT_RESULT1: &str = "output_result1";
+const OUT_RESULT2: &str = "output_result2";
+
+#[derive(Default)]
+pub struct OrderedSetIntersect;
+
+#[derive(serde::Deserialize)]
+pub struct OrderedSetIntersectArguments {
+    order: String,
+}
+
+impl TryFrom<FunctionArguments> for OrderedSetIntersectArguments {
+    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 OrderedSetIntersect {
+    pub const NAME: &'static str = "builtin-ordered-set-intersect";
+
+    pub fn new() -> Self {
+        Default::default()
+    }
+
+    pub fn run(
+        &self,
+        arguments: FunctionArguments,
+        runtime: FunctionRuntime,
+    ) -> anyhow::Result<String> {
+        let input1 = runtime.open_input(IN_DATA1)?;
+        let input2 = runtime.open_input(IN_DATA2)?;
+        let mut output1 = runtime.create_output(OUT_RESULT1)?;
+        let mut output2 = runtime.create_output(OUT_RESULT2)?;
+        let args = OrderedSetIntersectArguments::try_from(arguments)?;
+        let order = &args.order[..];
+        let ascending_order = match order {
+            "ascending" => true,
+            "desending" => false,
+            _ => bail!("Invalid order"),
+        };
+
+        let vec1 = parse_input_data(input1, ascending_order)?;
+        let vec2 = parse_input_data(input2, ascending_order)?;
+        let (result1, result2) = intersection_ordered_vec(&vec1, &vec2, 
ascending_order)?;
+
+        let mut common_sets = 0;
+
+        for item in result1 {
+            write!(&mut output1, "{}", item as u32)?;
+            if item {
+                common_sets += 1;
+            }
+        }
+        for item in result2 {
+            write!(&mut output2, "{}", item as u32)?;
+        }
+        Ok(format!("{} common items", common_sets))
+    }
+}
+
+fn parse_input_data(input: impl io::Read, ascending_order: bool) -> 
anyhow::Result<Vec<Vec<u8>>> {
+    let mut samples: Vec<Vec<u8>> = Vec::new();
+    let reader = BufReader::new(input);
+    for (index, byte_result) in reader.lines().enumerate() {
+        let byte = byte_result?;
+        let result = hex::decode(byte)?;
+        if index > 0 {
+            // If vec has more than 2 elements, then verify the ordering
+            let last_element = &samples[index - 1];
+            if ascending_order && result < *last_element
+                || !ascending_order && result > *last_element
+            {
+                bail!("Invalid ordering");
+            }
+        }
+        samples.push(result)
+    }
+
+    Ok(samples)
+}
+
+fn intersection_ordered_vec(
+    input1: &[Vec<u8>],
+    input2: &[Vec<u8>],
+    ascending_order: bool,
+) -> anyhow::Result<(Vec<bool>, Vec<bool>)> {
+    let v1_len = input1.len();
+    let v2_len = input2.len();
+
+    let mut res1 = std::vec![false; v1_len];
+    let mut res2 = std::vec![false; v2_len];
+
+    let mut i = 0;
+    let mut j = 0;
+
+    while i < v1_len && j < v2_len {
+        let order = &input1[i].cmp(&input2[j]);
+        match order {
+            cmp::Ordering::Equal => {
+                res1[i] = true;
+                res2[j] = true;
+                i += 1;
+                j += 1;
+            }
+            cmp::Ordering::Less => {
+                if ascending_order {
+                    i += 1;
+                } else {
+                    j += 1;
+                }
+            }
+            cmp::Ordering::Greater => {
+                if ascending_order {
+                    j += 1;
+                } else {
+                    i += 1;
+                }
+            }
+        }
+    }
+    Ok((res1, res2))
+}
+
+#[cfg(feature = "enclave_unit_test")]
+pub mod tests {
+    use super::*;
+    use serde_json::json;
+    use std::path::Path;
+    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_ordered_set_intersect)
+    }
+
+    fn test_ordered_set_intersect() {
+        let arguments = FunctionArguments::from_json(json!({
+            "order": "ascending"
+        }))
+        .unwrap();
+
+        let base = Path::new("fixtures/functions/ordered_set_intersect");
+
+        let user1_input = base.join("psi0.txt");
+        let user1_output = base.join("output_psi0.txt");
+
+        let user2_input = base.join("psi1.txt");
+        let user2_output = base.join("output_psi1.txt");
+
+        let input_files = StagedFiles::new(hashmap!(
+            IN_DATA1 =>
+            StagedFileInfo::new(&user1_input, TeaclaveFile128Key::random(), 
FileAuthTag::mock()),
+            IN_DATA2 =>
+            StagedFileInfo::new(&user2_input, TeaclaveFile128Key::random(), 
FileAuthTag::mock()),
+        ));
+
+        let output_files = StagedFiles::new(hashmap!(
+            OUT_RESULT1 =>
+            StagedFileInfo::new(&user1_output, TeaclaveFile128Key::random(), 
FileAuthTag::mock()),
+            OUT_RESULT2 =>
+            StagedFileInfo::new(&user2_output, TeaclaveFile128Key::random(), 
FileAuthTag::mock()),
+        ));
+
+        let runtime = Box::new(RawIoRuntime::new(input_files, output_files));
+        let summary = OrderedSetIntersect::new().run(arguments, 
runtime).unwrap();
+
+        let user1_result = fs::read_to_string(&user1_output).unwrap();
+        let user2_result = fs::read_to_string(&user2_output).unwrap();
+
+        assert_eq!(&user1_result[..], "0101010");
+        assert_eq!(&user2_result[..], "01101");
+        assert_eq!(summary, "3 common items");
+    }
+}
diff --git a/tests/fixtures/functions/ordered_set_intersect/psi0.txt 
b/tests/fixtures/functions/ordered_set_intersect/psi0.txt
new file mode 100644
index 0000000..a2dd246
--- /dev/null
+++ b/tests/fixtures/functions/ordered_set_intersect/psi0.txt
@@ -0,0 +1,7 @@
+3129a6f57c01547906c4f851de448d4a85716927d9aae5d13955303833dea3be
+3c2ef1901bee3a4866d68e16de37a270e4f16d166132f14da88b5d0bb5c5a369
+699bd76eb9764233eade0f5ca571e86b01b59ef6051e6008f2ab1723b1ba20e8
+6b51d431df5d7f141cbececcf79edf3dd861c3b4069f0b11661a3eefacbba918
+7a90238b179e5d28faa81dcffee49fcd200d591a61f9d0ba9d76eca3cb71a813
+fa3cfb3f1bb823aa9501f88f1f95f732ee6fef2c3a48be7f1d38037b216a549f
+ffff5954ee15325a8af0a1251b5e6dc255975484df25c5f9f24542479d8d340e
\ No newline at end of file
diff --git a/tests/fixtures/functions/ordered_set_intersect/psi0.txt.enc 
b/tests/fixtures/functions/ordered_set_intersect/psi0.txt.enc
new file mode 100644
index 0000000..2b27556
Binary files /dev/null and 
b/tests/fixtures/functions/ordered_set_intersect/psi0.txt.enc differ
diff --git a/tests/fixtures/functions/ordered_set_intersect/psi1.txt 
b/tests/fixtures/functions/ordered_set_intersect/psi1.txt
new file mode 100644
index 0000000..618168e
--- /dev/null
+++ b/tests/fixtures/functions/ordered_set_intersect/psi1.txt
@@ -0,0 +1,5 @@
+35d85143c3bd10badcad7d3e01bdbad074e4d62a9f04f9c8652da5f5259fed7d
+3c2ef1901bee3a4866d68e16de37a270e4f16d166132f14da88b5d0bb5c5a369
+6b51d431df5d7f141cbececcf79edf3dd861c3b4069f0b11661a3eefacbba918
+87e58365cf5292ae0150b97d5bba026158e28a5c2fa32cb04cf4c6a0d0c97111
+fa3cfb3f1bb823aa9501f88f1f95f732ee6fef2c3a48be7f1d38037b216a549f
\ No newline at end of file
diff --git a/tests/fixtures/functions/ordered_set_intersect/psi1.txt.enc 
b/tests/fixtures/functions/ordered_set_intersect/psi1.txt.enc
new file mode 100644
index 0000000..2607b43
Binary files /dev/null and 
b/tests/fixtures/functions/ordered_set_intersect/psi1.txt.enc differ


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to