diff --git a/include/helib/keys.h b/include/helib/keys.h index 8da5fc394..c02eda4a6 100644 --- a/include/helib/keys.h +++ b/include/helib/keys.h @@ -10,6 +10,10 @@ * limitations under the License. See accompanying LICENSE file. */ +/* Copyright (C) 2022 Intel Corporation + * SPDX-License-Identifier: Apache-2.0 + */ + #ifndef HELIB_KEYS_H #define HELIB_KEYS_H /** @@ -423,61 +427,83 @@ class SecKey : public PubKey /** * @brief Write out the `SecKey` object in binary format. * @param str Output `std::ostream`. + * @param sk_only Whether to write only the secret key polynomial and context, + *and not the public key. **/ - void writeTo(std::ostream& str) const; + void writeTo(std::ostream& str, bool sk_only = false) const; /** * @brief Read from the stream the serialized `SecKey` object in binary * format. * @param str Input `std::istream`. + * @param sk_only Whether the stream contains only the secret key polynomial + *and context, and not the public key. * @return The deserialized `SecKey` object. **/ - static SecKey readFrom(std::istream& str, const Context& context); + static SecKey readFrom(std::istream& str, + const Context& context, + bool sk_only = false); /** * @brief Write out the secret key (`SecKey`) object to the output * stream using JSON format. * @param str Output `std::ostream`. + * @param sk_only whether to write only secret key polynomial and context, and + *not the public key. **/ - void writeToJSON(std::ostream& str) const; + void writeToJSON(std::ostream& str, bool sk_only = false) const; /** * @brief Write out the secret key (`SecKey`) object to a `JsonWrapper`. * @return The `JsonWrapper`. + * @param sk_only whether to write only secret key polynomial and context, and + *not the public key. **/ - JsonWrapper writeToJSON() const; + JsonWrapper writeToJSON(bool sk_only = false) const; /** * @brief Read from the stream the serialized secret key (`SecKey`) object * using JSON format. * @param str Input `std::istream`. * @param context The `Context` to be used. + * @param sk_only whether the stream contains only the secret key polynomial + *and context, and not the public key. * @return The deserialized `SecKey` object. **/ - static SecKey readFromJSON(std::istream& str, const Context& context); + static SecKey readFromJSON(std::istream& str, + const Context& context, + bool sk_only = false); /** * @brief Read from the `JsonWrapper` the serialized secret key (`SecKey`) * object. * @param j The `JsonWrapper` containing the serialized `SecKey` object. * @param context The `Context` to be used. + * @param sk_only whether the stream contains only the secret key polynomial + *and context, and not the public key. * @return The deserialized `SecKey` object. **/ - static SecKey readFromJSON(const JsonWrapper& j, const Context& context); + static SecKey readFromJSON(const JsonWrapper& j, + const Context& context, + bool sk_only = false); /** * @brief Read from the stream the serialized secret key (`SecKey`) object * using JSON format. * @param str Input `std::istream`. + * @param sk_only whether the stream contains only the secret key polynomial + *and context, and not the public key. **/ - void readJSON(std::istream& str); + void readJSON(std::istream& str, bool sk_only = false); /** * @brief Read from the `JsonWrapper` the serialized secret key (`SecKey`) * object. * @param j The `JsonWrapper` containing the serialized `SecKey` object. + * @param sk_only whether the stream contains only the secret key polynomial + *and context, and not the public key. **/ - void readJSON(const JsonWrapper& j); + void readJSON(const JsonWrapper& j, bool sk_only = false); // TODO: Add a similar method for binary serialization // This just writes the derived part, not including the public key diff --git a/misc/psi/tests/gen-params.batx b/misc/psi/tests/gen-params.batx old mode 100644 new mode 100755 index a4945876e..06df3e475 --- a/misc/psi/tests/gen-params.batx +++ b/misc/psi/tests/gen-params.batx @@ -11,6 +11,9 @@ # See the License for the specific language governing permissions and # limitations under the License. See accompanying LICENSE file. +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + utils_dir="../../../../utils" load "../../../utils/tests/std" @@ -18,9 +21,13 @@ nslots=3 #nslots=13860 #nslots=79872 datadir="data_and_params" +keygen_datadir="keygen_data_and_params" +sk_keygen_datadir="sk_keygen_data_and_params" data_prefix="../$datadir" db_encoded="../db${nslots}.enc" query_encoded="../query${nslots}.enc" +keygen_prefix="${prefix_bgv}_keygen" +sk_keygen_prefix="${prefix_bgv}_keygen" function create-toy-lookup-params { rm -f "${prefix_bgv}".params @@ -73,6 +80,17 @@ function create-serious-lookup-params { echo "Qbits=1800" >> "${prefix_bgv}".params } +function create-test-params { + rm -f "${prefix_bgv}".params + touch "${prefix_bgv}".params + echo "# Generated by bash test" >> "${prefix_bgv}".params + echo "p=65537" >> "${prefix_bgv}".params + echo "m=131072" >> "${prefix_bgv}".params + echo "r=1" >> "${prefix_bgv}".params + echo "c=3" >> "${prefix_bgv}".params + echo "Qbits=1130" >> "${prefix_bgv}".params +} + @test "only generate the context" { create-toy-lookup-params # create-serious-lookup-params @@ -85,6 +103,7 @@ function create-serious-lookup-params { rm -rf ../$datadir create-toy-lookup-params # create-serious-lookup-params +# create-test-params createContext BGV "${prefix_bgv}.params" "$prefix_bgv" "--frob-skm" # Encrypt the database and query $encrypt ${prefix_bgv}.pk ${db_encoded} -o "db.ctxt" @@ -93,3 +112,44 @@ function create-serious-lookup-params { mv $tmp_folder/ $datadir/ } +@test "keygen only generate the context" { + create-toy-lookup-params +# create-serious-lookup-params + keyGeneration BGV "${prefix_bgv}.params" "${prefix_bgv}" "--frob-skm" + techo "$(cat ${prefix_bgv}.info)" +} + +@test "keygen generate params and encrypt query and database" { + # Delete the existing data and params directory. + rm -rf ../$keygen_datadir + create-toy-lookup-params +# create-serious-lookup-params +# create-test-params + keyGeneration BGV "${prefix_bgv}.params" "${prefix_bgv}" "--frob-skm" + # Encrypt the database and query + $encrypt ${prefix_bgv}Enc.pk ${db_encoded} -o "db.ctxt" + $encrypt ${prefix_bgv}Enc.pk ${query_encoded} -o "query.ctxt" + cd - + mv $tmp_folder/ $keygen_datadir/ +} + +@test "keygen, sk only only generate the context" { + create-toy-lookup-params +# create-serious-lookup-params + keyGeneration BGV "${prefix_bgv}.params" "${prefix_bgv}" "--frob-skm" -s + techo "$(cat ${prefix_bgv}.info)" +} + +@test "keygen, sk only generate params and encrypt query and database" { + # Delete the existing data and params directory. + rm -rf ../$sk_keygen_datadir + create-toy-lookup-params +# create-serious-lookup-params +# create-test-params + keyGeneration BGV "${prefix_bgv}.params" "${prefix_bgv}" "--frob-skm" -s + # Encrypt the database and query + $encrypt ${prefix_bgv}Enc.pk ${db_encoded} -o "db.ctxt" + $encrypt ${prefix_bgv}Enc.pk ${query_encoded} -o "query.ctxt" + cd - + mv $tmp_folder/ $sk_keygen_datadir/ +} diff --git a/misc/psi/tests/lookup.bats b/misc/psi/tests/lookup.bats index 20bbdce23..2c16c2539 100755 --- a/misc/psi/tests/lookup.bats +++ b/misc/psi/tests/lookup.bats @@ -11,6 +11,9 @@ # See the License for the specific language governing permissions and # limitations under the License. See accompanying LICENSE file. +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + utils_dir="../../../../utils" load "../../../utils/tests/std" @@ -21,7 +24,12 @@ modulus=13 #nslots=79872 #modulus=1278209 datadir="data_and_params" +keygen_datadir="keygen_data_and_params" +sk_keygen_datadir="sk_keygen_data_and_params" data_prefix="../$datadir" +keygen_data_prefix="../$keygen_datadir" +sk_keygen_data_prefix="../$sk_keygen_datadir" + db_encoded="../db${nslots}.enc" query_encoded="../query${nslots}.enc" lookup="../../build/bin/lookup" @@ -46,18 +54,18 @@ function teardown { $decrypt ${data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" $decrypt ${data_prefix}/${prefix_bgv}.sk result.ctxt_and -o "result.ptxt_and" - $decrypt ${data_prefix}/${prefix_bgv}.sk result.ctxt_or -o "result.ptxt_or" - $decrypt ${data_prefix}/${prefix_bgv}.sk result.ctxt_expand -o "result.ptxt_expand" +# $decrypt ${data_prefix}/${prefix_bgv}.sk result.ctxt_or -o "result.ptxt_or" +# $decrypt ${data_prefix}/${prefix_bgv}.sk result.ctxt_expand -o "result.ptxt_expand" ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SAME > "expected.mask" ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test AND > "expected.mask_and" - ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test OR > "expected.mask_or" - ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test EXPAND > "expected.mask_expand" +# ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test OR > "expected.mask_or" +# ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test EXPAND > "expected.mask_expand" diff "result.ptxt" "expected.mask" diff "result.ptxt_and" "expected.mask_and" - diff "result.ptxt_or" "expected.mask_or" - diff "result.ptxt_expand" "expected.mask_expand" +# diff "result.ptxt_or" "expected.mask_or" +# diff "result.ptxt_expand" "expected.mask_expand" } @test "lookup 32 threads" { @@ -185,3 +193,297 @@ function teardown { diff "result.ptxt_or" "expected.mask_or" diff "result.ptxt_expand" "expected.mask_expand" } + +@test "keygen lookup 64 threads" { + skip + echo "keygen lookup 64 threads" > README + $lookup ${keygen_data_prefix}/${prefix_bgv}Eval.pk $keygen_data_prefix/db.ctxt $keygen_data_prefix/query.ctxt result.ctxt -n=64 + + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_and -o "result.ptxt_and" +# $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_or -o "result.ptxt_or" +# $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_expand -o "result.ptxt_expand" + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SAME > "expected.mask" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test AND > "expected.mask_and" +# ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test OR > "expected.mask_or" +# ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test EXPAND > "expected.mask_expand" + + diff "result.ptxt" "expected.mask" + diff "result.ptxt_and" "expected.mask_and" +# diff "result.ptxt_or" "expected.mask_or" +# diff "result.ptxt_expand" "expected.mask_expand" +} + +@test "keygen lookup 32 threads" { + skip + echo "keygen lookup 32 threads" > README + $lookup ${keygen_data_prefix}/${prefix_bgv}Eval.pk $keygen_data_prefix/db.ctxt $keygen_data_prefix/query.ctxt result.ctxt -n=32 + + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_and -o "result.ptxt_and" + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_or -o "result.ptxt_or" + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_expand -o "result.ptxt_expand" + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SAME > "expected.mask" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test AND > "expected.mask_and" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test OR > "expected.mask_or" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test EXPAND > "expected.mask_expand" + + diff "result.ptxt" "expected.mask" + diff "result.ptxt_and" "expected.mask_and" + diff "result.ptxt_or" "expected.mask_or" + diff "result.ptxt_expand" "expected.mask_expand" +} + +@test "keygen lookup 16 threads" { + skip + echo "keygen lookup 16 threads" > README + $lookup ${keygen_data_prefix}/${prefix_bgv}Eval.pk $keygen_data_prefix/db.ctxt $keygen_data_prefix/query.ctxt result.ctxt -n=16 + + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_and -o "result.ptxt_and" + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_or -o "result.ptxt_or" + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_expand -o "result.ptxt_expand" + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SAME > "expected.mask" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test AND > "expected.mask_and" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test OR > "expected.mask_or" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test EXPAND > "expected.mask_expand" + + diff "result.ptxt" "expected.mask" + diff "result.ptxt_and" "expected.mask_and" + diff "result.ptxt_or" "expected.mask_or" + diff "result.ptxt_expand" "expected.mask_expand" +} + +@test "keygen lookup 8 threads" { + skip + echo "keygen lookup 8 threads" > README + $lookup ${keygen_data_prefix}/${prefix_bgv}Eval.pk $keygen_data_prefix/db.ctxt $keygen_data_prefix/query.ctxt result.ctxt -n=8 + + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_and -o "result.ptxt_and" + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_or -o "result.ptxt_or" + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_expand -o "result.ptxt_expand" + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SAME > "expected.mask" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test AND > "expected.mask_and" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test OR > "expected.mask_or" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test EXPAND > "expected.mask_expand" + + diff "result.ptxt" "expected.mask" + diff "result.ptxt_and" "expected.mask_and" + diff "result.ptxt_or" "expected.mask_or" + diff "result.ptxt_expand" "expected.mask_expand" +} + +@test "keygen lookup 4 threads" { + skip + echo "keygen lookup 4 threads" > README + $lookup ${keygen_data_prefix}/${prefix_bgv}Eval.pk $keygen_data_prefix/db.ctxt $keygen_data_prefix/query.ctxt result.ctxt -n=4 + + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_and -o "result.ptxt_and" + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_or -o "result.ptxt_or" + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_expand -o "result.ptxt_expand" + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SAME > "expected.mask" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test AND > "expected.mask_and" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test OR > "expected.mask_or" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test EXPAND > "expected.mask_expand" + + diff "result.ptxt" "expected.mask" + diff "result.ptxt_and" "expected.mask_and" + diff "result.ptxt_or" "expected.mask_or" + diff "result.ptxt_expand" "expected.mask_expand" +} + +@test "keygen lookup 2 threads" { + skip + echo "keygen lookup 2 threads" > README + $lookup ${keygen_data_prefix}/${prefix_bgv}Eval.pk $keygen_data_prefix/db.ctxt $keygen_data_prefix/query.ctxt result.ctxt -n=2 + + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_and -o "result.ptxt_and" + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_or -o "result.ptxt_or" + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_expand -o "result.ptxt_expand" + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SAME > "expected.mask" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test AND > "expected.mask_and" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test OR > "expected.mask_or" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test EXPAND > "expected.mask_expand" + + diff "result.ptxt" "expected.mask" + diff "result.ptxt_and" "expected.mask_and" + diff "result.ptxt_or" "expected.mask_or" + diff "result.ptxt_expand" "expected.mask_expand" +} + +@test "keygen lookup 1 thread" { + skip + echo "keygen lookup 1 thread" > README + $lookup ${keygen_data_prefix}/${prefix_bgv}Eval.pk $keygen_data_prefix/db.ctxt $keygen_data_prefix/query.ctxt result.ctxt -n=1 + + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_and -o "result.ptxt_and" + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_or -o "result.ptxt_or" + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_expand -o "result.ptxt_expand" + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SAME > "expected.mask" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test AND > "expected.mask_and" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test OR > "expected.mask_or" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test EXPAND > "expected.mask_expand" + + diff "result.ptxt" "expected.mask" + diff "result.ptxt_and" "expected.mask_and" + diff "result.ptxt_or" "expected.mask_or" + diff "result.ptxt_expand" "expected.mask_expand" +} + +@test "keygen, sk only lookup 64 threads" { + skip + echo "keygen, sk only lookup 64 threads" > README + $lookup ${sk_keygen_data_prefix}/${prefix_bgv}Eval.pk $sk_keygen_data_prefix/db.ctxt $sk_keygen_data_prefix/query.ctxt result.ctxt -n=64 + + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" -s + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_and -o "result.ptxt_and" -s +# $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_or -o "result.ptxt_or" -s +# $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_expand -o "result.ptxt_expand" -s + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SAME > "expected.mask" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test AND > "expected.mask_and" +# ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test OR > "expected.mask_or" +# ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test EXPAND > "expected.mask_expand" + + diff "result.ptxt" "expected.mask" + diff "result.ptxt_and" "expected.mask_and" +# diff "result.ptxt_or" "expected.mask_or" +# diff "result.ptxt_expand" "expected.mask_expand" +} + +@test "keygen, sk only lookup 32 threads" { + skip + echo "keygen, sk only lookup 32 threads" > README + $lookup ${sk_keygen_data_prefix}/${prefix_bgv}Eval.pk $sk_keygen_data_prefix/db.ctxt $sk_keygen_data_prefix/query.ctxt result.ctxt -n=32 + + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" -s + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_and -o "result.ptxt_and" -s + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_or -o "result.ptxt_or" -s + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_expand -o "result.ptxt_expand" -s + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SAME > "expected.mask" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test AND > "expected.mask_and" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test OR > "expected.mask_or" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test EXPAND > "expected.mask_expand" + + diff "result.ptxt" "expected.mask" + diff "result.ptxt_and" "expected.mask_and" + diff "result.ptxt_or" "expected.mask_or" + diff "result.ptxt_expand" "expected.mask_expand" +} + +@test "keygen, sk only lookup 16 threads" { + skip + echo "keygen, sk only lookup 16 threads" > README + $lookup ${sk_keygen_data_prefix}/${prefix_bgv}Eval.pk $sk_keygen_data_prefix/db.ctxt $sk_keygen_data_prefix/query.ctxt result.ctxt -n=16 + + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" -s + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_and -o "result.ptxt_and" -s + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_or -o "result.ptxt_or" -s + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_expand -o "result.ptxt_expand" -s + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SAME > "expected.mask" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test AND > "expected.mask_and" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test OR > "expected.mask_or" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test EXPAND > "expected.mask_expand" + + diff "result.ptxt" "expected.mask" + diff "result.ptxt_and" "expected.mask_and" + diff "result.ptxt_or" "expected.mask_or" + diff "result.ptxt_expand" "expected.mask_expand" +} + +@test "keygen, sk only lookup 8 threads" { + skip + echo "keygen, sk only lookup 8 threads" > README + $lookup ${sk_keygen_data_prefix}/${prefix_bgv}Eval.pk $sk_keygen_data_prefix/db.ctxt $sk_keygen_data_prefix/query.ctxt result.ctxt -n=8 + + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" -s + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_and -o "result.ptxt_and" -s + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_or -o "result.ptxt_or" -s + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_expand -o "result.ptxt_expand" -s + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SAME > "expected.mask" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test AND > "expected.mask_and" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test OR > "expected.mask_or" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test EXPAND > "expected.mask_expand" + + diff "result.ptxt" "expected.mask" + diff "result.ptxt_and" "expected.mask_and" + diff "result.ptxt_or" "expected.mask_or" + diff "result.ptxt_expand" "expected.mask_expand" +} + +@test "keygen, sk only lookup 4 threads" { + skip + echo "keygen, sk only lookup 4 threads" > README + $lookup ${sk_keygen_data_prefix}/${prefix_bgv}Eval.pk $sk_keygen_data_prefix/db.ctxt $sk_keygen_data_prefix/query.ctxt result.ctxt -n=4 + + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" -s + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_and -o "result.ptxt_and" -s + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_or -o "result.ptxt_or" -s + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_expand -o "result.ptxt_expand" -s + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SAME > "expected.mask" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test AND > "expected.mask_and" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test OR > "expected.mask_or" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test EXPAND > "expected.mask_expand" + + diff "result.ptxt" "expected.mask" + diff "result.ptxt_and" "expected.mask_and" + diff "result.ptxt_or" "expected.mask_or" + diff "result.ptxt_expand" "expected.mask_expand" +} + +@test "keygen, sk only lookup 2 threads" { + skip + echo "keygen, sk only lookup 2 threads" > README + $lookup ${sk_keygen_data_prefix}/${prefix_bgv}Eval.pk $sk_keygen_data_prefix/db.ctxt $sk_keygen_data_prefix/query.ctxt result.ctxt -n=2 + + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" -s + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_and -o "result.ptxt_and" -s + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_or -o "result.ptxt_or" -s + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_expand -o "result.ptxt_expand" -s + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SAME > "expected.mask" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test AND > "expected.mask_and" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test OR > "expected.mask_or" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test EXPAND > "expected.mask_expand" + + diff "result.ptxt" "expected.mask" + diff "result.ptxt_and" "expected.mask_and" + diff "result.ptxt_or" "expected.mask_or" + diff "result.ptxt_expand" "expected.mask_expand" +} + +@test "keygen, sk only lookup 1 threads" { + skip + echo "keygen, sk only lookup 1 threads" > README + $lookup ${sk_keygen_data_prefix}/${prefix_bgv}Eval.pk $sk_keygen_data_prefix/db.ctxt $sk_keygen_data_prefix/query.ctxt result.ctxt -n=1 + + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" -s + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_and -o "result.ptxt_and" -s + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_or -o "result.ptxt_or" -s + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt_expand -o "result.ptxt_expand" -s + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SAME > "expected.mask" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test AND > "expected.mask_and" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test OR > "expected.mask_or" + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test EXPAND > "expected.mask_expand" + + diff "result.ptxt" "expected.mask" + diff "result.ptxt_and" "expected.mask_and" + diff "result.ptxt_or" "expected.mask_or" + diff "result.ptxt_expand" "expected.mask_expand" +} diff --git a/misc/psi/tests/scoring.bats b/misc/psi/tests/scoring.bats index e09271ddb..63d15b1c2 100755 --- a/misc/psi/tests/scoring.bats +++ b/misc/psi/tests/scoring.bats @@ -11,6 +11,9 @@ # See the License for the specific language governing permissions and # limitations under the License. See accompanying LICENSE file. +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + utils_dir="../../../../utils" load "../../../utils/tests/std" @@ -21,7 +24,12 @@ modulus=13 #nslots=79872 #modulus=1278209 datadir="data_and_params" +keygen_datadir="keygen_data_and_params" +sk_keygen_datadir="sk_keygen_data_and_params" data_prefix="../$datadir" +keygen_data_prefix="../$keygen_datadir" +sk_keygen_data_prefix="../$sk_keygen_datadir" + db_encoded="../db${nslots}.enc" query_encoded="../query${nslots}.enc" scoring="../../build/bin/scoring" @@ -122,3 +130,171 @@ function teardown { diff "result.ptxt" "expected.mask" } + +@test "keygen scoring 64 threads" { + skip + echo "keygen scoring 64 threads" > README + $scoring ${keygen_data_prefix}/${prefix_bgv}Eval.pk ${keygen_data_prefix}/db.ctxt ${keygen_data_prefix}/query.ctxt result.ctxt -n=64 + + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SCORE > "expected.mask" + + diff "result.ptxt" "expected.mask" +} + +@test "keygen scoring 32 threads" { + skip + echo "keygen scoring 32 threads" > README + $scoring ${keygen_data_prefix}/${prefix_bgv}Eval.pk ${keygen_data_prefix}/db.ctxt ${keygen_data_prefix}/query.ctxt result.ctxt -n=32 + + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SCORE > "expected.mask" + + diff "result.ptxt" "expected.mask" +} + +@test "keygen scoring 16 threads" { + skip + echo "keygen scoring 16 threads" > README + $scoring ${keygen_data_prefix}/${prefix_bgv}Eval.pk ${keygen_data_prefix}/db.ctxt ${keygen_data_prefix}/query.ctxt result.ctxt -n=16 + + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SCORE > "expected.mask" + + diff "result.ptxt" "expected.mask" +} + +@test "keygen scoring 8 threads" { + skip + echo "keygen scoring 8 threads" > README + $scoring ${keygen_data_prefix}/${prefix_bgv}Eval.pk ${keygen_data_prefix}/db.ctxt ${keygen_data_prefix}/query.ctxt result.ctxt -n=8 + + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SCORE > "expected.mask" + + diff "result.ptxt" "expected.mask" +} + +@test "keygen scoring 4 threads" { + skip + echo "keygen scoring 4 threads" > README + $scoring ${keygen_data_prefix}/${prefix_bgv}Eval.pk ${keygen_data_prefix}/db.ctxt ${keygen_data_prefix}/query.ctxt result.ctxt -n=4 + + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SCORE > "expected.mask" + + diff "result.ptxt" "expected.mask" +} + +@test "keygen scoring 2 threads" { + skip + echo "keygen scoring 2 threads" > README + $scoring ${keygen_data_prefix}/${prefix_bgv}Eval.pk ${keygen_data_prefix}/db.ctxt ${keygen_data_prefix}/query.ctxt result.ctxt -n=2 + + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SCORE > "expected.mask" + + diff "result.ptxt" "expected.mask" +} + +@test "keygen scoring 1 thread" { + skip + echo "keygen scoring 1 threads" > README + $scoring ${keygen_data_prefix}/${prefix_bgv}Eval.pk ${keygen_data_prefix}/db.ctxt ${keygen_data_prefix}/query.ctxt result.ctxt -n=1 + + $decrypt ${keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SCORE > "expected.mask" + + diff "result.ptxt" "expected.mask" +} + +@test "keygen, sk only scoring 64 threads" { + skip + echo "keygen, sk only 64 threads" > README + $scoring ${sk_keygen_data_prefix}/${prefix_bgv}Eval.pk ${sk_keygen_data_prefix}/db.ctxt ${sk_keygen_data_prefix}/query.ctxt result.ctxt -n=64 + + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" -s + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SCORE > "expected.mask" + + diff "result.ptxt" "expected.mask" +} + +@test "keygen, sk only scoring 32 threads" { + skip + echo "keygen, sk only 32 threads" > README + $scoring ${sk_keygen_data_prefix}/${prefix_bgv}Eval.pk ${sk_keygen_data_prefix}/db.ctxt ${sk_keygen_data_prefix}/query.ctxt result.ctxt -n=32 + + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" -s + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SCORE > "expected.mask" + + diff "result.ptxt" "expected.mask" +} + +@test "keygen, sk only scoring 16 threads" { + skip + echo "keygen, sk only 16 threads" > README + $scoring ${sk_keygen_data_prefix}/${prefix_bgv}Eval.pk ${sk_keygen_data_prefix}/db.ctxt ${sk_keygen_data_prefix}/query.ctxt result.ctxt -n=16 + + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" -s + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SCORE > "expected.mask" + + diff "result.ptxt" "expected.mask" +} + +@test "keygen, sk only scoring 8 threads" { + skip + echo "keygen, sk only 8 threads" > README + $scoring ${sk_keygen_data_prefix}/${prefix_bgv}Eval.pk ${sk_keygen_data_prefix}/db.ctxt ${sk_keygen_data_prefix}/query.ctxt result.ctxt -n=8 + + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" -s + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SCORE > "expected.mask" + + diff "result.ptxt" "expected.mask" +} + +@test "keygen, sk only scoring 4 threads" { + skip + echo "keygen, sk only 4 threads" > README + $scoring ${sk_keygen_data_prefix}/${prefix_bgv}Eval.pk ${sk_keygen_data_prefix}/db.ctxt ${sk_keygen_data_prefix}/query.ctxt result.ctxt -n=4 + + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" -s + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SCORE > "expected.mask" + + diff "result.ptxt" "expected.mask" +} + +@test "keygen, sk only scoring 2 threads" { + skip + echo "keygen, sk only 2 threads" > README + $scoring ${sk_keygen_data_prefix}/${prefix_bgv}Eval.pk ${sk_keygen_data_prefix}/db.ctxt ${sk_keygen_data_prefix}/query.ctxt result.ctxt -n=2 + + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" -s + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SCORE > "expected.mask" + + diff "result.ptxt" "expected.mask" +} + +@test "keygen, sk only scoring 1 thread" { + skip + echo "keygen, sk only 1 thread" > README + $scoring ${sk_keygen_data_prefix}/${prefix_bgv}Eval.pk ${sk_keygen_data_prefix}/db.ctxt ${sk_keygen_data_prefix}/query.ctxt result.ctxt -n=1 + + $decrypt ${sk_keygen_data_prefix}/${prefix_bgv}.sk result.ctxt -o "result.ptxt" -s + + ../gen-expected-mask.py ${query_encoded} ${db_encoded} --mod-p $modulus --test SCORE > "expected.mask" + + diff "result.ptxt" "expected.mask" +} diff --git a/src/eqtesting.cpp b/src/eqtesting.cpp index d11bb1786..91b6f5576 100644 --- a/src/eqtesting.cpp +++ b/src/eqtesting.cpp @@ -18,11 +18,11 @@ * Modified: * mapTo01 * added parallelism to existing logic for norm calculation - * added alternative logic for norm calculation which uses log(d) + * added alternative logic for norm calculation which uses log(d) * automorphisms on a single core - * added an additional optional argument `multithread` which determines + * added an additional optional argument `multithread` which determines * which version to run - * + * */ /** * @file eqtesting.cpp @@ -58,7 +58,7 @@ void mapTo01(const EncryptedArray& ea, Ctxt& ctxt, bool multithread) // Computing in parallel over t threads has runtime approximately // (d - 1)/t, whereas single thread has runtime approx log(d) if ((NTL::AvailableThreads() > 1) && multithread) { - // Compute O(d) Frobenius automorphisms in parallel + // Compute O(d) Frobenius automorphisms in parallel if (d > 1) { // compute the d - 1 automorphisms in parallel std::vector v(d, ctxt); diff --git a/src/keys.cpp b/src/keys.cpp index 7d3a2dfd3..59c24f1c7 100644 --- a/src/keys.cpp +++ b/src/keys.cpp @@ -9,6 +9,13 @@ * See the License for the specific language governing permissions and * limitations under the License. See accompanying LICENSE file. */ + +/* Copyright (C) 2022 Intel Corporation +* SPDX-License-Identifier: Apache-2.0 +* +* Added functionallity for separating the SK, PK, and key switching matrices. +*/ + #include #include @@ -1726,23 +1733,26 @@ std::istream& operator>>(std::istream& str, SecKey& sk) return str; } -void SecKey::writeTo(std::ostream& str) const +void SecKey::writeTo(std::ostream& str, bool sk_only) const { SerializeHeader().writeTo(str); writeEyeCatcher(str, EyeCatcher::SK_BEGIN); - // Write out the public key part first. - this->PubKey::writeTo(str); - - // Write out - // 1. vector sKeys + if (!sk_only) { + // Write out the public key part first. + this->PubKey::writeTo(str); + } else { + // If only writing sk, just write the context. + this->getContext().writeTo(str); + } + // Write out vector sKeys write_raw_vector(str, this->sKeys); writeEyeCatcher(str, EyeCatcher::SK_END); } -SecKey SecKey::readFrom(std::istream& str, const Context& context) +SecKey SecKey::readFrom(std::istream& str, const Context& context, bool sk_only) { const auto header = SerializeHeader::readFrom(str); assertEq(header.version, @@ -1753,9 +1763,15 @@ SecKey SecKey::readFrom(std::istream& str, const Context& context) bool eyeCatcherFound = readEyeCatcher(str, EyeCatcher::SK_BEGIN); assertTrue(eyeCatcherFound, "Could not find pre-secret key eyecatcher"); - - // Create a secret key from its public part. - SecKey ret(PubKey::readFrom(str, context)); + if (sk_only) { + // there should be a context written at this point in the file, check it + // matches provided context + assertEq(context, Context::readFrom(str), "Context mismatch"); + } + // now construct a secret key. If public key is written, construct from pk, + // otherwise, construct from context + SecKey ret = + sk_only ? SecKey(context) : SecKey(PubKey::readFrom(str, context)); // Set the secret part of the secret key. ret.sKeys = read_raw_vector(str, context); @@ -1767,56 +1783,70 @@ SecKey SecKey::readFrom(std::istream& str, const Context& context) return ret; } -void SecKey::writeToJSON(std::ostream& str) const +void SecKey::writeToJSON(std::ostream& str, bool sk_only) const { - executeRedirectJsonError([&]() { str << writeToJSON(); }); + executeRedirectJsonError([&]() { str << writeToJSON(sk_only); }); } -JsonWrapper SecKey::writeToJSON() const +JsonWrapper SecKey::writeToJSON(bool sk_only) const { - auto body = [this]() { - json j = {{"PubKey", unwrap(this->PubKey::writeToJSON())}, - {"sKeys", writeVectorToJSON(this->sKeys)}}; - return wrap(toTypedJson(j)); + auto body = [this, sk_only]() { + if (sk_only) { + // json contains context and secret key(s) + json j = {{"context", unwrap(this->getContext().writeToJSON())}, + {"sKeys", writeVectorToJSON(this->sKeys)}}; + return wrap(toTypedJson(j)); + } else { + // json contains public key and secret key(s) + json j = {{"PubKey", unwrap(this->PubKey::writeToJSON())}, + {"sKeys", writeVectorToJSON(this->sKeys)}}; + return wrap(toTypedJson(j)); + } }; - return executeRedirectJsonError(body); } -SecKey SecKey::readFromJSON(std::istream& str, const Context& context) +SecKey SecKey::readFromJSON(std::istream& str, + const Context& context, + bool sk_only) { auto body = [&]() { json j; str >> j; - return SecKey::readFromJSON(wrap(j), context); + return SecKey::readFromJSON(wrap(j), context, sk_only); }; - return executeRedirectJsonError(body); } -SecKey SecKey::readFromJSON(const JsonWrapper& jw, const Context& context) +SecKey SecKey::readFromJSON(const JsonWrapper& jw, + const Context& context, + bool sk_only) { SecKey ret{context}; - ret.readJSON(jw); + ret.readJSON(jw, sk_only); return ret; } -void SecKey::readJSON(std::istream& str) +void SecKey::readJSON(std::istream& str, bool sk_only) { executeRedirectJsonError([&]() { json j; str >> j; - this->readJSON(wrap(j)); + this->readJSON(wrap(j), sk_only); }); } -void SecKey::readJSON(const JsonWrapper& tjw) +void SecKey::readJSON(const JsonWrapper& tjw, bool sk_only) { executeRedirectJsonError([&]() { json j = fromTypedJson(unwrap(tjw)); this->clear(); - - this->PubKey::readJSON(wrap(j.at("PubKey"))); + if (sk_only) + assertEq(this->getContext(), + Context::readFromJSON(wrap(j.at("context"))), + "Context mismatch"); + else + this->PubKey::readJSON(wrap(j.at("PubKey"))); this->sKeys = readVectorFromJSON(j.at("sKeys"), context); }); diff --git a/tests/TestBinIO.cpp b/tests/TestBinIO.cpp index 00845fdc3..8f789a093 100644 --- a/tests/TestBinIO.cpp +++ b/tests/TestBinIO.cpp @@ -9,6 +9,13 @@ * See the License for the specific language governing permissions and * limitations under the License. See accompanying LICENSE file. */ + +/* Copyright (C) 2022 Intel Corporation +* SPDX-License-Identifier: Apache-2.0 +* +* Added tests for separated SK, PK and Key switching matrices +*/ + #include // isinf #include #include @@ -18,7 +25,13 @@ #include "test_common.h" #include "gtest/gtest.h" - +// Several of these tests relate to writing objects to stream and then reading +// them back without using test suite parameters, particularly using a +// "deserialized context". This causes challenges when validating against test +// suite parameters and variables, as the underlying contexts have different +// addresses. To accomodate this, in some places we will validate by writing +// test suite parameters and variables to stream and then reading them back +// using variables local to the specific test. namespace { struct BGVParameters @@ -392,6 +405,55 @@ TEST_P(TestBinIO_BGV, throwsWhenPostSecretKeyEyeCatcherNotFound) EXPECT_THROW(secretKey.readFrom(ss, context), helib::IOError); } +TEST_P(TestBinIO_BGV, throwsWhenPreOnlySecretKeyEyeCatcherNotFound) +{ + std::stringstream ss; + + EXPECT_NO_THROW(secretKey.writeTo(ss, /*sk_only=*/true)); + + // Delete pre-secretKey eye catcher + std::string s = ss.str(); + std::size_t pos = s.find(eyeCatcherToStr(helib::EyeCatcher::SK_BEGIN)); + s.erase(pos, pos + helib::EyeCatcher::SIZE); + ss.str(s); + + EXPECT_THROW(secretKey.readFrom(ss, context, /*sk_only=*/true), + helib::IOError); +} + +TEST_P(TestBinIO_BGV, throwsWhenPostOnlySecretKeyEyeCatcherNotFound) +{ + std::stringstream ss; + + EXPECT_NO_THROW(secretKey.writeTo(ss, /*sk_only=*/true)); + + // Delete post-secretKey eye catcher + std::string s = ss.str(); + s.erase(s.find(eyeCatcherToStr(helib::EyeCatcher::SK_END)), s.size() - 1); + ss.str(s); + + EXPECT_THROW(secretKey.readFrom(ss, context, /*sk_only=*/true), + helib::IOError); +} + +TEST_P(TestBinIO_BGV, readOnlySecretKeyThrowsWhenMismatchContext) +{ + std::stringstream str; + secretKey.writeTo(str, /*sk_only=*/true); + helib::Context new_context(helib::ContextBuilder() + .m(41) + .p(p) + .r(r) + .bits(bits) + .gens(gens) + .ords(ords) + .mvec(mvec) + .build()); + helib::SecKey deserialized_sk(new_context); + EXPECT_THROW(deserialized_sk.readFrom(str, new_context, /*sk_only=*/true), + helib::LogicError); +} + TEST_P(TestBinIO_BGV, readKeysFromDeserializeCorrectly) { std::stringstream str; @@ -453,6 +515,34 @@ TEST_P(TestBinIO_BGV, canEncryptWithDeserializedPublicKey) EXPECT_EQ(ptxt, decrypted_result); } +TEST_P(TestBinIO_BGV, canEncryptWithDeserializedPublicEncryptionKeyAndContext) +{ + std::stringstream ss; + + helib::PubKey& encPublicKey = secretKey; + + context.writeTo(ss); + encPublicKey.writeTo(ss); + + helib::Context deserialized_context = helib::Context::readFrom(ss); + helib::PubKey deserialized_enc_pk = + helib::PubKey::readFrom(ss, deserialized_context); + + helib::PtxtArray ptxt(deserialized_context), + decrypted_result(deserialized_context); + ptxt.random(); + helib::Ctxt ctxt(deserialized_enc_pk); + + EXPECT_NO_THROW(ptxt.encrypt(ctxt)); + + secretKey.writeTo(ss, /*sk_only=*/true); + helib::SecKey deserialized_sk = + helib::SecKey::readFrom(ss, deserialized_context, /*sk_only=*/true); + decrypted_result.decrypt(ctxt, deserialized_sk); + + EXPECT_EQ(ptxt, decrypted_result); +} + TEST_P(TestBinIO_BGV, canEncryptWithDeserializedSecretKey) { std::stringstream ss; @@ -461,6 +551,57 @@ TEST_P(TestBinIO_BGV, canEncryptWithDeserializedSecretKey) helib::SecKey deserialized_sk = helib::SecKey::readFrom(ss, context); + helib::PtxtArray ptxt(ea), decrypted_result(ea); + ptxt.random(); + helib::Ctxt ctxt(deserialized_sk); + + EXPECT_NO_THROW(ptxt.encrypt(ctxt)); + + decrypted_result.decrypt(ctxt, secretKey); + + EXPECT_EQ(ptxt, decrypted_result); +} + +TEST_P(TestBinIO_BGV, canEncryptWithDeserializedSecretKeyOnly) +{ + std::stringstream ss; + secretKey.writeTo(ss, /*sk_only=*/true); + + helib::SecKey deserialized_sk = + helib::SecKey::readFrom(ss, context, /*sk_only=*/true); + + helib::PtxtArray ptxt(ea), decrypted_result(ea); + ptxt.random(); + helib::Ctxt ctxt(deserialized_sk); + EXPECT_NO_THROW(ptxt.encrypt(ctxt)); + decrypted_result.decrypt(ctxt, secretKey); + EXPECT_EQ(ptxt, decrypted_result); +} + +TEST_P(TestBinIO_BGV, canDecryptWithDeserializedSecretKey) +{ + std::stringstream ss; + secretKey.writeTo(ss); + helib::SecKey deserialized_sk = helib::SecKey::readFrom(ss, context); + helib::PtxtArray ptxt(ea), decrypted_result(ea); + ptxt.random(); + helib::Ctxt ctxt(publicKey); + + ptxt.encrypt(ctxt); + + EXPECT_NO_THROW(decrypted_result.decrypt(ctxt, deserialized_sk)); + EXPECT_EQ(ptxt, decrypted_result); +} + +TEST_P(TestBinIO_BGV, canDecryptWithDeserializedSecretKeyOnly) +{ + std::stringstream ss; + + secretKey.writeTo(ss, /*sk_only=*/true); + + helib::SecKey deserialized_sk = + helib::SecKey::readFrom(ss, context, /*sk_only=*/true); + helib::PtxtArray ptxt(ea), decrypted_result(ea); ptxt.random(); helib::Ctxt ctxt(publicKey); @@ -471,6 +612,25 @@ TEST_P(TestBinIO_BGV, canEncryptWithDeserializedSecretKey) EXPECT_EQ(ptxt, decrypted_result); } +TEST_P(TestBinIO_BGV, canDecryptWithDeserializedSecretKeyOnlyAndContext) +{ + std::stringstream ss; + context.writeTo(ss); + secretKey.writeTo(ss, /*sk_only=*/true); + helib::Context deserialized_context = helib::Context::readFrom(ss); + helib::SecKey deserialized_sk = + helib::SecKey::readFrom(ss, deserialized_context, /*sk_only=*/true); + helib::PtxtArray ptxt(deserialized_context), + decrypted_result(deserialized_context); + ptxt.random(); + helib::Ctxt ctxt(deserialized_sk); + + ptxt.encrypt(ctxt); + + EXPECT_NO_THROW(decrypted_result.decrypt(ctxt, deserialized_sk)); + EXPECT_EQ(ptxt, decrypted_result); +} + TEST_P(TestBinIO_BGV, singleFunctionSerializationOfCiphertext) { std::stringstream str; @@ -561,12 +721,13 @@ TEST_P(TestBinIO_BGV, throwsWhenPostCiphertextEyeCatcherNotFoundInPlace) TEST_P(TestBinIO_BGV, readCiphertextFromDeserializeCorrectly) { - std::stringstream str; + std::stringstream ss; helib::Ctxt ctxt(publicKey); - EXPECT_NO_THROW(ctxt.writeTo(str)); + ss << ctxt; - helib::Ctxt deserialized_ctxt = helib::Ctxt::readFrom(str, publicKey); + helib::Ctxt deserialized_ctxt(publicKey); + ss >> deserialized_ctxt; EXPECT_EQ(ctxt, deserialized_ctxt); } @@ -629,6 +790,129 @@ TEST_P(TestBinIO_BGV, canPerformOperationsOnDeserializedCiphertext) EXPECT_EQ(ptxt1, ptxt2); } +TEST_P( + TestBinIO_BGV, + canPerformOperationsOnDeserializedCiphertextWithDeserializedContextAndEvalKey) +{ + std::stringstream ss1, ss2; + helib::PtxtArray ptxt(context); + ptxt.random(); + helib::Ctxt ctxt(publicKey); + ptxt.encrypt(ctxt); + + ctxt.writeTo(ss1); + context.writeTo(ss2); + publicKey.writeTo(ss2); + + helib::Context deserialized_context = helib::Context::readFrom(ss2); + helib::PubKey deserialized_eval_pk = + helib::PubKey::readFrom(ss2, deserialized_context); + + helib::Ctxt deserialized_ctxt = + helib::Ctxt::readFrom(ss1, deserialized_eval_pk); + helib::Ctxt deserialized_ctxt_copy(deserialized_ctxt); + EXPECT_NO_THROW(deserialized_ctxt *= deserialized_ctxt_copy); + EXPECT_NO_THROW(deserialized_ctxt += deserialized_ctxt_copy); + EXPECT_NO_THROW(deserialized_context.getEA().rotate(deserialized_ctxt, 1)); + + helib::PtxtArray ptxt_copy(ptxt); + ptxt *= ptxt_copy; + ptxt += ptxt_copy; + rotate(ptxt, 1); + helib::PtxtArray decrypted_result(deserialized_context); + std::stringstream ss3; + secretKey.writeTo(ss3, /*sk_only=*/true); + helib::SecKey deserialized_sk = + helib::SecKey::readFrom(ss3, deserialized_context, /*sk_only=*/true); + // to have consistent contexts, read ptxt to stream and then read with this + // context + ptxt.writeToJSON(ss3); + helib::PtxtArray ptxt_result = + helib::PtxtArray::readFromJSON(ss3, deserialized_context); + decrypted_result.decrypt(deserialized_ctxt, deserialized_sk); + EXPECT_EQ(decrypted_result, ptxt_result); +} + +TEST_P(TestBinIO_BGV, decryptWithDeserializedSecretKeyOnlyAfterComputation) +{ + std::stringstream ss; + + secretKey.writeTo(ss, /*sk_only=*/true); + + helib::PtxtArray ptxt(ea), decrypted_result(ea); + ptxt.random(); + helib::Ctxt ctxt(publicKey); + ptxt.encrypt(ctxt); + + EXPECT_NO_THROW(ptxt *= ptxt); + EXPECT_NO_THROW(ptxt += ptxt); + EXPECT_NO_THROW(rotate(ptxt, 1)); + + EXPECT_NO_THROW(ctxt *= ctxt); + EXPECT_NO_THROW(ctxt += ctxt); + EXPECT_NO_THROW(ctxt.reLinearize()); + EXPECT_NO_THROW(ea.rotate(ctxt, 1)); + + helib::SecKey deserialized_sk = + helib::SecKey::readFrom(ss, context, /*sk_only=*/true); + EXPECT_NO_THROW(decrypted_result.decrypt(ctxt, deserialized_sk)); + + EXPECT_EQ(ptxt, decrypted_result); +} + +TEST_P(TestBinIO_BGV, decryptWithDeserializedSecretKeyOnlyAfterMultLowLvl) +{ + std::stringstream ss; + + secretKey.writeTo(ss, /*sk_only=*/true); + + helib::PtxtArray ptxt1(ea), ptxt2(ea), decrypted_result(ea); + ptxt1.random(); + ptxt2.random(); + helib::Ctxt ctxt1(publicKey); + ptxt1.encrypt(ctxt1); + helib::Ctxt ctxt2(publicKey); + ptxt2.encrypt(ctxt2); + + EXPECT_NO_THROW(ptxt1 *= ptxt2); + + EXPECT_NO_THROW(ctxt1.multLowLvl(ctxt2)); + + helib::SecKey deserialized_sk = + helib::SecKey::readFrom(ss, context, /*sk_only=*/true); + EXPECT_NO_THROW(decrypted_result.decrypt(ctxt1, deserialized_sk)); + + EXPECT_EQ(ptxt1, decrypted_result); +} + +TEST_P(TestBinIO_BGV, + decryptDeserializedCiphertextWithDeserializedSecretKeyOnly) +{ + std::stringstream ss1, ss2; + helib::Ctxt ctxt(publicKey); + helib::PtxtArray ptxt(context); + ptxt.decrypt(ctxt, secretKey); + + ctxt.writeTo(ss1); + context.writeTo(ss2); + secretKey.writeTo(ss2, /*sk_only=*/true); + + helib::Context deserialized_context = helib::Context::readFrom(ss2); + helib::SecKey deserialized_sk = + helib::SecKey::readFrom(ss2, deserialized_context, /*sk_only=*/true); + helib::Ctxt deserialized_ctxt = helib::Ctxt::readFrom(ss1, deserialized_sk); + helib::PtxtArray decrypted_result(deserialized_context); + decrypted_result.decrypt(deserialized_ctxt, deserialized_sk); + + // for consistent contexts, write ptxt to stream and read with context + std::stringstream ss3; + ptxt.writeToJSON(ss3); + helib::PtxtArray ptxt_result = + helib::PtxtArray::readFromJSON(ss3, deserialized_context); + + EXPECT_EQ(ptxt_result, decrypted_result); +} + TEST_P(TestBinIO_CKKS, singleFunctionSerialization) { std::stringstream str; @@ -798,6 +1082,51 @@ TEST_P(TestBinIO_CKKS, throwsWhenPostSecretKeyEyeCatcherNotFound) EXPECT_THROW(secretKey.readFrom(ss, context), helib::IOError); } +TEST_P(TestBinIO_CKKS, throwsWhenPreOnlySecretKeyEyeCatcherNotFound) +{ + std::stringstream ss; + + EXPECT_NO_THROW(secretKey.writeTo(ss, /*sk_only=*/true)); + + // Delete pre-secretKey eye catcher + std::string s = ss.str(); + std::size_t pos = s.find(eyeCatcherToStr(helib::EyeCatcher::SK_BEGIN)); + s.erase(pos, pos + helib::EyeCatcher::SIZE); + ss.str(s); + + EXPECT_THROW(secretKey.readFrom(ss, context, /*sk_only=*/true), + helib::IOError); +} + +TEST_P(TestBinIO_CKKS, throwsWhenPostOnlySecretKeyEyeCatcherNotFound) +{ + std::stringstream ss; + + EXPECT_NO_THROW(secretKey.writeTo(ss, /*sk_only=*/true)); + + // Delete post-secretKey eye catcher + std::string s = ss.str(); + s.erase(s.find(eyeCatcherToStr(helib::EyeCatcher::SK_END)), s.size() - 1); + ss.str(s); + + EXPECT_THROW(secretKey.readFrom(ss, context, /*sk_only=*/true), + helib::IOError); +} + +TEST_P(TestBinIO_CKKS, readOnlySecretKeyThrowsWhenMismatchContext) +{ + std::stringstream str; + secretKey.writeTo(str, /*sk_only=*/true); + helib::Context new_context(helib::ContextBuilder() + .m(32) + .precision(precision) + .bits(bits) + .build()); + helib::SecKey deserialized_sk(new_context); + EXPECT_THROW(deserialized_sk.readFrom(str, new_context, /*sk_only=*/true), + helib::LogicError); +} + TEST_P(TestBinIO_CKKS, readKeysFromDeserializeCorrectly) { std::stringstream str; @@ -843,9 +1172,7 @@ TEST_P(TestBinIO_CKKS, readKeyPtrsFromDeserializeCorrectly) TEST_P(TestBinIO_CKKS, canEncryptWithDeserializedPublicKey) { std::stringstream ss; - publicKey.writeTo(ss); - helib::PubKey deserialized_pk = helib::PubKey::readFrom(ss, context); helib::PtxtArray ptxt(ea), decrypted_result(ea); @@ -853,18 +1180,69 @@ TEST_P(TestBinIO_CKKS, canEncryptWithDeserializedPublicKey) helib::Ctxt ctxt(deserialized_pk); EXPECT_NO_THROW(ptxt.encrypt(ctxt)); - decrypted_result.decrypt(ctxt, secretKey); + EXPECT_EQ(ptxt, helib::Approx(decrypted_result)); +} + +TEST_P(TestBinIO_CKKS, canEncryptWithDeserializedPublicEncryptionKeyAndContext) +{ + std::stringstream ss; + + helib::PubKey& encPublicKey = secretKey; + context.writeTo(ss); + encPublicKey.writeTo(ss); + helib::Context deserialized_context = helib::Context::readFrom(ss); + helib::PubKey deserialized_enc_pk = + helib::PubKey::readFrom(ss, deserialized_context); + helib::PtxtArray ptxt(deserialized_context), + decrypted_result(deserialized_context); + ptxt.random(); + helib::Ctxt ctxt(deserialized_enc_pk); + EXPECT_NO_THROW(ptxt.encrypt(ctxt)); + // now make a secret key with the exact same context + secretKey.writeTo(ss, /*sk_only=*/true); + helib::SecKey deserialized_sk = + helib::SecKey::readFrom(ss, deserialized_context, /*sk_only=*/true); + decrypted_result.decrypt(ctxt, deserialized_sk); EXPECT_EQ(ptxt, helib::Approx(decrypted_result)); } TEST_P(TestBinIO_CKKS, canEncryptWithDeserializedSecretKey) { std::stringstream ss; - secretKey.writeTo(ss); + helib::SecKey deserialized_sk = helib::SecKey::readFrom(ss, context); + helib::PtxtArray ptxt(ea), decrypted_result(ea); + ptxt.random(); + helib::Ctxt ctxt(deserialized_sk); + + EXPECT_NO_THROW(ptxt.encrypt(ctxt)); + decrypted_result.decrypt(ctxt, secretKey); + EXPECT_EQ(ptxt, helib::Approx(decrypted_result)); +} + +TEST_P(TestBinIO_CKKS, canEncryptWithDeserializedSecretKeyOnly) +{ + std::stringstream ss; + secretKey.writeTo(ss, /*sk_only=*/true); + helib::SecKey deserialized_sk = + helib::SecKey::readFrom(ss, context, /*sk_only=*/true); + + helib::PtxtArray ptxt(ea), decrypted_result(ea); + ptxt.random(); + helib::Ctxt ctxt(deserialized_sk); + + EXPECT_NO_THROW(ptxt.encrypt(ctxt)); + decrypted_result.decrypt(ctxt, secretKey); + EXPECT_EQ(ptxt, helib::Approx(decrypted_result)); +} + +TEST_P(TestBinIO_CKKS, canDecryptWithDeserializedSecretKey) +{ + std::stringstream ss; + secretKey.writeTo(ss); helib::SecKey deserialized_sk = helib::SecKey::readFrom(ss, context); helib::PtxtArray ptxt(ea), decrypted_result(ea); @@ -877,6 +1255,42 @@ TEST_P(TestBinIO_CKKS, canEncryptWithDeserializedSecretKey) EXPECT_EQ(ptxt, helib::Approx(decrypted_result)); } +TEST_P(TestBinIO_CKKS, canDecryptWithDeserializedSecretKeyOnly) +{ + std::stringstream ss; + secretKey.writeTo(ss, /*sk_only=*/true); + + helib::SecKey deserialized_sk = + helib::SecKey::readFrom(ss, context, /*sk_only=*/true); + helib::PtxtArray ptxt(ea), decrypted_result(ea); + ptxt.random(); + helib::Ctxt ctxt(publicKey); + + ptxt.encrypt(ctxt); + + EXPECT_NO_THROW(decrypted_result.decrypt(ctxt, deserialized_sk)); + EXPECT_EQ(ptxt, helib::Approx(decrypted_result)); +} + +TEST_P(TestBinIO_CKKS, canDecryptWithDeserializedSecretKeyOnlyAndContext) +{ + std::stringstream ss; + context.writeTo(ss); + secretKey.writeTo(ss, /*sk_only=*/true); + helib::Context deserialized_context = helib::Context::readFrom(ss); + helib::SecKey deserialized_sk = + helib::SecKey::readFrom(ss, deserialized_context, /*sk_only=*/true); + helib::PtxtArray ptxt(deserialized_context), + decrypted_result(deserialized_context); + ptxt.random(); + helib::Ctxt ctxt(deserialized_sk); + + ptxt.encrypt(ctxt); + + EXPECT_NO_THROW(decrypted_result.decrypt(ctxt, deserialized_sk)); + EXPECT_EQ(ptxt, helib::Approx(decrypted_result)); +} + TEST_P(TestBinIO_CKKS, singleFunctionSerializationOfCiphertext) { std::stringstream str; @@ -1010,7 +1424,6 @@ TEST_P(TestBinIO_CKKS, canPerformOperationsOnDeserializedCiphertext) { std::stringstream ss1, ss2; helib::Ctxt ctxt(publicKey); - ctxt.writeTo(ss1); ctxt.writeTo(ss2); @@ -1035,6 +1448,129 @@ TEST_P(TestBinIO_CKKS, canPerformOperationsOnDeserializedCiphertext) EXPECT_EQ(ptxt1, ptxt2); } +TEST_P( + TestBinIO_CKKS, + canPerformOperationsOnDeserializedCiphertextWithDeserializedContextAndEvalKey) +{ + std::stringstream ss1, ss2; + helib::PtxtArray ptxt(context); + ptxt.random(); + helib::Ctxt ctxt(publicKey); + ptxt.encrypt(ctxt); + + ctxt.writeTo(ss1); + context.writeTo(ss2); + publicKey.writeTo(ss2); + + helib::Context deserialized_context = helib::Context::readFrom(ss2); + helib::PubKey deserialized_eval_pk = + helib::PubKey::readFrom(ss2, deserialized_context); + + helib::Ctxt deserialized_ctxt = + helib::Ctxt::readFrom(ss1, deserialized_eval_pk); + helib::Ctxt deserialized_ctxt_copy(deserialized_ctxt); + EXPECT_NO_THROW(deserialized_ctxt *= deserialized_ctxt_copy); + EXPECT_NO_THROW(deserialized_ctxt += deserialized_ctxt_copy); + EXPECT_NO_THROW(deserialized_context.getEA().rotate(deserialized_ctxt, 1)); + + helib::PtxtArray ptxt_copy(ptxt); + ptxt *= ptxt_copy; + ptxt += ptxt_copy; + rotate(ptxt, 1); + helib::PtxtArray decrypted_result(deserialized_context); + std::stringstream ss3; + secretKey.writeTo(ss3, /*sk_only=*/true); + helib::SecKey deserialized_sk = + helib::SecKey::readFrom(ss3, deserialized_context, /*sk_only=*/true); + // to have consistent contexts, write ptxt to stream and then read with this + // context + ptxt.writeToJSON(ss3); + helib::PtxtArray ptxt_result = + helib::PtxtArray::readFromJSON(ss3, deserialized_context); + decrypted_result.decrypt(deserialized_ctxt, deserialized_sk); + EXPECT_EQ(decrypted_result, helib::Approx(ptxt_result)); +} + +TEST_P(TestBinIO_CKKS, decryptWithDeserializedSecretKeyOnlyAfterComputation) +{ + std::stringstream ss; + + secretKey.writeTo(ss, /*sk_only=*/true); + + helib::PtxtArray ptxt(ea), decrypted_result(ea); + ptxt.random(); + helib::Ctxt ctxt(publicKey); + ptxt.encrypt(ctxt); + + EXPECT_NO_THROW(ptxt *= ptxt); + EXPECT_NO_THROW(ptxt += ptxt); + EXPECT_NO_THROW(rotate(ptxt, 1)); + + EXPECT_NO_THROW(ctxt *= ctxt); + EXPECT_NO_THROW(ctxt += ctxt); + EXPECT_NO_THROW(ctxt.reLinearize()); + EXPECT_NO_THROW(ea.rotate(ctxt, 1)); + + helib::SecKey deserialized_sk = + helib::SecKey::readFrom(ss, context, /*sk_only=*/true); + EXPECT_NO_THROW(decrypted_result.decrypt(ctxt, deserialized_sk)); + + EXPECT_EQ(ptxt, helib::Approx(decrypted_result)); +} + +TEST_P(TestBinIO_CKKS, decryptWithDeserializedSecretKeyOnlyAfterMultLowLvl) +{ + std::stringstream ss; + + secretKey.writeTo(ss, /*sk_only=*/true); + + helib::PtxtArray ptxt1(ea), ptxt2(ea), decrypted_result(ea); + ptxt1.random(); + ptxt2.random(); + helib::Ctxt ctxt1(publicKey); + ptxt1.encrypt(ctxt1); + helib::Ctxt ctxt2(publicKey); + ptxt2.encrypt(ctxt2); + + EXPECT_NO_THROW(ptxt1 *= ptxt2); + + EXPECT_NO_THROW(ctxt1.multLowLvl(ctxt2)); + + helib::SecKey deserialized_sk = + helib::SecKey::readFrom(ss, context, /*sk_only=*/true); + EXPECT_NO_THROW(decrypted_result.decrypt(ctxt1, deserialized_sk)); + + EXPECT_EQ(ptxt1, helib::Approx(decrypted_result)); +} + +TEST_P(TestBinIO_CKKS, + decryptDeserializedCiphertextWithDeserializedSecretKeyOnly) +{ + std::stringstream ss1, ss2; + helib::Ctxt ctxt(publicKey); + helib::PtxtArray ptxt(context); + ptxt.decrypt(ctxt, secretKey); + + ctxt.writeTo(ss1); + context.writeTo(ss2); + secretKey.writeTo(ss2, /*sk_only=*/true); + + helib::Context deserialized_context = helib::Context::readFrom(ss2); + helib::SecKey deserialized_sk = + helib::SecKey::readFrom(ss2, deserialized_context, /*sk_only=*/true); + helib::Ctxt deserialized_ctxt = helib::Ctxt::readFrom(ss1, deserialized_sk); + helib::PtxtArray decrypted_result(deserialized_context); + decrypted_result.decrypt(deserialized_ctxt, deserialized_sk); + + // for consistent contexts, write ptxt to stream and read with context + std::stringstream ss3; + ptxt.writeToJSON(ss3); + helib::PtxtArray ptxt_result = + helib::PtxtArray::readFromJSON(ss3, deserialized_context); + + EXPECT_EQ(ptxt_result, decrypted_result); +} + INSTANTIATE_TEST_SUITE_P(Parameters, TestBinIO_BGV, ::testing::Values(BGVParameters(/*m=*/45, @@ -1049,6 +1585,6 @@ INSTANTIATE_TEST_SUITE_P(Parameters, TestBinIO_CKKS, ::testing::Values(CKKSParameters(/*m=*/64, /*precision=*/30, - /*bits=*/30))); + /*bits=*/60))); } // namespace diff --git a/tests/TestIO.cpp b/tests/TestIO.cpp index 72ebcdc12..fd3b01bb6 100644 --- a/tests/TestIO.cpp +++ b/tests/TestIO.cpp @@ -10,6 +10,12 @@ * limitations under the License. See accompanying LICENSE file. */ +/* Copyright (C) 2022 Intel Corporation +* SPDX-License-Identifier: Apache-2.0 +* +* Added tests for separated SK, PK and Key switching matrices +*/ + /* Note this file only tests JSON (de)serialization.*/ /* If you are searching for binary (de)serialization go to TestBinIO.cpp*/ @@ -701,6 +707,24 @@ TEST_P(TestIO_BGV, readKeysFromDeserializeCorrectly) EXPECT_EQ(secretKey, deserialized_sk); } +TEST_P(TestIO_BGV, secretKeyOnlyThrowsOnMismatchContext) +{ + std::stringstream str; + secretKey.writeToJSON(str, /*sk_only=*/true); + helib::Context new_context(helib::ContextBuilder() + .m(41) + .p(p) + .r(r) + .bits(bits) + .gens(gens) + .ords(ords) + .mvec(mvec) + .build()); + helib::SecKey deserialized_sk(new_context); + EXPECT_THROW(deserialized_sk.readFromJSON(str, new_context, /*sk_only=*/true), + helib::LogicError); +} + TEST_P(TestIO_BGV, handlingPublicKeyDeserializationWithMissingField) { std::stringstream ss; @@ -756,6 +780,24 @@ TEST_P(TestIO_BGV, canEncryptWithDeserializedPublicKey) EXPECT_EQ(ptxt, decrypted_result); } +TEST_P(TestIO_BGV, canEncryptWithDeserializedSecretKeyOnly) +{ + std::stringstream ss; + + secretKey.writeToJSON(ss, /*sk_only=*/true); + helib::SecKey deserialized_sk = + helib::SecKey::readFromJSON(ss, context, /*sk_only=*/true); + helib::PtxtArray ptxt(ea), decrypted_result(ea); + ptxt.random(); + helib::Ctxt ctxt(deserialized_sk); + + EXPECT_NO_THROW(ptxt.encrypt(ctxt)); + + decrypted_result.decrypt(ctxt, secretKey); + + EXPECT_EQ(ptxt, decrypted_result); +} + TEST_P(TestIO_BGV, canDecryptWithDeserializedSecretKey) { std::stringstream ss; @@ -774,6 +816,24 @@ TEST_P(TestIO_BGV, canDecryptWithDeserializedSecretKey) EXPECT_EQ(ptxt, decrypted_result); } +TEST_P(TestIO_BGV, canDecryptWithDeserializedSecretKeyOnly) +{ + std::stringstream ss; + + secretKey.writeToJSON(ss, /*sk_only=*/true); + helib::SecKey deserialized_sk = + helib::SecKey::readFromJSON(ss, context, /*sk_only=*/true); + + helib::PtxtArray ptxt(ea), decrypted_result(ea); + ptxt.random(); + + helib::Ctxt ctxt(publicKey); + ptxt.encrypt(ctxt); + + EXPECT_NO_THROW(decrypted_result.decrypt(ctxt, deserialized_sk)); + EXPECT_EQ(ptxt, decrypted_result); +} + TEST_P(TestIO_BGV, serializeCiphertextWithStreamOperator) { std::stringstream str; @@ -1478,6 +1538,20 @@ TEST_P(TestIO_CKKS, readKeysFromDeserializeCorrectly) EXPECT_EQ(secretKey, deserialized_sk); } +TEST_P(TestIO_CKKS, secretKeyOnlyThrowsOnMismatchContext) +{ + std::stringstream str; + secretKey.writeToJSON(str, /*sk_only=*/true); + helib::Context new_context(helib::ContextBuilder() + .m(32) + .precision(precision) + .bits(bits) + .build()); + helib::SecKey deserialized_sk(new_context); + EXPECT_THROW(deserialized_sk.readFromJSON(str, new_context, /*sk_only=*/true), + helib::LogicError); +} + TEST_P(TestIO_CKKS, handlingPublicKeyDeserializationWithMissingField) { std::stringstream ss; @@ -1534,6 +1608,24 @@ TEST_P(TestIO_CKKS, canEncryptWithDeserializedPublicKey) EXPECT_EQ(ptxt, helib::Approx(decrypted_result)); } +TEST_P(TestIO_CKKS, canEncryptWithDeserializedSecretKeyOnly) +{ + std::stringstream ss; + + secretKey.writeToJSON(ss, /*sk_only=*/true); + helib::SecKey deserialized_sk = + helib::SecKey::readFromJSON(ss, context, /*sk_only=*/true); + + helib::PtxtArray ptxt(ea), decrypted_result(ea); + ptxt.random(); + + helib::Ctxt ctxt(deserialized_sk); + EXPECT_NO_THROW(ptxt.encrypt(ctxt)); + + decrypted_result.decrypt(ctxt, secretKey); + EXPECT_EQ(ptxt, helib::Approx(decrypted_result)); +} + TEST_P(TestIO_CKKS, canDecryptWithDeserializedSecretKey) { std::stringstream ss; @@ -1552,6 +1644,23 @@ TEST_P(TestIO_CKKS, canDecryptWithDeserializedSecretKey) EXPECT_EQ(ptxt, helib::Approx(decrypted_result)); } +TEST_P(TestIO_CKKS, canDecryptWithDeserializedSecretKeyOnly) +{ + std::stringstream ss; + + secretKey.writeToJSON(ss, /*sk_only=*/true); + helib::SecKey deserialized_sk = + helib::SecKey::readFromJSON(ss, context, /*sk_only=*/true); + helib::PtxtArray ptxt(ea), decrypted_result(ea); + ptxt.random(); + + helib::Ctxt ctxt(publicKey); + ptxt.encrypt(ctxt); + + EXPECT_NO_THROW(decrypted_result.decrypt(ctxt, deserialized_sk)); + EXPECT_EQ(ptxt, helib::Approx(decrypted_result)); +} + TEST_P(TestIO_CKKS, serializeCiphertextWithStreamOperator) { std::stringstream str; diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 28be2de9b..46a08f29f 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -9,6 +9,9 @@ # See the License for the specific language governing permissions and # limitations under the License. See accompanying LICENSE file. +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + cmake_minimum_required(VERSION 3.10.2 FATAL_ERROR) ## Use -std=c++17 as default. @@ -39,6 +42,6 @@ file(STRINGS "../VERSION" HELIB_VERSION) find_package(helib "${HELIB_VERSION}" EXACT REQUIRED) add_subdirectory(create-context) +add_subdirectory(key-gen) add_subdirectory(crypto) - add_subdirectory(test_bootstrapping) diff --git a/utils/common/common.h b/utils/common/common.h index ecbd264c5..a77e7ded9 100644 --- a/utils/common/common.h +++ b/utils/common/common.h @@ -10,6 +10,12 @@ * limitations under the License. See accompanying LICENSE file. */ +/* Copyright (C) 2022 Intel Corporation +* SPDX-License-Identifier: Apache-2.0 +* +* Stream serialization for context and key +*/ + #ifndef COMMON_H #define COMMON_H @@ -35,24 +41,32 @@ inline std::string readline(std::istream& is) template using uniq_pair = std::pair, std::unique_ptr>; +/** + * @brief Read from the stream a serialized context and key. + * + * @tparam KEY The key type + * @param keyFilePath Location of context and key + * @param read_only_sk Whether the secret key was serialized using + * by writing only the secret key polynomial, defaulted to false. + * @return uniq_pair + */ template -uniq_pair loadContextAndKey(const std::string& keyFilePath) +uniq_pair loadContextAndKey(const std::string& keyFilePath, + bool read_only_sk = false) { std::ifstream keyFile(keyFilePath, std::ios::binary); if (!keyFile.is_open()) throw std::runtime_error("Cannot open Public Key file '" + keyFilePath + "'."); - unsigned long m, p, r; std::vector gens, ords; std::unique_ptr contextp( helib::Context::readPtrFrom(keyFile)); - std::unique_ptr keyp = std::make_unique(*contextp); if constexpr (std::is_same_v) { keyp = std::make_unique( - helib::SecKey::readFrom(keyFile, *contextp)); + helib::SecKey::readFrom(keyFile, *contextp, read_only_sk)); } else { keyp = std::make_unique( helib::PubKey::readFrom(keyFile, *contextp)); diff --git a/utils/crypto/decrypt.cpp b/utils/crypto/decrypt.cpp index fd04bd407..31a924e57 100644 --- a/utils/crypto/decrypt.cpp +++ b/utils/crypto/decrypt.cpp @@ -9,6 +9,12 @@ * See the License for the specific language governing permissions and * limitations under the License. See accompanying LICENSE file. */ + + /* Copyright (C) 2022 Intel Corporation +* SPDX-License-Identifier: Apache-2.0 +* +* Added option to load SK only +*/ #include #include @@ -26,6 +32,7 @@ struct CmdLineOpts std::string skFilePath; std::string ctxtFilePath; std::string outFilePath; + bool read_only_sk = false; // Default to false for backward compatibility. long batchSize = 0; long nthreads = 0; // Default is 0 for number of cpus. }; @@ -134,6 +141,7 @@ int main(int argc, char* argv[]) "batch size, how many ctxts in memory. If not set or 0 defaults to the number of threads used.") .arg("-n", cmdLineOpts.nthreads, "number of threads to use. If not set or 0 defaults to the number of concurrent threads supported.", "num. of cores") + .toggle(true).arg("-s", cmdLineOpts.read_only_sk, "whether only the secret key is written.") .parse(argc, argv); // clang-format on @@ -180,11 +188,10 @@ int main(int argc, char* argv[]) } // Load Context and SecKey - std::unique_ptr contextp; - std::unique_ptr skp; - std::tie(contextp, skp) = - loadContextAndKey(cmdLineOpts.skFilePath); + auto [contextp, skp] = + loadContextAndKey(cmdLineOpts.skFilePath, + cmdLineOpts.read_only_sk); // Read in, decrypt, output. try { diff --git a/utils/crypto/encrypt.cpp b/utils/crypto/encrypt.cpp index 5bc710cce..bc1bdc69f 100644 --- a/utils/crypto/encrypt.cpp +++ b/utils/crypto/encrypt.cpp @@ -178,10 +178,7 @@ int main(int argc, char* argv[]) } // Load Context and PubKey - std::unique_ptr contextp; - std::unique_ptr pkp; - - std::tie(contextp, pkp) = + auto [contextp, pkp] = loadContextAndKey(cmdLineOpts.pkFilePath); try { diff --git a/utils/key-gen/CMakeLists.txt b/utils/key-gen/CMakeLists.txt new file mode 100644 index 000000000..54b6ac7a7 --- /dev/null +++ b/utils/key-gen/CMakeLists.txt @@ -0,0 +1,12 @@ +# Copyright (C) 2020 IBM Corp. +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +add_executable(key-gen key-gen.cpp) +add_executable(gen-data gen-data.cpp) + +target_include_directories(key-gen PRIVATE "../common") +target_include_directories(gen-data PRIVATE "../common") + +target_link_libraries(key-gen helib) +target_link_libraries(gen-data helib) diff --git a/utils/key-gen/README.md b/utils/key-gen/README.md new file mode 100644 index 000000000..ba2568d43 --- /dev/null +++ b/utils/key-gen/README.md @@ -0,0 +1,38 @@ +# Key Generation and Management Pipeline + +## Introduction +This subdirectory provides additional utilities for use within a key management system. This code should be considered experimental. + +## Difference to other utils +This folder contains a plaintext generator `gen-data.cpp`, as well as `key-gen.cpp` an alternative key generation to `create-context.cpp`. `key-gen.cpp` differs from `create-context.cpp` in the following respects: +- `key-gen` only generates keys for BGV without bootstrapping, resulting in a smaller executable. +- `key-gen` writes only the secret key polynomial to the secret key file, rather than printing both the associated public key and the secret key. +- `key-gen` creates two public keys - `Enc.pk`, a public key for encryption, and `Eval.pk`, a public key which can be used for homomorphic function evaluation. `Enc.pk` is significantly smaller than `Eval.pk`. + +## Installation +Installation is identical to the larger utils directory, and proceeds via running CMake and then make from the build directory. This will create five executables in `build/bin`, namely `create-context`, `decrypt`, `encrypt`, `gen-data`, and `key-gen`. + +## Running the utilities +All utilities have a help method by passing the `-h` flag. In the `key-gen` folder, three parameter sets have been included for demonstration, `small_`, `medium_` and `large_params.txt`. We describe an example pipeline using the `key-gen` utilities from within the key-gen folder, with the small parameters as an example. This will create all files inside the key-gen folder. +1. Generate some test data using the `gen-data` program +``` +./../build/bin/gen-data small_params.txt -o small_data -n 5 +``` +The optional `-o` flag defines the output file name, while the optional `-n` flag determines the number of plaintexts to generate. Running this command will create a file `small_data.json` with five plaintexts in the key-gen directory. +2. Generate keys using `key-gen` +``` +./../build/bin/key-gen small_params.txt -s -o small +``` +The optional `-s` flag means that only the secret key polynomial will be written, resulting in a smaller file. The optional `-o` flag specifies the prefix of the generated keys: in this case, `small`. This will create three key files: `small.sk`, a secret key, `smallEnc.pk`, a public key for encryption, and `smallEval.pk`, a public key for homomorphic evaluation. + +All optional flags that can be passed to `create-context` can also be passed to `key-gen` -- to see a list, run the help method `-h`. +3. Encrypt data +``` +./../build/bin/encrypt smallEnc.pk small_data.json -o small.ctxt +``` +The optional `-o` argument determines the file name of the resulting ciphertext file. Running this command will encrypt `small_data.json` with public key `smallEnc.pk` to give 5 ciphertexts in the file `small.ctxt`. +4. Decrypt the data +``` +./../build/bin/decrypt small.sk small.ctxt -o small_result.json -s +``` +This decrypts `small.ctxt` with the secret key `small.sk` and puts the resulting plaintext into `small_result.json`. The `-s` flag indicates that only the secret key polynomial is stored in `small.sk`, and must be used if keys were generated using the `-s` flag with `key-gen`. diff --git a/utils/key-gen/gen-data.cpp b/utils/key-gen/gen-data.cpp new file mode 100644 index 000000000..e6349b850 --- /dev/null +++ b/utils/key-gen/gen-data.cpp @@ -0,0 +1,129 @@ +/* Copyright (C) 2022 Intel Corporation + * SPDX-License-Identifier: Apache-2.0 + */ + +// a file to generate random plaintexts for testing the key-gen pipeline. + +#include +#include + +#include +#include +#include + +#include +#include "common.h" + +struct CmdLineOpts +{ + std::string paramFileName; + std::string outputPrefixPath; + std::string ptxtCount = "1"; +}; + +// Captures parameters for BGV +struct ParamsFileOpts +{ + long m = 0; + long p = 0; + long r = 0; + long c = 0; + long Qbits = 0; +}; + +int main(int argc, char* argv[]) +{ + CmdLineOpts cmdLineOpts; + + // clang-format off + helib::ArgMap() + .toggle() + .separator(helib::ArgMap::Separator::WHITESPACE) + .named() + .arg("-o", cmdLineOpts.outputPrefixPath, + "choose an output prefix path.", nullptr) + .arg("-n", cmdLineOpts.ptxtCount, "the number of plaintexts to generate.") + .required() + .positional() + .arg("", cmdLineOpts.paramFileName, + "the parameters file.", nullptr) + .parse(argc, argv); + // clang-format on + // If not set by user, returns params file name without extension and "params" + if (cmdLineOpts.outputPrefixPath.empty()) { + std::string prefix = stripExtension(cmdLineOpts.paramFileName); + std::size_t params_pos = prefix.rfind("params"); + cmdLineOpts.outputPrefixPath = (params_pos == std::string::npos) + ? prefix + : prefix.substr(0, params_pos); + std::cout << "File prefix: " << cmdLineOpts.outputPrefixPath << std::endl; + } + ParamsFileOpts paramsOpts; + + try { + // clang-format off + helib::ArgMap() + .arg("p", paramsOpts.p, "require p.", "") + .arg("m", paramsOpts.m, "require m.", "") + .arg("r", paramsOpts.r, "require r.", "") + .arg("c", paramsOpts.c, "require c.", "") + .arg("Qbits", paramsOpts.Qbits, "require Q bits.", "") + .parse(cmdLineOpts.paramFileName); + // clang-format on + } catch (const helib::RuntimeError& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + + if (paramsOpts.p < 2) { + std::cerr << "BGV invalid plaintext modulus. " + "In BGV it must be a prime number greater than 1." + << std::endl; + return EXIT_FAILURE; + } + + try { + const helib::Context* contextp = helib::ContextBuilder() + .m(paramsOpts.m) + .p(paramsOpts.p) + .r(paramsOpts.r) + .bits(paramsOpts.Qbits) + .c(paramsOpts.c) + .buildPtr(); + + std::string ptxt_path = cmdLineOpts.outputPrefixPath + ".json"; + std::ofstream outPtxtFile(ptxt_path, std::ios::out); + if (!outPtxtFile.is_open()) { + throw std::runtime_error("Could not open file '" + ptxt_path + "'."); + } + // to call encrypt, need to have number of plaintexts at top of file + outPtxtFile << stoi(cmdLineOpts.ptxtCount) << std::endl; + for (int i = 0; i < stoi(cmdLineOpts.ptxtCount); i++) { + helib::Ptxt ptxt(*contextp); + ptxt.random(); + ptxt.writeToJSON(outPtxtFile); + outPtxtFile << std::endl; + } + + } catch (const std::invalid_argument& e) { + std::cerr << "Exit due to invalid argument thrown:\n" + << e.what() << std::endl; + return EXIT_FAILURE; + } catch (const helib::IOError& e) { + std::cerr << "Exit due to IOError thrown:\n" << e.what() << std::endl; + return EXIT_FAILURE; + } catch (const std::runtime_error& e) { + std::cerr << "Exit due to runtime error thrown:\n" << e.what() << std::endl; + return EXIT_FAILURE; + } catch (const std::logic_error& e) { + std::cerr << "Exit due to logic error thrown:\n" << e.what() << std::endl; + return EXIT_FAILURE; + } catch (const std::exception& e) { + std::cerr << "Exit due to unknown exception thrown:\n" + << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/utils/key-gen/key-gen.cpp b/utils/key-gen/key-gen.cpp new file mode 100644 index 000000000..c21d64435 --- /dev/null +++ b/utils/key-gen/key-gen.cpp @@ -0,0 +1,317 @@ +/* Copyright (C) 2020 IBM Corp. + * Copyright (C) 2022 Intel Corporation + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include +#include + +#include + +#include "common.h" + +struct CmdLineOpts +{ + std::string paramFileName; + std::string outputPrefixPath; + bool write_only_sk = 0; // Default to false for backward compatibility. + std::string scheme = "BGV"; + std::string bootstrappable = "NONE"; // NONE | THIN | FAT + bool noSKM = false; + bool frobSKM = false; + bool infoFile = false; +}; + +// Captures parameters of both BGV and CKKS +struct ParamsFileOpts +{ + long m = 0; + long p = 0; + long r = 0; + long c = 0; + long Qbits = 0; + long scale = 4; + long c_m = 100; + NTL::Vec mvec; + NTL::Vec gens; + NTL::Vec ords; +}; + +// Write context.printout to file.out +void printoutToStream(const helib::Context& context, + std::ostream& out, + bool noSKM, + bool frobSKM, + bool bootstrappable) +{ + if (!noSKM || bootstrappable) + out << "Key switching matrices created.\n"; + if (frobSKM || bootstrappable) + out << "Frobenius matrices created.\n"; + if (bootstrappable) + out << "Recrypt data created.\n"; + + // write the algebra info + context.printout(out); +} + +// sk is child of pk in HElib. +void writeKeyToFile(std::string& pathPrefix, + helib::Context& context, + helib::SecKey& secretKey, + bool pkNotSk, + bool write_only_sk = false) +{ + std::string path = pathPrefix + (pkNotSk ? ".pk" : ".sk"); + std::ofstream keysFile(path, std::ios::binary); + if (!keysFile.is_open()) { + std::runtime_error("Cannot write keys to file at '" + path); + } + + // write the context + context.writeTo(keysFile); + + // write the keys + if (pkNotSk) { + const helib::PubKey& pk = secretKey; + pk.writeTo(keysFile); + } else { + secretKey.writeTo(keysFile, write_only_sk); + } +} + +int main(int argc, char* argv[]) +{ + CmdLineOpts cmdLineOpts; + + // clang-format off + helib::ArgMap() + .toggle() + .arg("--no-skm", cmdLineOpts.noSKM, + "disable switch-key matrices.", nullptr) + .arg("--frob-skm", cmdLineOpts.frobSKM, + "generate Frobenius switch-key matrices.", nullptr) + .arg("--info-file", cmdLineOpts.infoFile, + "print algebra info to file.", nullptr) + .arg("-s",cmdLineOpts.write_only_sk,"write only the secret key polynomial to the secret key file.") + .separator(helib::ArgMap::Separator::WHITESPACE) + .named() + .arg("--scheme", cmdLineOpts.scheme, + "choose scheme BGV | CKKS.") + .arg("-o", cmdLineOpts.outputPrefixPath, + "choose an output prefix path.", nullptr) + .arg("--bootstrap", cmdLineOpts.bootstrappable, + "choose boostrapping option NONE | THIN | THICK.") + .required() + .positional() + .arg("", cmdLineOpts.paramFileName, + "the parameters file.", nullptr) + .parse(argc, argv); + // clang-format on + + ParamsFileOpts paramsOpts; + + try { + // clang-format off + helib::ArgMap() + .arg("p", paramsOpts.p, "require p.", "") + .arg("m", paramsOpts.m, "require m.", "") + .arg("r", paramsOpts.r, "require r.", "") + .arg("c", paramsOpts.c, "require c.", "") + .arg("Qbits", paramsOpts.Qbits, "require Q bits.", "") + .optional() + .arg("scale", paramsOpts.scale, "require scale for CKKS") + .arg("c_m", paramsOpts.c_m, "require c_m for bootstrapping.", "") + .arg("mvec", paramsOpts.mvec, "require mvec for bootstrapping.", "") + .arg("gens", paramsOpts.gens, "require gens for bootstrapping.", "") + .arg("ords", paramsOpts.ords, "require ords for bootstrapping.", "") + .parse(cmdLineOpts.paramFileName); + // clang-format on + } catch (const helib::RuntimeError& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + // Create the FHE context + long p; + if (cmdLineOpts.scheme.empty() || cmdLineOpts.scheme == "BGV") { + if (paramsOpts.p < 2) { + std::cerr << "BGV invalid plaintext modulus. " + "In BGV it must be a prime number greater than 1." + << std::endl; + return EXIT_FAILURE; + } + p = paramsOpts.p; + } else if (cmdLineOpts.scheme == "CKKS") { + if (paramsOpts.p != -1) { + std::cerr << "CKKS invalid plaintext modulus. " + "In CKKS it must be set to -1." + << std::endl; + return EXIT_FAILURE; + } + p = -1; + if (cmdLineOpts.bootstrappable != "NONE") { + std::cerr << "CKKS does not currently support bootstrapping." + << std::endl; + return EXIT_FAILURE; + } + } else { + std::cerr << "Unrecognized scheme '" << cmdLineOpts.scheme << "'." + << std::endl; + return EXIT_FAILURE; + } + + if (cmdLineOpts.noSKM && cmdLineOpts.frobSKM) { + std::cerr << "Frobenius matrices reqires switch-key matrices to be " + "generated." + << std::endl; + return EXIT_FAILURE; + } + + if (cmdLineOpts.bootstrappable != "NONE" && + cmdLineOpts.bootstrappable != "THIN" && + cmdLineOpts.bootstrappable != "THICK") { + std::cerr << "Bad boostrap option: " << cmdLineOpts.bootstrappable + << ". Allowed options are NONE, THIN, THICK." << std::endl; + return EXIT_FAILURE; + } + + if (cmdLineOpts.bootstrappable != "NONE") { + if (cmdLineOpts.noSKM) { + std::cerr << "Cannot generate bootstrappable context without switch-key " + "and frobenius matrices." + << std::endl; + return EXIT_FAILURE; + } + if (paramsOpts.mvec.length() == 0) { + std::cerr << "Missing mvec parameter for bootstrapping in " + << cmdLineOpts.paramFileName << "." << std::endl; + return EXIT_FAILURE; + } + if (paramsOpts.gens.length() == 0) { + std::cerr << "Missing gens parameter for bootstrapping in " + << cmdLineOpts.paramFileName << "." << std::endl; + return EXIT_FAILURE; + } + if (paramsOpts.ords.length() == 0) { + std::cerr << "Missing ords parameter for bootstrapping in " + << cmdLineOpts.paramFileName << "." << std::endl; + return EXIT_FAILURE; + } + } + + try { + helib::Context* contextp; + + if (cmdLineOpts.scheme == "BGV") { + helib::ContextBuilder cb; + cb.m(paramsOpts.m) + .p(p) + .r(paramsOpts.r) + .gens(helib::convert>(paramsOpts.gens)) + .ords(helib::convert>(paramsOpts.ords)) + .bits(paramsOpts.Qbits) + .c(paramsOpts.c); + + if (cmdLineOpts.bootstrappable != "NONE") { + cb.bootstrappable(true).mvec(paramsOpts.mvec); + if (cmdLineOpts.bootstrappable == "THICK") + cb.thickboot(); + else if (cmdLineOpts.bootstrappable == "THIN") + cb.thinboot(); + } + contextp = cb.buildPtr(); + } else if (cmdLineOpts.scheme == "CKKS") { + contextp = helib::ContextBuilder() + .m(paramsOpts.m) + .precision(paramsOpts.r) + .bits(paramsOpts.Qbits) + .scale(paramsOpts.scale) + .buildPtr(); + } + + // and a new secret/public key + helib::SecKey secretKey(*contextp); + secretKey.GenSecKey(); // A +-1/0 secret key + + // If not set by user, returns params file name with truncated UTC + if (cmdLineOpts.outputPrefixPath.empty()) { + cmdLineOpts.outputPrefixPath = + stripExtension(cmdLineOpts.paramFileName) + + std::to_string(std::time(nullptr) % 100000); + std::cout << "File prefix: " << cmdLineOpts.outputPrefixPath << std::endl; + } + + // Printout important info + std::ostream* outp = &std::cout; + std::ofstream fout; + if (cmdLineOpts.infoFile) { + // outputPrefixPath should be set further up main. + std::string path = cmdLineOpts.outputPrefixPath + ".info"; + fout.open(path); + if (!fout.is_open()) { + throw std::runtime_error("Cannot write keys to file at '" + path + + "'."); + } + outp = &fout; + } + + printoutToStream(*contextp, + *outp, + cmdLineOpts.noSKM, + cmdLineOpts.frobSKM, + cmdLineOpts.bootstrappable != "NONE"); + + NTL::SetNumThreads(2); + // write the secret key to .sk and the encryption public + // key to Enc.pk + NTL_EXEC_INDEX(2, skOrPk) + std::string path = cmdLineOpts.outputPrefixPath + (skOrPk ? "Enc" : ""); + writeKeyToFile(path, + *contextp, + secretKey, + skOrPk, + cmdLineOpts.write_only_sk); + NTL_EXEC_INDEX_END + // now compute the evaluation key material + + // compute key-switching matrices + if (!cmdLineOpts.noSKM || cmdLineOpts.bootstrappable != "NONE") { + helib::addSome1DMatrices(secretKey); + if (cmdLineOpts.frobSKM || cmdLineOpts.bootstrappable != "NONE") { + helib::addFrbMatrices(secretKey); + } + } + + if (cmdLineOpts.bootstrappable != "NONE") { + secretKey.genRecryptData(); + } + // and write to file Eval.pk + std::string path = cmdLineOpts.outputPrefixPath + "Eval"; + writeKeyToFile(path, *contextp, secretKey, 1); + + } catch (const std::invalid_argument& e) { + std::cerr << "Exit due to invalid argument thrown:\n" + << e.what() << std::endl; + return EXIT_FAILURE; + } catch (const helib::IOError& e) { + std::cerr << "Exit due to IOError thrown:\n" << e.what() << std::endl; + return EXIT_FAILURE; + } catch (const std::runtime_error& e) { + std::cerr << "Exit due to runtime error thrown:\n" << e.what() << std::endl; + return EXIT_FAILURE; + } catch (const std::logic_error& e) { + std::cerr << "Exit due to logic error thrown:\n" << e.what() << std::endl; + return EXIT_FAILURE; + } catch (const std::exception& e) { + std::cerr << "Exit due to unknown exception thrown:\n" + << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/utils/key-gen/large_params.txt b/utils/key-gen/large_params.txt new file mode 100644 index 000000000..39bd4c0f2 --- /dev/null +++ b/utils/key-gen/large_params.txt @@ -0,0 +1,5 @@ +p=257 +m=56803 +r=1 +c=2 +Qbits=1800 \ No newline at end of file diff --git a/utils/key-gen/medium_params.txt b/utils/key-gen/medium_params.txt new file mode 100644 index 000000000..79907b894 --- /dev/null +++ b/utils/key-gen/medium_params.txt @@ -0,0 +1,5 @@ +p=65537 +m=256 +r=1 +c=2 +Qbits=900 \ No newline at end of file diff --git a/utils/key-gen/small_params.txt b/utils/key-gen/small_params.txt new file mode 100644 index 000000000..aa85d99f3 --- /dev/null +++ b/utils/key-gen/small_params.txt @@ -0,0 +1,5 @@ +p=13 +m=7 +r=1 +c=2 +Qbits=500 \ No newline at end of file diff --git a/utils/test_bootstrapping/CMakeLists.txt b/utils/test_bootstrapping/CMakeLists.txt index 5ed3f50b4..3ccf4fb46 100644 --- a/utils/test_bootstrapping/CMakeLists.txt +++ b/utils/test_bootstrapping/CMakeLists.txt @@ -1,13 +1,6 @@ # Copyright (C) 2020 IBM Corp. -# This program is Licensed under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. See accompanying LICENSE file. +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/test/${CMAKE_INSTALL_LIBDIR}") @@ -17,7 +10,10 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/test/${CMAKE_INSTALL_BINDIR}") add_executable(test_bootstrap test_bootstrap.cpp) +add_executable(test_bootstrap_keygen test_bootstrap_keygen.cpp) target_include_directories(test_bootstrap PRIVATE "../common") +target_include_directories(test_bootstrap_keygen PRIVATE "../common") target_link_libraries(test_bootstrap helib) +target_link_libraries(test_bootstrap_keygen helib) diff --git a/utils/test_bootstrapping/test_bootstrap_keygen.cpp b/utils/test_bootstrapping/test_bootstrap_keygen.cpp new file mode 100644 index 000000000..e55131846 --- /dev/null +++ b/utils/test_bootstrapping/test_bootstrap_keygen.cpp @@ -0,0 +1,165 @@ +/* Copyright (C) 2020 IBM Corp. + * Copyright (C) 2022 Intel Corporation + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include + +helib::Ptxt generateRandomPtxt(const helib::Context& context) +{ + // Note: this function uses a single coefficient in each slot. For this + // reason there is no difference between THIN and THICK bootstrapping. + // Consider generating random polynomials if THICK + helib::Ptxt ptxt(context); + std::mt19937 gen(231087); + std::uniform_int_distribution coinFlipDist(0, context.getP() - 1); + for (std::size_t i = 0; i < ptxt.size(); ++i) { + ptxt[i] = coinFlipDist(gen); + } + return ptxt; +} + +int main(int argc, char** argv) +{ + std::string sk_file_name; + std::string pk_file_name; + + bool thick = false; + bool quiet = false; + bool sk_only = false; + + // clang-format off + helib::ArgMap() + .toggle() + .arg("--thick", thick, "perform thick bootstrapping", nullptr) + .arg("--quiet", quiet, "Suppress most of the output", nullptr) + .arg("-s", sk_only, "only the secret key polynomial is written to the secret key file.") + .positional() + .required() + .arg("", sk_file_name, + "The file containing context and secret key.", nullptr) + .arg("", pk_file_name, "The file containing context and evaluation public key.", nullptr) + .parse(argc, argv); + // clang-format on + + std::unique_ptr pk_contextp; + std::unique_ptr pkp; + + try { + std::tie(pk_contextp, pkp) = loadContextAndKey(pk_file_name); + } catch (const helib::RuntimeError& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + // Load Context and PubKey + helib::Context& context(*pk_contextp); + helib::PubKey& publicKey(*pkp); + + if (!context.isBootstrappable()) { + std::cerr << "Context is not bootstrappable" << std::endl; + return EXIT_FAILURE; + } + const helib::EncryptedArray& ea(context.getEA()); + helib::Ptxt ptxt = + generateRandomPtxt(context); // Random in [0, p) + helib::Ctxt ctxt(publicKey); + publicKey.Encrypt(ctxt, ptxt); + + // Start doing bootstrapping + if (!quiet) { + std::cout << "Initial bootstrap" << std::endl; + } + if (thick) { + publicKey.reCrypt(ctxt); + } else { + publicKey.thinReCrypt(ctxt); + } + if (!quiet) { + helib::CheckCtxt(ctxt, "After recryption"); + std::cout << "ctxt.bitCapacity: " << ctxt.bitCapacity() << std::endl; + } + + for (std::size_t round = 0; round < 2; ++round) { + if (!quiet) { + std::cout << "Round: " << round << std::endl; + helib::CheckCtxt(ctxt, "Before multiplication"); + } + // Multiply the ciphertext with itself n times + // until number of bits falls below threshold + HELIB_NTIMER_START(RoundTotal); + while (ctxt.bitCapacity() >= ctxt.getContext().BPL() * 3) { + long count = 0; // number of multiplications + if (!quiet) { + std::cout << "multiplication: " << count++ << std::endl; + std::cout << "ctxt.bitCapacity: " << ctxt.bitCapacity() << std::endl; + } + HELIB_NTIMER_START(Multiplications); + ctxt.square(); + HELIB_NTIMER_STOP(Multiplications); + ptxt.square(); + } + + if (!quiet) { + helib::CheckCtxt(ctxt, "Before recryption"); + std::cout << "ctxt.bitCapacity: " << ctxt.bitCapacity() << std::endl; + } + + // Time the recryption step + HELIB_NTIMER_START(Bootstrap); + // Recrypt/Bootstrap the ctxt + if (thick) { + publicKey.reCrypt(ctxt); + } else { + publicKey.thinReCrypt(ctxt); + } + HELIB_NTIMER_STOP(Bootstrap); + + if (!quiet) { + helib::CheckCtxt(ctxt, "After recryption"); + std::cout << "ctxt.bitCapacity " << ctxt.bitCapacity() << std::endl; + } + HELIB_NTIMER_STOP(RoundTotal); + + // Plaintext operation + // Multiply with itself n times + } + + std::unique_ptr sk_contextp; + std::unique_ptr skp; + + try { + std::tie(sk_contextp, skp) = + loadContextAndKey(sk_file_name, sk_only); + } catch (const helib::RuntimeError& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + // Load secret key context and secret key + helib::SecKey& secretKey(*skp); + helib::Context& sk_context(*sk_contextp); + + // to avoid a context mismatch, we write ctxt to stream and then read it back + // with the secret key + std::stringstream ss; + ctxt.writeTo(ss); + helib::Ctxt ctxt_copy = helib::Ctxt::readFrom(ss, secretKey); + helib::Ptxt decrypted(sk_context); + secretKey.Decrypt(decrypted, ctxt_copy); + + if (ptxt != decrypted) { + std::cerr << "Decryption error." << std::endl; + return EXIT_FAILURE; + } + + if (!quiet) { + for (const auto& timerName : + {"Setup", "Multiplications", "Bootstrap", "RoundTotal"}) { + helib::printNamedTimer(std::cout << std::endl, timerName); + } + } +} diff --git a/utils/tests/full-pipeline.bats b/utils/tests/full-pipeline.bats index 9659e8ed9..61699cb7d 100755 --- a/utils/tests/full-pipeline.bats +++ b/utils/tests/full-pipeline.bats @@ -1,15 +1,11 @@ #!/usr/bin/env bats # Copyright (C) 2020 IBM Corp. -# This program is Licensed under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. See accompanying LICENSE file. +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# +# Added tests for separated SK, PK, and Key Switching matrices +# load "std" @@ -80,3 +76,106 @@ function teardown { ${diff_threshold} ${prefix_ckks}.dat ${prefix_ckks}.decoded } +@test "BGV (keygen): full-pipeline encode - encrypt - decrypt - decode" { + keyGeneration BGV "${prefix_bgv}.params" "$prefix_bgv" + run bash -c "$encode ${prefix_bgv}.dat ${prefix_bgv}.info BGV > ${prefix_bgv}.ptxt" + assert [ "$status" -eq 0 ] + run $encrypt "$enc_pk_file_bgv" "${prefix_bgv}.ptxt" + assert [ "$status" -eq 0 ] + run $decrypt "$sk_file_bgv" "${prefix_bgv}.ctxt" -o "$prefix_bgv.dec.ptxt" + assert [ "$status" -eq 0 ] + run bash -c "$decode ${prefix_bgv}.dec.ptxt ${prefix_bgv}.info BGV > ${prefix_bgv}.decoded" + assert [ "$status" -eq 0 ] + diff "${prefix_bgv}.dat" "${prefix_bgv}.decoded" +} + +@test "BGV (keygen, sk only): full-pipeline encode - encrypt - decrypt - decode" { + keyGeneration BGV "${prefix_bgv}.params" "$prefix_bgv" -s + run bash -c "$encode ${prefix_bgv}.dat ${prefix_bgv}.info BGV > ${prefix_bgv}.ptxt" + assert [ "$status" -eq 0 ] + run $encrypt "$enc_pk_file_bgv" "${prefix_bgv}.ptxt" + assert [ "$status" -eq 0 ] + run $decrypt "$sk_file_bgv" "${prefix_bgv}.ctxt" -o "$prefix_bgv.dec.ptxt" -s + assert [ "$status" -eq 0 ] + run bash -c "$decode ${prefix_bgv}.dec.ptxt ${prefix_bgv}.info BGV > ${prefix_bgv}.decoded" + assert [ "$status" -eq 0 ] + diff "${prefix_bgv}.dat" "${prefix_bgv}.decoded" +} + +@test "CKKS (keygen): full-pipeline encode - encrypt - decrypt - decode" { + keyGeneration CKKS "${prefix_ckks}.params" "$prefix_ckks" + run bash -c "$encode ${prefix_ckks}.dat ${prefix_ckks}.info CKKS > ${prefix_ckks}.ptxt" + assert [ "$status" -eq 0 ] + run $encrypt "$enc_pk_file_ckks" "${prefix_ckks}.ptxt" + assert [ "$status" -eq 0 ] + run $decrypt "$sk_file_ckks" "${prefix_ckks}.ctxt" -o "$prefix_ckks.dec.ptxt" + assert [ "$status" -eq 0 ] + run bash -c "$decode ${prefix_ckks}.dec.ptxt ${prefix_ckks}.info CKKS > ${prefix_ckks}.decoded" + assert [ "$status" -eq 0 ] + ${diff_threshold} ${prefix_ckks}.dat ${prefix_ckks}.decoded +} + +@test "CKKS (keygen, sk only): full-pipeline encode - encrypt - decrypt - decode" { + keyGeneration CKKS "${prefix_ckks}.params" "$prefix_ckks" -s + run bash -c "$encode ${prefix_ckks}.dat ${prefix_ckks}.info CKKS > ${prefix_ckks}.ptxt" + assert [ "$status" -eq 0 ] + run $encrypt "$enc_pk_file_ckks" "${prefix_ckks}.ptxt" + assert [ "$status" -eq 0 ] + run $decrypt "$sk_file_ckks" "${prefix_ckks}.ctxt" -o "$prefix_ckks.dec.ptxt" -s + assert [ "$status" -eq 0 ] + run bash -c "$decode ${prefix_ckks}.dec.ptxt ${prefix_ckks}.info CKKS > ${prefix_ckks}.decoded" + assert [ "$status" -eq 0 ] + ${diff_threshold} ${prefix_ckks}.dat ${prefix_ckks}.decoded +} + +@test "BGV (keygen): matrix full-pipeline encode - encrypt - decrypt - decode" { + keyGeneration BGV "${prefix_bgv}.params" "$prefix_bgv" + run bash -c "$encode ${prefix_bgv}.dat ${prefix_bgv}.info BGV --dims 2,3 > ${prefix_bgv}.ptxt" + assert [ "$status" -eq 0 ] + run $encrypt "$enc_pk_file_bgv" "${prefix_bgv}.ptxt" + assert [ "$status" -eq 0 ] + run $decrypt "$sk_file_bgv" "${prefix_bgv}.ctxt" -o "$prefix_bgv.dec.ptxt" + assert [ "$status" -eq 0 ] + run bash -c "$decode ${prefix_bgv}.dec.ptxt ${prefix_bgv}.info BGV --nelements 12 > ${prefix_bgv}.decoded" + assert [ "$status" -eq 0 ] + diff "${prefix_bgv}.dat" "${prefix_bgv}.decoded" +} + +@test "BGV (keygen, sk only): matrix full-pipeline encode - encrypt - decrypt - decode" { + keyGeneration BGV "${prefix_bgv}.params" "$prefix_bgv" -s + run bash -c "$encode ${prefix_bgv}.dat ${prefix_bgv}.info BGV --dims 2,3 > ${prefix_bgv}.ptxt" + assert [ "$status" -eq 0 ] + run $encrypt "$enc_pk_file_bgv" "${prefix_bgv}.ptxt" + assert [ "$status" -eq 0 ] + run $decrypt "$sk_file_bgv" "${prefix_bgv}.ctxt" -o "$prefix_bgv.dec.ptxt" -s + assert [ "$status" -eq 0 ] + run bash -c "$decode ${prefix_bgv}.dec.ptxt ${prefix_bgv}.info BGV --nelements 12 > ${prefix_bgv}.decoded" + assert [ "$status" -eq 0 ] + diff "${prefix_bgv}.dat" "${prefix_bgv}.decoded" +} + +@test "CKKS (keygen): matrix full-pipeline encode - encrypt - decrypt - decode" { + keyGeneration CKKS "${prefix_ckks}.params" "$prefix_ckks" + run bash -c "$encode ${prefix_ckks}.dat ${prefix_ckks}.info CKKS --dims 2,3 > ${prefix_ckks}.ptxt" + assert [ "$status" -eq 0 ] + run $encrypt "$enc_pk_file_ckks" "${prefix_ckks}.ptxt" + assert [ "$status" -eq 0 ] + run $decrypt "$sk_file_ckks" "${prefix_ckks}.ctxt" -o "$prefix_ckks.dec.ptxt" + assert [ "$status" -eq 0 ] + run bash -c "$decode ${prefix_ckks}.dec.ptxt ${prefix_ckks}.info CKKS --nelements 12 > ${prefix_ckks}.decoded" + assert [ "$status" -eq 0 ] + ${diff_threshold} ${prefix_ckks}.dat ${prefix_ckks}.decoded +} + +@test "CKKS (keygen, sk only): matrix full-pipeline encode - encrypt - decrypt - decode" { + keyGeneration CKKS "${prefix_ckks}.params" "$prefix_ckks" -s + run bash -c "$encode ${prefix_ckks}.dat ${prefix_ckks}.info CKKS --dims 2,3 > ${prefix_ckks}.ptxt" + assert [ "$status" -eq 0 ] + run $encrypt "$enc_pk_file_ckks" "${prefix_ckks}.ptxt" + assert [ "$status" -eq 0 ] + run $decrypt "$sk_file_ckks" "${prefix_ckks}.ctxt" -o "$prefix_ckks.dec.ptxt" -s + assert [ "$status" -eq 0 ] + run bash -c "$decode ${prefix_ckks}.dec.ptxt ${prefix_ckks}.info CKKS --nelements 12 > ${prefix_ckks}.decoded" + assert [ "$status" -eq 0 ] + ${diff_threshold} ${prefix_ckks}.dat ${prefix_ckks}.decoded +} diff --git a/utils/tests/key-gen-crypto.bats b/utils/tests/key-gen-crypto.bats new file mode 100755 index 000000000..d6b9b5e15 --- /dev/null +++ b/utils/tests/key-gen-crypto.bats @@ -0,0 +1,170 @@ +#!/usr/bin/env bats + +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +load "std" + +# Specialised function for creating BGV data with invalid slot size +function generate-invalid-bgv-data { + genData "${prefix_bgv}_invalid.dat" 4 "BGV" "--columns 3" # 3 in a slot + "${encode}" "${prefix_bgv}_invalid.dat" "${prefix_bgv}.info" "BGV" > "${prefix_bgv}_invalid.ptxt" +} + +# Specialised function for creating CKKS data with invalid slot size +function generate-invalid-ckks-data { + genData "${prefix_ckks}_invalid.dat" 4 "CKKS" "--columns 3" # 3 in a slot + "${encode}" "${prefix_ckks}_invalid.dat" "${prefix_ckks}.info" "CKKS" > "${prefix_ckks}_invalid.ptxt" +} + +# Only needed for BGV context with bootstrapping +function generate-bootstrap-data { + create-bootstrap-toy-params + keyGeneration BGV "${prefix_bootstrap}.params" "${prefix_bootstrap}" "--bootstrap THIN" + genData "${prefix_bootstrap}.dat" 12 "BGV" + "${encode}" "${prefix_bootstrap}.dat" "${prefix_bootstrap}.info" "BGV" > "${prefix_bootstrap}.ptxt" +} + +function setup { + mkdir -p $tmp_folder + cd $tmp_folder + print-info-location + check_python36 + check_locations + create-bgv-toy-params + keyGeneration BGV "${prefix_bgv}.params" "$prefix_bgv" + genData "${prefix_bgv}.dat" 12 "BGV" + "${encode}" "${prefix_bgv}.dat" "${prefix_bgv}.info" "BGV" > "${prefix_bgv}.ptxt" + create-ckks-toy-params + keyGeneration CKKS "${prefix_ckks}.params" "$prefix_ckks" + genData "${prefix_ckks}.dat" 12 "CKKS" + "${encode}" "${prefix_ckks}.dat" "${prefix_ckks}.info" "CKKS" > "${prefix_ckks}.ptxt" +} + +function teardown { + cd $tmp_folder/.. + remove-test-directory "$tmp_folder" +} + +# Common parameter-related tests +@test "encrypt with Enc key works with batch size greater than 1" { + run $encrypt "${enc_pk_file_bgv}" "${prefix_bgv}.ptxt" -b 10 + assert [ "$status" -eq 0 ] + assert [ -f "${prefix_bgv}.ctxt" ] +} + +@test "encrypt with Eval key works with batch size greater than 1" { + run $encrypt "${eval_pk_file_bgv}" "${prefix_bgv}.ptxt" -b 10 + assert [ "$status" -eq 0 ] + assert [ -f "${prefix_bgv}.ctxt" ] +} + +@test "decrypt (keygen) works with batch size greater than 1" { + run $encrypt "${enc_pk_file_bgv}" "${prefix_bgv}.ptxt" + assert [ "$status" -eq 0 ] + run $decrypt "${sk_file_bgv}" "${prefix_bgv}.ctxt" -o result_bgv.decrypt -b 10 + assert [ "$status" -eq 0 ] + assert [ -f "result_bgv.decrypt" ] +} + +@test "decrypt (keygen, sk only) works with batch size greater than 1" { + keyGeneration BGV "${prefix_bgv}.params" "$prefix_bgv" -s + run $encrypt "${enc_pk_file_bgv}" "${prefix_bgv}.ptxt" + assert [ "$status" -eq 0 ] + run $decrypt "${sk_file_bgv}" "${prefix_bgv}.ctxt" -o result_bgv.decrypt -b 10 -s + assert [ "$status" -eq 0 ] + assert [ -f "result_bgv.decrypt" ] +} + +@test "decrypt (keygen) works with nthreads size greater than 1" { + run $encrypt "${enc_pk_file_bgv}" "${prefix_bgv}.ptxt" + assert [ "$status" -eq 0 ] + run $decrypt "${sk_file_bgv}" "${prefix_bgv}.ctxt" -o result_bgv.decrypt -n 4 + assert [ "$status" -eq 0 ] + assert [ -f "result_bgv.decrypt" ] +} + +@test "decrypt (keygen, sk only) works with nthreads size greater than 1" { + keyGeneration BGV "${prefix_bgv}.params" "$prefix_bgv" -s + run $encrypt "${enc_pk_file_bgv}" "${prefix_bgv}.ptxt" + assert [ "$status" -eq 0 ] + run $decrypt "${sk_file_bgv}" "${prefix_bgv}.ctxt" -o result_bgv.decrypt -n 4 -s + assert [ "$status" -eq 0 ] + assert [ -f "result_bgv.decrypt" ] +} + +@test "BGV (keygen): data == decrypt(encrypt(data))" { + run $encrypt "${enc_pk_file_bgv}" "${prefix_bgv}.ptxt" + assert [ "$status" -eq 0 ] + assert [ -f "${prefix_bgv}.ctxt" ] + run $decrypt "${sk_file_bgv}" "${prefix_bgv}.ctxt" -o result_bgv.decrypt + assert [ "$status" -eq 0 ] + assert [ -f result_bgv.decrypt ] + diff "${prefix_bgv}.ptxt" result_bgv.decrypt + assert [ "$status" -eq 0 ] +} + +@test "BGV (keygen, sk only): data == decrypt(encrypt(data))" { + keyGeneration BGV "${prefix_bgv}.params" "$prefix_bgv" -s + run $encrypt "${enc_pk_file_bgv}" "${prefix_bgv}.ptxt" + assert [ "$status" -eq 0 ] + assert [ -f "${prefix_bgv}.ctxt" ] + run $decrypt "${sk_file_bgv}" "${prefix_bgv}.ctxt" -o result_bgv.decrypt -s + assert [ "$status" -eq 0 ] + assert [ -f result_bgv.decrypt ] + diff "${prefix_bgv}.ptxt" result_bgv.decrypt + assert [ "$status" -eq 0 ] +} + +@test "BGV (keygen): data == decrypt(encrypt(data)) with bootstrappable context" { + # This test needs bootstrapping context to work + generate-bootstrap-data + run $encrypt "${eval_pk_file_bootstrap}" "${prefix_bootstrap}.ptxt" + assert [ "$status" -eq 0 ] + assert [ -f "${prefix_bootstrap}."ctxt ] + run $decrypt "${sk_file_bootstrap}" "${prefix_bootstrap}.ctxt" -o "${prefix_bootstrap}.decrypt" + assert [ "$status" -eq 0 ] + assert [ -f "${prefix_bootstrap}.decrypt" ] + diff "${prefix_bootstrap}.ptxt" "${prefix_bootstrap}.decrypt" + assert [ "$status" -eq 0 ] +} + +@test "BGV (keygen, sk only): data == decrypt(encrypt(data)) with bootstrappable context" { + # This test needs bootstrapping context to work + create-bootstrap-toy-params + keyGeneration BGV "${prefix_bootstrap}.params" "${prefix_bootstrap}" "--bootstrap THIN" -s + genData "${prefix_bootstrap}.dat" 12 "BGV" + "${encode}" "${prefix_bootstrap}.dat" "${prefix_bootstrap}.info" "BGV" > "${prefix_bootstrap}.ptxt" + run $encrypt "${eval_pk_file_bootstrap}" "${prefix_bootstrap}.ptxt" + assert [ "$status" -eq 0 ] + assert [ -f "${prefix_bootstrap}."ctxt ] + run $decrypt "${sk_file_bootstrap}" "${prefix_bootstrap}.ctxt" -o "${prefix_bootstrap}.decrypt" -s + assert [ "$status" -eq 0 ] + assert [ -f "${prefix_bootstrap}.decrypt" ] + diff "${prefix_bootstrap}.ptxt" "${prefix_bootstrap}.decrypt" + assert [ "$status" -eq 0 ] +} + +# CKKS encryption tests +@test "CKKS: data == decrypt(encrypt(data))" { + run $encrypt "${enc_pk_file_ckks}" "${prefix_ckks}.ptxt" + assert [ "$status" -eq 0 ] + assert [ -f "${prefix_ckks}.ctxt" ] + run $decrypt "${sk_file_ckks}" "${prefix_ckks}.ctxt" -o "${prefix_ckks}.decrypt" + assert [ "$status" -eq 0 ] + assert [ -f "${prefix_ckks}.decrypt" ] + ${diff_threshold} --decrypt "${prefix_ckks}.ptxt" "${prefix_ckks}.decrypt" + assert [ "$status" -eq 0 ] +} + +@test "CKKS (keygen, sk only): data == decrypt(encrypt(data))" { + keyGeneration CKKS "${prefix_ckks}.params" "$prefix_ckks" -s + run $encrypt "${enc_pk_file_ckks}" "${prefix_ckks}.ptxt" + assert [ "$status" -eq 0 ] + assert [ -f "${prefix_ckks}.ctxt" ] + run $decrypt "${sk_file_ckks}" "${prefix_ckks}.ctxt" -o "${prefix_ckks}.decrypt" -s + assert [ "$status" -eq 0 ] + assert [ -f "${prefix_ckks}.decrypt" ] + ${diff_threshold} --decrypt "${prefix_ckks}.ptxt" "${prefix_ckks}.decrypt" + assert [ "$status" -eq 0 ] +} \ No newline at end of file diff --git a/utils/tests/key-gen.bats b/utils/tests/key-gen.bats new file mode 100644 index 000000000..7399daf8b --- /dev/null +++ b/utils/tests/key-gen.bats @@ -0,0 +1,295 @@ +#!/usr/bin/env bats + +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +load "std" + +function setup { + mkdir -p $tmp_folder + cd $tmp_folder + print-info-location + check_python36 + check_locations + create-bgv-toy-params + create-ckks-toy-params +} + +function teardown { + cd - + remove-test-directory "$tmp_folder" +} + +@test "params file does not exist" { + run "${key_gen}" abcd.params + assert [ "$status" -ne 0 ] + assert [ "$output" == "Could not open file 'abcd.params'." ] +} + +@test "create BGV toy params" { + run "${key_gen}" "${prefix_bgv}.params" + assert [ "$status" -eq 0 ] + filePrefix=$(sed -n 's,File prefix:.*\(test_bgv[0-9]*\).*,\1,p' <<< "${lines[0]}") + # techo "Detected file prefix: '$filePrefix'" + assert [ -f "${filePrefix}.sk" ] + assert [ -f "${filePrefix}Enc.pk" ] + assert [ -f "${filePrefix}Eval.pk" ] +} + +@test "create BGV toy params secret key only" { + run "${key_gen}" "${prefix_bgv}.params" -s + assert [ "$status" -eq 0 ] + filePrefix=$(sed -n 's,File prefix:.*\(test_bgv[0-9]*\).*,\1,p' <<< "${lines[0]}") + # techo "Detected file prefix: '$filePrefix'" + assert [ -f "${filePrefix}.sk" ] + assert [ -f "${filePrefix}Enc.pk" ] + assert [ -f "${filePrefix}Eval.pk" ] +} + +@test "creates the correct files given a prefix" { + run "${key_gen}" "${prefix_bgv}.params" -o abcd + assert [ "$status" -eq 0 ] + assert [ -f "abcdEnc.pk" ] + assert [ -f "abcdEval.pk" ] + assert [ -f "abcd.sk" ] +} + +@test "create BGV toy params with info file" { + run "${key_gen}" "${prefix_bgv}.params" --info-file -o bcde + assert [ "$status" -eq 0 ] + assert [ -f "bcde.info" ] +} + +@test "key switching matrices are created by default and indicated to cmd line" { + run "${key_gen}" "${prefix_bgv}.params" -o a + assert [ "$status" -eq 0 ] + assert [ "${lines[0]}" == "Key switching matrices created." ] +} + +@test "key switching matrices are created by default and indicated in the info-file" { + run "${key_gen}" "${prefix_bgv}.params" --info-file -o defg + assert [ "$status" -eq 0 ] + assert [ -f "defg.info" ] + line=$(head -n 1 "defg.info") + assert [ "$line" == "Key switching matrices created." ] +} + +@test "no-skm flag disables generation of key switching matrices" { + run "${key_gen}" "${prefix_bgv}.params" --info-file --no-skm -o cdef + assert [ "$status" -eq 0 ] + assert [ -f "cdef.info" ] + line=$(awk '/Key switching matrices created/ { print $1 }' cdef.info) + assert [ "$line" == '' ] +} + +# Frobenius matrices created.\n + +@test "Frobenius switching matrices are not created by default and indicated to cmd line" { + run "${key_gen}" "${prefix_bgv}.params" -o a + assert [ "$status" -eq 0 ] + line=$(echo $output | awk '/Frobenius matrices created./ { print $1 }') + assert [ "${line}" == "" ] +} + +@test "Frobenius switching matrices are not created by default and not indicated in the info-file" { + run "${key_gen}" "${prefix_bgv}.params" --info-file -o defg + assert [ "$status" -eq 0 ] + assert [ -f "defg.info" ] + line=$(awk '/Frobenius matrices created./ { print $1 }' defg.info) + assert [ "${line}" == "" ] +} + +@test "frob-skm creates Frobenius matrices and indicates it to cmd line" { + run "${key_gen}" "${prefix_bgv}.params" -o a --frob-skm + assert [ "$status" -eq 0 ] + assert [ "${lines[1]}" == "Frobenius matrices created." ] +} + +@test "frob-skm creates Frobenius matrices and indicates it to the info-file" { + run "${key_gen}" "${prefix_bgv}.params" --info-file --frob-skm -o defg + assert [ "$status" -eq 0 ] + assert [ -f "defg.info" ] + line="$(sed '2q;d' defg.info)" + assert [ "$line" == "Frobenius matrices created." ] +} + +@test "no-skm and frob-skm flags are not a valid combination" { + run "${key_gen}" "${prefix_bgv}.params" --no-skm --frob-skm -o cdef + assert [ "$status" -ne 0 ] + assert [ "$output" == "Frobenius matrices reqires switch-key matrices to be generated." ] +} + +@test "default scheme fails if p is less than 2" { + run "${key_gen}" "${prefix_ckks}.params" + assert [ "$status" -ne 0 ] + assert [ "$output" == "BGV invalid plaintext modulus. In BGV it must be a prime number greater than 1." ] +} + +@test "BGV: fails if p is less than 2" { + run "${key_gen}" "${prefix_ckks}.params" --scheme BGV + assert [ "$status" -ne 0 ] + assert [ "$output" == "BGV invalid plaintext modulus. In BGV it must be a prime number greater than 1." ] +} + +@test "CKKS: fails if p does not equal -1" { + run "${key_gen}" "${prefix_bgv}.params" --scheme CKKS + assert [ "$status" -ne 0 ] + assert [ "$output" == "CKKS invalid plaintext modulus. In CKKS it must be set to -1." ] +} + +@test "BGV: fails on non-prime p" { + sed -e 's/p=[0-9][0-9]*/p=4/' < "${prefix_bgv}.params" > ${prefix_bgv}_non_prime_p.params + run "${key_gen}" "${prefix_bgv}_non_prime_p.params" --scheme BGV + assert [ "$status" -ne 0 ] + assert [ "${lines[1]}" == "Modulus pp is not prime (nor -1)" ] +} + +@test "scheme flag works correctly for BGV and CKKS" { + run "${key_gen}" "${prefix_bgv}.params" --scheme BGV -o bgv + assert [ "$status" -eq 0 ] + p=$(sed -n 's,.*p = \([0-9]*\).*,\1,p' <<< "${lines[1]}") + # techo "${lines[1]}" + # techo "p = '$p'" + assert [ "$p" != "-1" ] + run "${create_context}" "${prefix_ckks}.params" --scheme CKKS -o ckks + assert [ "$status" -eq 0 ] + p=$(sed -n 's,.*p = \(-[0-9]*\).*,\1,p' <<< "${lines[1]}") + # techo "${lines[1]}" + # techo "p = '$p'" + assert [ "$p" == "-1" ] +} + +@test "CKKS: fails on odd m" { + sed -e 's/m=[0-9][0-9]*/m=33/' < "${prefix_ckks}.params" > ${prefix_ckks}_odd_m.params + run "${key_gen}" "${prefix_ckks}_odd_m.params" --scheme CKKS + assert [ "$status" -ne 0 ] + assert [ "${lines[1]}" == "CKKS scheme only supports m as a power of two." ] +} + +@test "CKKS: fails on m non-power of 2" { + sed -e 's/m=[0-9][0-9]*/m=34/' < "${prefix_ckks}.params" > ${prefix_ckks}_nonp2_m.params + run "${key_gen}" "${prefix_ckks}_nonp2_m.params" --scheme CKKS + assert [ "$status" -ne 0 ] + assert [ "${lines[1]}" == "CKKS scheme only supports m as a power of two." ] +} + +@test "fails on invalid scheme option" { + run "${key_gen}" "${prefix_bgv}.params" --scheme BFV -o bfv + assert [ "$status" -ne 0 ] + assert [ "$output" == "Unrecognized scheme 'BFV'." ] +} + +@test "bootstrap = NONE option works as normal" { + run "${key_gen}" "${prefix_bgv}.params" --bootstrap NONE -o test + assert [ "$status" -eq 0 ] + assert [ -f "testEnc.pk" ] + assert [ -f "testEval.pk" ] + assert [ -f "test.sk" ] +} + +@test "bootstrap = THIN option works with bootstrap params" { + create-bootstrap-toy-params + run "${key_gen}" "${prefix_bootstrap}.params" --bootstrap THIN -o thin + assert [ "$status" -eq 0 ] + assert [ -f "thinEnc.pk" ] + assert [ -f "thinEval.pk" ] + assert [ -f "thin.sk" ] +} + +@test "bootstrap = THICK option works with bootstrap params" { + create-bootstrap-toy-params + run "${key_gen}" "${prefix_bootstrap}.params" --bootstrap THICK -o thick + assert [ "$status" -eq 0 ] + assert [ -f "thickEnc.pk" ] + assert [ -f "thickEval.pk" ] + assert [ -f "thick.sk" ] +} + +@test "fails when passing invalid bootstrap option" { + create-bootstrap-toy-params + run "${key_gen}" "${prefix_bootstrap}.params" --bootstrap INVALID + assert [ "$status" -ne 0 ] + assert [ "$output" == "Bad boostrap option: INVALID. Allowed options are NONE, THIN, THICK." ] +} + +@test "bootstrap fails when --no-skm is specified" { + create-bootstrap-toy-params + run "${key_gen}" "${prefix_bootstrap}.params" --bootstrap THIN --no-skm + assert [ "$status" -ne 0 ] + assert [ "$output" == "Cannot generate bootstrappable context without switch-key and frobenius matrices." ] + run "${create_context}" "${prefix_bootstrap}.params" --bootstrap THICK --no-skm + assert [ "$status" -ne 0 ] + assert [ "$output" == "Cannot generate bootstrappable context without switch-key and frobenius matrices." ] +} + +@test "bootstrap fails when missing parameters (mvec, gens, ords)" { + create-bootstrap-toy-params + awk '!/mvec/' "${prefix_bootstrap}.params" > "${prefix_bootstrap}_mvec.params" + run "${key_gen}" "${prefix_bootstrap}_mvec.params" --bootstrap THICK + assert [ "$status" -ne 0 ] + assert [ "$output" == "Missing mvec parameter for bootstrapping in ${prefix_bootstrap}_mvec.params." ] + awk '!/gens/' "${prefix_bootstrap}.params" > "${prefix_bootstrap}_gens.params" + run "${create_context}" "${prefix_bootstrap}_gens.params" --bootstrap THICK + assert [ "$status" -ne 0 ] + assert [ "$output" == "Missing gens parameter for bootstrapping in ${prefix_bootstrap}_gens.params." ] + awk '!/ords/' "${prefix_bootstrap}.params" > "${prefix_bootstrap}_ords.params" + run "${create_context}" "${prefix_bootstrap}_ords.params" --bootstrap THICK + assert [ "$status" -ne 0 ] + assert [ "$output" == "Missing ords parameter for bootstrapping in ${prefix_bootstrap}_ords.params." ] +} + +# bootstrapping creates SKM and frobenius +@test "bootstrap creates SKM, Frobenius matrices and recrypt data and indicates it to cmd line" { + create-bootstrap-toy-params + run "${key_gen}" "${prefix_bootstrap}.params" --bootstrap THICK -o defg + assert [ "$status" -eq 0 ] + assert [ "${lines[0]}" == "Key switching matrices created." ] + assert [ "${lines[1]}" == "Frobenius matrices created." ] + assert [ "${lines[2]}" == "Recrypt data created." ] +} + +@test "bootstrap creates SKM, Frobenius matrices and recrypt data and indicates it to the info-file" { + create-bootstrap-toy-params + run "${key_gen}" "${prefix_bootstrap}.params" --info-file --bootstrap THICK -o defg + assert [ "$status" -eq 0 ] + assert [ -f "defg.info" ] + line="$(sed '1q;d' defg.info)" + assert [ "$line" == "Key switching matrices created." ] + line="$(sed '2q;d' defg.info)" + assert [ "$line" == "Frobenius matrices created." ] + line="$(sed '3q;d' defg.info)" + assert [ "$line" == "Recrypt data created." ] +} + +@test "bootstrap thick context can perform bootstrapping" { + create-bootstrap-toy-params + cat "${prefix_bootstrap}.params" + run "${key_gen}" "${prefix_bootstrap}.params" --bootstrap THICK -o boots + assert [ "$status" -eq 0 ] + run "${test_bootstrap_keygen}" --thick boots.sk bootsEval.pk --quiet + assert [ "$status" -eq 0 ] +} + +@test "bootstrap thick context can perform bootstrapping (sk only)" { + create-bootstrap-toy-params + run "${key_gen}" "${prefix_bootstrap}.params" --bootstrap THICK -o boots -s + assert [ "$status" -eq 0 ] + run "${test_bootstrap_keygen}" --thick boots.sk bootsEval.pk --quiet -s + assert [ "$status" -eq 0 ] +} + +@test "bootstrap thin context can perform bootstrapping" { + create-bootstrap-toy-params + run "${key_gen}" "${prefix_bootstrap}.params" --bootstrap THIN -o boots + assert [ "$status" -eq 0 ] + run "${test_bootstrap_keygen}" boots.sk bootsEval.pk --quiet + assert [ "$status" -eq 0 ] +} + +@test "bootstrap thin context can perform bootstrapping (sk only)" { + create-bootstrap-toy-params + run "${key_gen}" "${prefix_bootstrap}.params" --bootstrap THIN -o boots -s + assert [ "$status" -eq 0 ] + run "${test_bootstrap_keygen}" boots.sk bootsEval.pk --quiet -s + assert [ "$status" -eq 0 ] +} \ No newline at end of file diff --git a/utils/tests/std.bash b/utils/tests/std.bash index 90cf5fd0d..f6f468a0a 100644 --- a/utils/tests/std.bash +++ b/utils/tests/std.bash @@ -9,6 +9,11 @@ # See the License for the specific language governing permissions and # limitations under the License. See accompanying LICENSE file. +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# +# Added Key-gen tests + function random-char-string { local N=${1:-8} echo $(head /dev/urandom | LC_ALL=C tr -dc A-Za-z0-9 | head -c $N) @@ -22,9 +27,11 @@ generate_data="$utils_dir/tests/gen-data.py" encode="$utils_dir/coders/encode.py" decode="$utils_dir/coders/decode.py" create_context="$utils_dir/build/bin/create-context" +key_gen="$utils_dir/build/bin/key-gen" encrypt="$utils_dir/build/bin/encrypt" decrypt="$utils_dir/build/bin/decrypt" test_bootstrap="$utils_dir/build/test/bin/test_bootstrap" +test_bootstrap_keygen="$utils_dir/build/test/bin/test_bootstrap_keygen" tmp_folder="tmp_$(random-char-string)" prefix="test" prefix_bgv="test_bgv" @@ -37,6 +44,11 @@ sk_file_ckks="${prefix_ckks}.sk" pk_file_ckks="${prefix_ckks}.pk" sk_file_bootstrap="${prefix_bootstrap}.sk" pk_file_bootstrap="${prefix_bootstrap}.pk" +enc_pk_file_bgv="${prefix_bgv}Enc.pk" +enc_pk_file_ckks="${prefix_ckks}Enc.pk" +eval_pk_file_bgv="${prefix_bgv}Eval.pk" +eval_pk_file_ckks="${prefix_ckks}Eval.pk" +eval_pk_file_bootstrap="${prefix_bootstrap}Eval.pk" function assert { if "$@"; then @@ -154,6 +166,15 @@ function createContext { "$create_context" "$src" -o "$dest" --info-file $boot --scheme "$scheme" $other_args } +function keyGeneration { + local scheme="$1" + local src=$2 + local dest=$3 + local boot="$4" + local other_args="$5" + "$key_gen" "$src" -o "$dest" --info-file $boot --scheme "$scheme" $other_args +} + function genData { local dest=$1 local nelements=$2