diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dc4e2de..5c6e069 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,24 @@ on: branches: [ "*" ] jobs: + check-linting: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.12 + uses: actions/setup-python@v3 + with: + python-version: "3.12" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install black + - name: Check linting + run: black --check . + # If PR fails in this stage, it's because of linting issues: run `black .` to fix them + build: + needs: check-linting runs-on: ubuntu-latest strategy: fail-fast: false diff --git a/examples/broadcasting/README.md b/examples/broadcasting/README.md new file mode 100644 index 0000000..10ec16b --- /dev/null +++ b/examples/broadcasting/README.md @@ -0,0 +1,46 @@ +# Broadcasting Tutorial + +This tutorial shows how to efficiently use broadcasting in Nada using Nada Algebra. + +```python +from nada_dsl import * + +# Step 0: Nada Algebra is imported with this line +import nada_algebra as na + + +def nada_main(): + # Step 1: We use Nada Algebra wrapper to create "Party0", "Party1" and "Party2" + parties = na.parties(3) + + # Step 2: Party0 creates an array of dimension (3, ) with name "A" + a = na.array([3], parties[0], "A") + + # Step 3: Party1 creates an array of dimension (3, ) with name "B" + b = na.array([3], parties[1], "B") + + # Step 4: Party0 creates an array of dimension (3, ) with name "C" + c = na.array([3], parties[0], "C") + + # Step 5: Party1 creates an array of dimension (3, ) with name "D" + d = na.array([3], parties[1], "D") + + # Step 4: The result is of computing SIMD operations on top of the elements of the array + # SIMD operations are performed on all the elements of the array. + # The equivalent would be: for i in range(3): result += a[i] + b[i] - c[i] * d[i] + result = a + b - c * d + # Step 5: We can use result.output() to produce the output for Party2 and variable name "my_output" + return result.output(parties[2], "my_output") +``` + +0. We import Nada algebra using `import nada_algebra as na`. +1. We create an array of parties, with our wrapper using `parties = na.parties(3)` which creates an array of parties named: `Party0`, `Party1` and `Party2`. +2. We create our input array `a` with `na.array([3], parties[0], "A")`, meaning our array will have dimension 3, `Party0` will be in charge of giving its inputs and the name of the variable is `"A"`. +3. We create our input array `b` with `na.array([3], parties[1], "B")`, meaning our array will have dimension 3, `Party1` will be in charge of giving its inputs and the name of the variable is `"B"`. +4. We create our input array `c` with `na.array([3], parties[1], "C")`, meaning our array will have dimension 3, `Party0` will be in charge of giving its inputs and the name of the variable is `"C"`. +5. We create our input array `d` with `na.array([3], parties[1], "D")`, meaning our array will have dimension 3, `Party1` will be in charge of giving its inputs and the name of the variable is `"D"`. +5. Finally, we use Nada Algebra to produce the outputs of the array like: `result.output(parties[2], "my_output")` establishing that the output party will be `Party2`and the name of the output variable will be `my_output`. +# How to run the tutorial. + +1. First, we need to compile the nada program running: `nada build`. +2. Then, we can test our program is running with: `nada test`. diff --git a/examples/broadcasting/nada-project.toml b/examples/broadcasting/nada-project.toml new file mode 100644 index 0000000..0262c47 --- /dev/null +++ b/examples/broadcasting/nada-project.toml @@ -0,0 +1,7 @@ +name = "broadcasting" +version = "0.1.0" +authors = [""] + +[[programs]] +path = "src/broadcasting.py" +prime_size = 128 diff --git a/examples/broadcasting/network/compute.py b/examples/broadcasting/network/compute.py new file mode 100644 index 0000000..4a2ea03 --- /dev/null +++ b/examples/broadcasting/network/compute.py @@ -0,0 +1,118 @@ +# Import necessary libraries and modules +import asyncio +import py_nillion_client as nillion +import os +import sys +import pytest +import numpy as np +import time +from dotenv import load_dotenv + +# Add the parent directory to the system path to import modules from it +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) + +# Import helper functions for creating nillion client and getting keys +from dot_product.network.helpers.nillion_client_helper import create_nillion_client +from dot_product.network.helpers.nillion_keypath_helper import ( + getUserKeyFromFile, + getNodeKeyFromFile, +) +import nada_algebra.client as na_client + +# Load environment variables from a .env file +load_dotenv() +from dot_product.config.parameters import DIM + + +# Main asynchronous function to coordinate the process +async def main(): + print(f"USING: {DIM}") + cluster_id = os.getenv("NILLION_CLUSTER_ID") + userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) + nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + client = create_nillion_client(userkey, nodekey) + party_id = client.party_id + user_id = client.user_id + party_names = na_client.parties(3) + program_name = "main" + program_mir_path = f"./target/{program_name}.nada.bin" + + # Store the program + action_id = await client.store_program(cluster_id, program_name, program_mir_path) + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) + + # Create and store secrets for two parties + A = np.ones([DIM]) + C = np.ones([DIM]) + stored_secret = nillion.Secrets( + na_client.concat( + [ + na_client.array(A, "A"), + na_client.array(C, "C"), + ] + ) + ) + secret_bindings = nillion.ProgramBindings(program_id) + secret_bindings.add_input_party(party_names[0], party_id) + + # Store the secret for the specified party + A_store_id = await client.store_secrets( + cluster_id, secret_bindings, stored_secret, None + ) + + B = np.ones([DIM]) + D = np.ones([DIM]) + stored_secret = nillion.Secrets( + na_client.concat( + [ + na_client.array(B, "B"), + na_client.array(D, "D"), + ] + ) + ) + secret_bindings = nillion.ProgramBindings(program_id) + secret_bindings.add_input_party(party_names[1], party_id) + + # Store the secret for the specified party + B_store_id = await client.store_secrets( + cluster_id, secret_bindings, stored_secret, None + ) + + # Set up the compute bindings for the parties + compute_bindings = nillion.ProgramBindings(program_id) + [ + compute_bindings.add_input_party(party_name, party_id) + for party_name in party_names[:-1] + ] + compute_bindings.add_output_party(party_names[-1], party_id) + + print(f"Computing using program {program_id}") + print(f"Use secret store_id: {A_store_id}, {B_store_id}") + + computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) + + # Perform the computation and return the result + compute_id = await client.compute( + cluster_id, + compute_bindings, + [A_store_id, B_store_id], + computation_time_secrets, + nillion.PublicVariables({}), + ) + + # Monitor and print the computation result + print(f"The computation was sent to the network. compute_id: {compute_id}") + while True: + compute_event = await client.next_compute_event() + if isinstance(compute_event, nillion.ComputeFinishedEvent): + print(f"✅ Compute complete for compute_id {compute_event.uuid}") + print(f"🖥️ The result is {compute_event.result.value}") + return compute_event.result.value + return result + + +# Run the main function if the script is executed directly +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/broadcasting/network/helpers/nillion_client_helper.py b/examples/broadcasting/network/helpers/nillion_client_helper.py new file mode 100644 index 0000000..5247aee --- /dev/null +++ b/examples/broadcasting/network/helpers/nillion_client_helper.py @@ -0,0 +1,12 @@ +import os +import py_nillion_client as nillion +from helpers.nillion_payments_helper import create_payments_config + + +def create_nillion_client(userkey, nodekey): + bootnodes = [os.getenv("NILLION_BOOTNODE_MULTIADDRESS")] + payments_config = create_payments_config() + + return nillion.NillionClient( + nodekey, bootnodes, nillion.ConnectionMode.relay(), userkey, payments_config + ) diff --git a/examples/broadcasting/network/helpers/nillion_keypath_helper.py b/examples/broadcasting/network/helpers/nillion_keypath_helper.py new file mode 100644 index 0000000..4a45413 --- /dev/null +++ b/examples/broadcasting/network/helpers/nillion_keypath_helper.py @@ -0,0 +1,10 @@ +import os +import py_nillion_client as nillion + + +def getUserKeyFromFile(userkey_filepath): + return nillion.UserKey.from_file(userkey_filepath) + + +def getNodeKeyFromFile(nodekey_filepath): + return nillion.NodeKey.from_file(nodekey_filepath) diff --git a/examples/broadcasting/network/helpers/nillion_payments_helper.py b/examples/broadcasting/network/helpers/nillion_payments_helper.py new file mode 100644 index 0000000..f68b33c --- /dev/null +++ b/examples/broadcasting/network/helpers/nillion_payments_helper.py @@ -0,0 +1,12 @@ +import os +import py_nillion_client as nillion + + +def create_payments_config(): + return nillion.PaymentsConfig( + os.getenv("NILLION_BLOCKCHAIN_RPC_ENDPOINT"), + os.getenv("NILLION_WALLET_PRIVATE_KEY"), + int(os.getenv("NILLION_CHAIN_ID")), + os.getenv("NILLION_PAYMENTS_SC_ADDRESS"), + os.getenv("NILLION_BLINDING_FACTORS_MANAGER_SC_ADDRESS"), + ) diff --git a/examples/broadcasting/src/broadcasting.py b/examples/broadcasting/src/broadcasting.py new file mode 100644 index 0000000..8c1358f --- /dev/null +++ b/examples/broadcasting/src/broadcasting.py @@ -0,0 +1,28 @@ +from nada_dsl import * + +# Step 0: Nada Algebra is imported with this line +import nada_algebra as na + + +def nada_main(): + # Step 1: We use Nada Algebra wrapper to create "Party0", "Party1" and "Party2" + parties = na.parties(3) + + # Step 2: Party0 creates an array of dimension (3, ) with name "A" + a = na.array([3], parties[0], "A") + + # Step 3: Party1 creates an array of dimension (3, ) with name "B" + b = na.array([3], parties[1], "B") + + # Step 4: Party0 creates an array of dimension (3, ) with name "C" + c = na.array([3], parties[0], "C") + + # Step 5: Party1 creates an array of dimension (3, ) with name "D" + d = na.array([3], parties[1], "D") + + # Step 4: The result is of computing SIMD operations on top of the elements of the array + # SIMD operations are performed on all the elements of the array. + # The equivalent would be: for i in range(3): result += a[i] + b[i] - c[i] * d[i] + result = a + b - c * d + # Step 5: We can use result.output() to produce the output for Party2 and variable name "my_output" + return result.output(parties[2], "my_output") diff --git a/examples/broadcasting/target/.gitignore b/examples/broadcasting/target/.gitignore new file mode 100644 index 0000000..5291a74 --- /dev/null +++ b/examples/broadcasting/target/.gitignore @@ -0,0 +1,5 @@ +# This directory is kept purposely, so that no compilation errors arise. +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file diff --git a/examples/broadcasting/tests/broadcasting.yaml b/examples/broadcasting/tests/broadcasting.yaml new file mode 100644 index 0000000..b8e97bb --- /dev/null +++ b/examples/broadcasting/tests/broadcasting.yaml @@ -0,0 +1,36 @@ +--- +program: broadcasting +inputs: + secrets: + B_1: + SecretInteger: "3" + A_0: + SecretInteger: "3" + C_1: + SecretInteger: "3" + B_0: + SecretInteger: "3" + D_0: + SecretInteger: "3" + C_2: + SecretInteger: "3" + C_0: + SecretInteger: "3" + A_2: + SecretInteger: "3" + A_1: + SecretInteger: "3" + D_1: + SecretInteger: "3" + B_2: + SecretInteger: "3" + D_2: + SecretInteger: "3" + public_variables: {} +expected_outputs: + my_output_1: + SecretInteger: "-3" + my_output_2: + SecretInteger: "-3" + my_output_0: + SecretInteger: "-3" diff --git a/examples/dot_product/network/compute.py b/examples/dot_product/network/compute.py new file mode 100644 index 0000000..7024239 --- /dev/null +++ b/examples/dot_product/network/compute.py @@ -0,0 +1,150 @@ +# Import necessary libraries and modules +import asyncio +import py_nillion_client as nillion +import os +import sys +import pytest +import numpy as np +import time +from dotenv import load_dotenv + +# Add the parent directory to the system path to import modules from it +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) + +# Import helper functions for creating nillion client and getting keys +from dot_product.network.helpers.nillion_client_helper import create_nillion_client +from dot_product.network.helpers.nillion_keypath_helper import ( + getUserKeyFromFile, + getNodeKeyFromFile, +) +import nada_algebra.client as na_client + +# Load environment variables from a .env file +load_dotenv() +from dot_product.config.parameters import DIM + + +# Decorator function to measure and log the execution time of asynchronous functions +def async_timer(file_path): + def decorator(func): + async def wrapper(*args, **kwargs): + start_time = time.time() + result = await func(*args, **kwargs) + end_time = time.time() + elapsed_time = end_time - start_time + + # Log the execution time to a file + with open(file_path, "a") as file: + file.write(f"{DIM}: {elapsed_time:.6f},\n") + return result + + return wrapper + + return decorator + + +# Asynchronous function to store a program on the nillion client +@async_timer("bench/store_program.json") +async def store_program(client, user_id, cluster_id, program_name, program_mir_path): + action_id = await client.store_program(cluster_id, program_name, program_mir_path) + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) + return program_id + + +# Asynchronous function to store secrets on the nillion client +@async_timer("bench/store_secrets.json") +async def store_secrets( + client, cluster_id, program_id, party_id, party_name, secret_array, prefix +): + stored_secret = nillion.Secrets(na_client.array(secret_array, prefix)) + secret_bindings = nillion.ProgramBindings(program_id) + secret_bindings.add_input_party(party_name, party_id) + + # Store the secret for the specified party + store_id = await client.store_secrets( + cluster_id, secret_bindings, stored_secret, None + ) + return store_id + + +# Asynchronous function to perform computation on the nillion client +@async_timer("bench/compute.json") +async def compute( + client, cluster_id, compute_bindings, store_ids, computation_time_secrets +): + compute_id = await client.compute( + cluster_id, + compute_bindings, + store_ids, + computation_time_secrets, + nillion.PublicVariables({}), + ) + + # Monitor and print the computation result + print(f"The computation was sent to the network. compute_id: {compute_id}") + while True: + compute_event = await client.next_compute_event() + if isinstance(compute_event, nillion.ComputeFinishedEvent): + print(f"✅ Compute complete for compute_id {compute_event.uuid}") + print(f"🖥️ The result is {compute_event.result.value}") + return compute_event.result.value + + +# Main asynchronous function to coordinate the process +async def main(): + print(f"USING: {DIM}") + cluster_id = os.getenv("NILLION_CLUSTER_ID") + userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) + nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + client = create_nillion_client(userkey, nodekey) + party_id = client.party_id + user_id = client.user_id + party_names = na_client.parties(3) + program_name = "main" + program_mir_path = f"./target/{program_name}.nada.bin" + + # Store the program + program_id = await store_program( + client, user_id, cluster_id, program_name, program_mir_path + ) + + # Create and store secrets for two parties + A = np.ones([DIM]) + A_store_id = await store_secrets( + client, cluster_id, program_id, party_id, party_names[0], A, "A" + ) + + B = np.ones([DIM]) + B_store_id = await store_secrets( + client, cluster_id, program_id, party_id, party_names[1], B, "B" + ) + + # Set up the compute bindings for the parties + compute_bindings = nillion.ProgramBindings(program_id) + [ + compute_bindings.add_input_party(party_name, party_id) + for party_name in party_names[:-1] + ] + compute_bindings.add_output_party(party_names[-1], party_id) + + print(f"Computing using program {program_id}") + print(f"Use secret store_id: {A_store_id}, {B_store_id}") + + computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) + + # Perform the computation and return the result + result = await compute( + client, + cluster_id, + compute_bindings, + [A_store_id, B_store_id], + computation_time_secrets, + ) + return result + + +# Run the main function if the script is executed directly +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/dot_product/network/helpers/nillion_client_helper.py b/examples/dot_product/network/helpers/nillion_client_helper.py new file mode 100644 index 0000000..5247aee --- /dev/null +++ b/examples/dot_product/network/helpers/nillion_client_helper.py @@ -0,0 +1,12 @@ +import os +import py_nillion_client as nillion +from helpers.nillion_payments_helper import create_payments_config + + +def create_nillion_client(userkey, nodekey): + bootnodes = [os.getenv("NILLION_BOOTNODE_MULTIADDRESS")] + payments_config = create_payments_config() + + return nillion.NillionClient( + nodekey, bootnodes, nillion.ConnectionMode.relay(), userkey, payments_config + ) diff --git a/examples/dot_product/network/helpers/nillion_keypath_helper.py b/examples/dot_product/network/helpers/nillion_keypath_helper.py new file mode 100644 index 0000000..4a45413 --- /dev/null +++ b/examples/dot_product/network/helpers/nillion_keypath_helper.py @@ -0,0 +1,10 @@ +import os +import py_nillion_client as nillion + + +def getUserKeyFromFile(userkey_filepath): + return nillion.UserKey.from_file(userkey_filepath) + + +def getNodeKeyFromFile(nodekey_filepath): + return nillion.NodeKey.from_file(nodekey_filepath) diff --git a/examples/dot_product/network/helpers/nillion_payments_helper.py b/examples/dot_product/network/helpers/nillion_payments_helper.py new file mode 100644 index 0000000..f68b33c --- /dev/null +++ b/examples/dot_product/network/helpers/nillion_payments_helper.py @@ -0,0 +1,12 @@ +import os +import py_nillion_client as nillion + + +def create_payments_config(): + return nillion.PaymentsConfig( + os.getenv("NILLION_BLOCKCHAIN_RPC_ENDPOINT"), + os.getenv("NILLION_WALLET_PRIVATE_KEY"), + int(os.getenv("NILLION_CHAIN_ID")), + os.getenv("NILLION_PAYMENTS_SC_ADDRESS"), + os.getenv("NILLION_BLINDING_FACTORS_MANAGER_SC_ADDRESS"), + ) diff --git a/examples/dot_product/tests/dot-product.yaml b/examples/dot_product/tests/dot_product.yaml similarity index 93% rename from examples/dot_product/tests/dot-product.yaml rename to examples/dot_product/tests/dot_product.yaml index 3d04043..6bc493e 100644 --- a/examples/dot_product/tests/dot-product.yaml +++ b/examples/dot_product/tests/dot_product.yaml @@ -1,5 +1,5 @@ --- -program: main +program: dot_product inputs: secrets: A_0: diff --git a/tests/nada-tests/tests/matrix-multiplication.yaml b/examples/matrix_multiplication/tests/matrix_multiplication.yaml similarity index 100% rename from tests/nada-tests/tests/matrix-multiplication.yaml rename to examples/matrix_multiplication/tests/matrix_multiplication.yaml diff --git a/nada_algebra/types.py b/nada_algebra/types.py index dc97258..d6e29d2 100644 --- a/nada_algebra/types.py +++ b/nada_algebra/types.py @@ -30,7 +30,9 @@ class Rational: """Wrapper class to store scaled Integer values representing e.g. a python float""" - def __init__(self, value: _NadaInteger, scale: UnsignedInteger, is_scaled: bool=True) -> None: + def __init__( + self, value: _NadaInteger, scale: UnsignedInteger, is_scaled: bool = True + ) -> None: """Initializes wrapper around Integer object. Args: @@ -69,7 +71,9 @@ def value(self) -> _NadaInteger: return self._value @classmethod - def from_number(cls, value: _Number, scale: UnsignedInteger, is_scaled: bool=False) -> "Rational": + def from_number( + cls, value: _Number, scale: UnsignedInteger, is_scaled: bool = False + ) -> "Rational": """Converts and scales a Python-native (or light wrapper by e.g., NumPy) number (floating-point or integer). @@ -94,7 +98,9 @@ def from_number(cls, value: _Number, scale: UnsignedInteger, is_scaled: bool=Fal if not isinstance(value, (float, int)): raise TypeError("Cannot instantiate Rational from type `%s`" % type(value)) - quantized = Integer(value) if is_scaled else Integer(round(value * (2**scale.value))) + quantized = ( + Integer(value) if is_scaled else Integer(round(value * (2**scale.value))) + ) return Rational(quantized, scale) @@ -130,10 +136,12 @@ def __rmod__(self, other: _NadaLike) -> Union["Rational", "SecretRational"]: def __pow__(self, other: int) -> "Rational": if not isinstance(other, int): - raise TypeError("Cannot raise Rational to a power of type `%s`" % type(other)) + raise TypeError( + "Cannot raise Rational to a power of type `%s`" % type(other) + ) # TODO: try to group truncation if no overflow result = self.value - for _ in range(other-1): + for _ in range(other - 1): result = rescale(result * self.value, self.scale, "down") return Rational(result, self.scale) @@ -161,7 +169,9 @@ def __eq__(self, other: _NadaLike) -> SecretBoolean: class SecretRational: """Wrapper class to store scaled SecretInteger values representing e.g. a python float""" - def __init__(self, value: _NadaSecretInteger, scale: UnsignedInteger, is_scaled: bool=True) -> None: + def __init__( + self, value: _NadaSecretInteger, scale: UnsignedInteger, is_scaled: bool = True + ) -> None: """Initializes wrapper around _NadaSecretInteger object. Args: @@ -233,10 +243,13 @@ def __rmod__(self, other: _NadaLike) -> "SecretRational": def __pow__(self, other: _NadaLike) -> "SecretRational": if not isinstance(other, int): - raise TypeError("Cannot raise SecretRational to power of type `%s`" % type(other).__name__) + raise TypeError( + "Cannot raise SecretRational to power of type `%s`" + % type(other).__name__ + ) # TODO: try to group truncation if no overflow result = self.value - for _ in range(other-1): + for _ in range(other - 1): result = rescale(result * self.value, self.scale, "down") return SecretRational(result, self.scale) @@ -303,13 +316,16 @@ def rescale(value: NadaType, scale: UnsignedInteger, direction: str) -> NadaType except: return value / (1 << scale) - raise ValueError("Invalid scaling direction `%s`. Expected \"up\" or \"down\"" % direction) + raise ValueError( + 'Invalid scaling direction `%s`. Expected "up" or "down"' % direction + ) + def apply_arithmetic_op( op: Callable[[Any, Any], Any], this: Union[Rational, SecretRational], other: _NadaLike, - op_rescaling: str=None, + op_rescaling: str = None, ) -> Union[Rational, SecretRational]: """Applies arithmetic operation between this value and an other value, accounting for any possible rescaling. @@ -346,7 +362,11 @@ def apply_arithmetic_op( return SecretRational(result, this.scale) return Rational(result, this.scale) - raise TypeError("Cannot perform operation between type `%s` and type `%s`" % (type(this).__name__, type(other).__name__)) + raise TypeError( + "Cannot perform operation between type `%s` and type `%s`" + % (type(this).__name__, type(other).__name__) + ) + def apply_comparison_op( comparison_op: Callable[[Any, Any], Any], @@ -375,6 +395,9 @@ def apply_comparison_op( elif isinstance(other, NadaType): other = rescale(other, this.scale, "up") else: - raise TypeError("Cannot perform comparison between type `%s` and type `%s`" % (type(this).__name__, type(other).__name__)) + raise TypeError( + "Cannot perform comparison between type `%s` and type `%s`" + % (type(this).__name__, type(other).__name__) + ) return comparison_op(this.value, other) diff --git a/tests/nada-tests/src/get_attr.py b/tests/nada-tests/src/get_attr.py index 7e2de9c..8b4f34b 100644 --- a/tests/nada-tests/src/get_attr.py +++ b/tests/nada-tests/src/get_attr.py @@ -7,6 +7,8 @@ def nada_main(): a = na.array([3], parties[0], "A") - result = a[0] + a[1] + a[2] + result = Integer(0) + for i in range(a.shape[0]): # GET ATTR + result += a[i] # GET ITEM return na.output(result, parties[1], "my_output") diff --git a/tests/nada-tests/src/secret_rational_arithmetic.py b/tests/nada-tests/src/secret_rational_arithmetic.py index bae6456..438e548 100644 --- a/tests/nada-tests/src/secret_rational_arithmetic.py +++ b/tests/nada-tests/src/secret_rational_arithmetic.py @@ -5,7 +5,11 @@ def nada_main(): parties = na.parties(1) - a = na.SecretRational(SecretInteger(Input("my_input_0", parties[0])), scale=UnsignedInteger(16), is_scaled=False) + a = na.SecretRational( + SecretInteger(Input("my_input_0", parties[0])), + scale=UnsignedInteger(16), + is_scaled=False, + ) b = na.Rational(Integer(2), scale=UnsignedInteger(16), is_scaled=False) c = SecretInteger(Input("my_input_1", parties[0])) @@ -53,35 +57,35 @@ def nada_main(): out_30 = a % f return [ - Output(out_0.value / Integer(2**16), "my_output_0", parties[0]), - Output(out_1.value / Integer(2**16), "my_output_1", parties[0]), - Output(out_2.value / Integer(2**16), "my_output_2", parties[0]), - Output(out_3.value / Integer(2**16), "my_output_3", parties[0]), - Output(out_4.value / Integer(2**16), "my_output_4", parties[0]), - Output(out_5.value / Integer(2**16), "my_output_5", parties[0]), - Output(out_6.value / Integer(2**16), "my_output_6", parties[0]), - Output(out_7.value / Integer(2**16), "my_output_7", parties[0]), - Output(out_8.value / Integer(2**16), "my_output_8", parties[0]), - Output(out_9.value / Integer(2**16), "my_output_9", parties[0]), - Output(out_10.value / Integer(2**16), "my_output_10", parties[0]), - Output(out_11.value / Integer(2**16), "my_output_11", parties[0]), - Output(out_12.value / Integer(2**16), "my_output_12", parties[0]), - Output(out_13.value / Integer(2**16), "my_output_13", parties[0]), - Output(out_14.value / Integer(2**16), "my_output_14", parties[0]), - Output(out_15.value / Integer(2**16), "my_output_15", parties[0]), - Output(out_16.value / Integer(2**16), "my_output_16", parties[0]), - Output(out_17.value / Integer(2**16), "my_output_17", parties[0]), - Output(out_18.value / Integer(2**16), "my_output_18", parties[0]), - Output(out_19.value / Integer(2**16), "my_output_19", parties[0]), - Output(out_20.value / Integer(2**16), "my_output_20", parties[0]), - Output(out_21.value / Integer(2**16), "my_output_21", parties[0]), - Output(out_22.value / Integer(2**16), "my_output_22", parties[0]), - Output(out_23.value / Integer(2**16), "my_output_23", parties[0]), - Output(out_24 / Integer(2**16), "my_output_24", parties[0]), - Output(out_25.value / Integer(2**16), "my_output_25", parties[0]), - Output(out_26.value / Integer(2**16), "my_output_26", parties[0]), - Output(out_27.value / Integer(2**16), "my_output_27", parties[0]), - Output(out_28.value / Integer(2**16), "my_output_28", parties[0]), - Output(out_29.value / Integer(2**16), "my_output_29", parties[0]), - Output(out_30.value / Integer(2**16), "my_output_30", parties[0]), + Output(out_0.value / Integer(1 << 16), "my_output_0", parties[0]), + Output(out_1.value / Integer(1 << 16), "my_output_1", parties[0]), + Output(out_2.value / Integer(1 << 16), "my_output_2", parties[0]), + Output(out_3.value / Integer(1 << 16), "my_output_3", parties[0]), + Output(out_4.value / Integer(1 << 16), "my_output_4", parties[0]), + Output(out_5.value / Integer(1 << 16), "my_output_5", parties[0]), + Output(out_6.value / Integer(1 << 16), "my_output_6", parties[0]), + Output(out_7.value / Integer(1 << 16), "my_output_7", parties[0]), + Output(out_8.value / Integer(1 << 16), "my_output_8", parties[0]), + Output(out_9.value / Integer(1 << 16), "my_output_9", parties[0]), + Output(out_10.value / Integer(1 << 16), "my_output_10", parties[0]), + Output(out_11.value / Integer(1 << 16), "my_output_11", parties[0]), + Output(out_12.value / Integer(1 << 16), "my_output_12", parties[0]), + Output(out_13.value / Integer(1 << 16), "my_output_13", parties[0]), + Output(out_14.value / Integer(1 << 16), "my_output_14", parties[0]), + Output(out_15.value / Integer(1 << 16), "my_output_15", parties[0]), + Output(out_16.value / Integer(1 << 16), "my_output_16", parties[0]), + Output(out_17.value / Integer(1 << 16), "my_output_17", parties[0]), + Output(out_18.value / Integer(1 << 16), "my_output_18", parties[0]), + Output(out_19.value / Integer(1 << 16), "my_output_19", parties[0]), + Output(out_20.value / Integer(1 << 16), "my_output_20", parties[0]), + Output(out_21.value / Integer(1 << 16), "my_output_21", parties[0]), + Output(out_22.value / Integer(1 << 16), "my_output_22", parties[0]), + Output(out_23.value / Integer(1 << 16), "my_output_23", parties[0]), + Output(out_24 / Integer(1 << 16), "my_output_24", parties[0]), + Output(out_25.value / Integer(1 << 16), "my_output_25", parties[0]), + Output(out_26.value / Integer(1 << 16), "my_output_26", parties[0]), + Output(out_27.value / Integer(1 << 16), "my_output_27", parties[0]), + Output(out_28.value / Integer(1 << 16), "my_output_28", parties[0]), + Output(out_29.value / Integer(1 << 16), "my_output_29", parties[0]), + Output(out_30.value / Integer(1 << 16), "my_output_30", parties[0]), ] diff --git a/tests/nada-tests/src/secret_rational_comparison.py b/tests/nada-tests/src/secret_rational_comparison.py index 22c5b61..df8f9db 100644 --- a/tests/nada-tests/src/secret_rational_comparison.py +++ b/tests/nada-tests/src/secret_rational_comparison.py @@ -5,7 +5,11 @@ def nada_main(): parties = na.parties(1) - a = na.SecretRational(SecretInteger(Input("my_input_0", parties[0])), scale=UnsignedInteger(16), is_scaled=False) + a = na.SecretRational( + SecretInteger(Input("my_input_0", parties[0])), + scale=UnsignedInteger(16), + is_scaled=False, + ) b = na.Rational(Integer(2), scale=UnsignedInteger(16), is_scaled=False) c = SecretInteger(Input("my_input_1", parties[0])) diff --git a/tests/nada-tests/tests/get_attr.yaml b/tests/nada-tests/tests/get_attr.yaml new file mode 100644 index 0000000..18f8097 --- /dev/null +++ b/tests/nada-tests/tests/get_attr.yaml @@ -0,0 +1,14 @@ +--- +program: get_item +inputs: + secrets: + A_1: + SecretInteger: "3" + A_0: + SecretInteger: "3" + A_2: + SecretInteger: "3" + public_variables: {} +expected_outputs: + my_output_0: + SecretInteger: "9" diff --git a/examples/matrix_multiplication/tests/matrix-multiplication.yaml b/tests/nada-tests/tests/matrix_multiplication.yaml similarity index 97% rename from examples/matrix_multiplication/tests/matrix-multiplication.yaml rename to tests/nada-tests/tests/matrix_multiplication.yaml index fa41462..9b3ff34 100644 --- a/examples/matrix_multiplication/tests/matrix-multiplication.yaml +++ b/tests/nada-tests/tests/matrix_multiplication.yaml @@ -1,5 +1,5 @@ --- -program: main +program: matrix_multiplication inputs: secrets: A_0_0: diff --git a/tests/nada-tests/tests/matrix_inverse.yaml b/tests/nada-tests/tests/unsigned_matrix_inverse.yaml similarity index 100% rename from tests/nada-tests/tests/matrix_inverse.yaml rename to tests/nada-tests/tests/unsigned_matrix_inverse.yaml diff --git a/tests/nada-tests/tests/matrix_inverse_2.yaml b/tests/nada-tests/tests/unsigned_matrix_inverse_2.yaml similarity index 100% rename from tests/nada-tests/tests/matrix_inverse_2.yaml rename to tests/nada-tests/tests/unsigned_matrix_inverse_2.yaml diff --git a/tests/test_all.py b/tests/test_all.py index 101e7f2..e0def51 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -23,20 +23,24 @@ "gauss_jordan", "generate_array", "supported_operations", - "unsigned_matrix_inverse", "rational_arithmetic", "rational_comparison", "secret_rational_arithmetic", "secret_rational_comparison", + # Not supported yet + # "unsigned_matrix_inverse", + # "private_inverse" + # "unsigned_matrix_inverse_2" ] EXAMPLES = [ "dot_product", "matrix_multiplication", + "broadcasting", ] TESTS = [("tests/nada-tests/", test) for test in TESTS] + [ - ("examples/" + test, test) for test in EXAMPLES + (os.path.join("examples/", test), test) for test in EXAMPLES ] @@ -50,39 +54,29 @@ def build_nada(test_dir): result = subprocess.run( ["nada", "build", test_dir[1]], cwd=test_dir[0], capture_output=True, text=True ) - if result.returncode != 0: - pytest.fail(f"Build failed: {result.stderr}") + err = result.stderr.lower() + result.stdout.lower() + if result.returncode != 0 or "error" in err or "fail" in err: + pytest.fail(f"Build {test_dir}:\n{result.stdout + result.stderr}") def run_nada(test_dir): result = subprocess.run( ["nada", "test", test_dir[1]], cwd=test_dir[0], capture_output=True, text=True ) - if result.returncode != 0: - pytest.fail(f"Tests failed: {result.stderr}") + err = result.stderr.lower() + result.stdout.lower() + if result.returncode != 0 or "error" in err or "fail" in err: + pytest.fail(f"Run {test_dir}:\n{result.stdout + result.stderr}") class TestSuite: def test_build(self, testname): - # Get current working directory - cwd = os.getcwd() - try: - # Build Nada Program - build_nada(testname) - finally: - # Return to initial directory - os.chdir(cwd) + # Build Nada Program + build_nada(testname) def test_run(self, testname): - # Get current working directory - cwd = os.getcwd() - try: - # Build Nada Program - build_nada(testname) - finally: - # Return to initial directory - os.chdir(cwd) + # Build Nada Program + run_nada(testname) def test_client():