From 9d883e98192fff910cfc8e4cf7638c01d36fa2be Mon Sep 17 00:00:00 2001 From: davetbutler Date: Thu, 6 Jun 2024 17:30:21 +0100 Subject: [PATCH 01/43] first example with new flow - addition_simple --- .../addition_simple.py | 64 ++++++++++++++----- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py b/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py index 400cc1c3..5e82a8d3 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py @@ -6,8 +6,16 @@ from dotenv import load_dotenv +from cosmpy.aerial.client import LedgerClient, NetworkConfig +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey +from cosmpy.aerial.tx import Transaction +from cosmpy.aerial.client.utils import prepare_and_broadcast_basic_transaction +from cosmpy.crypto.address import Address + + sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from helpers.nillion_client_helper import create_nillion_client, pay, create_payments_config from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() @@ -15,6 +23,8 @@ # 1 Party running simple addition on 1 stored secret and 1 compute time secret async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_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) @@ -24,15 +34,31 @@ async def main(): program_name="addition_simple" program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), prefix="nillion" + ) + + receipt_store_program = await pay( + client, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id) + # store program action_id = await client.store_program( - cluster_id, program_name, program_mir_path + cluster_id, program_name, program_mir_path, receipt_store_program ) program_id=f"{user_id}/{program_name}" print('Stored program. action_id:', action_id) print('Stored program_id:', program_id) - + + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) + # Create a secret stored_secret = nillion.Secrets({ "my_int1": nillion.SecretInteger(500), @@ -40,9 +66,13 @@ async def main(): secret_bindings = nillion.ProgramBindings(program_id) secret_bindings.add_input_party(party_name, party_id) + receipt_store = await pay( + client, nillion.Operation.store_secrets(stored_secret), payments_wallet, payments_client, cluster_id + ) + # Store a secret store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None + cluster_id, stored_secret, permissions, receipt_store ) # Bind the parties in the computation to the client to set input and output parties @@ -50,22 +80,26 @@ async def main(): compute_bindings.add_input_party(party_name, party_id) compute_bindings.add_output_party(party_name, party_id) - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") + receipt_compute = await pay( + client, + nillion.Operation.compute(program_id, stored_secret), payments_wallet, payments_client, cluster_id + ) computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) - - # Compute on the secret - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - nillion.PublicVariables({}), + + uuid = await client.compute( + cluster_id=cluster_id, + bindings=compute_bindings, + store_ids=[store_id], + secrets=computation_time_secrets, + receipt=receipt_compute, + public_variables=nillion.PublicVariables({}), ) + print(f"Computing using program {program_id}") + print(f"Use secret store_id: {store_id}") # Print compute result - print(f"The computation was sent to the network. compute_id: {compute_id}") + print(f"The computation was sent to the network. compute_id: {uuid}") while True: compute_event = await client.next_compute_event() if isinstance(compute_event, nillion.ComputeFinishedEvent): From becfeff9359623f17c93bd6700f68b026e08e0f9 Mon Sep 17 00:00:00 2001 From: davetbutler Date: Thu, 6 Jun 2024 18:12:54 +0100 Subject: [PATCH 02/43] helpers and bootstrap changed --- bootstrap-local-environment.sh | 29 +++++++++---------- helpers/nillion_client_helper.py | 48 +++++++++++++++++++++++++------- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/bootstrap-local-environment.sh b/bootstrap-local-environment.sh index 2ad6f7f1..9df2d980 100755 --- a/bootstrap-local-environment.sh +++ b/bootstrap-local-environment.sh @@ -45,16 +45,14 @@ echo "ℹ️ Cluster has been STARTED (see $OUTFILE)" cat "$OUTFILE" # grep cluster info from nillion-devnet -CLUSTER_ID=$(grep "cluster id is" "$OUTFILE" | awk '{print $4}'); -WEBSOCKET=$(grep "websocket:" "$OUTFILE" | awk '{print $2}'); -BOOT_MULTIADDR=$(grep "cluster is running, bootnode is at" "$OUTFILE" | awk '{print $7}'); -PAYMENTS_CONFIG_FILE=$(grep "payments configuration written to" "$OUTFILE" | awk '{print $5}'); -WALLET_KEYS_FILE=$(grep "wallet keys written to" "$OUTFILE" | awk '{print $5}'); -PAYMENTS_RPC=$(grep "blockchain_rpc_endpoint:" "$PAYMENTS_CONFIG_FILE" | awk '{print $2}'); -PAYMENTS_CHAIN=$(grep "chain_id:" "$PAYMENTS_CONFIG_FILE" | awk '{print $2}'); -PAYMENTS_SC_ADDR=$(grep "payments_sc_address:" "$PAYMENTS_CONFIG_FILE" | awk '{print $2}'); -PAYMENTS_BF_ADDR=$(grep "blinding_factors_manager_sc_address:" "$PAYMENTS_CONFIG_FILE" | awk '{print $2}'); -WALLET_PRIVATE_KEY=$(tail -n1 "$WALLET_KEYS_FILE") +CLUSTER_ID=$(grep "cluster id is" "$OUTFILE" | awk '{print $5}'); +BOOT_MULTIADDR=$(grep "cluster is running, bootnode is at" "$OUTFILE" | awk '{print $8}'); +JSON_RPC=$(grep "nilchain JSON RPC available at" "$OUTFILE" | awk '{print $7}'); +GRPC=$(grep "nilchain gRPC available at" "$OUTFILE" | awk '{print $6}'); +CHAIN_DIR=$(grep 'starting nilchain node in:' "$OUTFILE" | awk -F'"' '{print $2}') + +# Retrieve the wallet private key +WALLET_PRIVATE_KEY=$(echo "y" | "$CHAIN_DIR/bin/nilchaind" keys export stash --home "$CHAIN_DIR" --keyring-backend test --unsafe --unarmored-hex) # update or add an environment variable to one or more files update_env() { @@ -118,12 +116,11 @@ echo "🔑 Node key and user keys have been generated and added to .env" # Add environment variables to .env update_env "NILLION_WEBSOCKETS" "$WEBSOCKET" $ENV_TO_UPDATE update_env "NILLION_CLUSTER_ID" "$CLUSTER_ID" $ENV_TO_UPDATE -update_env "NILLION_BLOCKCHAIN_RPC_ENDPOINT" "$PAYMENTS_RPC" $ENV_TO_UPDATE -update_env "NILLION_BLINDING_FACTORS_MANAGER_SC_ADDRESS" "$PAYMENTS_BF_ADDR" $ENV_TO_UPDATE -update_env "NILLION_PAYMENTS_SC_ADDRESS" "$PAYMENTS_SC_ADDR" $ENV_TO_UPDATE -update_env "NILLION_CHAIN_ID" "$PAYMENTS_CHAIN" $ENV_TO_UPDATE -update_env "NILLION_WALLET_PRIVATE_KEY" "$WALLET_PRIVATE_KEY" $ENV_TO_UPDATE update_env "NILLION_BOOTNODE_MULTIADDRESS" "$BOOT_MULTIADDR" $ENV_TO_UPDATE +update_env "NILLION_JSON_RPC" "$JSON_RPC" $ENV_TO_UPDATE +update_env "NILLION_GRPC" "$GRPC" $ENV_TO_UPDATE +update_env "NILLION_CHAIN_ID" "nillion-chain-testnet" $ENV_TO_UPDATE +update_env "NILLION_WALLET_PRIVATE_KEY" "$WALLET_PRIVATE_KEY" $ENV_TO_UPDATE echo "Running at process pid: $(pgrep -f $NILLION_DEVNET)" @@ -149,4 +146,4 @@ echo "--------------------" echo "💻 Your Nillion local cluster is still running - process pid: $(pgrep -f $NILLION_DEVNET)" echo "ℹ️ Updated your .env file configuration variables: bootnode, cluster id, keys, blockchain info" -exit 0 \ No newline at end of file +exit 0 diff --git a/helpers/nillion_client_helper.py b/helpers/nillion_client_helper.py index bf40cdab..43412dc4 100644 --- a/helpers/nillion_client_helper.py +++ b/helpers/nillion_client_helper.py @@ -1,20 +1,48 @@ import os import py_nillion_client as nillion +from cosmpy.aerial.client import LedgerClient, NetworkConfig +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey +from cosmpy.aerial.tx import Transaction +from cosmpy.aerial.client.utils import prepare_and_broadcast_basic_transaction +from cosmpy.crypto.address import Address + def create_nillion_client(userkey, nodekey): bootnodes = [os.getenv("NILLION_BOOTNODE_MULTIADDRESS")] - payments_config = 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"), - ) return nillion.NillionClient( nodekey, bootnodes, nillion.ConnectionMode.relay(), - userkey, - payments_config, - ) \ No newline at end of file + userkey + ) + +async def pay( + client: nillion.NillionClient, + operation: nillion.Operation, + payments_wallet, + payments_client, + cluster_id + ) -> nillion.PaymentReceipt: + quote = await client.request_price_quote(cluster_id, operation) + address = str(Address(payments_wallet.public_key(), "nillion")) + message = nillion.create_payments_message(quote, address) + tx = Transaction() + tx.add_message(message) + submitted_tx = prepare_and_broadcast_basic_transaction( + payments_client, tx, payments_wallet, gas_limit=1000000 + ) + submitted_tx.wait_to_complete() + return nillion.PaymentReceipt(quote, submitted_tx.tx_hash) + +def create_payments_config(chain_id, payments_endpoint): + + return NetworkConfig( + chain_id=chain_id, + url=f"grpc+http://{payments_endpoint}/", + fee_minimum_gas_price=0, + fee_denomination="unil", + staking_denomination="unil", + faucet_url=None, + ) \ No newline at end of file From 65d86a2d291927be1c3a4883d23d7bbdec232eb9 Mon Sep 17 00:00:00 2001 From: oceans404 Date: Thu, 6 Jun 2024 10:25:35 -0700 Subject: [PATCH 03/43] add websockets, update sample env --- .env.sample | 31 +++++++++++++++++++++---------- bootstrap-local-environment.sh | 1 + 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/.env.sample b/.env.sample index f843ceec..38492505 100644 --- a/.env.sample +++ b/.env.sample @@ -1,18 +1,29 @@ # Leave these blank - the env variables will be populated when you run ./bootstrap-local-environment.sh -NILLION_BOOTNODE_MULTIADDRESS= -NILLION_CLUSTER_ID= -NILLION_USERKEY_PATH_PARTY_1= -NILLION_USERKEY_PATH_PARTY_2= -NILLION_USERKEY_PATH_PARTY_3= -NILLION_USERKEY_PATH_PARTY_4= -NILLION_USERKEY_PATH_PARTY_5= NILLION_NODEKEY_PATH_PARTY_1= +NILLION_NODEKEY_TEXT_PARTY_1= NILLION_NODEKEY_PATH_PARTY_2= +NILLION_NODEKEY_TEXT_PARTY_2= NILLION_NODEKEY_PATH_PARTY_3= +NILLION_NODEKEY_TEXT_PARTY_3= NILLION_NODEKEY_PATH_PARTY_4= +NILLION_NODEKEY_TEXT_PARTY_4= NILLION_NODEKEY_PATH_PARTY_5= -NILLION_BLOCKCHAIN_RPC_ENDPOINT= +NILLION_NODEKEY_TEXT_PARTY_5= +NILLION_USERKEY_PATH_PARTY_1= +NILLION_USERKEY_TEXT_PARTY_1= +NILLION_USERKEY_PATH_PARTY_2= +NILLION_USERKEY_TEXT_PARTY_2= +NILLION_USERKEY_PATH_PARTY_3= +NILLION_USERKEY_TEXT_PARTY_3= +NILLION_USERKEY_PATH_PARTY_4= +NILLION_USERKEY_TEXT_PARTY_4= +NILLION_USERKEY_PATH_PARTY_5= +NILLION_USERKEY_TEXT_PARTY_5= +NILLION_WEBSOCKETS= +NILLION_CLUSTER_ID= +NILLION_BOOTNODE_MULTIADDRESS= +NILLION_JSON_RPC= +NILLION_GRPC= NILLION_CHAIN_ID= -NILLION_PAYMENTS_SC_ADDRESS= -NILLION_BLINDING_FACTORS_MANAGER_SC_ADDRESS= NILLION_WALLET_PRIVATE_KEY= + diff --git a/bootstrap-local-environment.sh b/bootstrap-local-environment.sh index 9df2d980..e0fbccfe 100755 --- a/bootstrap-local-environment.sh +++ b/bootstrap-local-environment.sh @@ -50,6 +50,7 @@ BOOT_MULTIADDR=$(grep "cluster is running, bootnode is at" "$OUTFILE" | awk '{pr JSON_RPC=$(grep "nilchain JSON RPC available at" "$OUTFILE" | awk '{print $7}'); GRPC=$(grep "nilchain gRPC available at" "$OUTFILE" | awk '{print $6}'); CHAIN_DIR=$(grep 'starting nilchain node in:' "$OUTFILE" | awk -F'"' '{print $2}') +WEBSOCKET=$(grep 'websocket:' "$OUTFILE" | awk '{print $3}'); # Retrieve the wallet private key WALLET_PRIVATE_KEY=$(echo "y" | "$CHAIN_DIR/bin/nilchaind" keys export stash --home "$CHAIN_DIR" --keyring-backend test --unsafe --unarmored-hex) From d7291b386d2ac95c5420b5e860ce87c44983e6d3 Mon Sep 17 00:00:00 2001 From: oceans404 Date: Thu, 6 Jun 2024 12:28:32 -0700 Subject: [PATCH 04/43] update README to explain new installation --- README.md | 92 ++++++++++++++----- .../addition_simple.py | 33 ++++--- .../store_and_retrieve_blob.py | 46 ++++++++-- .../store_and_retrieve_integer.py | 46 ++++++++-- helpers/nillion_client_helper.py | 3 + requirements.txt | 3 +- 6 files changed, 168 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index ab1eda19..bb47fde3 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,78 @@ # Nillion Python Starter -This is a python starter repo for building on the Nillion Network. Complete environment setup, then run the examples: +This is an EXPERIMENTAL PAYMENTS ENABLED BRANCH of python starter repo for building on the Nillion Network. In order to use this branch, you'll need the latest experimental version of the Nillion SDK, nada library, and python client library. -- To run multi party examples, go to the [multi party compute](examples_and_tutorials/core_concept_multi_party_compute) folder. - -- To run single party examples, go to the [single party compute](examples_and_tutorials/core_concept_single_party_compute) folder. - -- To run permissions examples (storing and retrieving permissioned secrets, revoking permissions, etc.), go to the [permissions](examples_and_tutorials/core_concept_permissions) folder. +## Installing the latest experimental versions of Nillion -### Prerequisites: Install the CLI Dependencies +#### Prerequisites: Install the CLI Dependencies -The `nillion-devnet` tool spins up `anvil` under the hood, so you need to have `foundry` installed. The [`bootstrap-local-environment.sh`](./bootstrap-local-environment.sh) file uses `pidof` and `grep`. +The [`bootstrap-local-environment.sh`](./bootstrap-local-environment.sh) file uses `pidof` and `grep`. -- [Install `foundry`](https://book.getfoundry.sh/getting-started/installation) - [Install `pidof`](https://command-not-found.com/pidof) - [Install `grep`](https://command-not-found.com/grep) -## Environment Setup +1. Install the specific version of nillion with flags to also download nada and the python client at the same specific version by running + +```bash +nilup install {version} --nada-dsl --python-client +``` + +This results in a message that shows you the downloaded paths to the versions of nada_dsl and the python client you’ll need to use, for example if I was downloading a version `vABCD`, I would see: -1. Create a `.env` file by copying the sample: +```bash +Using pip to install /Users/steph/.nilup/sdks/vABCD/nada_dsl-0.1.0-py3-none-any.whl +nada_dsl version vABCD installed +Installing python client version vABCD +Downloading vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl to /Users/steph/.nilup/sdks/vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl +Using pip to install /Users/steph/.nilup/sdks/vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl +python client version vABCD installed +``` + +Use this message to figure out your local paths to the latest experimental versions of - ```shell - cp .env.sample .env - ``` + - python client path: `/Users/steph/.nilup/sdks/vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl` + - nada path: `/Users/steph/.nilup/sdks/vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl` -2. Create the virtual environment (`.venv`), install dependencies, and activate the virtual environment +2. Set nillion version + +```bash +nilup use {version} +``` - ```shell - bash ./create_venv.sh && source .venv/bin/activate - ``` +3. Change directories into this branch of this repo - Run the [`bootstrap-local-environment.sh`](./bootstrap-local-environment.sh) script to spin up `nillion-devnet`, generate keys, and get bootnodes, cluster, and payment info: +``` +cd nillion-python-starter +git branch payments-flow-update +git checkout payments-flow-update +git pull origin payments-flow-update +``` - ```shell - ./bootstrap-local-environment.sh - ``` +4. Create venv -3. Check `.env` file - keys, bootnodes, cluster, and payment info should now be present. If you want to run against a local cluster, use this configuration. Otherwise, replace values with testnet bootnodes, cluster, and payment info. +```bash +bash ./create_venv.sh && source .venv/bin/activate +``` -4. Look through the [programs](./programs/) folder to see examples of Nada programs. +5. Manually install nada using the path printed by nilup installation in step 1 -## Compiling Programs +```bash +python3 -m pip install {your/nada/path} +``` + +6. Manually install the Python client using the path printed by nilup installation in step 1 + +```bash +python3 -m pip install {your/python/client/path} +``` + +7. Bootstrap local environment to connect to the nillion-devnet and add the configuration to your .env file + +```bash +./bootstrap-local-environment.sh +``` + +8. Compile all programs Nada programs need to be compiled ahead of being stored. Compile all programs in the [programs](./programs/) folder with the script [`compile_programs.sh`](./compile_programs.sh): @@ -50,6 +82,16 @@ bash compile_programs.sh This generates a `programs-compiled` folder containing the compiled programs. +## Usage + +After completing environment setup, then run the examples: + +- To run multi party examples, go to the [multi party compute](examples_and_tutorials/core_concept_multi_party_compute) folder. + +- To run single party examples, go to the [single party compute](examples_and_tutorials/core_concept_single_party_compute) folder. + +- To run permissions examples (storing and retrieving permissioned secrets, revoking permissions, etc.), go to the [permissions](examples_and_tutorials/core_concept_permissions) folder. + ## Store a Compiled Program Store a compiled program in the network with this script: diff --git a/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py b/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py index 5e82a8d3..b8e29857 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py @@ -5,13 +5,9 @@ import pytest from dotenv import load_dotenv - -from cosmpy.aerial.client import LedgerClient, NetworkConfig +from cosmpy.aerial.client import LedgerClient from cosmpy.aerial.wallet import LocalWallet from cosmpy.crypto.keypairs import PrivateKey -from cosmpy.aerial.tx import Transaction -from cosmpy.aerial.client.utils import prepare_and_broadcast_basic_transaction -from cosmpy.crypto.address import Address sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) @@ -34,12 +30,17 @@ async def main(): program_name="addition_simple" program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + # Create payments config and set up Nillion wallet with a private key to pay for storage and compute operations payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), prefix="nillion" ) + ##### STORE PROGRAM + print('-----STORE PROGRAM') + + # Get cost quote, then pay for operation to store program receipt_store_program = await pay( client, nillion.Operation.store_program(), @@ -47,46 +48,56 @@ async def main(): payments_client, cluster_id) - # store program + # Store program, passing in the receipt that shows proof of payment action_id = await client.store_program( cluster_id, program_name, program_mir_path, receipt_store_program ) + # Print details about stored program program_id=f"{user_id}/{program_name}" print('Stored program. action_id:', action_id) print('Stored program_id:', program_id) - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) + ##### STORE SECRETS + print('-----STORE SECRETS') # Create a secret stored_secret = nillion.Secrets({ "my_int1": nillion.SecretInteger(500), }) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) + # Create a permissions object to attach to the stored secret + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) + + # Get cost quote, then pay for operation to store the secret receipt_store = await pay( client, nillion.Operation.store_secrets(stored_secret), payments_wallet, payments_client, cluster_id ) - # Store a secret + # Store a secret, passing in the receipt that shows proof of payment store_id = await client.store_secrets( cluster_id, stored_secret, permissions, receipt_store ) + ##### COMPUTE + print('-----COMPUTE') + # Bind the parties in the computation to the client to set input and output parties compute_bindings = nillion.ProgramBindings(program_id) compute_bindings.add_input_party(party_name, party_id) compute_bindings.add_output_party(party_name, party_id) + # Get cost quote, then pay for operation to compute receipt_compute = await pay( client, nillion.Operation.compute(program_id, stored_secret), payments_wallet, payments_client, cluster_id ) + # Create a computation time secret to use computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) + # Compute, passing all params including the receipt that shows proof of payment uuid = await client.compute( cluster_id=cluster_id, bindings=compute_bindings, diff --git a/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_blob.py b/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_blob.py index 90ca81fb..24883f31 100644 --- a/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_blob.py +++ b/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_blob.py @@ -5,9 +5,12 @@ import pytest from dotenv import load_dotenv +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from helpers.nillion_client_helper import create_nillion_client, pay, create_payments_config from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() @@ -15,30 +18,57 @@ # Store and retrieve a SecretBlob using the Python Client async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_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) + # Create payments config and set up Nillion wallet with a private key to pay for storage and compute operations + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), prefix="nillion" + ) + + ##### STORE SECRET + print('-----STORE SECRET') + # Create a SecretBlob secret_name = "my_blob" # create a bytearray from the string using UTF-8 encoding secret_value = bytearray("gm, builder!", "utf-8") - secret_integer = nillion.Secrets({ + + # Create a secret + stored_secret = nillion.Secrets({ secret_name: nillion.SecretBlob(secret_value), }) - # Store a SecretBlob - # Notice that both bindings and permissions are set to None - # Secrets of type SecretBlob don't need bindings because they aren't used in programs - # Permissions need to be set to allow users other than the secret creator to use the secret + # Create a permissions object to attach to the stored secret + permissions = nillion.Permissions.default_for_user(client.user_id) + + # Get cost quote, then pay for operation to store the secret + receipt_store = await pay( + client, nillion.Operation.store_secrets(stored_secret), payments_wallet, payments_client, cluster_id + ) + + # Store a secret, passing in the receipt that shows proof of payment store_id = await client.store_secrets( - cluster_id, None, secret_integer, None + cluster_id, stored_secret, permissions, receipt_store ) print(f"The secret is stored at store_id: {store_id}") - result_tuple = await client.retrieve_secret(cluster_id, store_id, secret_name) + ##### RETRIEVE SECRET + print('-----RETRIEVE SECRET') + + # Get cost quote, then pay for operation to retrieve the secret + receipt_retrieve = await pay( + client, nillion.Operation.retrieve_secret(), payments_wallet, payments_client, cluster_id + ) + + result_tuple = await client.retrieve_secret(cluster_id, store_id, secret_name, receipt_retrieve) print(f"The secret name as a uuid is {result_tuple[0]}") decoded_secret_value = result_tuple[1].value.decode('utf-8') diff --git a/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_integer.py b/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_integer.py index 781dcabb..a9a535da 100644 --- a/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_integer.py +++ b/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_integer.py @@ -5,9 +5,12 @@ import pytest from dotenv import load_dotenv +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from helpers.nillion_client_helper import create_nillion_client, pay, create_payments_config from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() @@ -15,28 +18,53 @@ # Store and retrieve a SecretInteger using the Python Client async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_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) + # Create payments config and set up Nillion wallet with a private key to pay for storage and compute operations + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), prefix="nillion" + ) + + ##### STORE SECRET + print('-----STORE SECRET') + # Create a SecretInteger secret_name = "my_int1" secret_value = 100 - secret_integer = nillion.Secrets({ + stored_secret = nillion.Secrets({ secret_name: nillion.SecretInteger(secret_value), }) - # Store a SecretInteger - # Notice that both bindings and permissions are set to None - # Bindings need to be set to use secrets in programs - # Permissions need to be set to allow users other than the secret creator to use the secret + # Create a permissions object to attach to the stored secret + permissions = nillion.Permissions.default_for_user(client.user_id) + + # Get cost quote, then pay for operation to store the secret + receipt_store = await pay( + client, nillion.Operation.store_secrets(stored_secret), payments_wallet, payments_client, cluster_id + ) + + # Store a secret, passing in the receipt that shows proof of payment store_id = await client.store_secrets( - cluster_id, None, secret_integer, None + cluster_id, stored_secret, permissions, receipt_store ) print(f"The secret is stored at store_id: {store_id}") - result_tuple = await client.retrieve_secret(cluster_id, store_id, secret_name) + ##### RETRIEVE SECRET + print('-----RETRIEVE SECRET') + + # Get cost quote, then pay for operation to retrieve the secret + receipt_retrieve = await pay( + client, nillion.Operation.retrieve_secret(), payments_wallet, payments_client, cluster_id + ) + + result_tuple = await client.retrieve_secret(cluster_id, store_id, secret_name, receipt_retrieve) print(f"The secret name as a uuid is {result_tuple[0]}") print(f"The secret value is {result_tuple[1].value}") return result_tuple[1].value @@ -47,4 +75,4 @@ async def main(): @pytest.mark.asyncio async def test_main(): result = await main() - assert result == 100 + assert result == 'gm, builder!' diff --git a/helpers/nillion_client_helper.py b/helpers/nillion_client_helper.py index 43412dc4..59a805b2 100644 --- a/helpers/nillion_client_helper.py +++ b/helpers/nillion_client_helper.py @@ -25,7 +25,9 @@ async def pay( payments_client, cluster_id ) -> nillion.PaymentReceipt: + print("Getting quote for operation...") quote = await client.request_price_quote(cluster_id, operation) + print(f"Quote cost is {quote.cost} unil") address = str(Address(payments_wallet.public_key(), "nillion")) message = nillion.create_payments_message(quote, address) tx = Transaction() @@ -34,6 +36,7 @@ async def pay( payments_client, tx, payments_wallet, gas_limit=1000000 ) submitted_tx.wait_to_complete() + print(f"Submitting payment receipt {quote.cost} unil, tx hash {submitted_tx.tx_hash}") return nillion.PaymentReceipt(quote, submitted_tx.tx_hash) def create_payments_config(chain_id, payments_endpoint): diff --git a/requirements.txt b/requirements.txt index b5f8c0a2..d980720d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ python-dotenv==1.0.0 -nada-dsl>=0.2.1 -py-nillion-client>=0.2.1 pytest-asyncio>=0.23.6 +cosmpy>=0.9.2 \ No newline at end of file From 1cce2a6437ac51579aa74f4537675a01537baa4f Mon Sep 17 00:00:00 2001 From: oceans404 Date: Thu, 6 Jun 2024 12:31:12 -0700 Subject: [PATCH 05/43] fix readme formatting --- README.md | 82 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index bb47fde3..7122d374 100644 --- a/README.md +++ b/README.md @@ -11,29 +11,31 @@ The [`bootstrap-local-environment.sh`](./bootstrap-local-environment.sh) file us - [Install `pidof`](https://command-not-found.com/pidof) - [Install `grep`](https://command-not-found.com/grep) -1. Install the specific version of nillion with flags to also download nada and the python client at the same specific version by running +### Setup -```bash -nilup install {version} --nada-dsl --python-client -``` +1. Install the specific version of nillion with flags to also download nada and the python client at the same specific version by running -This results in a message that shows you the downloaded paths to the versions of nada_dsl and the python client you’ll need to use, for example if I was downloading a version `vABCD`, I would see: + ```bash + nilup install {version} --nada-dsl --python-client + ``` -```bash -Using pip to install /Users/steph/.nilup/sdks/vABCD/nada_dsl-0.1.0-py3-none-any.whl -nada_dsl version vABCD installed -Installing python client version vABCD -Downloading vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl to /Users/steph/.nilup/sdks/vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl -Using pip to install /Users/steph/.nilup/sdks/vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl -python client version vABCD installed -``` + This results in a message that shows you the downloaded paths to the versions of nada_dsl and the python client you’ll need to use, for example if I was downloading a version `vABCD`, I would see: + + ```bash + Using pip to install /Users/steph/.nilup/sdks/vABCD/nada_dsl-0.1.0-py3-none-any.whl + nada_dsl version vABCD installed + Installing python client version vABCD + Downloading vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl to /Users/steph/.nilup/sdks/vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl + Using pip to install /Users/steph/.nilup/sdks/vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl + python client version vABCD installed + ``` -Use this message to figure out your local paths to the latest experimental versions of + Use this message to figure out your local paths to the latest experimental versions of - - python client path: `/Users/steph/.nilup/sdks/vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl` - - nada path: `/Users/steph/.nilup/sdks/vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl` + - python client path: `/Users/steph/.nilup/sdks/vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl` + - nada path: `/Users/steph/.nilup/sdks/vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl` -2. Set nillion version +2. Set nillion version ```bash nilup use {version} @@ -41,46 +43,46 @@ nilup use {version} 3. Change directories into this branch of this repo -``` -cd nillion-python-starter -git branch payments-flow-update -git checkout payments-flow-update -git pull origin payments-flow-update -``` + ``` + cd nillion-python-starter + git branch payments-flow-update + git checkout payments-flow-update + git pull origin payments-flow-update + ``` 4. Create venv -```bash -bash ./create_venv.sh && source .venv/bin/activate -``` + ```bash + bash ./create_venv.sh && source .venv/bin/activate + ``` 5. Manually install nada using the path printed by nilup installation in step 1 -```bash -python3 -m pip install {your/nada/path} -``` + ```bash + python3 -m pip install {your/nada/path} + ``` 6. Manually install the Python client using the path printed by nilup installation in step 1 -```bash -python3 -m pip install {your/python/client/path} -``` + ```bash + python3 -m pip install {your/python/client/path} + ``` 7. Bootstrap local environment to connect to the nillion-devnet and add the configuration to your .env file -```bash -./bootstrap-local-environment.sh -``` + ```bash + ./bootstrap-local-environment.sh + ``` 8. Compile all programs -Nada programs need to be compiled ahead of being stored. Compile all programs in the [programs](./programs/) folder with the script [`compile_programs.sh`](./compile_programs.sh): + Nada programs need to be compiled ahead of being stored. Compile all programs in the [programs](./programs/) folder with the script [`compile_programs.sh`](./compile_programs.sh): -```shell -bash compile_programs.sh -``` + ```shell + bash compile_programs.sh + ``` -This generates a `programs-compiled` folder containing the compiled programs. + This generates a `programs-compiled` folder containing the compiled programs. ## Usage From 7d0ec7563f25a3e345ae7f5fccc8550f3d742faf Mon Sep 17 00:00:00 2001 From: oceans404 Date: Thu, 6 Jun 2024 12:57:13 -0700 Subject: [PATCH 06/43] update installation formatting --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7122d374..c2a3875a 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ The [`bootstrap-local-environment.sh`](./bootstrap-local-environment.sh) file us python client version vABCD installed ``` - Use this message to figure out your local paths to the latest experimental versions of + Use this message to figure out your local paths to the latest experimental versions of python client and nada. You'll need to know these paths in steps 5 and 6 to manually install these 2 packages. - python client path: `/Users/steph/.nilup/sdks/vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl` - nada path: `/Users/steph/.nilup/sdks/vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl` @@ -86,7 +86,7 @@ nilup use {version} ## Usage -After completing environment setup, then run the examples: +After completing environment setup, run the examples: - To run multi party examples, go to the [multi party compute](examples_and_tutorials/core_concept_multi_party_compute) folder. From 621bc00294af2fdece0410bbdc170545cc77551e Mon Sep 17 00:00:00 2001 From: davetbutler Date: Thu, 6 Jun 2024 22:05:25 +0100 Subject: [PATCH 07/43] single party compute examples complete (modulo correlation coefficient) --- .../01_store_secret_party1.py | 49 ++++++++ .../addition_simple.py | 61 ++++++---- .../circuit_simple.py | 95 +++++++++++---- .../circuit_simple_2.py | 109 +++++++++++++----- .../complex.py | 101 ++++++++++++---- .../correlation_coefficient.py | 107 ++++++++++++----- .../division_simple.py | 92 ++++++++++++--- .../input_integer.py | 90 ++++++++++++--- .../input_single.py | 90 ++++++++++++--- .../modulo_simple.py | 94 +++++++++++---- .../multiplication_simple.py | 87 +++++++++++--- .../nada_fn_composition.py | 88 +++++++++++--- .../nada_fn_simple.py | 81 ------------- .../reduce_simple.py | 88 -------------- .../reuse.py | 94 +++++++++++---- .../reuse_flipped1.py | 86 -------------- .../reuse_flipped2.py | 86 -------------- .../reuse_simple_1.py | 83 ------------- .../reuse_simple_2.py | 83 ------------- .../reuse_simple_sub.py | 84 -------------- .../simple.py | 92 +++++++++++---- .../simple_literals.py | 88 +++++++++++--- .../simple_public_variables.py | 102 ++++++++++++---- .../simple_public_variables_only.py | 79 ++++++++++--- .../simple_sub.py | 92 +++++++++++---- .../single_addition.py | 84 -------------- .../subtraction_simple.py | 94 +++++++++++---- .../subtraction_simple_neg.py | 90 +++++++++++---- .../tiny_secret_addition.py | 16 ++- .../tiny_secret_addition_complete.py | 102 +++++++++++----- 30 files changed, 1471 insertions(+), 1116 deletions(-) delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/nada_fn_simple.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/reduce_simple.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/reuse_flipped1.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/reuse_flipped2.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/reuse_simple_1.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/reuse_simple_2.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/reuse_simple_sub.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/single_addition.py diff --git a/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py b/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py index a1025c66..2cb7f9c7 100644 --- a/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py +++ b/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py @@ -18,6 +18,9 @@ # The 1st Party stores a secret async def main(): + + cluster_id = os.getenv("NILLION_CLUSTER_ID") + cluster_id = os.getenv("NILLION_CLUSTER_ID") client_1 = create_nillion_client( getUserKeyFromFile(CONFIG_PARTY_1["userkey_file"]), getNodeKeyFromFile(CONFIG_PARTY_1["nodekey_file"]) @@ -25,6 +28,52 @@ async def main(): party_id_1 = client_1.party_id user_id_1 = client_1.user_id + program_mir_path = f"../../programs-compiled/{CONFIG_PROGRAM_NAME}.nada.bin" + + # 1st Party stores program + action_id = await client_1.store_program( + cluster_id, CONFIG_PROGRAM_NAME, program_mir_path + ) + + program_id = f"{user_id_1}/{CONFIG_PROGRAM_NAME}" + + program_name = "simple" + + program_id = f"{Test.programs_namespace}/{program_name}" + + permissions = py_nillion_client.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) + + inputs = Test.load_inputs(program_name) + receipt = await self.pay( + client, py_nillion_client.Operation.store_secrets(inputs.store_secrets) + ) + store_id = await client.store_secrets( + self.cluster_id, inputs.store_secrets, permissions, receipt + ) + + bindings = py_nillion_client.ProgramBindings(program_id) + bindings.add_input_party("Dealer", client.party_id) + bindings.add_output_party("Result", client.party_id) + receipt = await self.pay( + client, + py_nillion_client.Operation.compute(program_id, inputs.compute_secrets), + ) + uuid = await client.compute( + self.cluster_id, + bindings, + [store_id], + inputs.compute_secrets, + inputs.compute_public_variables, + receipt, + ) + + + ###### + + + + program_mir_path=f"../../programs-compiled/{CONFIG_PROGRAM_NAME}.nada.bin" diff --git a/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py b/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py index b8e29857..b5a10760 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py @@ -9,13 +9,17 @@ from cosmpy.aerial.wallet import LocalWallet from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client, pay, create_payments_config +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() + # 1 Party running simple addition on 1 stored secret and 1 compute time secret async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") @@ -26,19 +30,20 @@ async def main(): client = create_nillion_client(userkey, nodekey) party_id = client.party_id user_id = client.user_id - party_name="Party1" - program_name="addition_simple" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + party_name = "Party1" + program_name = "addition_simple" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" # Create payments config and set up Nillion wallet with a private key to pay for storage and compute operations payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), prefix="nillion" + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", ) ##### STORE PROGRAM - print('-----STORE PROGRAM') + print("-----STORE PROGRAM") # Get cost quote, then pay for operation to store program receipt_store_program = await pay( @@ -46,7 +51,8 @@ async def main(): nillion.Operation.store_program(), payments_wallet, payments_client, - cluster_id) + cluster_id, + ) # Store program, passing in the receipt that shows proof of payment action_id = await client.store_program( @@ -54,25 +60,31 @@ async def main(): ) # Print details about stored program - program_id=f"{user_id}/{program_name}" - print('Stored program. action_id:', action_id) - print('Stored program_id:', program_id) + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) ##### STORE SECRETS - print('-----STORE SECRETS') + print("-----STORE SECRETS") # Create a secret - stored_secret = nillion.Secrets({ - "my_int1": nillion.SecretInteger(500), - }) + stored_secret = nillion.Secrets( + { + "my_int1": nillion.SecretInteger(500), + } + ) - # Create a permissions object to attach to the stored secret + # Create a permissions object to attach to the stored secret permissions = nillion.Permissions.default_for_user(client.user_id) permissions.add_compute_permissions({client.user_id: {program_id}}) # Get cost quote, then pay for operation to store the secret receipt_store = await pay( - client, nillion.Operation.store_secrets(stored_secret), payments_wallet, payments_client, cluster_id + client, + nillion.Operation.store_secrets(stored_secret), + payments_wallet, + payments_client, + cluster_id, ) # Store a secret, passing in the receipt that shows proof of payment @@ -81,7 +93,7 @@ async def main(): ) ##### COMPUTE - print('-----COMPUTE') + print("-----COMPUTE") # Bind the parties in the computation to the client to set input and output parties compute_bindings = nillion.ProgramBindings(program_id) @@ -91,7 +103,10 @@ async def main(): # Get cost quote, then pay for operation to compute receipt_compute = await pay( client, - nillion.Operation.compute(program_id, stored_secret), payments_wallet, payments_client, cluster_id + nillion.Operation.compute(program_id, stored_secret), + payments_wallet, + payments_client, + cluster_id, ) # Create a computation time secret to use @@ -117,11 +132,13 @@ async def main(): print(f"✅ Compute complete for compute_id {compute_event.uuid}") print(f"🖥️ The result is {compute_event.result.value}") return compute_event.result.value - + + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): result = await main() - assert result == {'my_output': 510} + assert result == {"my_output": 510} diff --git a/examples_and_tutorials/core_concept_single_party_compute/circuit_simple.py b/examples_and_tutorials/core_concept_single_party_compute/circuit_simple.py index 86194bcd..978dbab4 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/circuit_simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/circuit_simple.py @@ -6,44 +6,82 @@ from dotenv import load_dotenv -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() + # 1 Party running a simple circuit on 2 stored secrets and 2 compute time secrets async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_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_name="Party1" - program_name="circuit_simple" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + party_name = "Party1" + program_name = "circuit_simple" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" + + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + receipt_store_program = await pay( + client, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) # store program action_id = await client.store_program( - cluster_id, program_name, program_mir_path + cluster_id, program_name, program_mir_path, receipt_store_program ) - program_id=f"{user_id}/{program_name}" - print('Stored program. action_id:', action_id) - print('Stored program_id:', program_id) + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) + + # Set permissions for the client to compute on the program + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) # Create a secret - stored_secret = nillion.Secrets({ - "my_int1": nillion.SecretInteger(3), - "my_int2": nillion.SecretInteger(4), - }) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) + stored_secret = nillion.Secrets( + { + "my_int1": nillion.SecretInteger(3), + "my_int2": nillion.SecretInteger(4), + } + ) + + receipt_store = await pay( + client, + nillion.Operation.store_secrets(stored_secret), + payments_wallet, + payments_client, + cluster_id, + ) # Store a secret store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None + cluster_id, stored_secret, permissions, receipt_store ) # Bind the parties in the computation to the client to set input and output parties @@ -54,11 +92,19 @@ async def main(): print(f"Computing using program {program_id}") print(f"Use secret store_id: {store_id}") - computation_time_secrets = nillion.Secrets({ - "my_int3": nillion.SecretInteger(2), - "my_int4": nillion.SecretInteger(5) - }) - + computation_time_secrets = nillion.Secrets( + {"my_int3": nillion.SecretInteger(2), "my_int4": nillion.SecretInteger(5)} + ) + + # Pay for the compute + receipt_compute = await pay( + client, + nillion.Operation.compute(program_id, computation_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + # Compute on the secret compute_id = await client.compute( cluster_id, @@ -66,6 +112,7 @@ async def main(): [store_id], computation_time_secrets, nillion.PublicVariables({}), + receipt_compute, ) # Print compute result @@ -76,11 +123,13 @@ async def main(): print(f"✅ Compute complete for compute_id {compute_event.uuid}") print(f"🖥️ The result is {compute_event.result.value}") return compute_event.result.value - + + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): result = await main() - assert result == {'my_output': 22} + assert result == {"my_output": 22} diff --git a/examples_and_tutorials/core_concept_single_party_compute/circuit_simple_2.py b/examples_and_tutorials/core_concept_single_party_compute/circuit_simple_2.py index 0fe9db9d..0e03f31a 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/circuit_simple_2.py +++ b/examples_and_tutorials/core_concept_single_party_compute/circuit_simple_2.py @@ -6,48 +6,87 @@ from dotenv import load_dotenv -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() + # 1 Party running a simple circuit on 6 stored secrets and 3 compute time secrets async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_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_name="Party1" - program_name="circuit_simple_2" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + party_name = "Party1" + program_name = "circuit_simple_2" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" + + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + # Pay to store the program + receipt_store_program = await pay( + client, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) # store program action_id = await client.store_program( - cluster_id, program_name, program_mir_path + cluster_id, program_name, program_mir_path, receipt_store_program ) - program_id=f"{user_id}/{program_name}" - print('Stored program. action_id:', action_id) - print('Stored program_id:', program_id) + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) + + # Set permissions for the client to compute on the program + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) # Create a secret - stored_secret = nillion.Secrets({ - "A": nillion.SecretInteger(3), - "B": nillion.SecretInteger(14), - "C": nillion.SecretInteger(5), - "D": nillion.SecretInteger(6), - "E": nillion.SecretInteger(2), - "F": nillion.SecretInteger(1), - }) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) + stored_secret = nillion.Secrets( + { + "A": nillion.SecretInteger(3), + "B": nillion.SecretInteger(14), + "C": nillion.SecretInteger(5), + "D": nillion.SecretInteger(6), + "E": nillion.SecretInteger(2), + "F": nillion.SecretInteger(1), + } + ) + + receipt_store = await pay( + client, + nillion.Operation.store_secrets(stored_secret), + payments_wallet, + payments_client, + cluster_id, + ) # Store a secret store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None + cluster_id, stored_secret, permissions, receipt_store ) # Bind the parties in the computation to the client to set input and output parties @@ -58,12 +97,23 @@ async def main(): print(f"Computing using program {program_id}") print(f"Use secret store_id: {store_id}") - computation_time_secrets = nillion.Secrets({ - "G": nillion.SecretInteger(9), - "H": nillion.SecretInteger(10), - "I": nillion.SecretInteger(7), - }) - + computation_time_secrets = nillion.Secrets( + { + "G": nillion.SecretInteger(9), + "H": nillion.SecretInteger(10), + "I": nillion.SecretInteger(7), + } + ) + + # Pay for the compute + receipt_compute = await pay( + client, + nillion.Operation.compute(program_id, computation_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + # Compute on the secrets compute_id = await client.compute( cluster_id, @@ -71,6 +121,7 @@ async def main(): [store_id], computation_time_secrets, nillion.PublicVariables({}), + receipt_compute, ) # Print compute result @@ -81,11 +132,13 @@ async def main(): print(f"✅ Compute complete for compute_id {compute_event.uuid}") print(f"🖥️ The result is {compute_event.result.value}") return compute_event.result.value - + + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): result = await main() - assert result == {'my_output': 319} + assert result == {"my_output": 319} diff --git a/examples_and_tutorials/core_concept_single_party_compute/complex.py b/examples_and_tutorials/core_concept_single_party_compute/complex.py index 5cb0d2e7..86937541 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/complex.py +++ b/examples_and_tutorials/core_concept_single_party_compute/complex.py @@ -6,47 +6,88 @@ from dotenv import load_dotenv -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() + # 1 Party running complex program on 5 stored secrets and 1 runtime secret async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_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_name="Party1" - program_name="complex" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + party_name = "Party1" + program_name = "complex" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" + + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + # Pay to store the program + receipt_store_program = await pay( + client, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) # store program action_id = await client.store_program( - cluster_id, program_name, program_mir_path + cluster_id, program_name, program_mir_path, receipt_store_program ) - program_id=f"{user_id}/{program_name}" - print('Stored program. action_id:', action_id) - print('Stored program_id:', program_id) + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) + + # Set permissions for the client to compute on the program + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) # Create a secret - stored_secret = nillion.Secrets({ - "A": nillion.SecretInteger(10), - "B": nillion.SecretInteger(2), - "C": nillion.SecretInteger(1), - "D": nillion.SecretInteger(5), - "E": nillion.SecretInteger(4), - }) + stored_secret = nillion.Secrets( + { + "A": nillion.SecretInteger(10), + "B": nillion.SecretInteger(2), + "C": nillion.SecretInteger(1), + "D": nillion.SecretInteger(5), + "E": nillion.SecretInteger(4), + } + ) secret_bindings = nillion.ProgramBindings(program_id) secret_bindings.add_input_party(party_name, party_id) + receipt_store = await pay( + client, + nillion.Operation.store_secrets(stored_secret), + payments_wallet, + payments_client, + cluster_id, + ) + # Store a secret store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None + cluster_id, stored_secret, permissions, receipt_store ) # Bind the parties in the computation to the client to set input and output parties @@ -57,10 +98,21 @@ async def main(): print(f"Computing using program {program_id}") print(f"Use secret store_id: {store_id}") - computation_time_secrets = nillion.Secrets({ - "F": nillion.SecretInteger(3), - }) - + computation_time_secrets = nillion.Secrets( + { + "F": nillion.SecretInteger(3), + } + ) + + # Pay for the compute + receipt_compute = await pay( + client, + nillion.Operation.compute(program_id, computation_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + # Compute on the secret compute_id = await client.compute( cluster_id, @@ -68,6 +120,7 @@ async def main(): [store_id], computation_time_secrets, nillion.PublicVariables({}), + receipt_compute, ) # Print compute result @@ -78,11 +131,13 @@ async def main(): print(f"✅ Compute complete for compute_id {compute_event.uuid}") print(f"🖥️ The result is {compute_event.result.value}") return compute_event.result.value - + + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): result = await main() - assert result == {'FINAL': 52} + assert result == {"FINAL": 52} diff --git a/examples_and_tutorials/core_concept_single_party_compute/correlation_coefficient.py b/examples_and_tutorials/core_concept_single_party_compute/correlation_coefficient.py index d4f3d964..a67a6172 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/correlation_coefficient.py +++ b/examples_and_tutorials/core_concept_single_party_compute/correlation_coefficient.py @@ -1,3 +1,8 @@ +import asyncio +import py_nillion_client as nillion +import os +import sys +import pytest from pdb import set_trace as bp import argparse import asyncio @@ -9,37 +14,70 @@ from math import sqrt import pytest -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from dotenv import load_dotenv + +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() + # 1 Party running simple addition on 1 stored secret and 1 compute time secret async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_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_0_name="Party0" - party_1_name="Party1" - out_party_name="OutParty" - program_name="correlation_coefficient" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + party_0_name = "Party0" + party_1_name = "Party1" + out_party_name = "OutParty" + program_name = "correlation_coefficient" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" + + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + # Pay to store the program + receipt_store_program = await pay( + client, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) # store program action_id = await client.store_program( - cluster_id, program_name, program_mir_path + cluster_id, program_name, program_mir_path, receipt_store_program ) - program_id=f"{user_id}/{program_name}" - print('Stored program. action_id:', action_id) - print('Stored program_id:', program_id) - + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) + + # Set permissions for the client to compute on the program + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) + # Config elements - linear = lambda x: 30*x + 21 + linear = lambda x: 30 * x + 21 p0_points = 10 p1_points = 10 precision = 5 @@ -49,40 +87,39 @@ async def main(): # Create inputs for both parties party_0_dict = {} for i in range(p0_points): - party_0_dict[f"x{i}"] = nillion.SecretInteger(i+1) - party_0_dict[f"y{i}"] = nillion.SecretInteger(linear(i+1) + random.randint(0, 10)) + party_0_dict[f"x{i}"] = nillion.SecretInteger(i + 1) + party_0_dict[f"y{i}"] = nillion.SecretInteger( + linear(i + 1) + random.randint(0, 10) + ) party_1_dict = {} for i in range(p0_points, p0_points + p1_points): - party_1_dict[f"x{i}"] = nillion.SecretInteger(i+1) - party_1_dict[f"y{i}"] = nillion.SecretInteger(linear(i+1) + random.randint(0, 10)) - + party_1_dict[f"x{i}"] = nillion.SecretInteger(i + 1) + party_1_dict[f"y{i}"] = nillion.SecretInteger( + linear(i + 1) + random.randint(0, 10) + ) # Parties store the secrets party_0_secrets = nillion.Secrets(party_0_dict) party_1_secrets = nillion.Secrets(party_1_dict) - # Bind to the corresponding pary in the program - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_0_name, party_id) - secret_bindings.add_input_party(party_1_name, party_id) - # Give core_concept_permissions to owner to compute with my vote secret_permissions = nillion.Permissions.default_for_user(user_id) - secret_permissions.add_compute_permissions({ - user_id: {program_id}, - }) + secret_permissions.add_compute_permissions( + { + user_id: {program_id}, + } + ) store_ids = [] # Store in the network print(f"Storing party 0: {party_0_secrets}") store_id = await client.store_secrets( - cluster_id, secret_bindings, party_0_secrets, secret_permissions + cluster_id, party_0_secrets, secret_permissions ) store_ids.append(store_id) print(f"Stored party 0 with store_id ={store_id}") - print(f"Storing party 1: {party_1_secrets}") store_id = await client.store_secrets( cluster_id, secret_bindings, party_1_secrets, secret_permissions @@ -99,7 +136,7 @@ async def main(): print(f"Computing using program {program_id}") print(f"Use secret store_id: {store_id}") - + # Compute on the secret compute_id = await client.compute( cluster_id, @@ -116,17 +153,23 @@ async def main(): 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}") - corr_coeff_squared = compute_event.result.value["correlation_coefficient_squared"] / 10**precision + corr_coeff_squared = ( + compute_event.result.value["correlation_coefficient_squared"] + / 10**precision + ) sign = 1 if compute_event.result.value["sign"] else -1 corr_coeff = round(sign * sqrt(corr_coeff_squared), precision) - print(f"📈 Correlation coefficient = {corr_coeff} with precision {precision}.") + print( + f"📈 Correlation coefficient = {corr_coeff} with precision {precision}." + ) return compute_event.result.value if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): result = await main() - assert result == {'correlation_coefficient_squared': 99958, 'sign': 1} \ No newline at end of file + assert result == {"correlation_coefficient_squared": 99958, "sign": 1} diff --git a/examples_and_tutorials/core_concept_single_party_compute/division_simple.py b/examples_and_tutorials/core_concept_single_party_compute/division_simple.py index 10749918..b15700d4 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/division_simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/division_simple.py @@ -6,43 +6,81 @@ from dotenv import load_dotenv -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() + # 1 Party running simple division on 2 stored secrets async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_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_name="Party1" - program_name="division_simple" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + party_name = "Party1" + program_name = "division_simple" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" + + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + # Pay to store the program + receipt_store_program = await pay( + client, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) # store program action_id = await client.store_program( - cluster_id, program_name, program_mir_path + cluster_id, program_name, program_mir_path, receipt_store_program ) - program_id=f"{user_id}/{program_name}" - print('Stored program. action_id:', action_id) - print('Stored program_id:', program_id) + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) + + # Set permissions for the client to compute on the program + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) # Create a secret - stored_secret = nillion.Secrets({ - "my_int1": nillion.SecretInteger(10), - }) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) + stored_secret = nillion.Secrets( + { + "my_int1": nillion.SecretInteger(10), + } + ) + receipt_store = await pay( + client, + nillion.Operation.store_secrets(stored_secret), + payments_wallet, + payments_client, + cluster_id, + ) # Store a secret store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None + cluster_id, stored_secret, permissions, receipt_store ) # Bind the parties in the computation to the client to set input and output parties @@ -54,14 +92,28 @@ async def main(): print(f"Use secret store_id: {store_id}") computation_time_secrets = nillion.Secrets({}) - + + # Pay for the compute + receipt_compute = await pay( + client, + nillion.Operation.compute(program_id, computation_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + # Compute on the secret compute_id = await client.compute( cluster_id, compute_bindings, [store_id], computation_time_secrets, - nillion.PublicVariables({"my_int3": nillion.PublicVariableInteger(2),}), + nillion.PublicVariables( + { + "my_int3": nillion.PublicInteger(2), + } + ), + receipt_compute, ) # Print compute result @@ -72,11 +124,13 @@ async def main(): print(f"✅ Compute complete for compute_id {compute_event.uuid}") print(f"🖥️ The result is {compute_event.result.value}") return compute_event.result.value - + + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): result = await main() - assert result == {'my_output': 5} + assert result == {"my_output": 5} diff --git a/examples_and_tutorials/core_concept_single_party_compute/input_integer.py b/examples_and_tutorials/core_concept_single_party_compute/input_integer.py index ab468c81..d4f03f51 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/input_integer.py +++ b/examples_and_tutorials/core_concept_single_party_compute/input_integer.py @@ -6,43 +6,82 @@ from dotenv import load_dotenv -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() + # 1 Party, returns the secret input async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_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_name="Party1" - program_name="input_integer" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + party_name = "Party1" + program_name = "input_integer" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" + + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + # Pay to store the program + receipt_store_program = await pay( + client, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) # store program action_id = await client.store_program( - cluster_id, program_name, program_mir_path + cluster_id, program_name, program_mir_path, receipt_store_program ) - program_id=f"{user_id}/{program_name}" - print('Stored program. action_id:', action_id) - print('Stored program_id:', program_id) + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) + + # Set permissions for the client to compute on the program + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) # Create a secret - stored_secret = nillion.Secrets({ - "a": nillion.SecretInteger(16), - }) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) + stored_secret = nillion.Secrets( + { + "a": nillion.SecretInteger(16), + } + ) + + receipt_store = await pay( + client, + nillion.Operation.store_secrets(stored_secret), + payments_wallet, + payments_client, + cluster_id, + ) # Store a secret store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None + cluster_id, stored_secret, permissions, receipt_store ) # Bind the parties in the computation to the client to set input and output parties @@ -53,14 +92,25 @@ async def main(): print(f"Computing using program {program_id}") print(f"Use secret store_id: {store_id}") - + compute_time_secrets = nillion.Secrets({}) + + # Pay for the compute + receipt_compute = await pay( + client, + nillion.Operation.compute(program_id, compute_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + # Compute on the secret compute_id = await client.compute( cluster_id, compute_bindings, [store_id], - nillion.Secrets({}), + compute_time_secrets, nillion.PublicVariables({}), + receipt_compute, ) # Print compute result @@ -71,11 +121,13 @@ async def main(): print(f"✅ Compute complete for compute_id {compute_event.uuid}") print(f"🖥️ The result is {compute_event.result.value}") return compute_event.result.value - + + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): result = await main() - assert result == {'my_output': 16} + assert result == {"my_output": 16} diff --git a/examples_and_tutorials/core_concept_single_party_compute/input_single.py b/examples_and_tutorials/core_concept_single_party_compute/input_single.py index da14b96f..62151ccb 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/input_single.py +++ b/examples_and_tutorials/core_concept_single_party_compute/input_single.py @@ -6,43 +6,82 @@ from dotenv import load_dotenv -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() + # 1 Party, returns the secret input async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_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_name="Party1" - program_name="input_single" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + party_name = "Party1" + program_name = "input_single" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" + + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + # Pay to store the program + receipt_store_program = await pay( + client, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) # store program action_id = await client.store_program( - cluster_id, program_name, program_mir_path + cluster_id, program_name, program_mir_path, receipt_store_program ) - program_id=f"{user_id}/{program_name}" - print('Stored program. action_id:', action_id) - print('Stored program_id:', program_id) + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) + + # Set permissions for the client to compute on the program + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) # Create a secret - stored_secret = nillion.Secrets({ - "my_int1": nillion.SecretInteger(91), - }) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) + stored_secret = nillion.Secrets( + { + "my_int1": nillion.SecretInteger(91), + } + ) + + receipt_store = await pay( + client, + nillion.Operation.store_secrets(stored_secret), + payments_wallet, + payments_client, + cluster_id, + ) # Store a secret store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None + cluster_id, stored_secret, permissions, receipt_store ) # Bind the parties in the computation to the client to set input and output parties @@ -53,14 +92,25 @@ async def main(): print(f"Computing using program {program_id}") print(f"Use secret store_id: {store_id}") - + compute_time_secrets = nillion.Secrets({}) + + # Pay for the compute + receipt_compute = await pay( + client, + nillion.Operation.compute(program_id, compute_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + # Compute on the secret compute_id = await client.compute( cluster_id, compute_bindings, [store_id], - nillion.Secrets({}), + compute_time_secrets, nillion.PublicVariables({}), + receipt_compute, ) # Print compute result @@ -71,11 +121,13 @@ async def main(): print(f"✅ Compute complete for compute_id {compute_event.uuid}") print(f"🖥️ The result is {compute_event.result.value}") return compute_event.result.value - + + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): result = await main() - assert result == {'my_output': 91} + assert result == {"my_output": 91} diff --git a/examples_and_tutorials/core_concept_single_party_compute/modulo_simple.py b/examples_and_tutorials/core_concept_single_party_compute/modulo_simple.py index 6dc55160..b5e21c33 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/modulo_simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/modulo_simple.py @@ -6,43 +6,82 @@ from dotenv import load_dotenv -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() + # 1 Party running modulo on 1 stored secrets and a public variable async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_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_name="Party1" - program_name="modulo_simple" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + party_name = "Party1" + program_name = "modulo_simple" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" + + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + # Pay to store the program + receipt_store_program = await pay( + client, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) # store program action_id = await client.store_program( - cluster_id, program_name, program_mir_path + cluster_id, program_name, program_mir_path, receipt_store_program ) - program_id=f"{user_id}/{program_name}" - print('Stored program. action_id:', action_id) - print('Stored program_id:', program_id) + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) + + # Set permissions for the client to compute on the program + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) # Create a secret - stored_secret = nillion.Secrets({ - "my_int1": nillion.SecretInteger(37), - }) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) + stored_secret = nillion.Secrets( + { + "my_int1": nillion.SecretInteger(37), + } + ) + + receipt_store = await pay( + client, + nillion.Operation.store_secrets(stored_secret), + payments_wallet, + payments_client, + cluster_id, + ) # Store a secret store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None + cluster_id, stored_secret, permissions, receipt_store ) # Bind the parties in the computation to the client to set input and output parties @@ -54,10 +93,20 @@ async def main(): print(f"Use secret store_id: {store_id}") computation_time_secrets = nillion.Secrets({}) - public_variables = nillion.PublicVariables({ - "public_my_int2": nillion.PublicVariableInteger(12) - }) - + + # Pay for the compute + receipt_compute = await pay( + client, + nillion.Operation.compute(program_id, computation_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + + public_variables = nillion.PublicVariables( + {"public_my_int2": nillion.PublicVariableInteger(12)} + ) + # Compute on the secret compute_id = await client.compute( cluster_id, @@ -65,6 +114,7 @@ async def main(): [store_id], computation_time_secrets, public_variables, + receipt_compute, ) # Print compute result @@ -75,11 +125,13 @@ async def main(): print(f"✅ Compute complete for compute_id {compute_event.uuid}") print(f"🖥️ The result is {compute_event.result.value}") return compute_event.result.value - + + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): result = await main() - assert result == {'my_output': 1} + assert result == {"my_output": 1} diff --git a/examples_and_tutorials/core_concept_single_party_compute/multiplication_simple.py b/examples_and_tutorials/core_concept_single_party_compute/multiplication_simple.py index 2f682ddf..22ed5813 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/multiplication_simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/multiplication_simple.py @@ -6,44 +6,83 @@ from dotenv import load_dotenv -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() + # 1 Party running simple multiplication on 1 stored secret and 1 compute time secret async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_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 user_id = client.user_id - party_name="Party1" - program_name="multiplication_simple" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + party_name = "Party1" + program_name = "multiplication_simple" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" + + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + # Pay to store the program + receipt_store_program = await pay( + client, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) # store program action_id = await client.store_program( - cluster_id, program_name, program_mir_path + cluster_id, program_name, program_mir_path, receipt_store_program ) - program_id=f"{user_id}/{program_name}" - print('Stored program. action_id:', action_id) - print('Stored program_id:', program_id) + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) + + # Set permissions for the client to compute on the program + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) # Create a secret - stored_secret = nillion.Secrets({ - "my_int1": nillion.SecretInteger(500), - }) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) + stored_secret = nillion.Secrets( + { + "my_int1": nillion.SecretInteger(500), + } + ) + + receipt_store = await pay( + client, + nillion.Operation.store_secrets(stored_secret), + payments_wallet, + payments_client, + cluster_id, + ) # Store a secret store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None + cluster_id, stored_secret, permissions, receipt_store ) # Bind the parties in the computation to the client to set input and output parties @@ -55,7 +94,16 @@ async def main(): print(f"Use secret store_id: {store_id}") computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) - + + # Pay for the compute + receipt_compute = await pay( + client, + nillion.Operation.compute(program_id, computation_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + # Compute on the secret compute_id = await client.compute( cluster_id, @@ -63,6 +111,7 @@ async def main(): [store_id], computation_time_secrets, nillion.PublicVariables({}), + receipt_compute, ) # Print compute result @@ -73,11 +122,13 @@ async def main(): print(f"✅ Compute complete for compute_id {compute_event.uuid}") print(f"🖥️ The result is {compute_event.result.value}") return compute_event.result.value - + + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): result = await main() - assert result == {'my_output': 5000} + assert result == {"my_output": 5000} diff --git a/examples_and_tutorials/core_concept_single_party_compute/nada_fn_composition.py b/examples_and_tutorials/core_concept_single_party_compute/nada_fn_composition.py index a4987ff0..e171837c 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/nada_fn_composition.py +++ b/examples_and_tutorials/core_concept_single_party_compute/nada_fn_composition.py @@ -6,42 +6,81 @@ from dotenv import load_dotenv -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() + async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_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_name="Party1" - program_name="nada_fn_composition" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + party_name = "Party1" + program_name = "nada_fn_composition" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" + + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + # Pay to store the program + receipt_store_program = await pay( + client, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) # store program action_id = await client.store_program( - cluster_id, program_name, program_mir_path + cluster_id, program_name, program_mir_path, receipt_store_program ) - program_id=f"{user_id}/{program_name}" - print('Stored program. action_id:', action_id) - print('Stored program_id:', program_id) + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) + + # Set permissions for the client to compute on the program + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) # Create a secret - stored_secret = nillion.Secrets({ - "my_int1": nillion.SecretInteger(500), - }) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) + stored_secret = nillion.Secrets( + { + "my_int1": nillion.SecretInteger(500), + } + ) + + receipt_store = await pay( + client, + nillion.Operation.store_secrets(stored_secret), + payments_wallet, + payments_client, + cluster_id, + ) # Store a secret store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None + cluster_id, stored_secret, permissions, receipt_store ) # Bind the parties in the computation to the client to set input and output parties @@ -50,10 +89,18 @@ async def main(): compute_bindings.add_output_party(party_name, party_id) print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) - + + # Pay for the compute + receipt_compute = await pay( + client, + nillion.Operation.compute(program_id, computation_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + # Compute on the secret compute_id = await client.compute( cluster_id, @@ -61,6 +108,7 @@ async def main(): [store_id], computation_time_secrets, nillion.PublicVariables({}), + receipt_compute, ) # Print compute result @@ -71,11 +119,13 @@ async def main(): print(f"✅ Compute complete for compute_id {compute_event.uuid}") print(f"🖥️ The result is {compute_event.result.value}") return compute_event.result.value - + + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): result = await main() - assert result == {'my_output': 255000} + assert result == {"my_output": 255000} diff --git a/examples_and_tutorials/core_concept_single_party_compute/nada_fn_simple.py b/examples_and_tutorials/core_concept_single_party_compute/nada_fn_simple.py deleted file mode 100644 index f45734c9..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/nada_fn_simple.py +++ /dev/null @@ -1,81 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from dotenv import load_dotenv - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() - -async def main(): - 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_name="Party1" - program_name="nada_fn_simple" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" - - # store 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 a secret - stored_secret = nillion.Secrets({ - "my_int1": nillion.SecretInteger(500), - }) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) - - # Store a secret - store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) - - # Compute on the secret - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - nillion.PublicVariables({}), - ) - - # Print compute 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 - -if __name__ == "__main__": - asyncio.run(main()) - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {'my_output': 510} diff --git a/examples_and_tutorials/core_concept_single_party_compute/reduce_simple.py b/examples_and_tutorials/core_concept_single_party_compute/reduce_simple.py deleted file mode 100644 index 52766d9d..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/reduce_simple.py +++ /dev/null @@ -1,88 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from dotenv import load_dotenv - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() - -async def main(): - 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_name="Party1" - program_name="reduce_simple" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" - - # store 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 a secret array - secret_name = "my_array_1" - - secret_array= nillion.SecretArray([ - nillion.SecretInteger(1), - nillion.SecretInteger(2), - nillion.SecretInteger(3), - nillion.SecretInteger(4), - ]) - - stored_secret_array = nillion.Secrets({secret_name: secret_array}) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) - - # Store a secret array - store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret_array, None - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - computation_time_secrets = nillion.Secrets({"my_int1": nillion.SecretInteger(5)}) - - # Compute on the secret - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - nillion.PublicVariables({}), - ) - - # Print compute 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 - -if __name__ == "__main__": - asyncio.run(main()) - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {'reduce.addition': 15} diff --git a/examples_and_tutorials/core_concept_single_party_compute/reuse.py b/examples_and_tutorials/core_concept_single_party_compute/reuse.py index 55d61343..55a58f12 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/reuse.py +++ b/examples_and_tutorials/core_concept_single_party_compute/reuse.py @@ -6,8 +6,16 @@ from dotenv import load_dotenv -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() @@ -15,35 +23,67 @@ async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_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_name="Party1" - program_name="reuse" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + party_name = "Party1" + program_name = "reuse" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" + + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + # Pay to store the program + receipt_store_program = await pay( + client, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) # store program action_id = await client.store_program( - cluster_id, program_name, program_mir_path + cluster_id, program_name, program_mir_path, receipt_store_program ) - program_id=f"{user_id}/{program_name}" - print('Stored program. action_id:', action_id) - print('Stored program_id:', program_id) + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) + + # Set permissions for the client to compute on the program + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) # Create a secret - stored_secret = nillion.Secrets({ - "A": nillion.SecretInteger(3), - "B": nillion.SecretInteger(4), - }) + stored_secret = nillion.Secrets( + { + "A": nillion.SecretInteger(3), + "B": nillion.SecretInteger(4), + } + ) secret_bindings = nillion.ProgramBindings(program_id) secret_bindings.add_input_party(party_name, party_id) + receipt_store = await pay( + client, + nillion.Operation.store_secrets(stored_secret), + payments_wallet, + payments_client, + cluster_id, + ) + # Store a secret store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None + cluster_id, stored_secret, permissions, receipt_store ) # Bind the parties in the computation to the client to set input and output parties @@ -54,10 +94,21 @@ async def main(): print(f"Computing using program {program_id}") print(f"Use secret store_id: {store_id}") - computation_time_secrets = nillion.Secrets({ - "C": nillion.SecretInteger(5), - }) - + computation_time_secrets = nillion.Secrets( + { + "C": nillion.SecretInteger(5), + } + ) + + # Pay for the compute + receipt_compute = await pay( + client, + nillion.Operation.compute(program_id, computation_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + # Compute on the secrets compute_id = await client.compute( cluster_id, @@ -65,6 +116,7 @@ async def main(): [store_id], computation_time_secrets, nillion.PublicVariables({}), + receipt_compute, ) # Print compute result @@ -75,11 +127,13 @@ async def main(): print(f"✅ Compute complete for compute_id {compute_event.uuid}") print(f"🖥️ The result is {compute_event.result.value}") return compute_event.result.value - + + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): result = await main() - assert result == {'R': 27} + assert result == {"R": 27} diff --git a/examples_and_tutorials/core_concept_single_party_compute/reuse_flipped1.py b/examples_and_tutorials/core_concept_single_party_compute/reuse_flipped1.py deleted file mode 100644 index 57e74be8..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/reuse_flipped1.py +++ /dev/null @@ -1,86 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from dotenv import load_dotenv - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() - - -async def main(): - 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_name="Party1" - program_name="reuse_flipped1" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" - - # store 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 a secret - stored_secret = nillion.Secrets({ - "A": nillion.SecretInteger(3), - "B": nillion.SecretInteger(14), - - }) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) - - # Store a secret - store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - computation_time_secrets = nillion.Secrets({ - "C": nillion.SecretInteger(5), - }) - - # Compute on the secrets - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - nillion.PublicVariables({}), - ) - - # Print compute 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 - -if __name__ == "__main__": - asyncio.run(main()) - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {'R': 57} diff --git a/examples_and_tutorials/core_concept_single_party_compute/reuse_flipped2.py b/examples_and_tutorials/core_concept_single_party_compute/reuse_flipped2.py deleted file mode 100644 index 006d8f89..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/reuse_flipped2.py +++ /dev/null @@ -1,86 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from dotenv import load_dotenv - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() - - -async def main(): - 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_name="Party1" - program_name="reuse_flipped2" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" - - # store 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 a secret - stored_secret = nillion.Secrets({ - "A": nillion.SecretInteger(3), - "B": nillion.SecretInteger(14), - - }) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) - - # Store a secret - store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - computation_time_secrets = nillion.Secrets({ - "C": nillion.SecretInteger(5), - }) - - # Compute on the secrets - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - nillion.PublicVariables({}), - ) - - # Print compute 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 - -if __name__ == "__main__": - asyncio.run(main()) - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {'R': 112} diff --git a/examples_and_tutorials/core_concept_single_party_compute/reuse_simple_1.py b/examples_and_tutorials/core_concept_single_party_compute/reuse_simple_1.py deleted file mode 100644 index 44716cee..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/reuse_simple_1.py +++ /dev/null @@ -1,83 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from dotenv import load_dotenv - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() - - -async def main(): - 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_name="Party1" - program_name="reuse_simple_1" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" - - # store 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 a secret - stored_secret = nillion.Secrets({ - "A": nillion.SecretInteger(3), - "B": nillion.SecretInteger(14), - }) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) - - # Store a secret - store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - computation_time_secrets = nillion.Secrets({}) - - # Compute on the secrets - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - nillion.PublicVariables({}), - ) - - # Print compute 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 - -if __name__ == "__main__": - asyncio.run(main()) - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {'my_output': 84} diff --git a/examples_and_tutorials/core_concept_single_party_compute/reuse_simple_2.py b/examples_and_tutorials/core_concept_single_party_compute/reuse_simple_2.py deleted file mode 100644 index a10f2617..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/reuse_simple_2.py +++ /dev/null @@ -1,83 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from dotenv import load_dotenv - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() - - -async def main(): - 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_name="Party1" - program_name="reuse_simple_2" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" - - # store 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 a secret - stored_secret = nillion.Secrets({ - "A": nillion.SecretInteger(3), - "B": nillion.SecretInteger(4), - }) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) - - # Store a secret - store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - computation_time_secrets = nillion.Secrets({}) - - # Compute on the secrets - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - nillion.PublicVariables({}), - ) - - # Print compute 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 - -if __name__ == "__main__": - asyncio.run(main()) - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {'my_output': 49} diff --git a/examples_and_tutorials/core_concept_single_party_compute/reuse_simple_sub.py b/examples_and_tutorials/core_concept_single_party_compute/reuse_simple_sub.py deleted file mode 100644 index 3becc296..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/reuse_simple_sub.py +++ /dev/null @@ -1,84 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from dotenv import load_dotenv - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() - - -async def main(): - 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_name="Party1" - program_name="reuse_simple_sub" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" - - # store 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 a secret - stored_secret = nillion.Secrets({ - "A": nillion.SecretInteger(50), - }) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) - - # Store a secret - store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - computation_time_secrets = nillion.Secrets({ - "B": nillion.SecretInteger(20), - }) - - # Compute on the secrets - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - nillion.PublicVariables({}), - ) - - # Print compute 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 - -if __name__ == "__main__": - asyncio.run(main()) - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {'my_output': 2100} diff --git a/examples_and_tutorials/core_concept_single_party_compute/simple.py b/examples_and_tutorials/core_concept_single_party_compute/simple.py index ef2b1fef..43e93c0c 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/simple.py @@ -6,45 +6,83 @@ from dotenv import load_dotenv -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() + async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_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_name="Party1" - program_name="simple" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + party_name = "Party1" + program_name = "simple" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" + + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + # Pay to store the program + receipt_store_program = await pay( + client, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) # store program action_id = await client.store_program( - cluster_id, program_name, program_mir_path + cluster_id, program_name, program_mir_path, receipt_store_program ) - program_id=f"{user_id}/{program_name}" - print('Stored program. action_id:', action_id) - print('Stored program_id:', program_id) + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) + + # Set permissions for the client to compute on the program + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) # Create a secret - stored_secret = nillion.Secrets({ - "A": nillion.SecretInteger(3), - "B": nillion.SecretInteger(14), - "C": nillion.SecretInteger(5), - "D": nillion.SecretInteger(6), - }) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) + stored_secret = nillion.Secrets( + { + "A": nillion.SecretInteger(3), + "B": nillion.SecretInteger(14), + "C": nillion.SecretInteger(5), + "D": nillion.SecretInteger(6), + } + ) + receipt_store = await pay( + client, + nillion.Operation.store_secrets(stored_secret), + payments_wallet, + payments_client, + cluster_id, + ) # Store a secret store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None + cluster_id, stored_secret, permissions, receipt_store ) # Bind the parties in the computation to the client to set input and output parties @@ -56,7 +94,16 @@ async def main(): print(f"Use secret store_id: {store_id}") computation_time_secrets = nillion.Secrets({}) - + + # Pay for the compute + receipt_compute = await pay( + client, + nillion.Operation.compute(program_id, computation_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + # Compute on the secrets compute_id = await client.compute( cluster_id, @@ -64,6 +111,7 @@ async def main(): [store_id], computation_time_secrets, nillion.PublicVariables({}), + receipt_compute, ) # Print compute result @@ -74,11 +122,13 @@ async def main(): print(f"✅ Compute complete for compute_id {compute_event.uuid}") print(f"🖥️ The result is {compute_event.result.value}") return compute_event.result.value - + + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): result = await main() - assert result == {'O': 72} + assert result == {"O": 72} diff --git a/examples_and_tutorials/core_concept_single_party_compute/simple_literals.py b/examples_and_tutorials/core_concept_single_party_compute/simple_literals.py index 7dba12cf..a0aeb790 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/simple_literals.py +++ b/examples_and_tutorials/core_concept_single_party_compute/simple_literals.py @@ -6,43 +6,81 @@ from dotenv import load_dotenv -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() + async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_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_name="Party1" - program_name="simple_literals" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + party_name = "Party1" + program_name = "simple_literals" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" + + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + # Pay to store the program + receipt_store_program = await pay( + client, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) # store program action_id = await client.store_program( - cluster_id, program_name, program_mir_path + cluster_id, program_name, program_mir_path, receipt_store_program ) - program_id=f"{user_id}/{program_name}" - print('Stored program. action_id:', action_id) - print('Stored program_id:', program_id) + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) + + # Set permissions for the client to compute on the program + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) # Create a secret - stored_secret = nillion.Secrets({ - "A": nillion.SecretInteger(3), - "C": nillion.SecretInteger(5), - }) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) + stored_secret = nillion.Secrets( + { + "A": nillion.SecretInteger(3), + "C": nillion.SecretInteger(5), + } + ) + receipt_store = await pay( + client, + nillion.Operation.store_secrets(stored_secret), + payments_wallet, + payments_client, + cluster_id, + ) # Store a secret store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None + cluster_id, stored_secret, permissions, receipt_store ) # Bind the parties in the computation to the client to set input and output parties @@ -54,7 +92,16 @@ async def main(): print(f"Use secret store_id: {store_id}") computation_time_secrets = nillion.Secrets({}) - + + # Pay for the compute + receipt_compute = await pay( + client, + nillion.Operation.compute(program_id, computation_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + # Compute on the secrets compute_id = await client.compute( cluster_id, @@ -62,6 +109,7 @@ async def main(): [store_id], computation_time_secrets, nillion.PublicVariables({}), + receipt_compute, ) # Print compute result @@ -72,11 +120,13 @@ async def main(): print(f"✅ Compute complete for compute_id {compute_event.uuid}") print(f"🖥️ The result is {compute_event.result.value}") return compute_event.result.value - + + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): result = await main() - assert result == {'O': 1015} + assert result == {"O": 1015} diff --git a/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables.py b/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables.py index 95354635..8a89b585 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables.py +++ b/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables.py @@ -6,44 +6,82 @@ from dotenv import load_dotenv -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() + # 1 Party running compute with only public variables async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_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_name="Party1" - program_name="simple_public_variables" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + party_name = "Party1" + program_name = "simple_public_variables" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" + + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + # Pay to store the program + receipt_store_program = await pay( + client, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) # store program action_id = await client.store_program( - cluster_id, program_name, program_mir_path + cluster_id, program_name, program_mir_path, receipt_store_program ) - program_id=f"{user_id}/{program_name}" - print('Stored program. action_id:', action_id) - print('Stored program_id:', program_id) + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) + + # Set permissions for the client to compute on the program + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) # Create a secret - stored_secret = nillion.Secrets({ - "A": nillion.SecretInteger(3), - "C": nillion.SecretInteger(5), - }) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) + stored_secret = nillion.Secrets( + { + "A": nillion.SecretInteger(3), + "C": nillion.SecretInteger(5), + } + ) + receipt_store = await pay( + client, + nillion.Operation.store_secrets(stored_secret), + payments_wallet, + payments_client, + cluster_id, + ) # Store a secret store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None + cluster_id, stored_secret, permissions, receipt_store ) # Bind the parties in the computation to the client to set input and output parties @@ -54,18 +92,32 @@ async def main(): print(f"Computing using program {program_id}") print(f"Use secret store_id: {store_id}") - public_variables = nillion.PublicVariables({ - "B": nillion.PublicVariableInteger(10), - "D": nillion.PublicVariableInteger(6), - }) - + public_variables = nillion.PublicVariables( + { + "B": nillion.PublicInteger(10), + "D": nillion.PublicInteger(6), + } + ) + + computation_time_secrets = nillion.Secrets({}) + + # Pay for the compute + receipt_compute = await pay( + client, + nillion.Operation.compute(program_id, computation_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + # Compute on the secrets compute_id = await client.compute( cluster_id, compute_bindings, [store_id], - nillion.Secrets({}), + computation_time_secrets, public_variables, + receipt_compute, ) # Print compute result @@ -76,11 +128,13 @@ async def main(): print(f"✅ Compute complete for compute_id {compute_event.uuid}") print(f"🖥️ The result is {compute_event.result.value}") return compute_event.result.value - + + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): result = await main() - assert result == {'O': 119} + assert result == {"O": 119} diff --git a/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables_only.py b/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables_only.py index ba97ffba..a00956c4 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables_only.py +++ b/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables_only.py @@ -6,32 +6,63 @@ from dotenv import load_dotenv -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() + # 1 Party running compute with only public variables async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_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_name="Party1" - program_name="simple_public_variables_only" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + party_name = "Party1" + program_name = "simple_public_variables_only" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" + + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + # Pay to store the program + receipt_store_program = await pay( + client, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) # store program action_id = await client.store_program( - cluster_id, program_name, program_mir_path + cluster_id, program_name, program_mir_path, receipt_store_program ) - program_id=f"{user_id}/{program_name}" - print('Stored program. action_id:', action_id) - print('Stored program_id:', program_id) + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) + + # Set permissions for the client to compute on the program + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) # Bind the parties in the computation to the client to set input and output parties compute_bindings = nillion.ProgramBindings(program_id) @@ -40,11 +71,24 @@ async def main(): print(f"Computing using program {program_id}") - public_variables = nillion.PublicVariables({ - "A": nillion.PublicVariableInteger(10), - "B": nillion.PublicVariableInteger(6), - }) - + public_variables = nillion.PublicVariables( + { + "A": nillion.PublicInteger(10), + "B": nillion.PublicInteger(6), + } + ) + + computation_time_secrets = nillion.Secrets({}) + + # Pay for the compute + receipt_compute = await pay( + client, + nillion.Operation.compute(program_id, computation_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + # Compute on the secrets compute_id = await client.compute( cluster_id, @@ -52,6 +96,7 @@ async def main(): [], nillion.Secrets({}), public_variables, + receipt_compute, ) # Print compute result @@ -62,11 +107,13 @@ async def main(): print(f"✅ Compute complete for compute_id {compute_event.uuid}") print(f"🖥️ The result is {compute_event.result.value}") return compute_event.result.value - + + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): result = await main() - assert result == {'O': 60} + assert result == {"O": 60} diff --git a/examples_and_tutorials/core_concept_single_party_compute/simple_sub.py b/examples_and_tutorials/core_concept_single_party_compute/simple_sub.py index 261a66df..3f53851b 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/simple_sub.py +++ b/examples_and_tutorials/core_concept_single_party_compute/simple_sub.py @@ -6,45 +6,83 @@ from dotenv import load_dotenv -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() + async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_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_name="Party1" - program_name="simple_sub" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + party_name = "Party1" + program_name = "simple_sub" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" + + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + # Pay to store the program + receipt_store_program = await pay( + client, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) # store program action_id = await client.store_program( - cluster_id, program_name, program_mir_path + cluster_id, program_name, program_mir_path, receipt_store_program ) - program_id=f"{user_id}/{program_name}" - print('Stored program. action_id:', action_id) - print('Stored program_id:', program_id) + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) + + # Set permissions for the client to compute on the program + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) # Create a secret - stored_secret = nillion.Secrets({ - "A": nillion.SecretInteger(3), - "B": nillion.SecretInteger(14), - "C": nillion.SecretInteger(5), - "D": nillion.SecretInteger(6), - }) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) + stored_secret = nillion.Secrets( + { + "A": nillion.SecretInteger(3), + "B": nillion.SecretInteger(14), + "C": nillion.SecretInteger(5), + "D": nillion.SecretInteger(6), + } + ) + receipt_store = await pay( + client, + nillion.Operation.store_secrets(stored_secret), + payments_wallet, + payments_client, + cluster_id, + ) # Store a secret store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None + cluster_id, stored_secret, permissions, receipt_store ) # Bind the parties in the computation to the client to set input and output parties @@ -56,7 +94,16 @@ async def main(): print(f"Use secret store_id: {store_id}") computation_time_secrets = nillion.Secrets({}) - + + # Pay for the compute + receipt_compute = await pay( + client, + nillion.Operation.compute(program_id, computation_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + # Compute on the secrets compute_id = await client.compute( cluster_id, @@ -64,6 +111,7 @@ async def main(): [store_id], computation_time_secrets, nillion.PublicVariables({}), + receipt_compute, ) # Print compute result @@ -74,11 +122,13 @@ async def main(): print(f"✅ Compute complete for compute_id {compute_event.uuid}") print(f"🖥️ The result is {compute_event.result.value}") return compute_event.result.value - + + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): result = await main() - assert result == {'O': 12} + assert result == {"O": 12} diff --git a/examples_and_tutorials/core_concept_single_party_compute/single_addition.py b/examples_and_tutorials/core_concept_single_party_compute/single_addition.py deleted file mode 100644 index ff36d500..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/single_addition.py +++ /dev/null @@ -1,84 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from dotenv import load_dotenv - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() - -# 1 Party running a simple circuit on 6 stored secrets and 3 compute time secrets -async def main(): - 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_name="Party1" - program_name="single_addition" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" - - # store 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 a secret - stored_secret = nillion.Secrets({ - "A": nillion.SecretInteger(3), - }) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) - - # Store a secret - store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - computation_time_secrets = nillion.Secrets({ - "B": nillion.SecretInteger(14), - }) - - # Compute on the secrets - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - nillion.PublicVariables({}), - ) - - # Print compute 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 - -if __name__ == "__main__": - asyncio.run(main()) - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {'R': 17} diff --git a/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple.py b/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple.py index e25df271..aa5d07ef 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple.py @@ -6,43 +6,81 @@ from dotenv import load_dotenv -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() + # 1 Party running a simple circuit on 6 stored secrets and 3 compute time secrets async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_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_name="Party1" - program_name="subtraction_simple" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + party_name = "Party1" + program_name = "subtraction_simple" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" + + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + # Pay to store the program + receipt_store_program = await pay( + client, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) # store program action_id = await client.store_program( - cluster_id, program_name, program_mir_path + cluster_id, program_name, program_mir_path, receipt_store_program ) - program_id=f"{user_id}/{program_name}" - print('Stored program. action_id:', action_id) - print('Stored program_id:', program_id) + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) + + # Set permissions for the client to compute on the program + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) # Create a secret - stored_secret = nillion.Secrets({ - "my_int1": nillion.SecretInteger(30), - }) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) + stored_secret = nillion.Secrets( + { + "my_int1": nillion.SecretInteger(30), + } + ) + receipt_store = await pay( + client, + nillion.Operation.store_secrets(stored_secret), + payments_wallet, + payments_client, + cluster_id, + ) # Store a secret store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None + cluster_id, stored_secret, permissions, receipt_store ) # Bind the parties in the computation to the client to set input and output parties @@ -53,10 +91,21 @@ async def main(): print(f"Computing using program {program_id}") print(f"Use secret store_id: {store_id}") - computation_time_secrets = nillion.Secrets({ - "my_int2": nillion.SecretInteger(90), - }) - + computation_time_secrets = nillion.Secrets( + { + "my_int2": nillion.SecretInteger(90), + } + ) + + # Pay for the compute + receipt_compute = await pay( + client, + nillion.Operation.compute(program_id, computation_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + # Compute on the secrets compute_id = await client.compute( cluster_id, @@ -64,6 +113,7 @@ async def main(): [store_id], computation_time_secrets, nillion.PublicVariables({}), + receipt_compute, ) # Print compute result @@ -74,11 +124,13 @@ async def main(): print(f"✅ Compute complete for compute_id {compute_event.uuid}") print(f"🖥️ The result is {compute_event.result.value}") return compute_event.result.value - + + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): result = await main() - assert result == {'my_output': 60} + assert result == {"my_output": 60} diff --git a/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple_neg.py b/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple_neg.py index 8bdb441a..57c83718 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple_neg.py +++ b/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple_neg.py @@ -6,42 +6,80 @@ from dotenv import load_dotenv -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() + async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_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_name="Party1" - program_name="subtraction_simple_neg" - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + party_name = "Party1" + program_name = "subtraction_simple_neg" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" + + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + # Pay to store the program + receipt_store_program = await pay( + client, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) # store program action_id = await client.store_program( - cluster_id, program_name, program_mir_path + cluster_id, program_name, program_mir_path, receipt_store_program ) - program_id=f"{user_id}/{program_name}" - print('Stored program. action_id:', action_id) - print('Stored program_id:', program_id) + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) + + # Set permissions for the client to compute on the program + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) # Create a secret - stored_secret = nillion.Secrets({ - "my_int4": nillion.SecretInteger(3), - }) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) + stored_secret = nillion.Secrets( + { + "my_int4": nillion.SecretInteger(3), + } + ) + receipt_store = await pay( + client, + nillion.Operation.store_secrets(stored_secret), + payments_wallet, + payments_client, + cluster_id, + ) # Store a secret store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None + cluster_id, stored_secret, permissions, receipt_store ) # Bind the parties in the computation to the client to set input and output parties @@ -52,10 +90,17 @@ async def main(): print(f"Computing using program {program_id}") print(f"Use secret store_id: {store_id}") - computation_time_secrets = nillion.Secrets({ - "my_int2": nillion.SecretInteger(9) - }) - + computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(9)}) + + # Pay for the compute + receipt_compute = await pay( + client, + nillion.Operation.compute(program_id, computation_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + # Compute on the secrets compute_id = await client.compute( cluster_id, @@ -63,6 +108,7 @@ async def main(): [store_id], computation_time_secrets, nillion.PublicVariables({}), + receipt_compute, ) # Print compute result @@ -73,11 +119,13 @@ async def main(): print(f"✅ Compute complete for compute_id {compute_event.uuid}") print(f"🖥️ The result is {compute_event.result.value}") return compute_event.result.value - + + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): result = await main() - assert result == {'my_output': 6} + assert result == {"my_output": 6} diff --git a/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition.py b/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition.py index 2ad87f97..bbec2e96 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition.py +++ b/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition.py @@ -5,12 +5,13 @@ from dotenv import load_dotenv -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) from helpers.nillion_client_helper import create_nillion_client from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() + # Complete the 🎯 TODOs to store the tiny_secret_addition program in the network, store secrets, and compute async def main(): # 0. The bootstrap-local-environment.sh script put nillion-devnet config variables into the .env file @@ -22,41 +23,38 @@ async def main(): # 🎯 TODO 1. Initialize NillionClient against nillion-devnet # Create Nillion Client for user client = create_nillion_client(userkey, nodekey) - + # 🎯 TODO 2. Get the user id and party id from NillionClient - # 🎯 TODO 3. Store a compiled Nada program in the network # Set the program name - program_name="tiny_secret_addition" + program_name = "tiny_secret_addition" # Set the path to the compiled program - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" # Store the program # Create a variable for the program_id, which is the {user_id}/{program_name} - # 🎯 TODO 4. Create the 1st secret with bindings to the program # Create a secret named "my_int1" with any value, ex: 500 # Create secret bindings object to bind the secret to the program and set the input party # Set the input party for the secret # The party name needs to match the party name that is storing "my_int1" in the program - # 🎯 TODO 5. Store the secret in the network and print the returned store_id - # 🎯 TODO 6. Create compute bindings to set input and output parties - # 🎯 TODO 7. Compute on the program with 1st secret from the network, and the 2nd secret, provided at compute time # Add my_int2, the 2nd secret at computation time # Compute on the secret # 🎯 TODO 8. Print the computation result + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): pass diff --git a/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition_complete.py b/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition_complete.py index 84213fe7..4c94b23d 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition_complete.py +++ b/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition_complete.py @@ -6,17 +6,28 @@ from dotenv import load_dotenv -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() + # This python script stores the tiny_secret_addition_complete program in the network, store secrets, and compute async def main(): # 0. The bootstrap-local-environment.sh script put nillion-devnet config variables into the .env file - # Get cluster_id, user_key, and node_key from the .env file + # Get cluster_id, gprc endpoint, chain id, user_key,node_key from the .env file cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_ID") userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) @@ -28,38 +39,65 @@ async def main(): party_id = client.party_id user_id = client.user_id - # ✅ 3. Store a compiled Nada program in the network + # ✅ 3. Create a payments config, payments client and payments wallet then pay for and store a compiled Nada program in the network # Set the program name - program_name="tiny_secret_addition_complete" + program_name = "tiny_secret_addition_complete" # Set the path to the compiled program - program_mir_path=f"../../programs-compiled/{program_name}.nada.bin" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" # Store the program + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + # Pay to store the program + receipt_store_program = await pay( + client, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) + + # store program action_id = await client.store_program( - cluster_id, program_name, program_mir_path + cluster_id, program_name, program_mir_path, receipt_store_program ) - # Create a variable for the program_id, which is the {user_id}/{program_name} - program_id=f"{user_id}/{program_name}" - print('Stored program. action_id:', action_id) - print('Stored program_id:', program_id) + # Create a variable for the program_id, which is the {user_id}/{program_name} + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) # ✅ 4. Create the 1st secret with bindings to the program # Create a secret named "my_int1" with any value, ex: 500 - new_secret = nillion.Secrets({ - "my_int1": nillion.SecretInteger(500), - }) - - # Create secret bindings object to bind the secret to the program and set the input party - secret_bindings = nillion.ProgramBindings(program_id) + new_secret = nillion.Secrets( + { + "my_int1": nillion.SecretInteger(500), + } + ) # Set the input party for the secret # The party name needs to match the party name that is storing "my_int1" in the program - party_name="Party1" - secret_bindings.add_input_party(party_name, party_id) - - # ✅ 5. Store the secret in the network and print the returned store_id + party_name = "Party1" + + # Set permissions for the client to compute on the program + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) + + # ✅ 5. Pay for and store the secret in the network and print the returned store_id + receipt_store = await pay( + client, + nillion.Operation.store_secrets(new_secret), + payments_wallet, + payments_client, + cluster_id, + ) + # Store a secret store_id = await client.store_secrets( - cluster_id, secret_bindings, new_secret, None + cluster_id, new_secret, permissions, receipt_store ) print(f"Computing using program {program_id}") print(f"Use secret store_id: {store_id}") @@ -69,11 +107,20 @@ async def main(): compute_bindings.add_input_party(party_name, party_id) compute_bindings.add_output_party(party_name, party_id) - # ✅ 7. Compute on the program with 1st secret from the network, and the 2nd secret, provided at compute time + # ✅ 7. Pay for and compute on the program with 1st secret from the network, and the 2nd secret, provided at compute time # Add my_int2, the 2nd secret at computation time computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) - + + # Pay for the compute + receipt_compute = await pay( + client, + nillion.Operation.compute(program_id, computation_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + # Compute on the secret compute_id = await client.compute( cluster_id, @@ -81,6 +128,7 @@ async def main(): [store_id], computation_time_secrets, nillion.PublicVariables({}), + receipt_compute, ) # ✅ 8. Print the computation result @@ -91,11 +139,13 @@ async def main(): print(f"✅ Compute complete for compute_id {compute_event.uuid}") print(f"🖥️ The result is {compute_event.result.value}") return compute_event.result.value - + + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): result = await main() - assert result == {'my_output': 510} + assert result == {"my_output": 510} From e2fb904032db478c12c7e5ec70a756976c6d8cf7 Mon Sep 17 00:00:00 2001 From: davetbutler Date: Thu, 6 Jun 2024 22:20:45 +0100 Subject: [PATCH 08/43] millionaires example updated --- .../01_store_secret_party1.py | 61 +++++++++--- .../02_store_secret_party_n.py | 82 +++++++++++----- .../03_multi_party_compute.py | 92 ++++++++++++------ .../millionaires_problem_example/config.py | 7 +- .../millionaires.nada.bin | Bin 2375 -> 0 bytes 5 files changed, 172 insertions(+), 70 deletions(-) delete mode 100644 examples_and_tutorials/millionaires_problem_example/millionaires.nada.bin diff --git a/examples_and_tutorials/millionaires_problem_example/01_store_secret_party1.py b/examples_and_tutorials/millionaires_problem_example/01_store_secret_party1.py index f97083ba..d640bd5e 100644 --- a/examples_and_tutorials/millionaires_problem_example/01_store_secret_party1.py +++ b/examples_and_tutorials/millionaires_problem_example/01_store_secret_party1.py @@ -1,50 +1,87 @@ import asyncio +import py_nillion_client as nillion import os import sys import pytest from dotenv import load_dotenv -from config import ( - CONFIG_PARTY_1 -) +from config import CONFIG_PARTY_1 + +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() + # Alice stores the millionaires program in the network async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_ID") client_alice = create_nillion_client( - getUserKeyFromFile(CONFIG_PARTY_1["userkey_file"]), getNodeKeyFromFile(CONFIG_PARTY_1["nodekey_file"]) + getUserKeyFromFile(CONFIG_PARTY_1["userkey_file"]), + getNodeKeyFromFile(CONFIG_PARTY_1["nodekey_file"]), ) millionaires_program_name = "millionaires" - + # Note: check out the code for the full millionaires program in the programs folder - program_mir_path = "millionaires.nada.bin" + program_mir_path = "../../programs-compiled/millionaires.nada.bin" + + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + # Pay to store the program + receipt_store_program = await pay( + client_alice, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) # Store millionaires program in the network print(f"Storing program in the network: {millionaires_program_name}") - await client_alice.store_program( - cluster_id, millionaires_program_name, program_mir_path + program_id = await client_alice.store_program( + cluster_id, millionaires_program_name, program_mir_path, receipt_store_program ) + # Set permissions for the client to compute on the program + permissions = nillion.Permissions.default_for_user(client_alice.user_id) + permissions.add_compute_permissions({client_alice.user_id: {program_id}}) + user_id_alice = client_alice.user_id program_id = f"{user_id_alice}/{millionaires_program_name}" print(f"Alice stores millionaires program at program_id: {program_id}") print(f"Alice tells Bob and Charlie her user_id and the millionaires program_id") - print("\n📋⬇️ Copy and run the following command to store Bob and Charlie's salaries in the network") - print(f"\npython3 02_store_secret_party_n.py --user_id_1 {user_id_alice} --program_id {program_id}") + print( + "\n📋⬇️ Copy and run the following command to store Bob and Charlie's salaries in the network" + ) + print( + f"\npython3 02_store_secret_party_n.py --user_id_1 {user_id_alice} --program_id {program_id}" + ) return [user_id_alice, program_id] + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): pass diff --git a/examples_and_tutorials/millionaires_problem_example/02_store_secret_party_n.py b/examples_and_tutorials/millionaires_problem_example/02_store_secret_party_n.py index 63f5e910..be54c6c1 100644 --- a/examples_and_tutorials/millionaires_problem_example/02_store_secret_party_n.py +++ b/examples_and_tutorials/millionaires_problem_example/02_store_secret_party_n.py @@ -6,18 +6,25 @@ import pytest from dotenv import load_dotenv -from config import ( - CONFIG_N_PARTIES -) +from config import CONFIG_N_PARTIES + +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() + # Bob and Charlie store their salaries in the network -async def main(args = None): +async def main(args=None): parser = argparse.ArgumentParser( description="Create a secret on the Nillion network with set read/retrieve permissions" ) @@ -37,33 +44,36 @@ async def main(args = None): args = parser.parse_args(args) cluster_id = os.getenv("NILLION_CLUSTER_ID") - + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_ID") + # start a list of store ids to keep track of stored secrets store_ids = [] party_ids = [] for party_info in CONFIG_N_PARTIES: client_n = create_nillion_client( - getUserKeyFromFile(party_info["userkey_file"]), - getNodeKeyFromFile(party_info["nodekey_file"]) + getUserKeyFromFile(party_info["userkey_file"]), + getNodeKeyFromFile(party_info["nodekey_file"]), ) party_id_n = client_n.party_id user_id_n = client_n.user_id + + payments_config_n = create_payments_config(chain_id, grpc_endpoint) + payments_client_n = LedgerClient(payments_config_n) + payments_wallet_n = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + party_name = party_info["party_name"] secret_name = party_info["secret_name"] secret_value = party_info["secret_value"] # Create a secret for the current party - stored_secret = nillion.Secrets({ - secret_name: nillion.SecretInteger(secret_value) - }) - - # Create input bindings for the specific millionaires program by program id - secret_bindings = nillion.ProgramBindings(args.program_id) - - # Add the respective input party to say who will provide the input to the program - secret_bindings.add_input_party(party_name, party_id_n) - print(f"\n🔗 {party_name} sets bindings so that the secret can be input to a specific program (program_id: {args.program_id}) by a specific party (party_id: {party_id_n})") + stored_secret = nillion.Secrets( + {secret_name: nillion.SecretInteger(secret_value)} + ) # Create permissions object with default permissions for the current user permissions = nillion.Permissions.default_for_user(user_id_n) @@ -73,28 +83,46 @@ async def main(args = None): args.user_id_1: {args.program_id}, } permissions.add_compute_permissions(compute_permissions) - print(f"\n👍 {party_name} gives compute permissions on their secret to Alice's user_id: {args.user_id_1}") + print( + f"\n👍 {party_name} gives compute permissions on their secret to Alice's user_id: {args.user_id_1}" + ) + receipt_store = await pay( + client_n, + nillion.Operation.store_secrets(stored_secret), + payments_wallet_n, + payments_client_n, + cluster_id, + ) # Store the permissioned secret store_id = await client_n.store_secrets( - cluster_id, secret_bindings, stored_secret, permissions + cluster_id, stored_secret, permissions, receipt_store ) store_ids.append(store_id) party_ids.append(party_id_n) - print(f"\n🎉 {party_name} stored {secret_name}: {secret_value} at store id: {store_id}") - - - party_ids_to_store_ids = ' '.join([f'{party_id}:{store_id}' for party_id, store_id in zip(party_ids, store_ids)]) + print( + f"\n🎉 {party_name} stored {secret_name}: {secret_value} at store id: {store_id}" + ) + + party_ids_to_store_ids = " ".join( + [f"{party_id}:{store_id}" for party_id, store_id in zip(party_ids, store_ids)] + ) - print("\n📋⬇️ Copy and run the following command to run multi party computation using the secrets") - print(f"\npython3 03_multi_party_compute.py --program_id {args.program_id} --party_ids_to_store_ids {party_ids_to_store_ids}") + print( + "\n📋⬇️ Copy and run the following command to run multi party computation using the secrets" + ) + print( + f"\npython3 03_multi_party_compute.py --program_id {args.program_id} --party_ids_to_store_ids {party_ids_to_store_ids}" + ) return [args.program_id, party_ids_to_store_ids] + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): pass diff --git a/examples_and_tutorials/millionaires_problem_example/03_multi_party_compute.py b/examples_and_tutorials/millionaires_problem_example/03_multi_party_compute.py index d8ac2776..80b6a795 100644 --- a/examples_and_tutorials/millionaires_problem_example/03_multi_party_compute.py +++ b/examples_and_tutorials/millionaires_problem_example/03_multi_party_compute.py @@ -8,21 +8,27 @@ from dotenv import load_dotenv -from config import ( - CONFIG_PARTY_1, - CONFIG_N_PARTIES -) +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +from config import CONFIG_PARTY_1, CONFIG_N_PARTIES store_secret_party_1 = importlib.import_module("01_store_secret_party1") store_secret_party_n = importlib.import_module("02_store_secret_party_n") -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() -async def main(args = None): + +async def main(args=None): parser = argparse.ArgumentParser( description="Create a secret on the Nillion network with set read/retrieve permissions" ) @@ -37,30 +43,38 @@ async def main(args = None): parser.add_argument( "--party_ids_to_store_ids", required=True, - nargs='+', + nargs="+", type=str, help="List of partyid:storeid pairs of the secrets, with each pair separated by a space", ) - args = parser.parse_args(args) cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_ID") # Alice initializes a client client_alice = create_nillion_client( - getUserKeyFromFile(CONFIG_PARTY_1["userkey_file"]), - getNodeKeyFromFile(CONFIG_PARTY_1["nodekey_alternate_file"]) + getUserKeyFromFile(CONFIG_PARTY_1["userkey_file"]), + getNodeKeyFromFile(CONFIG_PARTY_1["nodekey_alternate_file"]), ) party_id_alice = client_alice.party_id + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + # Create computation bindings for millionaires program compute_bindings = nillion.ProgramBindings(args.program_id) # Add Alice as an input party compute_bindings.add_input_party(CONFIG_PARTY_1["party_name"], party_id_alice) - # Add an output party (Alice). + # Add an output party (Alice). # The output party reads the result of the blind computation compute_bindings.add_output_party(CONFIG_PARTY_1["party_name"], party_id_alice) @@ -68,32 +82,44 @@ async def main(args = None): # Also add Bob and Charlie as input parties party_ids_to_store_ids = {} - i=0 + i = 0 for pair in args.party_ids_to_store_ids: - party_id, store_id = pair.split(':') - party_name = CONFIG_N_PARTIES[i]['party_name'] + party_id, store_id = pair.split(":") + party_name = CONFIG_N_PARTIES[i]["party_name"] compute_bindings.add_input_party(party_name, party_id) party_ids_to_store_ids[party_id] = store_id - i=i+1 + i = i + 1 # Add any computation time secrets # Alice provides her salary at compute time party_name_alice = CONFIG_PARTY_1["party_name"] secret_name_alice = CONFIG_PARTY_1["secret_name"] secret_value_alice = CONFIG_PARTY_1["secret_value"] - compute_time_secrets = nillion.Secrets({ - secret_name_alice: nillion.SecretInteger(secret_value_alice) - }) + compute_time_secrets = nillion.Secrets( + {secret_name_alice: nillion.SecretInteger(secret_value_alice)} + ) - print(f"\n🎉 {party_name_alice} provided {secret_name_alice}: {secret_value_alice} as a compute time secret") + # Pay for the compute + receipt_compute = await pay( + client_alice, + nillion.Operation.compute(args.program_id, compute_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + + print( + f"\n🎉 {party_name_alice} provided {secret_name_alice}: {secret_value_alice} as a compute time secret" + ) # Compute on the secret with all store ids. Note that there are no compute time secrets or public variables compute_id = await client_alice.compute( cluster_id, compute_bindings, - list(party_ids_to_store_ids.values()), # Bob and Charlie's stored secrets - compute_time_secrets, # Alice's computation time secret + list(party_ids_to_store_ids.values()), # Bob and Charlie's stored secrets + compute_time_secrets, # Alice's computation time secret nillion.PublicVariables({}), + receipt_compute, ) # Print compute result @@ -109,19 +135,29 @@ async def main(args = None): # ['Alice', 'Bob', 'Charlie'] my_parties = CONFIG_N_PARTIES my_parties.insert(0, CONFIG_PARTY_1) - richest_party = my_parties[compute_event.result.value["largest_position"]]["party_name"] + richest_party = my_parties[compute_event.result.value["largest_position"]][ + "party_name" + ] print(f"The richest friend is {richest_party}") return richest_party - + + if __name__ == "__main__": asyncio.run(main()) + @pytest.mark.asyncio async def test_main(): result = await store_secret_party_1.main() - args = ['--user_id_1', result[0], '--program_id', result[1]] + args = ["--user_id_1", result[0], "--program_id", result[1]] result = await store_secret_party_n.main(args) - store_ids = result[1].split(' ', 1) - args = ['--program_id', result[0], '--party_ids_to_store_ids', store_ids[0], store_ids[1]] + store_ids = result[1].split(" ", 1) + args = [ + "--program_id", + result[0], + "--party_ids_to_store_ids", + store_ids[0], + store_ids[1], + ] result = await main(args) - assert result == 'Charlie' + assert result == "Charlie" diff --git a/examples_and_tutorials/millionaires_problem_example/config.py b/examples_and_tutorials/millionaires_problem_example/config.py index 24fb49d8..191e820d 100644 --- a/examples_and_tutorials/millionaires_problem_example/config.py +++ b/examples_and_tutorials/millionaires_problem_example/config.py @@ -1,10 +1,11 @@ import os import py_nillion_client as nillion from dotenv import load_dotenv + load_dotenv() # Alice -CONFIG_PARTY_1={ +CONFIG_PARTY_1 = { "userkey_file": os.getenv("NILLION_USERKEY_PATH_PARTY_1"), "nodekey_file": os.getenv("NILLION_NODEKEY_PATH_PARTY_1"), "nodekey_alternate_file": os.getenv("NILLION_NODEKEY_PATH_PARTY_4"), @@ -14,7 +15,7 @@ } # Bob and Charlie -CONFIG_N_PARTIES=[ +CONFIG_N_PARTIES = [ { "userkey_file": os.getenv("NILLION_USERKEY_PATH_PARTY_2"), "nodekey_file": os.getenv("NILLION_NODEKEY_PATH_PARTY_2"), @@ -29,4 +30,4 @@ "secret_name": "charlie_salary", "secret_value": 12000, }, -] \ No newline at end of file +] diff --git a/examples_and_tutorials/millionaires_problem_example/millionaires.nada.bin b/examples_and_tutorials/millionaires_problem_example/millionaires.nada.bin deleted file mode 100644 index b2ac6e1f1850d7a7373df01f0fde87f3b5ee9494..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2375 zcmb_dTdUJR5RP{(UJm#o__9=l=1>nwH*FK4f}W>>a0Go5DM@#845Ud(QsMY-eDsI- z>Ywos_)RX&#U>5vz;u)8%(vgnY-SZDlNvx-daA&UpAiadtqREk&Mr_S2tl+ zp{LLy?oH^lcoX6W=yT{DD9QO}JMz6Y{M+#O3Vi`B@M*1ayAK12O+;6*?c32+W>J(D zCU^1wcjyvIcm+1N5AaIjgib%KdvN{*J%s`(YA^`87qkz*=x)cqY+| zi8j+L*VYA}a?5ZHT{A4TQ-2D!^)y{X&CETB&`xYxbGbqWi z!$CeR6=;Jzc653|cH~^$xmx?b?lU!$KuPM~T?dPvm8a@Gy`GJZYUPi8EX1Xy@@XJa;AiSeDL*0_q&fprU@7I%dW5L{-Q8 zol~%KJM4W7O0Prhk+Rz7*@n9W30~|HYFjh)F&j2bs9M&+tXSy$E(+L3zJ)U From 74df06a668f1ce0b0209e19153fc0cf31a7f61c2 Mon Sep 17 00:00:00 2001 From: oceans404 Date: Thu, 6 Jun 2024 14:51:31 -0700 Subject: [PATCH 09/43] update tiny secret addition tutorial --- .../tiny_secret_addition.py | 14 +++++++++++--- .../tiny_secret_addition_complete.py | 4 ++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition.py b/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition.py index bbec2e96..9e246fb7 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition.py +++ b/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition.py @@ -17,6 +17,8 @@ async def main(): # 0. The bootstrap-local-environment.sh script put nillion-devnet config variables into the .env file # Get cluster_id, user_key, and node_key from the .env file cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_ID") userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) @@ -26,12 +28,14 @@ async def main(): # 🎯 TODO 2. Get the user id and party id from NillionClient - # 🎯 TODO 3. Store a compiled Nada program in the network + # 🎯 TODO 3. Create a payments config, payments client and payments wallet then pay for and store a compiled Nada program in the network # Set the program name program_name = "tiny_secret_addition" # Set the path to the compiled program program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - # Store the program + # Create payments config + # Pay to store the program + # Store program # Create a variable for the program_id, which is the {user_id}/{program_name} # 🎯 TODO 4. Create the 1st secret with bindings to the program @@ -39,13 +43,17 @@ async def main(): # Create secret bindings object to bind the secret to the program and set the input party # Set the input party for the secret # The party name needs to match the party name that is storing "my_int1" in the program + # Set permissions for the client to compute on the program # 🎯 TODO 5. Store the secret in the network and print the returned store_id + # Pay to store the secret + # Store a secret, passing in the receipt from payment # 🎯 TODO 6. Create compute bindings to set input and output parties - # 🎯 TODO 7. Compute on the program with 1st secret from the network, and the 2nd secret, provided at compute time + # 🎯 TODO 7. Pay for and compute on the program with 1st secret from the network, and the 2nd secret, provided at compute time # Add my_int2, the 2nd secret at computation time + # Pay for the compute # Compute on the secret # 🎯 TODO 8. Print the computation result diff --git a/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition_complete.py b/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition_complete.py index 4c94b23d..aee5b199 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition_complete.py +++ b/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition_complete.py @@ -44,7 +44,7 @@ async def main(): program_name = "tiny_secret_addition_complete" # Set the path to the compiled program program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - # Store the program + # Create payments config payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( @@ -61,7 +61,7 @@ async def main(): cluster_id, ) - # store program + # Store program action_id = await client.store_program( cluster_id, program_name, program_mir_path, receipt_store_program ) From 57a2c96d6a8ae55e5428594db0aae272c8a01616 Mon Sep 17 00:00:00 2001 From: oceans404 Date: Thu, 6 Jun 2024 16:03:47 -0700 Subject: [PATCH 10/43] update permissions examples --- .../02_store_permissioned_secret.py | 45 +++++++++++++++---- .../03_retrieve_secret.py | 40 +++++++++++++++-- .../04_revoke_read_permissions.py | 30 ++++++++++++- .../05_test_revoked_permissions.py | 37 ++++++++++++++- .../addition_simple.py | 2 +- .../store_and_retrieve_blob.py | 2 +- .../store_and_retrieve_integer.py | 2 +- 7 files changed, 138 insertions(+), 20 deletions(-) diff --git a/examples_and_tutorials/core_concept_permissions/02_store_permissioned_secret.py b/examples_and_tutorials/core_concept_permissions/02_store_permissioned_secret.py index 82c344a0..9941dc3c 100644 --- a/examples_and_tutorials/core_concept_permissions/02_store_permissioned_secret.py +++ b/examples_and_tutorials/core_concept_permissions/02_store_permissioned_secret.py @@ -6,9 +6,16 @@ import pytest from dotenv import load_dotenv +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() @@ -27,6 +34,8 @@ async def main(args = None): args = parser.parse_args(args) cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_ID") userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_2")) nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_2")) @@ -35,6 +44,22 @@ async def main(args = None): writer_user_id = writer.user_id print(writer_user_id, args.retriever_user_id) + # Create payments config and set up Nillion wallet with a private key to pay for operations + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + # Create secret + secret_name_1 = "my_int1" + secret_1 = nillion.SecretInteger(10) + + secret_name_2 = "my_int2" + secret_2 = nillion.SecretInteger(32) + secrets_object = nillion.Secrets({secret_name_1: secret_1, secret_name_2: secret_2}) + # Writer gives themself default core_concept_permissions permissions = nillion.Permissions.default_for_user(writer_user_id) # Writer gives the reader permission to read/retrieve secret @@ -50,22 +75,24 @@ async def main(args = None): print(f"ℹ️ Permissions set: Reader {args.retriever_user_id} is {result} to retrieve the secret") - secret_name_1 = "my_int1" - secret_1 = nillion.SecretInteger(10) - - secret_name_2 = "my_int2" - secret_2 = nillion.SecretInteger(32) - secrets_object = nillion.Secrets({secret_name_1: secret_1, secret_name_2: secret_2}) + # Get cost quote, then pay for operation to store the secret + receipt_store = await pay( + writer, + nillion.Operation.store_secrets(secrets_object), + payments_wallet, + payments_client, + cluster_id, + ) # Writer stores the permissioned secret, resulting in the secret's store id print(f"ℹ️ Storing permissioned secret: {secrets_object}") store_id = await writer.store_secrets( - cluster_id, None, secrets_object, permissions + cluster_id, secrets_object, permissions, receipt_store ) print("ℹ️ STORE ID:", store_id) print("\n\nRun the following command to retrieve the secret by store id as the reader") - print(f"\n📋 python3 03_retrieve_secret.py --store_id {store_id}") + print(f"\n📋 python3 03_retrieve_secret.py --store_id {store_id} --secret_name {secret_name_1}") return store_id if __name__ == "__main__": diff --git a/examples_and_tutorials/core_concept_permissions/03_retrieve_secret.py b/examples_and_tutorials/core_concept_permissions/03_retrieve_secret.py index 5076a968..cf58bc36 100644 --- a/examples_and_tutorials/core_concept_permissions/03_retrieve_secret.py +++ b/examples_and_tutorials/core_concept_permissions/03_retrieve_secret.py @@ -1,13 +1,21 @@ import argparse import asyncio +import py_nillion_client as nillion import os import sys import pytest from dotenv import load_dotenv +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() @@ -22,9 +30,18 @@ async def main(args = None): type=str, help="Store ID from the writer client store operation", ) + parser.add_argument( + "--secret_name", + required=True, + type=str, + help="Secret name from the writer client store operation", + ) + args = parser.parse_args(args) cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_ID") userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_3")) @@ -32,13 +49,28 @@ async def main(args = None): reader = create_nillion_client(userkey, nodekey) reader_user_id = reader.user_id - secret_name = "my_int1" + # Create payments config and set up Nillion wallet with a private key to pay for operations + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + # Get cost quote, then pay for operation to retrieve the secret + receipt_store = await pay( + reader, + nillion.Operation.retrieve_secret(), + payments_wallet, + payments_client, + cluster_id, + ) # Reader retrieves the named secret by store id print(f"Retrieving secret as reader: {reader_user_id}") - result = await reader.retrieve_secret(cluster_id, args.store_id, secret_name) + result = await reader.retrieve_secret(cluster_id, args.store_id, args.secret_name, receipt_store) - print(f"🦄 Retrieved {secret_name} secret, value = {result[1].value}", file=sys.stderr) + print(f"🦄 Retrieved {args.secret_name} secret, value = {result[1].value}", file=sys.stderr) print("\n\nRun the following command to revoke the reader's retrieve permissions to the secret") print(f"\n📋 python3 04_revoke_read_permissions.py --store_id {args.store_id} --revoked_user_id {reader_user_id}") return [args.store_id, reader_user_id] diff --git a/examples_and_tutorials/core_concept_permissions/04_revoke_read_permissions.py b/examples_and_tutorials/core_concept_permissions/04_revoke_read_permissions.py index 3a6d73ca..394b9a46 100644 --- a/examples_and_tutorials/core_concept_permissions/04_revoke_read_permissions.py +++ b/examples_and_tutorials/core_concept_permissions/04_revoke_read_permissions.py @@ -6,9 +6,16 @@ import pytest from dotenv import load_dotenv +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() @@ -32,12 +39,22 @@ async def main(args = None): args = parser.parse_args(args) cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_ID") userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_2")) nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_4")) # Writer Nillion client writer = create_nillion_client(userkey, nodekey) + # Create payments config and set up Nillion wallet with a private key to pay for operations + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + # Create new permissions object to rewrite permissions (reader no longer has retrieve permission) new_permissions = nillion.Permissions.default_for_user(writer.user_id) result = ( @@ -47,11 +64,20 @@ async def main(args = None): ) if result != "not allowed": raise Exception("failed to create valid permissions object") + + # Get cost quote, then pay for operation to update permissions + receipt_update_permissions = await pay( + writer, + nillion.Operation.update_permissions(), + payments_wallet, + payments_client, + cluster_id, + ) # Update the permission print(f"ℹ️ Updating permissions for secret: {args.store_id}.") print(f"ℹ️ Reset permissions so that user id {args.revoked_user_id} is {result} to retrieve object.", file=sys.stderr) - await writer.update_permissions( cluster_id, args.store_id , new_permissions) + await writer.update_permissions( cluster_id, args.store_id , new_permissions, receipt_update_permissions) print("\n\nRun the following command to test that permissions have been properly revoked") print(f"\n📋 python3 05_test_revoked_permissions.py --store_id {args.store_id}") diff --git a/examples_and_tutorials/core_concept_permissions/05_test_revoked_permissions.py b/examples_and_tutorials/core_concept_permissions/05_test_revoked_permissions.py index 21c257c1..f774c372 100644 --- a/examples_and_tutorials/core_concept_permissions/05_test_revoked_permissions.py +++ b/examples_and_tutorials/core_concept_permissions/05_test_revoked_permissions.py @@ -1,11 +1,25 @@ +import importlib import argparse import asyncio +import py_nillion_client as nillion import os import sys import pytest -import importlib from dotenv import load_dotenv +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) +from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile + +load_dotenv() fetch_reader_userid = importlib.import_module("01_fetch_reader_userid") store_permissioned_secret = importlib.import_module("02_store_permissioned_secret") @@ -31,6 +45,8 @@ async def main(args = None): args = parser.parse_args(args) cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_ID") userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_5")) @@ -38,9 +54,26 @@ async def main(args = None): reader = create_nillion_client(userkey, nodekey) reader_user_id = reader.user_id + # Create payments config and set up Nillion wallet with a private key to pay for operations + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + + # Get cost quote, then pay for operation to retrieve the secret + receipt_store = await pay( + reader, + nillion.Operation.retrieve_secret(), + payments_wallet, + payments_client, + cluster_id, + ) + try: secret_name = "my_int1" - await reader.retrieve_secret(cluster_id, args.store_id, secret_name) + await reader.retrieve_secret(cluster_id, args.store_id, secret_name, receipt_store) print(f"⛔ FAIL: {reader_user_id} user id with revoked permissions was allowed to access secret", file=sys.stderr) except Exception as e: if str(e) == "retrieving secret: the user is not authorized to access the secret": diff --git a/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py b/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py index b5a10760..f8348fba 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py @@ -34,7 +34,7 @@ async def main(): program_name = "addition_simple" program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - # Create payments config and set up Nillion wallet with a private key to pay for storage and compute operations + # Create payments config and set up Nillion wallet with a private key to pay for operations payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( diff --git a/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_blob.py b/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_blob.py index 24883f31..50d38884 100644 --- a/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_blob.py +++ b/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_blob.py @@ -24,7 +24,7 @@ async def main(): nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) client = create_nillion_client(userkey, nodekey) - # Create payments config and set up Nillion wallet with a private key to pay for storage and compute operations + # Create payments config and set up Nillion wallet with a private key to pay for operations payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( diff --git a/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_integer.py b/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_integer.py index a9a535da..c4632e60 100644 --- a/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_integer.py +++ b/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_integer.py @@ -24,7 +24,7 @@ async def main(): nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) client = create_nillion_client(userkey, nodekey) - # Create payments config and set up Nillion wallet with a private key to pay for storage and compute operations + # Create payments config and set up Nillion wallet with a private key to pay for operations payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( From 8cbdb28f867684f25d445dd565e365e31fe14b90 Mon Sep 17 00:00:00 2001 From: Steph <91382964+oceans404@users.noreply.github.com> Date: Thu, 6 Jun 2024 16:05:33 -0700 Subject: [PATCH 11/43] update permissions command --- examples_and_tutorials/core_concept_permissions/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples_and_tutorials/core_concept_permissions/README.md b/examples_and_tutorials/core_concept_permissions/README.md index 1be1b83b..89453611 100644 --- a/examples_and_tutorials/core_concept_permissions/README.md +++ b/examples_and_tutorials/core_concept_permissions/README.md @@ -13,7 +13,7 @@ To run through the example flow, simply run the python scripts in order. The out ```shell python3 01_fetch_reader_userid.py python3 02_store_permissioned_secret.py --retriever_user_id {READER_USER_ID} -python3 03_retrieve_secret.py --store_id {STORE_ID} +python3 03_retrieve_secret.py --store_id {STORE_ID} --secret_name {SECRET_NAME} python3 04_revoke_read_permissions.py --store_id {STORE_ID} --revoked_user_id {READER_USER_ID} python3 05_test_revoked_permissions.py --store_id {STORE_ID} ``` From ff9df1c1f132393aa35f5d113d54f9e6e32e1276 Mon Sep 17 00:00:00 2001 From: oceans404 Date: Thu, 6 Jun 2024 16:39:56 -0700 Subject: [PATCH 12/43] mpc example - error on step 3 --- .../01_store_secret_party1.py | 106 ++++++++---------- .../02_store_secret_party_n.py | 44 ++++++-- .../03_multi_party_compute.py | 58 ++++++---- 3 files changed, 116 insertions(+), 92 deletions(-) diff --git a/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py b/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py index 2cb7f9c7..10af026a 100644 --- a/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py +++ b/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py @@ -5,87 +5,68 @@ import pytest from dotenv import load_dotenv -from config import ( - CONFIG_PROGRAM_NAME, - CONFIG_PARTY_1 +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, ) - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() +from config import ( + CONFIG_PROGRAM_NAME, + CONFIG_PARTY_1 +) + # The 1st Party stores a secret async def main(): - - cluster_id = os.getenv("NILLION_CLUSTER_ID") - cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_ID") client_1 = create_nillion_client( getUserKeyFromFile(CONFIG_PARTY_1["userkey_file"]), getNodeKeyFromFile(CONFIG_PARTY_1["nodekey_file"]) ) party_id_1 = client_1.party_id user_id_1 = client_1.user_id + program_mir_path=f"../../programs-compiled/{CONFIG_PROGRAM_NAME}.nada.bin" - program_mir_path = f"../../programs-compiled/{CONFIG_PROGRAM_NAME}.nada.bin" - - # 1st Party stores program - action_id = await client_1.store_program( - cluster_id, CONFIG_PROGRAM_NAME, program_mir_path - ) - - program_id = f"{user_id_1}/{CONFIG_PROGRAM_NAME}" - - program_name = "simple" - - program_id = f"{Test.programs_namespace}/{program_name}" - - permissions = py_nillion_client.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) - - inputs = Test.load_inputs(program_name) - receipt = await self.pay( - client, py_nillion_client.Operation.store_secrets(inputs.store_secrets) - ) - store_id = await client.store_secrets( - self.cluster_id, inputs.store_secrets, permissions, receipt - ) - - bindings = py_nillion_client.ProgramBindings(program_id) - bindings.add_input_party("Dealer", client.party_id) - bindings.add_output_party("Result", client.party_id) - receipt = await self.pay( - client, - py_nillion_client.Operation.compute(program_id, inputs.compute_secrets), + # Create payments config and set up Nillion wallet with a private key to pay for operations + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", ) - uuid = await client.compute( - self.cluster_id, - bindings, - [store_id], - inputs.compute_secrets, - inputs.compute_public_variables, - receipt, - ) - - - ###### - + ##### STORE PROGRAM + print("-----STORE PROGRAM") - - - program_mir_path=f"../../programs-compiled/{CONFIG_PROGRAM_NAME}.nada.bin" + # Get cost quote, then pay for operation to store program + receipt_store_program = await pay( + client_1, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) # 1st Party stores program action_id = await client_1.store_program( - cluster_id, CONFIG_PROGRAM_NAME, program_mir_path + cluster_id, CONFIG_PROGRAM_NAME, program_mir_path, receipt_store_program ) program_id=f"{user_id_1}/{CONFIG_PROGRAM_NAME}" print('Stored program. action_id:', action_id) print('Stored program_id:', program_id) + ##### STORE SECRETS + print("-----STORE SECRETS") # 1st Party creates a secret stored_secret_1 = nillion.Secrets({ @@ -93,13 +74,18 @@ async def main(): for key, value in CONFIG_PARTY_1["secrets"].items() }) - # 1st Party creates input bindings for the program - secret_bindings_1 = nillion.ProgramBindings(program_id) - secret_bindings_1.add_input_party(CONFIG_PARTY_1["party_name"], party_id_1) + # Get cost quote, then pay for operation to store the secret + receipt_store = await pay( + client_1, + nillion.Operation.store_secrets(stored_secret_1), + payments_wallet, + payments_client, + cluster_id, + ) # 1st Party stores a secret store_id_1 = await client_1.store_secrets( - cluster_id, secret_bindings_1, stored_secret_1, None + cluster_id, stored_secret_1, None, receipt_store ) secrets_string = ", ".join(f"{key}: {value}" for key, value in CONFIG_PARTY_1["secrets"].items()) print(f"\n🎉1️⃣ Party {CONFIG_PARTY_1['party_name']} stored {secrets_string} at store id: {store_id_1}") @@ -112,4 +98,4 @@ async def main(): @pytest.mark.asyncio async def test_main(): - pass + pass \ No newline at end of file diff --git a/examples_and_tutorials/core_concept_multi_party_compute/02_store_secret_party_n.py b/examples_and_tutorials/core_concept_multi_party_compute/02_store_secret_party_n.py index 45dd79c7..efea10dd 100644 --- a/examples_and_tutorials/core_concept_multi_party_compute/02_store_secret_party_n.py +++ b/examples_and_tutorials/core_concept_multi_party_compute/02_store_secret_party_n.py @@ -6,17 +6,25 @@ import pytest from dotenv import load_dotenv -from config import ( - CONFIG_PROGRAM_NAME, - CONFIG_N_PARTIES +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, ) - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() +from config import ( + CONFIG_PROGRAM_NAME, + CONFIG_N_PARTIES +) + # N other parties store a secret async def main(args = None): parser = argparse.ArgumentParser( @@ -38,6 +46,8 @@ async def main(args = None): args = parser.parse_args(args) cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_ID") program_id=f"{args.user_id_1}/{CONFIG_PROGRAM_NAME}" # start a list of store ids to keep track of stored secrets @@ -55,19 +65,33 @@ async def main(args = None): secret_name = party_info["secret_name"] secret_value = party_info["secret_value"] + # Create payments config and set up Nillion wallet with a private key to pay for operations + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + # Create a secret for the current party stored_secret = nillion.Secrets({ secret_name: nillion.SecretInteger(secret_value) }) - # Create input bindings for the program - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id_n) + # Get cost quote, then pay for operation to store the secret + receipt_store = await pay( + client_n, + nillion.Operation.store_secrets(stored_secret), + payments_wallet, + payments_client, + cluster_id, + ) # Create permissions object permissions = nillion.Permissions.default_for_user(user_id_n) # Give compute permissions to the first party + print(program_id) compute_permissions = { args.user_id_1: {program_id}, } @@ -75,7 +99,7 @@ async def main(args = None): # Store the permissioned secret store_id = await client_n.store_secrets( - cluster_id, secret_bindings, stored_secret, permissions + cluster_id, stored_secret, permissions, receipt_store ) store_ids.append(store_id) diff --git a/examples_and_tutorials/core_concept_multi_party_compute/03_multi_party_compute.py b/examples_and_tutorials/core_concept_multi_party_compute/03_multi_party_compute.py index 04591710..65b9a44d 100644 --- a/examples_and_tutorials/core_concept_multi_party_compute/03_multi_party_compute.py +++ b/examples_and_tutorials/core_concept_multi_party_compute/03_multi_party_compute.py @@ -4,9 +4,21 @@ import os import sys import pytest -import importlib from dotenv import load_dotenv +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) +from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile + +load_dotenv() from config import ( CONFIG_PROGRAM_NAME, @@ -14,15 +26,6 @@ CONFIG_N_PARTIES ) -store_secret_party_1 = importlib.import_module("01_store_secret_party1") -store_secret_party_n = importlib.import_module("02_store_secret_party_n") - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() - async def main(args = None): parser = argparse.ArgumentParser( description="Create a secret on the Nillion network with set read/retrieve permissions" @@ -46,6 +49,8 @@ async def main(args = None): args = parser.parse_args(args) cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_ID") # 1st party computes on secrets client_1 = create_nillion_client( @@ -55,8 +60,15 @@ async def main(args = None): user_id_1 = client_1.user_id party_id_1 = client_1.party_id - program_id=f"{user_id_1}/{CONFIG_PROGRAM_NAME}" + # Create payments config and set up Nillion wallet with a private key to pay for operations + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + program_id=f"{user_id_1}/{CONFIG_PROGRAM_NAME}" # Bind the parties in the computation to the client to set inputs and output parties compute_bindings = nillion.ProgramBindings(program_id) @@ -75,14 +87,26 @@ async def main(args = None): compute_bindings.add_input_party(party_name, party_id) party_ids_to_store_ids[party_id] = store_id i=i+1 + + compute_time_secrets = nillion.Secrets({}) + + # Get cost quote, then pay for operation to compute + receipt_compute = await pay( + client_1, + nillion.Operation.compute(program_id, compute_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) # Compute on the secret with all store ids. Note that there are no compute time secrets or public variables compute_id = await client_1.compute( cluster_id, compute_bindings, [store_id_1] + list(party_ids_to_store_ids.values()), - nillion.Secrets({}), + compute_time_secrets, nillion.PublicVariables({}), + receipt_compute, ) # Print compute result @@ -96,13 +120,3 @@ async def main(args = None): if __name__ == "__main__": asyncio.run(main()) - -@pytest.mark.asyncio -async def test_main(): - result = await store_secret_party_1.main() - args = ['--user_id_1', result[0], '--store_id_1', result[1]] - result = await store_secret_party_n.main(args) - store_ids = result[1].split(' ', 1) - args = ['--store_id_1', result[0], '--party_ids_to_store_ids', store_ids[0], store_ids[1]] - result = await main(args) - assert result == {'my_output': 8} From 7750a5de5f5b4dead55ecd7786602486a40dcf5d Mon Sep 17 00:00:00 2001 From: davetbutler Date: Fri, 7 Jun 2024 02:10:10 +0100 Subject: [PATCH 13/43] multi-party example fix --- .../01_store_secret_party1.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py b/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py index 10af026a..b3ad1704 100644 --- a/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py +++ b/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py @@ -65,6 +65,11 @@ async def main(): print('Stored program. action_id:', action_id) print('Stored program_id:', program_id) + # Create a permissions object to attach to the stored secret + permissions = nillion.Permissions.default_for_user(client_1.user_id) + permissions.add_compute_permissions({client_1.user_id: {program_id}}) + + ##### STORE SECRETS print("-----STORE SECRETS") @@ -85,7 +90,7 @@ async def main(): # 1st Party stores a secret store_id_1 = await client_1.store_secrets( - cluster_id, stored_secret_1, None, receipt_store + cluster_id, stored_secret_1, permissions, receipt_store ) secrets_string = ", ".join(f"{key}: {value}" for key, value in CONFIG_PARTY_1["secrets"].items()) print(f"\n🎉1️⃣ Party {CONFIG_PARTY_1['party_name']} stored {secrets_string} at store id: {store_id_1}") From 3fe97e09058f4bd9df039af18540722423b583f3 Mon Sep 17 00:00:00 2001 From: davetbutler Date: Fri, 7 Jun 2024 12:31:45 +0100 Subject: [PATCH 14/43] tidy up --- .idea/.gitignore | 3 + .idea/inspectionProfiles/Project_Default.xml | 15 ++ .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/nillion-python-starter.iml | 14 ++ .idea/vcs.xml | 6 + .../addition_simple.py | 8 +- .../correlation_coefficient.py | 31 +++- .../arrays_playground.py | 150 ++++++++++++++++++ .../01_store_program_party1.py | 35 +++- .../02_store_secret_party_n.py | 44 +++-- .../voting_tutorial/03_multi_party_compute.py | 31 +++- .../voting_tutorial/client_voting.py | 62 ++++++-- helpers/nillion_client_helper.py | 62 ++++---- helpers/nillion_keypath_helper.py | 2 + old_addition.py | 86 ++++++++++ programs/arrays_playground.py | 20 +++ 18 files changed, 524 insertions(+), 66 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/nillion-python-starter.iml create mode 100644 .idea/vcs.xml create mode 100644 examples_and_tutorials/nada_algebra_examples/arrays_playground.py create mode 100644 old_addition.py create mode 100644 programs/arrays_playground.py diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..26d33521 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..b5bd3f40 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..105ce2da --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..7e452e53 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..068226f1 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/nillion-python-starter.iml b/.idea/nillion-python-starter.iml new file mode 100644 index 00000000..ee28fd34 --- /dev/null +++ b/.idea/nillion-python-starter.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py b/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py index f8348fba..d5d17342 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py @@ -100,18 +100,18 @@ async def main(): compute_bindings.add_input_party(party_name, party_id) compute_bindings.add_output_party(party_name, party_id) + # Create a computation time secret to use + computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) + # Get cost quote, then pay for operation to compute receipt_compute = await pay( client, - nillion.Operation.compute(program_id, stored_secret), + nillion.Operation.compute(program_id, computation_time_secrets), payments_wallet, payments_client, cluster_id, ) - # Create a computation time secret to use - computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) - # Compute, passing all params including the receipt that shows proof of payment uuid = await client.compute( cluster_id=cluster_id, diff --git a/examples_and_tutorials/core_concept_single_party_compute/correlation_coefficient.py b/examples_and_tutorials/core_concept_single_party_compute/correlation_coefficient.py index a67a6172..24e78434 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/correlation_coefficient.py +++ b/examples_and_tutorials/core_concept_single_party_compute/correlation_coefficient.py @@ -114,15 +114,33 @@ async def main(): store_ids = [] # Store in the network print(f"Storing party 0: {party_0_secrets}") + + # Get cost quote, then pay for operation to store the secret + receipt_store_0 = await pay( + client, + nillion.Operation.store_secrets(party_0_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + store_id = await client.store_secrets( - cluster_id, party_0_secrets, secret_permissions + cluster_id, party_0_secrets, secret_permissions, receipt_store_0 ) store_ids.append(store_id) print(f"Stored party 0 with store_id ={store_id}") + receipt_store_1 = await pay( + client, + nillion.Operation.store_secrets(party_1_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + print(f"Storing party 1: {party_1_secrets}") store_id = await client.store_secrets( - cluster_id, secret_bindings, party_1_secrets, secret_permissions + cluster_id, party_1_secrets, secret_permissions, receipt_store_1 ) store_ids.append(store_id) print(f"Stored party 1 with store_id ={store_id}") @@ -137,6 +155,14 @@ async def main(): print(f"Computing using program {program_id}") print(f"Use secret store_id: {store_id}") + # Get cost quote, then pay for operation to compute + receipt_compute = await pay( + client, + nillion.Operation.compute(program_id, computation_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) # Compute on the secret compute_id = await client.compute( cluster_id, @@ -144,6 +170,7 @@ async def main(): store_ids, computation_time_secrets, nillion.PublicVariables({}), + receipt_compute ) # Print compute result diff --git a/examples_and_tutorials/nada_algebra_examples/arrays_playground.py b/examples_and_tutorials/nada_algebra_examples/arrays_playground.py new file mode 100644 index 00000000..80c9c108 --- /dev/null +++ b/examples_and_tutorials/nada_algebra_examples/arrays_playground.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()) \ No newline at end of file diff --git a/examples_and_tutorials/voting_tutorial/01_store_program_party1.py b/examples_and_tutorials/voting_tutorial/01_store_program_party1.py index afbe2cbb..e63459f0 100644 --- a/examples_and_tutorials/voting_tutorial/01_store_program_party1.py +++ b/examples_and_tutorials/voting_tutorial/01_store_program_party1.py @@ -6,6 +6,7 @@ import asyncio +import py_nillion_client as nillion import os import sys from dotenv import load_dotenv @@ -13,8 +14,16 @@ CONFIG_PARTY_1 ) -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() @@ -22,6 +31,8 @@ # Alice stores the voting program in the network async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_ID") while True: @@ -77,14 +88,32 @@ async def main(): # 1.1 Owner initialization # ############################# + # Create client client_alice = create_nillion_client( getUserKeyFromFile(CONFIG_PARTY_1["userkey_file"]), getNodeKeyFromFile(CONFIG_PARTY_1["nodekey_file"]) ) + # Create payments config and set up Nillion wallet with a private key to pay for operations + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + ##################################### # 2. Storing program # ##################################### + # Get cost quote, then pay for operation to store program + receipt_store_program = await pay( + client_alice, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" if os.path.exists(program_mir_path): None @@ -94,7 +123,7 @@ async def main(): # Store program in the Network print(f"Storing program in the network: {program_name}") action_id = await client_alice.store_program( - cluster_id, program_name, program_mir_path + cluster_id, program_name, program_mir_path, receipt_store_program ) print("action_id is: ", action_id) user_id_alice = client_alice.user_id diff --git a/examples_and_tutorials/voting_tutorial/02_store_secret_party_n.py b/examples_and_tutorials/voting_tutorial/02_store_secret_party_n.py index 8a5ce0a5..605e38e5 100644 --- a/examples_and_tutorials/voting_tutorial/02_store_secret_party_n.py +++ b/examples_and_tutorials/voting_tutorial/02_store_secret_party_n.py @@ -14,8 +14,16 @@ CONFIG_N_PARTIES ) -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile load_dotenv() @@ -41,6 +49,8 @@ # Bob and Charlie store their votes in the network async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_ID") # start a list of store ids to keep track of stored secrets store_ids = [] @@ -59,6 +69,15 @@ async def main(): getUserKeyFromFile(party_info["userkey_file"]), getNodeKeyFromFile(party_info["nodekey_file"]) ) + + # Create payments config and set up Nillion wallet with a private key to pay for operations + payments_config_n = create_payments_config(chain_id, grpc_endpoint) + payments_client_n = LedgerClient(payments_config_n) + payments_wallet_n = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) + party_id_n = client_n.party_id user_id_n = client_n.user_id party_name = party_info["party_name"] @@ -72,16 +91,6 @@ async def main(): secret_votes = {key: nillion.SecretUnsignedInteger(value) for key, value in secret_votes.items()} stored_secret = nillion.Secrets(secret_votes) - ########################################### - # 4.1 Bind voter to party in the program # - ########################################### - # Create input bindings for the specific voting program by program id - secret_bindings = nillion.ProgramBindings(args.program_id) - - # Add the respective input party to say who will provide the input to the program - secret_bindings.add_input_party(party_role, party_id_n) - print(f"\n🔗 {party_name} sets bindings so that the secret can be input to a specific program (program_id: {args.program_id}) by a specific party (party_id: {party_id_n})") - ########################################### # 4.2 Set compute permissions to owner # ########################################### @@ -95,9 +104,18 @@ async def main(): permissions.add_compute_permissions(compute_permissions) print(f"\n👍 {party_name} gives compute permissions on their secret to Alice's user_id: {args.user_id_1}") + # Get cost quote, then pay for operation to store the secret + receipt_store = await pay( + client_n, + nillion.Operation.store_secrets(stored_secret), + payments_wallet_n, + payments_client_n, + cluster_id, + ) + # Store the permissioned secret store_id = await client_n.store_secrets( - cluster_id, secret_bindings, stored_secret, permissions + cluster_id, stored_secret, permissions, receipt_store ) store_ids.append(store_id) diff --git a/examples_and_tutorials/voting_tutorial/03_multi_party_compute.py b/examples_and_tutorials/voting_tutorial/03_multi_party_compute.py index f357de86..7d9155b6 100644 --- a/examples_and_tutorials/voting_tutorial/03_multi_party_compute.py +++ b/examples_and_tutorials/voting_tutorial/03_multi_party_compute.py @@ -17,9 +17,18 @@ CONFIG_N_PARTIES ) -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile + from digest_result import digest_plurality_vote_honest_result, digest_plurality_vote_dishonest_with_abort_result, digest_plurality_vote_robust_result @@ -48,6 +57,8 @@ async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_ID") ##################################### # 1. Parties initialization # @@ -61,6 +72,12 @@ async def main(): getUserKeyFromFile(CONFIG_PARTY_1["userkey_file"]), getNodeKeyFromFile(CONFIG_PARTY_1["nodekey_file"]) ) + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) party_id_alice = client_alice.party_id ##################################### @@ -105,6 +122,15 @@ async def main(): # The output party reads the result of the blind computation compute_bindings.add_output_party("OutParty", party_id_alice) + # Get cost quote, then pay for operation to compute + receipt_compute = await pay( + client_alice, + nillion.Operation.compute(args.program_id, compute_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + print(f"Computing using program {args.program_id}") # Compute on the secret with all store ids. Note that there are no compute time secrets or public variables @@ -114,6 +140,7 @@ async def main(): list(party_ids_to_store_ids.values()), # Bob and Charlie's stored secrets compute_time_secrets, # Alice's computation time secret nillion.PublicVariables({}), + receipt_compute ) # Print compute result diff --git a/examples_and_tutorials/voting_tutorial/client_voting.py b/examples_and_tutorials/voting_tutorial/client_voting.py index 8ac02a7d..5c15970b 100644 --- a/examples_and_tutorials/voting_tutorial/client_voting.py +++ b/examples_and_tutorials/voting_tutorial/client_voting.py @@ -14,9 +14,18 @@ CONFIG ) -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile + from digest_result import digest_plurality_vote_honest_result, digest_plurality_vote_dishonest_with_abort_result, digest_plurality_vote_robust_result load_dotenv() @@ -24,6 +33,8 @@ async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_GRPC") + chain_id = os.getenv("NILLION_CHAIN_ID") while True: @@ -99,6 +110,12 @@ async def main(): userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) general_client = create_nillion_client(userkey, nodekey) + general_payments_config = create_payments_config(chain_id, grpc_endpoint) + general_payments_client = LedgerClient(general_payments_config) + general_payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + prefix="nillion", + ) # ############################# # # 1.1 Owner initialization # @@ -150,9 +167,17 @@ async def main(): # Store program in the Network print(f"Storing program in the network: {program_name}") - # action_id = await owner.store_program( + # Get cost quote, then pay for operation to store program + receipt_store_program = await pay( + general_client, + nillion.Operation.store_program(), + general_payments_wallet, + general_payments_client, + cluster_id, + ) + action_id = await general_client.store_program( - cluster_id, program_name, program_mir_path + cluster_id, program_name, program_mir_path, receipt_store_program ) print("action_id is: ", action_id) # program_id = owner.user_id + "/" + program_name @@ -197,12 +222,6 @@ async def main(): v_vote_dic["v"+str(v)+"_c"+str(c)] = v_c_vote c += 1 v_to_be_store_secrets = nillion.Secrets(v_vote_dic) - - ########################################### - # 4.1 Bind voter to party in the program # - ########################################### - v_bindings = nillion.ProgramBindings(program_id) - v_bindings.add_input_party("Voter"+str(v), voter_v.party_id) ########################################### # 4.2 Set compute permissions to owner # @@ -214,10 +233,19 @@ async def main(): general_client.user_id: {program_id}, }) + # Get cost quote, then pay for operation to store the secret + receipt_store = await pay( + voter_v, + nillion.Operation.store_secrets(v_to_be_store_secrets), + general_payments_wallet, + general_payments_client, + cluster_id, + ) + # Store in the network print("Storing vote by voter "+str(v)+f": {v_to_be_store_secrets}") store_id = await voter_v.store_secrets( - cluster_id, v_bindings, v_to_be_store_secrets, v_permissions + cluster_id, v_to_be_store_secrets, v_permissions, receipt_store ) store_ids.append(store_id) print(f"Stored vote by voter "+str(v)+f" with store_id ={store_id}") @@ -256,7 +284,16 @@ async def main(): print(f"Computing using program {program_id}") print(f"Stored secrets: {store_ids}") print(f"Provided secret: {to_be_used_in_computation}") - + + # Get cost quote, then pay for operation to compute + receipt_compute = await pay( + general_client, + nillion.Operation.compute(program_id, to_be_used_in_computation), + general_payments_wallet, + general_payments_client, + cluster_id, + ) + # Owner can execute the computation # compute_id = await owner.compute( compute_id = await general_client.compute( @@ -265,6 +302,7 @@ async def main(): store_ids, to_be_used_in_computation, nillion.PublicVariables({}), + receipt_compute ) print(f"Computation sent to the network, compute_id = {compute_id}") diff --git a/helpers/nillion_client_helper.py b/helpers/nillion_client_helper.py index 59a805b2..53f8ebbb 100644 --- a/helpers/nillion_client_helper.py +++ b/helpers/nillion_client_helper.py @@ -8,44 +8,46 @@ from cosmpy.aerial.client.utils import prepare_and_broadcast_basic_transaction from cosmpy.crypto.address import Address + def create_nillion_client(userkey, nodekey): bootnodes = [os.getenv("NILLION_BOOTNODE_MULTIADDRESS")] return nillion.NillionClient( - nodekey, - bootnodes, - nillion.ConnectionMode.relay(), - userkey + nodekey, bootnodes, nillion.ConnectionMode.relay(), userkey ) + async def pay( - client: nillion.NillionClient, - operation: nillion.Operation, - payments_wallet, - payments_client, - cluster_id - ) -> nillion.PaymentReceipt: - print("Getting quote for operation...") - quote = await client.request_price_quote(cluster_id, operation) - print(f"Quote cost is {quote.cost} unil") - address = str(Address(payments_wallet.public_key(), "nillion")) - message = nillion.create_payments_message(quote, address) - tx = Transaction() - tx.add_message(message) - submitted_tx = prepare_and_broadcast_basic_transaction( - payments_client, tx, payments_wallet, gas_limit=1000000 - ) - submitted_tx.wait_to_complete() - print(f"Submitting payment receipt {quote.cost} unil, tx hash {submitted_tx.tx_hash}") - return nillion.PaymentReceipt(quote, submitted_tx.tx_hash) + client: nillion.NillionClient, + operation: nillion.Operation, + payments_wallet, + payments_client, + cluster_id, +) -> nillion.PaymentReceipt: + print("Getting quote for operation...") + quote = await client.request_price_quote(cluster_id, operation) + print(f"Quote cost is {quote.cost} unil") + address = str(Address(payments_wallet.public_key(), "nillion")) + message = nillion.create_payments_message(quote, address) + tx = Transaction() + tx.add_message(message) + submitted_tx = prepare_and_broadcast_basic_transaction( + payments_client, tx, payments_wallet, gas_limit=1000000 + ) + submitted_tx.wait_to_complete() + print( + f"Submitting payment receipt {quote.cost} unil, tx hash {submitted_tx.tx_hash}" + ) + return nillion.PaymentReceipt(quote, submitted_tx.tx_hash) + def create_payments_config(chain_id, payments_endpoint): return NetworkConfig( - chain_id=chain_id, - url=f"grpc+http://{payments_endpoint}/", - fee_minimum_gas_price=0, - fee_denomination="unil", - staking_denomination="unil", - faucet_url=None, - ) \ No newline at end of file + chain_id=chain_id, + url=f"grpc+http://{payments_endpoint}/", + fee_minimum_gas_price=0, + fee_denomination="unil", + staking_denomination="unil", + faucet_url=None, + ) diff --git a/helpers/nillion_keypath_helper.py b/helpers/nillion_keypath_helper.py index 47e08120..4a454137 100644 --- a/helpers/nillion_keypath_helper.py +++ b/helpers/nillion_keypath_helper.py @@ -1,8 +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/old_addition.py b/old_addition.py new file mode 100644 index 00000000..8a6b2dca --- /dev/null +++ b/old_addition.py @@ -0,0 +1,86 @@ +import asyncio +import py_nillion_client as nillion +import os +import sys +import pytest + +from dotenv import load_dotenv + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) +from helpers.nillion_client_helper import create_nillion_client +from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile + +load_dotenv() + + +# 1 Party running simple addition on 1 stored secret and 1 compute time secret +async def main(): + 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_name = "Party1" + program_name = "addition_simple" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" + + # store 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 a secret + stored_secret = nillion.Secrets({ + "my_int1": nillion.SecretInteger(500), + }) + + secret_bindings = nillion.ProgramBindings(program_id) + secret_bindings.add_input_party(party_name, party_id) + + # Store a secret + store_id = await client.store_secrets( + cluster_id, secret_bindings, stored_secret, None + ) + + # Bind the parties in the computation to the client to set input and output parties + compute_bindings = nillion.ProgramBindings(program_id) + compute_bindings.add_input_party(party_name, party_id) + compute_bindings.add_output_party(party_name, party_id) + + print(f"Computing using program {program_id}") + print(f"Use secret store_id: {store_id}") + + computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) + + # Compute on the secret + compute_id = await client.compute( + cluster_id, + compute_bindings, + [store_id], + computation_time_secrets, + nillion.PublicVariables({}), + ) + + # Print compute 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 + + +if __name__ == "__main__": + asyncio.run(main()) + + +@pytest.mark.asyncio +async def test_main(): + result = await main() + assert result == {'my_output': 510} \ No newline at end of file diff --git a/programs/arrays_playground.py b/programs/arrays_playground.py new file mode 100644 index 00000000..11a5cee4 --- /dev/null +++ b/programs/arrays_playground.py @@ -0,0 +1,20 @@ +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(1) + + # 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[0], "B") + + # Step 4: The result is of computing the dot product between the two + result = a.dot(b) + + # Step 5: We can use result.output() to produce the output for Party2 and variable name "my_output" + return result.output(parties[0], "my_output") From e8bc1c51846ce53bceb42d195cc768038312e76c Mon Sep 17 00:00:00 2001 From: davetbutler Date: Mon, 10 Jun 2024 13:44:44 +0100 Subject: [PATCH 15/43] remove erroneous files --- .../arrays_playground.py | 150 ------------------ old_addition.py | 86 ---------- programs/arrays_playground.py | 20 --- 3 files changed, 256 deletions(-) delete mode 100644 examples_and_tutorials/nada_algebra_examples/arrays_playground.py delete mode 100644 old_addition.py delete mode 100644 programs/arrays_playground.py diff --git a/examples_and_tutorials/nada_algebra_examples/arrays_playground.py b/examples_and_tutorials/nada_algebra_examples/arrays_playground.py deleted file mode 100644 index 80c9c108..00000000 --- a/examples_and_tutorials/nada_algebra_examples/arrays_playground.py +++ /dev/null @@ -1,150 +0,0 @@ -# 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()) \ No newline at end of file diff --git a/old_addition.py b/old_addition.py deleted file mode 100644 index 8a6b2dca..00000000 --- a/old_addition.py +++ /dev/null @@ -1,86 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from dotenv import load_dotenv - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() - - -# 1 Party running simple addition on 1 stored secret and 1 compute time secret -async def main(): - 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_name = "Party1" - program_name = "addition_simple" - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - - # store 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 a secret - stored_secret = nillion.Secrets({ - "my_int1": nillion.SecretInteger(500), - }) - - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) - - # Store a secret - store_id = await client.store_secrets( - cluster_id, secret_bindings, stored_secret, None - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) - - # Compute on the secret - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - nillion.PublicVariables({}), - ) - - # Print compute 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 - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {'my_output': 510} \ No newline at end of file diff --git a/programs/arrays_playground.py b/programs/arrays_playground.py deleted file mode 100644 index 11a5cee4..00000000 --- a/programs/arrays_playground.py +++ /dev/null @@ -1,20 +0,0 @@ -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(1) - - # 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[0], "B") - - # Step 4: The result is of computing the dot product between the two - result = a.dot(b) - - # Step 5: We can use result.output() to produce the output for Party2 and variable name "my_output" - return result.output(parties[0], "my_output") From 06b50e7afef23ba887b9f2e7ec688c554f904513 Mon Sep 17 00:00:00 2001 From: davetbutler Date: Mon, 10 Jun 2024 14:01:35 +0100 Subject: [PATCH 16/43] minor README update --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c2a3875a..d2d6bd25 100644 --- a/README.md +++ b/README.md @@ -35,10 +35,11 @@ The [`bootstrap-local-environment.sh`](./bootstrap-local-environment.sh) file us - python client path: `/Users/steph/.nilup/sdks/vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl` - nada path: `/Users/steph/.nilup/sdks/vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl` -2. Set nillion version +2. Set nillion version and initialise it (you only need to initialise on your first time installing/using) ```bash nilup use {version} +nilup init ``` 3. Change directories into this branch of this repo From 102b464f61558fbea4ffdf08167c404126bc826d Mon Sep 17 00:00:00 2001 From: davetbutler Date: Fri, 21 Jun 2024 23:48:36 +0100 Subject: [PATCH 17/43] update all examples to work with new env path and naming --- bootstrap-local-environment.sh | 150 ------------------ .../01_store_secret_party1.py | 19 +-- .../02_store_secret_party_n.py | 20 +-- .../03_multi_party_compute.py | 16 +- .../config.py | 12 +- .../01_fetch_reader_userid.py | 10 +- .../02_store_permissioned_secret.py | 21 +-- .../03_retrieve_secret.py | 20 +-- .../04_revoke_read_permissions.py | 16 +- .../05_test_revoked_permissions.py | 27 ++-- .../addition_simple.py | 21 +-- .../circuit_simple.py | 21 +-- .../circuit_simple_2.py | 21 +-- .../complex.py | 21 +-- .../correlation_coefficient.py | 26 +-- .../division_simple.py | 20 +-- .../modulo_simple.py | 23 +-- .../multiplication_simple.py | 21 +-- .../nada_fn_composition.py | 21 +-- .../reuse.py | 21 +-- .../simple.py | 21 +-- .../simple_literals.py | 21 +-- .../simple_public_variables.py | 21 +-- .../simple_public_variables_only.py | 17 +- .../simple_sub.py | 21 +-- .../subtraction_simple.py | 21 +-- .../subtraction_simple_neg.py | 21 +-- .../tiny_secret_addition.py | 15 +- .../tiny_secret_addition_complete.py | 21 +-- .../store_and_retrieve_blob.py | 24 +-- .../store_and_retrieve_integer.py | 24 +-- .../load_test_large.py} | 81 ++++++---- .../load_tests/load_test_medium.py | 146 +++++++++++++++++ .../load_tests_small.py} | 81 ++++++---- .../01_store_secret_party1.py | 17 +- .../02_store_secret_party_n.py | 20 +-- .../03_multi_party_compute.py | 17 +- .../millionaires_problem_example/config.py | 3 +- .../01_store_program_party1.py | 18 +-- .../02_store_secret_party_n.py | 20 +-- .../voting_tutorial/03_multi_party_compute.py | 17 +- .../voting_tutorial/client_voting.py | 26 +-- .../voting_tutorial/config.py | 12 +- programs/input_integer.py | 7 - programs/input_single.py | 10 -- 45 files changed, 640 insertions(+), 589 deletions(-) delete mode 100755 bootstrap-local-environment.sh rename examples_and_tutorials/{core_concept_single_party_compute/input_integer.py => load_tests/load_test_large.py} (59%) create mode 100644 examples_and_tutorials/load_tests/load_test_medium.py rename examples_and_tutorials/{core_concept_single_party_compute/input_single.py => load_tests/load_tests_small.py} (59%) delete mode 100644 programs/input_integer.py delete mode 100644 programs/input_single.py diff --git a/bootstrap-local-environment.sh b/bootstrap-local-environment.sh deleted file mode 100755 index e0fbccfe..00000000 --- a/bootstrap-local-environment.sh +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env bash - -# set number of node and user keys to be created -num_node_keys=5 -num_user_keys=5 - -# set env file to update -ENV_TO_UPDATE=".env" - -NILLION_DEVNET="nillion-devnet" -NILLION_CLI="nillion" -NILLION_CLI_COMMAND_USER_KEYGEN="user-key-gen" -NILLION_CLI_COMMAND_NODE_KEYGEN="node-key-gen" - -# kill any other nillion-devnet processes -pkill -9 -f $NILLION_DEVNET - -for var in NILLION_DEVNET NILLION_CLI; do - printf "ℹ️ found bin %-18s -> [${!var:?Failed to discover $var}]\n" "$var" -done - -OUTFILE=$(mktemp); -PIDFILE=$(mktemp); - -"$NILLION_DEVNET" >"$OUTFILE" & echo $! >"$PIDFILE"; -echo "--------------------" -echo "Updating your ${ENV_TO_UPDATE} files with nillion-devnet environment info... This may take a minute." -echo "--------------------" -time_limit=160 -while true; do - # Use 'wait' to check if the log file contains the string - if grep "cluster is running, bootnode is at" "$OUTFILE"; then - break - fi - - # If the time limit has been reached, print an error message and exit - if [[ $SECONDS -ge $time_limit ]]; then - echo "Timeout reached while waiting for cluster to be ready in '$OUTFILE'" >&2 - exit 1 - fi - sleep 5 -done - -echo "ℹ️ Cluster has been STARTED (see $OUTFILE)" -cat "$OUTFILE" - -# grep cluster info from nillion-devnet -CLUSTER_ID=$(grep "cluster id is" "$OUTFILE" | awk '{print $5}'); -BOOT_MULTIADDR=$(grep "cluster is running, bootnode is at" "$OUTFILE" | awk '{print $8}'); -JSON_RPC=$(grep "nilchain JSON RPC available at" "$OUTFILE" | awk '{print $7}'); -GRPC=$(grep "nilchain gRPC available at" "$OUTFILE" | awk '{print $6}'); -CHAIN_DIR=$(grep 'starting nilchain node in:' "$OUTFILE" | awk -F'"' '{print $2}') -WEBSOCKET=$(grep 'websocket:' "$OUTFILE" | awk '{print $3}'); - -# Retrieve the wallet private key -WALLET_PRIVATE_KEY=$(echo "y" | "$CHAIN_DIR/bin/nilchaind" keys export stash --home "$CHAIN_DIR" --keyring-backend test --unsafe --unarmored-hex) - -# update or add an environment variable to one or more files -update_env() { - local key=$1 - local value=$2 - # Skip the first two arguments to get the file paths - local files=("${@:3}") - - for file in "${files[@]}"; do - if [ -f "$file" ]; then # Check if file exists - # Check if the key exists in the file and remove it - if grep -q "^$key=" "$file"; then - # Key exists, remove it - grep -v "^$key=" "$file" > temp.txt && mv temp.txt "$file" - fi - - # Append the new key-value pair to the file - echo "$key=$value" >> "$file" - else - echo "File $file not found. Creating $file" - touch $file - echo "$key=$value" >> "$file" - fi - done -} - -# log file contents of key files to add to .env -log_file_contents() { - local file_path=$1 # Direct path to the target file - - # Check if the file exists at the path - if [[ ! -f "$file_path" ]]; then - echo "File $file_path does not exist." - return 1 # Exit the function with an error status if the file does not exist - fi - - # If the file exists, cat its contents - cat "$file_path" -} - -# Generate node keys and add to .env - ex: NILLION_NODEKEY_PATH_PARTY_1 -for ((i=1; i<=$num_node_keys; i++)); do - nodekey_file=$(mktemp) - "$NILLION_CLI" "$NILLION_CLI_COMMAND_NODE_KEYGEN" "$nodekey_file" - NODEKEY_FILES+=("$nodekey_file") - update_env "NILLION_NODEKEY_PATH_PARTY_$i" "$nodekey_file" $ENV_TO_UPDATE - update_env "NILLION_NODEKEY_TEXT_PARTY_$i" "$(log_file_contents $nodekey_file)" $ENV_TO_UPDATE -done - -# Generate user keys and add to .env - ex: NILLION_USERKEY_PATH_PARTY_1 -for ((i=1; i<=$num_user_keys; i++)); do - userkey_file=$(mktemp) - "$NILLION_CLI" "$NILLION_CLI_COMMAND_USER_KEYGEN" "$userkey_file" - USERKEY_FILES+=("$userkey_file") - update_env "NILLION_USERKEY_PATH_PARTY_$i" "$userkey_file" $ENV_TO_UPDATE - update_env "NILLION_USERKEY_TEXT_PARTY_$i" "$(log_file_contents $userkey_file)" $ENV_TO_UPDATE -done - -echo "🔑 Node key and user keys have been generated and added to .env" - -# Add environment variables to .env -update_env "NILLION_WEBSOCKETS" "$WEBSOCKET" $ENV_TO_UPDATE -update_env "NILLION_CLUSTER_ID" "$CLUSTER_ID" $ENV_TO_UPDATE -update_env "NILLION_BOOTNODE_MULTIADDRESS" "$BOOT_MULTIADDR" $ENV_TO_UPDATE -update_env "NILLION_JSON_RPC" "$JSON_RPC" $ENV_TO_UPDATE -update_env "NILLION_GRPC" "$GRPC" $ENV_TO_UPDATE -update_env "NILLION_CHAIN_ID" "nillion-chain-testnet" $ENV_TO_UPDATE -update_env "NILLION_WALLET_PRIVATE_KEY" "$WALLET_PRIVATE_KEY" $ENV_TO_UPDATE - -echo "Running at process pid: $(pgrep -f $NILLION_DEVNET)" - -echo "-------------------------------------------------------" -echo " 7MM 7MM " -echo " MM MM " -echo " db MM MM db " -echo " MM MM " -echo ".7MMpMMMb. 7MM MM MM 7MM ,pW-Wq. 7MMpMMMb. " -echo " MM MM MM MM MM MM 6W' Wb MM MM " -echo " MM MM MM MM MM MM 8M M8 MM MM " -echo " MM MM MM MM MM MM YA. ,A9 MM MM " -echo ".JMML JMML..JMML..JMML..JMML..JMML. Ybmd9 .JMML JMML." -echo "-------------------------------------------------------" -echo "-------------------------------------------------------" -echo "-----------🦆 CONNECTED TO NILLION-DEVNET 🦆-----------" -echo "-------------------------------------------------------" - -echo "ℹ️ Your $ENV_TO_UPDATE file configurations were updated with nillion-devnet connection variables: websocket, cluster id, keys, blockchain info" -echo "💻 The Nillion devnet is still running behind the scenes; to spin down the Nillion devnet at any time, run 'killall nillion-devnet'" - -echo "--------------------" -echo "💻 Your Nillion local cluster is still running - process pid: $(pgrep -f $NILLION_DEVNET)" -echo "ℹ️ Updated your .env file configuration variables: bootnode, cluster id, keys, blockchain info" - -exit 0 diff --git a/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py b/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py index b3ad1704..e1513730 100644 --- a/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py +++ b/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py @@ -4,6 +4,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient from cosmpy.aerial.wallet import LocalWallet @@ -15,9 +16,9 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") from config import ( CONFIG_PROGRAM_NAME, @@ -27,12 +28,12 @@ # The 1st Party stores a secret async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed_party_1 = CONFIG_PARTY_1["seed"] client_1 = create_nillion_client( - getUserKeyFromFile(CONFIG_PARTY_1["userkey_file"]), getNodeKeyFromFile(CONFIG_PARTY_1["nodekey_file"]) + UserKey.from_seed(seed_party_1), NodeKey.from_seed(seed_party_1) ) - party_id_1 = client_1.party_id user_id_1 = client_1.user_id program_mir_path=f"../../programs-compiled/{CONFIG_PROGRAM_NAME}.nada.bin" @@ -40,7 +41,7 @@ async def main(): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -82,14 +83,14 @@ async def main(): # Get cost quote, then pay for operation to store the secret receipt_store = await pay( client_1, - nillion.Operation.store_secrets(stored_secret_1), + nillion.Operation.store_values(stored_secret_1), payments_wallet, payments_client, cluster_id, ) # 1st Party stores a secret - store_id_1 = await client_1.store_secrets( + store_id_1 = await client_1.store_values( cluster_id, stored_secret_1, permissions, receipt_store ) secrets_string = ", ".join(f"{key}: {value}" for key, value in CONFIG_PARTY_1["secrets"].items()) diff --git a/examples_and_tutorials/core_concept_multi_party_compute/02_store_secret_party_n.py b/examples_and_tutorials/core_concept_multi_party_compute/02_store_secret_party_n.py index efea10dd..cfe05b88 100644 --- a/examples_and_tutorials/core_concept_multi_party_compute/02_store_secret_party_n.py +++ b/examples_and_tutorials/core_concept_multi_party_compute/02_store_secret_party_n.py @@ -5,6 +5,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient from cosmpy.aerial.wallet import LocalWallet @@ -16,9 +17,9 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") from config import ( CONFIG_PROGRAM_NAME, @@ -46,8 +47,8 @@ async def main(args = None): args = parser.parse_args(args) cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") program_id=f"{args.user_id_1}/{CONFIG_PROGRAM_NAME}" # start a list of store ids to keep track of stored secrets @@ -55,9 +56,10 @@ async def main(args = None): party_ids = [] for party_info in CONFIG_N_PARTIES: + seed = party_info["seed"] client_n = create_nillion_client( - getUserKeyFromFile(party_info["userkey_file"]), - getNodeKeyFromFile(party_info["nodekey_file"]) + UserKey.from_seed(seed), + NodeKey.from_seed(seed) ) party_id_n = client_n.party_id user_id_n = client_n.user_id @@ -69,7 +71,7 @@ async def main(args = None): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -81,7 +83,7 @@ async def main(args = None): # Get cost quote, then pay for operation to store the secret receipt_store = await pay( client_n, - nillion.Operation.store_secrets(stored_secret), + nillion.Operation.store_values(stored_secret), payments_wallet, payments_client, cluster_id, @@ -98,7 +100,7 @@ async def main(args = None): permissions.add_compute_permissions(compute_permissions) # Store the permissioned secret - store_id = await client_n.store_secrets( + store_id = await client_n.store_values( cluster_id, stored_secret, permissions, receipt_store ) diff --git a/examples_and_tutorials/core_concept_multi_party_compute/03_multi_party_compute.py b/examples_and_tutorials/core_concept_multi_party_compute/03_multi_party_compute.py index 65b9a44d..d984c57a 100644 --- a/examples_and_tutorials/core_concept_multi_party_compute/03_multi_party_compute.py +++ b/examples_and_tutorials/core_concept_multi_party_compute/03_multi_party_compute.py @@ -5,6 +5,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient from cosmpy.aerial.wallet import LocalWallet @@ -16,9 +17,9 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") from config import ( CONFIG_PROGRAM_NAME, @@ -49,13 +50,14 @@ async def main(args = None): args = parser.parse_args(args) cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") # 1st party computes on secrets + seed = CONFIG_PARTY_1["seed"] client_1 = create_nillion_client( - getUserKeyFromFile(CONFIG_PARTY_1["userkey_file"]), - getNodeKeyFromFile(CONFIG_PARTY_1["nodekey_alternate_file"]), + UserKey.from_seed(seed), + NodeKey.from_seed(seed) ) user_id_1 = client_1.user_id party_id_1 = client_1.party_id @@ -64,7 +66,7 @@ async def main(args = None): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) diff --git a/examples_and_tutorials/core_concept_multi_party_compute/config.py b/examples_and_tutorials/core_concept_multi_party_compute/config.py index 2c4ef4af..58348783 100644 --- a/examples_and_tutorials/core_concept_multi_party_compute/config.py +++ b/examples_and_tutorials/core_concept_multi_party_compute/config.py @@ -1,14 +1,14 @@ import os +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv -load_dotenv() +load_dotenv("/Users/davidtbutler/Library/Application Support/nillion.nillion/nillion-devnet.env") # replace this with your program_id CONFIG_PROGRAM_NAME="addition_simple_multi_party_3" # 1st party CONFIG_PARTY_1={ - "userkey_file": os.getenv("NILLION_USERKEY_PATH_PARTY_1"), - "nodekey_file": os.getenv("NILLION_NODEKEY_PATH_PARTY_1"), + "seed": "party_1_seed", "nodekey_alternate_file": os.getenv("NILLION_NODEKEY_PATH_PARTY_4"), "party_name": "Party1", "secrets": { @@ -19,15 +19,13 @@ # N other parties CONFIG_N_PARTIES=[ { - "userkey_file": os.getenv("NILLION_USERKEY_PATH_PARTY_2"), - "nodekey_file": os.getenv("NILLION_NODEKEY_PATH_PARTY_2"), + "seed": "party_2_seed", "party_name": "Party2", "secret_name": "my_int2", "secret_value": 5, }, { - "userkey_file": os.getenv("NILLION_USERKEY_PATH_PARTY_3"), - "nodekey_file": os.getenv("NILLION_NODEKEY_PATH_PARTY_3"), + "seed": "party_3_seed", "party_name": "Party3", "secret_name": "my_int3", "secret_value": 2, diff --git a/examples_and_tutorials/core_concept_permissions/01_fetch_reader_userid.py b/examples_and_tutorials/core_concept_permissions/01_fetch_reader_userid.py index f41f4636..dc6b8ad9 100644 --- a/examples_and_tutorials/core_concept_permissions/01_fetch_reader_userid.py +++ b/examples_and_tutorials/core_concept_permissions/01_fetch_reader_userid.py @@ -3,17 +3,19 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) from helpers.nillion_client_helper import create_nillion_client -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") async def main(): - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + seed_1 = "seed_1" + userkey = UserKey.from_seed((seed_1)) + nodekey = NodeKey.from_seed((seed_1)) # Reader Nillion client reader = create_nillion_client(userkey, nodekey) diff --git a/examples_and_tutorials/core_concept_permissions/02_store_permissioned_secret.py b/examples_and_tutorials/core_concept_permissions/02_store_permissioned_secret.py index 9941dc3c..ac653d23 100644 --- a/examples_and_tutorials/core_concept_permissions/02_store_permissioned_secret.py +++ b/examples_and_tutorials/core_concept_permissions/02_store_permissioned_secret.py @@ -5,6 +5,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient from cosmpy.aerial.wallet import LocalWallet @@ -16,10 +17,9 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") async def main(args = None): parser = argparse.ArgumentParser( @@ -34,10 +34,11 @@ async def main(args = None): args = parser.parse_args(args) cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_2")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_2")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed_2 = "seed_2" + userkey = UserKey.from_seed(seed_2) + nodekey = NodeKey.from_seed(seed_2) # Writer Nillion client writer = create_nillion_client(userkey, nodekey) @@ -48,7 +49,7 @@ async def main(args = None): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -78,7 +79,7 @@ async def main(args = None): # Get cost quote, then pay for operation to store the secret receipt_store = await pay( writer, - nillion.Operation.store_secrets(secrets_object), + nillion.Operation.store_values(secrets_object), payments_wallet, payments_client, cluster_id, @@ -86,7 +87,7 @@ async def main(args = None): # Writer stores the permissioned secret, resulting in the secret's store id print(f"ℹ️ Storing permissioned secret: {secrets_object}") - store_id = await writer.store_secrets( + store_id = await writer.store_values( cluster_id, secrets_object, permissions, receipt_store ) diff --git a/examples_and_tutorials/core_concept_permissions/03_retrieve_secret.py b/examples_and_tutorials/core_concept_permissions/03_retrieve_secret.py index cf58bc36..39164cff 100644 --- a/examples_and_tutorials/core_concept_permissions/03_retrieve_secret.py +++ b/examples_and_tutorials/core_concept_permissions/03_retrieve_secret.py @@ -5,6 +5,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient from cosmpy.aerial.wallet import LocalWallet @@ -16,9 +17,9 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") async def main(args = None): parser = argparse.ArgumentParser( @@ -40,10 +41,11 @@ async def main(args = None): args = parser.parse_args(args) cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_3")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed_1 = "seed_1" + userkey = UserKey.from_seed(seed_1) + nodekey = NodeKey.from_seed(seed_1) # Reader Nillion client reader = create_nillion_client(userkey, nodekey) @@ -53,14 +55,14 @@ async def main(args = None): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) # Get cost quote, then pay for operation to retrieve the secret receipt_store = await pay( reader, - nillion.Operation.retrieve_secret(), + nillion.Operation.retrieve_value(), payments_wallet, payments_client, cluster_id, @@ -68,7 +70,7 @@ async def main(args = None): # Reader retrieves the named secret by store id print(f"Retrieving secret as reader: {reader_user_id}") - result = await reader.retrieve_secret(cluster_id, args.store_id, args.secret_name, receipt_store) + result = await reader.retrieve_value(cluster_id, args.store_id, args.secret_name, receipt_store) print(f"🦄 Retrieved {args.secret_name} secret, value = {result[1].value}", file=sys.stderr) print("\n\nRun the following command to revoke the reader's retrieve permissions to the secret") diff --git a/examples_and_tutorials/core_concept_permissions/04_revoke_read_permissions.py b/examples_and_tutorials/core_concept_permissions/04_revoke_read_permissions.py index 394b9a46..efc33b93 100644 --- a/examples_and_tutorials/core_concept_permissions/04_revoke_read_permissions.py +++ b/examples_and_tutorials/core_concept_permissions/04_revoke_read_permissions.py @@ -5,6 +5,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient from cosmpy.aerial.wallet import LocalWallet @@ -16,9 +17,9 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") async def main(args = None): parser = argparse.ArgumentParser( @@ -39,10 +40,11 @@ async def main(args = None): args = parser.parse_args(args) cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_2")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_4")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed_2 = "seed_2" + userkey = UserKey.from_seed(seed_2) + nodekey = NodeKey.from_seed(seed_2) # Writer Nillion client writer = create_nillion_client(userkey, nodekey) @@ -51,7 +53,7 @@ async def main(args = None): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) diff --git a/examples_and_tutorials/core_concept_permissions/05_test_revoked_permissions.py b/examples_and_tutorials/core_concept_permissions/05_test_revoked_permissions.py index f774c372..ed1ec2cd 100644 --- a/examples_and_tutorials/core_concept_permissions/05_test_revoked_permissions.py +++ b/examples_and_tutorials/core_concept_permissions/05_test_revoked_permissions.py @@ -6,6 +6,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient from cosmpy.aerial.wallet import LocalWallet @@ -17,9 +18,10 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") + fetch_reader_userid = importlib.import_module("01_fetch_reader_userid") store_permissioned_secret = importlib.import_module("02_store_permissioned_secret") @@ -28,9 +30,9 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) from helpers.nillion_client_helper import create_nillion_client -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") async def main(args = None): parser = argparse.ArgumentParser( @@ -45,10 +47,11 @@ async def main(args = None): args = parser.parse_args(args) cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_5")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed_1 = "seed_1" + userkey = UserKey.from_seed(seed_1) + nodekey = NodeKey.from_seed(seed_1) # Reader Nillion client reader = create_nillion_client(userkey, nodekey) @@ -58,14 +61,14 @@ async def main(args = None): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) # Get cost quote, then pay for operation to retrieve the secret receipt_store = await pay( reader, - nillion.Operation.retrieve_secret(), + nillion.Operation.retrieve_value(), payments_wallet, payments_client, cluster_id, @@ -73,10 +76,10 @@ async def main(args = None): try: secret_name = "my_int1" - await reader.retrieve_secret(cluster_id, args.store_id, secret_name, receipt_store) + await reader.retrieve_value(cluster_id, args.store_id, secret_name, receipt_store) print(f"⛔ FAIL: {reader_user_id} user id with revoked permissions was allowed to access secret", file=sys.stderr) except Exception as e: - if str(e) == "retrieving secret: the user is not authorized to access the secret": + if str(e) == "retrieving value: the user is not authorized to access the secret": print(f"🦄 Success: After user permissions were revoked, {reader_user_id} was not allowed to access secret", file=sys.stderr) else: raise(e) diff --git a/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py b/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py index d5d17342..f70b1d30 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py @@ -4,6 +4,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient from cosmpy.aerial.wallet import LocalWallet @@ -15,18 +16,18 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") # 1 Party running simple addition on 1 stored secret and 1 compute time secret async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) client = create_nillion_client(userkey, nodekey) party_id = client.party_id user_id = client.user_id @@ -38,7 +39,7 @@ async def main(): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -81,14 +82,14 @@ async def main(): # Get cost quote, then pay for operation to store the secret receipt_store = await pay( client, - nillion.Operation.store_secrets(stored_secret), + nillion.Operation.store_values(stored_secret), payments_wallet, payments_client, cluster_id, ) # Store a secret, passing in the receipt that shows proof of payment - store_id = await client.store_secrets( + store_id = await client.store_values( cluster_id, stored_secret, permissions, receipt_store ) diff --git a/examples_and_tutorials/core_concept_single_party_compute/circuit_simple.py b/examples_and_tutorials/core_concept_single_party_compute/circuit_simple.py index 978dbab4..b44ccb8b 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/circuit_simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/circuit_simple.py @@ -4,6 +4,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient @@ -16,18 +17,18 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") # 1 Party running a simple circuit on 2 stored secrets and 2 compute time secrets async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) client = create_nillion_client(userkey, nodekey) party_id = client.party_id user_id = client.user_id @@ -38,7 +39,7 @@ async def main(): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -73,14 +74,14 @@ async def main(): receipt_store = await pay( client, - nillion.Operation.store_secrets(stored_secret), + nillion.Operation.store_values(stored_secret), payments_wallet, payments_client, cluster_id, ) # Store a secret - store_id = await client.store_secrets( + store_id = await client.store_values( cluster_id, stored_secret, permissions, receipt_store ) diff --git a/examples_and_tutorials/core_concept_single_party_compute/circuit_simple_2.py b/examples_and_tutorials/core_concept_single_party_compute/circuit_simple_2.py index 0e03f31a..f874d967 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/circuit_simple_2.py +++ b/examples_and_tutorials/core_concept_single_party_compute/circuit_simple_2.py @@ -4,6 +4,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient @@ -16,18 +17,18 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") # 1 Party running a simple circuit on 6 stored secrets and 3 compute time secrets async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) client = create_nillion_client(userkey, nodekey) party_id = client.party_id user_id = client.user_id @@ -38,7 +39,7 @@ async def main(): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -78,14 +79,14 @@ async def main(): receipt_store = await pay( client, - nillion.Operation.store_secrets(stored_secret), + nillion.Operation.store_values(stored_secret), payments_wallet, payments_client, cluster_id, ) # Store a secret - store_id = await client.store_secrets( + store_id = await client.store_values( cluster_id, stored_secret, permissions, receipt_store ) diff --git a/examples_and_tutorials/core_concept_single_party_compute/complex.py b/examples_and_tutorials/core_concept_single_party_compute/complex.py index 86937541..038948ab 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/complex.py +++ b/examples_and_tutorials/core_concept_single_party_compute/complex.py @@ -4,6 +4,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient @@ -16,18 +17,18 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") # 1 Party running complex program on 5 stored secrets and 1 runtime secret async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) client = create_nillion_client(userkey, nodekey) party_id = client.party_id user_id = client.user_id @@ -38,7 +39,7 @@ async def main(): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -79,14 +80,14 @@ async def main(): receipt_store = await pay( client, - nillion.Operation.store_secrets(stored_secret), + nillion.Operation.store_values(stored_secret), payments_wallet, payments_client, cluster_id, ) # Store a secret - store_id = await client.store_secrets( + store_id = await client.store_values( cluster_id, stored_secret, permissions, receipt_store ) diff --git a/examples_and_tutorials/core_concept_single_party_compute/correlation_coefficient.py b/examples_and_tutorials/core_concept_single_party_compute/correlation_coefficient.py index 24e78434..d09bce72 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/correlation_coefficient.py +++ b/examples_and_tutorials/core_concept_single_party_compute/correlation_coefficient.py @@ -9,11 +9,13 @@ import py_nillion_client as nillion import os import sys +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv import random from math import sqrt import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient @@ -26,18 +28,18 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") # 1 Party running simple addition on 1 stored secret and 1 compute time secret async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) client = create_nillion_client(userkey, nodekey) party_id = client.party_id user_id = client.user_id @@ -50,7 +52,7 @@ async def main(): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -118,13 +120,13 @@ async def main(): # Get cost quote, then pay for operation to store the secret receipt_store_0 = await pay( client, - nillion.Operation.store_secrets(party_0_secrets), + nillion.Operation.store_values(party_0_secrets), payments_wallet, payments_client, cluster_id, ) - store_id = await client.store_secrets( + store_id = await client.store_values( cluster_id, party_0_secrets, secret_permissions, receipt_store_0 ) store_ids.append(store_id) @@ -132,14 +134,14 @@ async def main(): receipt_store_1 = await pay( client, - nillion.Operation.store_secrets(party_1_secrets), + nillion.Operation.store_values(party_1_secrets), payments_wallet, payments_client, cluster_id, ) print(f"Storing party 1: {party_1_secrets}") - store_id = await client.store_secrets( + store_id = await client.store_values( cluster_id, party_1_secrets, secret_permissions, receipt_store_1 ) store_ids.append(store_id) diff --git a/examples_and_tutorials/core_concept_single_party_compute/division_simple.py b/examples_and_tutorials/core_concept_single_party_compute/division_simple.py index b15700d4..3d8aa072 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/division_simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/division_simple.py @@ -4,6 +4,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient @@ -16,18 +17,19 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") # 1 Party running simple division on 2 stored secrets async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) client = create_nillion_client(userkey, nodekey) party_id = client.party_id user_id = client.user_id @@ -38,7 +40,7 @@ async def main(): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -73,13 +75,13 @@ async def main(): receipt_store = await pay( client, - nillion.Operation.store_secrets(stored_secret), + nillion.Operation.store_values(stored_secret), payments_wallet, payments_client, cluster_id, ) # Store a secret - store_id = await client.store_secrets( + store_id = await client.store_values( cluster_id, stored_secret, permissions, receipt_store ) diff --git a/examples_and_tutorials/core_concept_single_party_compute/modulo_simple.py b/examples_and_tutorials/core_concept_single_party_compute/modulo_simple.py index b5e21c33..0a1acba9 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/modulo_simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/modulo_simple.py @@ -4,6 +4,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient @@ -16,18 +17,18 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") # 1 Party running modulo on 1 stored secrets and a public variable async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) client = create_nillion_client(userkey, nodekey) party_id = client.party_id user_id = client.user_id @@ -38,7 +39,7 @@ async def main(): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -73,14 +74,14 @@ async def main(): receipt_store = await pay( client, - nillion.Operation.store_secrets(stored_secret), + nillion.Operation.store_values(stored_secret), payments_wallet, payments_client, cluster_id, ) # Store a secret - store_id = await client.store_secrets( + store_id = await client.store_values( cluster_id, stored_secret, permissions, receipt_store ) @@ -104,7 +105,7 @@ async def main(): ) public_variables = nillion.PublicVariables( - {"public_my_int2": nillion.PublicVariableInteger(12)} + {"public_my_int2": nillion.PublicInteger(12)} ) # Compute on the secret diff --git a/examples_and_tutorials/core_concept_single_party_compute/multiplication_simple.py b/examples_and_tutorials/core_concept_single_party_compute/multiplication_simple.py index 22ed5813..58208870 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/multiplication_simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/multiplication_simple.py @@ -4,6 +4,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient @@ -16,18 +17,18 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") # 1 Party running simple multiplication on 1 stored secret and 1 compute time secret async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) client = create_nillion_client(userkey, nodekey) party_id = client.party_id user_id = client.user_id @@ -39,7 +40,7 @@ async def main(): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -74,14 +75,14 @@ async def main(): receipt_store = await pay( client, - nillion.Operation.store_secrets(stored_secret), + nillion.Operation.store_values(stored_secret), payments_wallet, payments_client, cluster_id, ) # Store a secret - store_id = await client.store_secrets( + store_id = await client.store_values( cluster_id, stored_secret, permissions, receipt_store ) diff --git a/examples_and_tutorials/core_concept_single_party_compute/nada_fn_composition.py b/examples_and_tutorials/core_concept_single_party_compute/nada_fn_composition.py index e171837c..ae98c54c 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/nada_fn_composition.py +++ b/examples_and_tutorials/core_concept_single_party_compute/nada_fn_composition.py @@ -4,6 +4,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient @@ -16,17 +17,17 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) client = create_nillion_client(userkey, nodekey) party_id = client.party_id user_id = client.user_id @@ -37,7 +38,7 @@ async def main(): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -72,14 +73,14 @@ async def main(): receipt_store = await pay( client, - nillion.Operation.store_secrets(stored_secret), + nillion.Operation.store_values(stored_secret), payments_wallet, payments_client, cluster_id, ) # Store a secret - store_id = await client.store_secrets( + store_id = await client.store_values( cluster_id, stored_secret, permissions, receipt_store ) diff --git a/examples_and_tutorials/core_concept_single_party_compute/reuse.py b/examples_and_tutorials/core_concept_single_party_compute/reuse.py index 55a58f12..f90fc36c 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/reuse.py +++ b/examples_and_tutorials/core_concept_single_party_compute/reuse.py @@ -4,6 +4,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient @@ -16,17 +17,17 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) client = create_nillion_client(userkey, nodekey) party_id = client.party_id user_id = client.user_id @@ -37,7 +38,7 @@ async def main(): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -75,14 +76,14 @@ async def main(): receipt_store = await pay( client, - nillion.Operation.store_secrets(stored_secret), + nillion.Operation.store_values(stored_secret), payments_wallet, payments_client, cluster_id, ) # Store a secret - store_id = await client.store_secrets( + store_id = await client.store_values( cluster_id, stored_secret, permissions, receipt_store ) diff --git a/examples_and_tutorials/core_concept_single_party_compute/simple.py b/examples_and_tutorials/core_concept_single_party_compute/simple.py index 43e93c0c..55f14dad 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/simple.py @@ -4,6 +4,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient @@ -16,17 +17,17 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) client = create_nillion_client(userkey, nodekey) party_id = client.party_id user_id = client.user_id @@ -37,7 +38,7 @@ async def main(): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -75,13 +76,13 @@ async def main(): receipt_store = await pay( client, - nillion.Operation.store_secrets(stored_secret), + nillion.Operation.store_values(stored_secret), payments_wallet, payments_client, cluster_id, ) # Store a secret - store_id = await client.store_secrets( + store_id = await client.store_values( cluster_id, stored_secret, permissions, receipt_store ) diff --git a/examples_and_tutorials/core_concept_single_party_compute/simple_literals.py b/examples_and_tutorials/core_concept_single_party_compute/simple_literals.py index a0aeb790..a04c2ac3 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/simple_literals.py +++ b/examples_and_tutorials/core_concept_single_party_compute/simple_literals.py @@ -4,6 +4,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient @@ -16,17 +17,17 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) client = create_nillion_client(userkey, nodekey) party_id = client.party_id user_id = client.user_id @@ -37,7 +38,7 @@ async def main(): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -73,13 +74,13 @@ async def main(): receipt_store = await pay( client, - nillion.Operation.store_secrets(stored_secret), + nillion.Operation.store_values(stored_secret), payments_wallet, payments_client, cluster_id, ) # Store a secret - store_id = await client.store_secrets( + store_id = await client.store_values( cluster_id, stored_secret, permissions, receipt_store ) diff --git a/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables.py b/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables.py index 8a89b585..ee24d7d6 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables.py +++ b/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables.py @@ -4,6 +4,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient @@ -16,18 +17,18 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") # 1 Party running compute with only public variables async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) client = create_nillion_client(userkey, nodekey) party_id = client.party_id user_id = client.user_id @@ -38,7 +39,7 @@ async def main(): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -74,13 +75,13 @@ async def main(): receipt_store = await pay( client, - nillion.Operation.store_secrets(stored_secret), + nillion.Operation.store_values(stored_secret), payments_wallet, payments_client, cluster_id, ) # Store a secret - store_id = await client.store_secrets( + store_id = await client.store_values( cluster_id, stored_secret, permissions, receipt_store ) diff --git a/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables_only.py b/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables_only.py index a00956c4..14d46bf8 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables_only.py +++ b/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables_only.py @@ -4,6 +4,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient @@ -16,18 +17,18 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") # 1 Party running compute with only public variables async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) client = create_nillion_client(userkey, nodekey) party_id = client.party_id user_id = client.user_id @@ -38,7 +39,7 @@ async def main(): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) diff --git a/examples_and_tutorials/core_concept_single_party_compute/simple_sub.py b/examples_and_tutorials/core_concept_single_party_compute/simple_sub.py index 3f53851b..82a9df94 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/simple_sub.py +++ b/examples_and_tutorials/core_concept_single_party_compute/simple_sub.py @@ -4,6 +4,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient @@ -16,17 +17,17 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) client = create_nillion_client(userkey, nodekey) party_id = client.party_id user_id = client.user_id @@ -37,7 +38,7 @@ async def main(): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -75,13 +76,13 @@ async def main(): receipt_store = await pay( client, - nillion.Operation.store_secrets(stored_secret), + nillion.Operation.store_values(stored_secret), payments_wallet, payments_client, cluster_id, ) # Store a secret - store_id = await client.store_secrets( + store_id = await client.store_values( cluster_id, stored_secret, permissions, receipt_store ) diff --git a/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple.py b/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple.py index aa5d07ef..3471e716 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple.py @@ -4,6 +4,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient @@ -16,18 +17,18 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") # 1 Party running a simple circuit on 6 stored secrets and 3 compute time secrets async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) client = create_nillion_client(userkey, nodekey) party_id = client.party_id user_id = client.user_id @@ -38,7 +39,7 @@ async def main(): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -73,13 +74,13 @@ async def main(): receipt_store = await pay( client, - nillion.Operation.store_secrets(stored_secret), + nillion.Operation.store_values(stored_secret), payments_wallet, payments_client, cluster_id, ) # Store a secret - store_id = await client.store_secrets( + store_id = await client.store_values( cluster_id, stored_secret, permissions, receipt_store ) diff --git a/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple_neg.py b/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple_neg.py index 57c83718..3244e4d3 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple_neg.py +++ b/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple_neg.py @@ -4,6 +4,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient @@ -16,17 +17,17 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) client = create_nillion_client(userkey, nodekey) party_id = client.party_id user_id = client.user_id @@ -37,7 +38,7 @@ async def main(): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -72,13 +73,13 @@ async def main(): receipt_store = await pay( client, - nillion.Operation.store_secrets(stored_secret), + nillion.Operation.store_values(stored_secret), payments_wallet, payments_client, cluster_id, ) # Store a secret - store_id = await client.store_secrets( + store_id = await client.store_values( cluster_id, stored_secret, permissions, receipt_store ) diff --git a/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition.py b/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition.py index 9e246fb7..dd68ebc8 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition.py +++ b/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition.py @@ -3,24 +3,25 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) from helpers.nillion_client_helper import create_nillion_client -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") # Complete the 🎯 TODOs to store the tiny_secret_addition program in the network, store secrets, and compute async def main(): # 0. The bootstrap-local-environment.sh script put nillion-devnet config variables into the .env file # Get cluster_id, user_key, and node_key from the .env file cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) # 🎯 TODO 1. Initialize NillionClient against nillion-devnet # Create Nillion Client for user diff --git a/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition_complete.py b/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition_complete.py index aee5b199..f9fe6d4e 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition_complete.py +++ b/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition_complete.py @@ -4,6 +4,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient @@ -16,20 +17,20 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") # This python script stores the tiny_secret_addition_complete program in the network, store secrets, and compute async def main(): # 0. The bootstrap-local-environment.sh script put nillion-devnet config variables into the .env file # Get cluster_id, gprc endpoint, chain id, user_key,node_key from the .env file cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) # ✅ 1. Initialize NillionClient against nillion-devnet # Create Nillion Client for user @@ -48,7 +49,7 @@ async def main(): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -90,13 +91,13 @@ async def main(): # ✅ 5. Pay for and store the secret in the network and print the returned store_id receipt_store = await pay( client, - nillion.Operation.store_secrets(new_secret), + nillion.Operation.store_values(new_secret), payments_wallet, payments_client, cluster_id, ) # Store a secret - store_id = await client.store_secrets( + store_id = await client.store_values( cluster_id, new_secret, permissions, receipt_store ) print(f"Computing using program {program_id}") diff --git a/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_blob.py b/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_blob.py index 50d38884..fd8f17cb 100644 --- a/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_blob.py +++ b/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_blob.py @@ -4,6 +4,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient from cosmpy.aerial.wallet import LocalWallet @@ -11,24 +12,25 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) from helpers.nillion_client_helper import create_nillion_client, pay, create_payments_config -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") # Store and retrieve a SecretBlob using the Python Client async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) client = create_nillion_client(userkey, nodekey) # Create payments config and set up Nillion wallet with a private key to pay for operations payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), prefix="nillion" + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion" ) ##### STORE SECRET @@ -50,11 +52,11 @@ async def main(): # Get cost quote, then pay for operation to store the secret receipt_store = await pay( - client, nillion.Operation.store_secrets(stored_secret), payments_wallet, payments_client, cluster_id + client, nillion.Operation.store_values(stored_secret), payments_wallet, payments_client, cluster_id ) # Store a secret, passing in the receipt that shows proof of payment - store_id = await client.store_secrets( + store_id = await client.store_values( cluster_id, stored_secret, permissions, receipt_store ) @@ -65,10 +67,10 @@ async def main(): # Get cost quote, then pay for operation to retrieve the secret receipt_retrieve = await pay( - client, nillion.Operation.retrieve_secret(), payments_wallet, payments_client, cluster_id + client, nillion.Operation.retrieve_value(), payments_wallet, payments_client, cluster_id ) - result_tuple = await client.retrieve_secret(cluster_id, store_id, secret_name, receipt_retrieve) + result_tuple = await client.retrieve_value(cluster_id, store_id, secret_name, receipt_retrieve) print(f"The secret name as a uuid is {result_tuple[0]}") decoded_secret_value = result_tuple[1].value.decode('utf-8') diff --git a/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_integer.py b/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_integer.py index c4632e60..103adb51 100644 --- a/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_integer.py +++ b/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_integer.py @@ -4,6 +4,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient from cosmpy.aerial.wallet import LocalWallet @@ -11,24 +12,25 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) from helpers.nillion_client_helper import create_nillion_client, pay, create_payments_config -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") # Store and retrieve a SecretInteger using the Python Client async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) client = create_nillion_client(userkey, nodekey) # Create payments config and set up Nillion wallet with a private key to pay for operations payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), prefix="nillion" + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion" ) ##### STORE SECRET @@ -46,11 +48,11 @@ async def main(): # Get cost quote, then pay for operation to store the secret receipt_store = await pay( - client, nillion.Operation.store_secrets(stored_secret), payments_wallet, payments_client, cluster_id + client, nillion.Operation.store_values(stored_secret), payments_wallet, payments_client, cluster_id ) # Store a secret, passing in the receipt that shows proof of payment - store_id = await client.store_secrets( + store_id = await client.store_values( cluster_id, stored_secret, permissions, receipt_store ) @@ -61,10 +63,10 @@ async def main(): # Get cost quote, then pay for operation to retrieve the secret receipt_retrieve = await pay( - client, nillion.Operation.retrieve_secret(), payments_wallet, payments_client, cluster_id + client, nillion.Operation.retrieve_value(), payments_wallet, payments_client, cluster_id ) - result_tuple = await client.retrieve_secret(cluster_id, store_id, secret_name, receipt_retrieve) + result_tuple = await client.retrieve_value(cluster_id, store_id, secret_name, receipt_retrieve) print(f"The secret name as a uuid is {result_tuple[0]}") print(f"The secret value is {result_tuple[1].value}") return result_tuple[1].value diff --git a/examples_and_tutorials/core_concept_single_party_compute/input_integer.py b/examples_and_tutorials/load_tests/load_test_large.py similarity index 59% rename from examples_and_tutorials/core_concept_single_party_compute/input_integer.py rename to examples_and_tutorials/load_tests/load_test_large.py index d4f03f51..579deedc 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/input_integer.py +++ b/examples_and_tutorials/load_tests/load_test_large.py @@ -4,8 +4,8 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv - from cosmpy.aerial.client import LedgerClient from cosmpy.aerial.wallet import LocalWallet from cosmpy.crypto.keypairs import PrivateKey @@ -18,31 +18,36 @@ ) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile -load_dotenv() +load_dotenv("/Users/davidtbutler/Library/Application Support/nillion.nillion/nillion-devnet.env") -# 1 Party, returns the secret input +# 1 Party running simple addition on 1 stored secret and 1 compute time secret async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) client = create_nillion_client(userkey, nodekey) party_id = client.party_id user_id = client.user_id party_name = "Party1" - program_name = "input_integer" + program_name = "load_test_large" program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" + # Create payments config and set up Nillion wallet with a private key to pay for operations payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) - # Pay to store the program + ##### STORE PROGRAM + print("-----STORE PROGRAM") + + # Get cost quote, then pay for operation to store program receipt_store_program = await pay( client, nillion.Operation.store_program(), @@ -51,70 +56,78 @@ async def main(): cluster_id, ) - # store program + # Store program, passing in the receipt that shows proof of payment action_id = await client.store_program( cluster_id, program_name, program_mir_path, receipt_store_program ) + # Print details about stored program program_id = f"{user_id}/{program_name}" print("Stored program. action_id:", action_id) print("Stored program_id:", program_id) - # Set permissions for the client to compute on the program - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) + ##### STORE SECRETS + print("-----STORE SECRETS") # Create a secret stored_secret = nillion.Secrets( { - "a": nillion.SecretInteger(16), + "my_int1": nillion.SecretInteger(500), } ) + # Create a permissions object to attach to the stored secret + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) + + # Get cost quote, then pay for operation to store the secret receipt_store = await pay( client, - nillion.Operation.store_secrets(stored_secret), + nillion.Operation.store_values(stored_secret), payments_wallet, payments_client, cluster_id, ) - # Store a secret - store_id = await client.store_secrets( + # Store a secret, passing in the receipt that shows proof of payment + store_id = await client.store_values( cluster_id, stored_secret, permissions, receipt_store ) + ##### COMPUTE + print("-----COMPUTE") + # Bind the parties in the computation to the client to set input and output parties compute_bindings = nillion.ProgramBindings(program_id) compute_bindings.add_input_party(party_name, party_id) compute_bindings.add_output_party(party_name, party_id) - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - compute_time_secrets = nillion.Secrets({}) + # Create a computation time secret to use + computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) - # Pay for the compute + # Get cost quote, then pay for operation to compute receipt_compute = await pay( client, - nillion.Operation.compute(program_id, compute_time_secrets), + nillion.Operation.compute(program_id, computation_time_secrets), payments_wallet, payments_client, cluster_id, ) - # Compute on the secret - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - compute_time_secrets, - nillion.PublicVariables({}), - receipt_compute, + # Compute, passing all params including the receipt that shows proof of payment + uuid = await client.compute( + cluster_id=cluster_id, + bindings=compute_bindings, + store_ids=[store_id], + secrets=computation_time_secrets, + receipt=receipt_compute, + public_variables=nillion.PublicVariables({"public_int1": nillion.PublicInteger(10)}), ) + print(f"Computing using program {program_id}") + print(f"Use secret store_id: {store_id}") # Print compute result - print(f"The computation was sent to the network. compute_id: {compute_id}") + print(f"The computation was sent to the network. compute_id: {uuid}") while True: compute_event = await client.next_compute_event() if isinstance(compute_event, nillion.ComputeFinishedEvent): @@ -130,4 +143,4 @@ async def main(): @pytest.mark.asyncio async def test_main(): result = await main() - assert result == {"my_output": 16} + assert result == {"my_output": 510} diff --git a/examples_and_tutorials/load_tests/load_test_medium.py b/examples_and_tutorials/load_tests/load_test_medium.py new file mode 100644 index 00000000..3fdbe135 --- /dev/null +++ b/examples_and_tutorials/load_tests/load_test_medium.py @@ -0,0 +1,146 @@ +import asyncio +import py_nillion_client as nillion +import os +import sys +import pytest + +from py_nillion_client import NodeKey, UserKey +from dotenv import load_dotenv +from cosmpy.aerial.client import LedgerClient +from cosmpy.aerial.wallet import LocalWallet +from cosmpy.crypto.keypairs import PrivateKey + +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) +from helpers.nillion_client_helper import ( + create_nillion_client, + pay, + create_payments_config, +) +from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile + +load_dotenv("/Users/davidtbutler/Library/Application Support/nillion.nillion/nillion-devnet.env") + + +# 1 Party running simple addition on 1 stored secret and 1 compute time secret +async def main(): + cluster_id = os.getenv("NILLION_CLUSTER_ID") + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) + client = create_nillion_client(userkey, nodekey) + party_id = client.party_id + user_id = client.user_id + party_name = "Party1" + program_name = "load_test_medium" + program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" + + # Create payments config and set up Nillion wallet with a private key to pay for operations + payments_config = create_payments_config(chain_id, grpc_endpoint) + payments_client = LedgerClient(payments_config) + payments_wallet = LocalWallet( + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), + prefix="nillion", + ) + + ##### STORE PROGRAM + print("-----STORE PROGRAM") + + # Get cost quote, then pay for operation to store program + receipt_store_program = await pay( + client, + nillion.Operation.store_program(), + payments_wallet, + payments_client, + cluster_id, + ) + + # Store program, passing in the receipt that shows proof of payment + action_id = await client.store_program( + cluster_id, program_name, program_mir_path, receipt_store_program + ) + + # Print details about stored program + program_id = f"{user_id}/{program_name}" + print("Stored program. action_id:", action_id) + print("Stored program_id:", program_id) + + ##### STORE SECRETS + print("-----STORE SECRETS") + + # Create a secret + stored_secret = nillion.Secrets( + { + "my_int1": nillion.SecretInteger(500), + } + ) + + # Create a permissions object to attach to the stored secret + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) + + # Get cost quote, then pay for operation to store the secret + receipt_store = await pay( + client, + nillion.Operation.store_values(stored_secret), + payments_wallet, + payments_client, + cluster_id, + ) + + # Store a secret, passing in the receipt that shows proof of payment + store_id = await client.store_values( + cluster_id, stored_secret, permissions, receipt_store + ) + + ##### COMPUTE + print("-----COMPUTE") + + # Bind the parties in the computation to the client to set input and output parties + compute_bindings = nillion.ProgramBindings(program_id) + compute_bindings.add_input_party(party_name, party_id) + compute_bindings.add_output_party(party_name, party_id) + + # Create a computation time secret to use + computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) + + # Get cost quote, then pay for operation to compute + receipt_compute = await pay( + client, + nillion.Operation.compute(program_id, computation_time_secrets), + payments_wallet, + payments_client, + cluster_id, + ) + + # Compute, passing all params including the receipt that shows proof of payment + uuid = await client.compute( + cluster_id=cluster_id, + bindings=compute_bindings, + store_ids=[store_id], + secrets=computation_time_secrets, + receipt=receipt_compute, + public_variables=nillion.PublicVariables({"public_int1": nillion.PublicInteger(10)}), + ) + print(f"Computing using program {program_id}") + print(f"Use secret store_id: {store_id}") + + # Print compute result + print(f"The computation was sent to the network. compute_id: {uuid}") + 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 + + +if __name__ == "__main__": + asyncio.run(main()) + + +@pytest.mark.asyncio +async def test_main(): + result = await main() + assert result == {"my_output": 510} diff --git a/examples_and_tutorials/core_concept_single_party_compute/input_single.py b/examples_and_tutorials/load_tests/load_tests_small.py similarity index 59% rename from examples_and_tutorials/core_concept_single_party_compute/input_single.py rename to examples_and_tutorials/load_tests/load_tests_small.py index 62151ccb..c6f8d0a8 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/input_single.py +++ b/examples_and_tutorials/load_tests/load_tests_small.py @@ -4,8 +4,8 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv - from cosmpy.aerial.client import LedgerClient from cosmpy.aerial.wallet import LocalWallet from cosmpy.crypto.keypairs import PrivateKey @@ -18,31 +18,36 @@ ) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile -load_dotenv() +load_dotenv("/Users/davidtbutler/Library/Application Support/nillion.nillion/nillion-devnet.env") -# 1 Party, returns the secret input +# 1 Party running simple addition on 1 stored secret and 1 compute time secret async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "my_seed" + userkey = UserKey.from_seed((seed)) + nodekey = NodeKey.from_seed((seed)) client = create_nillion_client(userkey, nodekey) party_id = client.party_id user_id = client.user_id party_name = "Party1" - program_name = "input_single" + program_name = "load_test_small" program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" + # Create payments config and set up Nillion wallet with a private key to pay for operations payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) - # Pay to store the program + ##### STORE PROGRAM + print("-----STORE PROGRAM") + + # Get cost quote, then pay for operation to store program receipt_store_program = await pay( client, nillion.Operation.store_program(), @@ -51,70 +56,78 @@ async def main(): cluster_id, ) - # store program + # Store program, passing in the receipt that shows proof of payment action_id = await client.store_program( cluster_id, program_name, program_mir_path, receipt_store_program ) + # Print details about stored program program_id = f"{user_id}/{program_name}" print("Stored program. action_id:", action_id) print("Stored program_id:", program_id) - # Set permissions for the client to compute on the program - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) + ##### STORE SECRETS + print("-----STORE SECRETS") # Create a secret stored_secret = nillion.Secrets( { - "my_int1": nillion.SecretInteger(91), + "my_int1": nillion.SecretInteger(500), } ) + # Create a permissions object to attach to the stored secret + permissions = nillion.Permissions.default_for_user(client.user_id) + permissions.add_compute_permissions({client.user_id: {program_id}}) + + # Get cost quote, then pay for operation to store the secret receipt_store = await pay( client, - nillion.Operation.store_secrets(stored_secret), + nillion.Operation.store_values(stored_secret), payments_wallet, payments_client, cluster_id, ) - # Store a secret - store_id = await client.store_secrets( + # Store a secret, passing in the receipt that shows proof of payment + store_id = await client.store_values( cluster_id, stored_secret, permissions, receipt_store ) + ##### COMPUTE + print("-----COMPUTE") + # Bind the parties in the computation to the client to set input and output parties compute_bindings = nillion.ProgramBindings(program_id) compute_bindings.add_input_party(party_name, party_id) compute_bindings.add_output_party(party_name, party_id) - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - compute_time_secrets = nillion.Secrets({}) + # Create a computation time secret to use + computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) - # Pay for the compute + # Get cost quote, then pay for operation to compute receipt_compute = await pay( client, - nillion.Operation.compute(program_id, compute_time_secrets), + nillion.Operation.compute(program_id, computation_time_secrets), payments_wallet, payments_client, cluster_id, ) - # Compute on the secret - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - compute_time_secrets, - nillion.PublicVariables({}), - receipt_compute, + # Compute, passing all params including the receipt that shows proof of payment + uuid = await client.compute( + cluster_id=cluster_id, + bindings=compute_bindings, + store_ids=[store_id], + secrets=computation_time_secrets, + receipt=receipt_compute, + public_variables=nillion.PublicVariables({"public_int1": nillion.PublicInteger(10)}), ) + print(f"Computing using program {program_id}") + print(f"Use secret store_id: {store_id}") # Print compute result - print(f"The computation was sent to the network. compute_id: {compute_id}") + print(f"The computation was sent to the network. compute_id: {uuid}") while True: compute_event = await client.next_compute_event() if isinstance(compute_event, nillion.ComputeFinishedEvent): @@ -130,4 +143,4 @@ async def main(): @pytest.mark.asyncio async def test_main(): result = await main() - assert result == {"my_output": 91} + assert result == {"my_output": 510} diff --git a/examples_and_tutorials/millionaires_problem_example/01_store_secret_party1.py b/examples_and_tutorials/millionaires_problem_example/01_store_secret_party1.py index d640bd5e..5a901e2e 100644 --- a/examples_and_tutorials/millionaires_problem_example/01_store_secret_party1.py +++ b/examples_and_tutorials/millionaires_problem_example/01_store_secret_party1.py @@ -4,6 +4,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from config import CONFIG_PARTY_1 @@ -17,19 +18,19 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") # Alice stores the millionaires program in the network async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + seed = "alice_seed" client_alice = create_nillion_client( - getUserKeyFromFile(CONFIG_PARTY_1["userkey_file"]), - getNodeKeyFromFile(CONFIG_PARTY_1["nodekey_file"]), + UserKey.from_seed(seed), + NodeKey.from_seed(seed), ) millionaires_program_name = "millionaires" @@ -40,7 +41,7 @@ async def main(): payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) diff --git a/examples_and_tutorials/millionaires_problem_example/02_store_secret_party_n.py b/examples_and_tutorials/millionaires_problem_example/02_store_secret_party_n.py index be54c6c1..1d393fe5 100644 --- a/examples_and_tutorials/millionaires_problem_example/02_store_secret_party_n.py +++ b/examples_and_tutorials/millionaires_problem_example/02_store_secret_party_n.py @@ -5,6 +5,7 @@ import sys import pytest +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from config import CONFIG_N_PARTIES @@ -20,8 +21,8 @@ ) from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile -load_dotenv() - +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") # Bob and Charlie store their salaries in the network async def main(args=None): @@ -44,17 +45,18 @@ async def main(args=None): args = parser.parse_args(args) cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") # start a list of store ids to keep track of stored secrets store_ids = [] party_ids = [] for party_info in CONFIG_N_PARTIES: + party_seed = party_info["party_name"] + "_seed" client_n = create_nillion_client( - getUserKeyFromFile(party_info["userkey_file"]), - getNodeKeyFromFile(party_info["nodekey_file"]), + UserKey.from_seed(party_seed), + NodeKey.from_seed(party_seed), ) party_id_n = client_n.party_id user_id_n = client_n.user_id @@ -62,7 +64,7 @@ async def main(args=None): payments_config_n = create_payments_config(chain_id, grpc_endpoint) payments_client_n = LedgerClient(payments_config_n) payments_wallet_n = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -89,13 +91,13 @@ async def main(args=None): receipt_store = await pay( client_n, - nillion.Operation.store_secrets(stored_secret), + nillion.Operation.store_values(stored_secret), payments_wallet_n, payments_client_n, cluster_id, ) # Store the permissioned secret - store_id = await client_n.store_secrets( + store_id = await client_n.store_values( cluster_id, stored_secret, permissions, receipt_store ) diff --git a/examples_and_tutorials/millionaires_problem_example/03_multi_party_compute.py b/examples_and_tutorials/millionaires_problem_example/03_multi_party_compute.py index 80b6a795..5fb8da30 100644 --- a/examples_and_tutorials/millionaires_problem_example/03_multi_party_compute.py +++ b/examples_and_tutorials/millionaires_problem_example/03_multi_party_compute.py @@ -6,6 +6,7 @@ import pytest import importlib +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from cosmpy.aerial.client import LedgerClient @@ -23,10 +24,9 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") async def main(args=None): parser = argparse.ArgumentParser( @@ -51,20 +51,21 @@ async def main(args=None): args = parser.parse_args(args) cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + alice_seed = "alice_seed" # Alice initializes a client client_alice = create_nillion_client( - getUserKeyFromFile(CONFIG_PARTY_1["userkey_file"]), - getNodeKeyFromFile(CONFIG_PARTY_1["nodekey_alternate_file"]), + UserKey.from_seed(alice_seed), + NodeKey.from_seed(alice_seed), ) party_id_alice = client_alice.party_id payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) diff --git a/examples_and_tutorials/millionaires_problem_example/config.py b/examples_and_tutorials/millionaires_problem_example/config.py index 191e820d..c506193d 100644 --- a/examples_and_tutorials/millionaires_problem_example/config.py +++ b/examples_and_tutorials/millionaires_problem_example/config.py @@ -1,8 +1,9 @@ import os import py_nillion_client as nillion +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv -load_dotenv() +load_dotenv("/Users/davidtbutler/Library/Application Support/nillion.nillion/nillion-devnet.env") # Alice CONFIG_PARTY_1 = { diff --git a/examples_and_tutorials/voting_tutorial/01_store_program_party1.py b/examples_and_tutorials/voting_tutorial/01_store_program_party1.py index e63459f0..a4fd53c9 100644 --- a/examples_and_tutorials/voting_tutorial/01_store_program_party1.py +++ b/examples_and_tutorials/voting_tutorial/01_store_program_party1.py @@ -10,29 +10,28 @@ import os import sys from dotenv import load_dotenv -from config import ( - CONFIG_PARTY_1 -) from cosmpy.aerial.client import LedgerClient from cosmpy.aerial.wallet import LocalWallet from cosmpy.crypto.keypairs import PrivateKey +from py_nillion_client import NodeKey, UserKey + sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) from helpers.nillion_client_helper import ( create_nillion_client, pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") # Alice stores the voting program in the network async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") while True: @@ -89,15 +88,16 @@ async def main(): ############################# # Create client + alice_seed = "alice_seed" client_alice = create_nillion_client( - getUserKeyFromFile(CONFIG_PARTY_1["userkey_file"]), getNodeKeyFromFile(CONFIG_PARTY_1["nodekey_file"]) + UserKey.from_seed(alice_seed), NodeKey.from_seed(alice_seed) ) # Create payments config and set up Nillion wallet with a private key to pay for operations payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) diff --git a/examples_and_tutorials/voting_tutorial/02_store_secret_party_n.py b/examples_and_tutorials/voting_tutorial/02_store_secret_party_n.py index 605e38e5..aa0617bf 100644 --- a/examples_and_tutorials/voting_tutorial/02_store_secret_party_n.py +++ b/examples_and_tutorials/voting_tutorial/02_store_secret_party_n.py @@ -9,6 +9,7 @@ import py_nillion_client as nillion import os import sys +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from config import ( CONFIG_N_PARTIES @@ -24,9 +25,9 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") parser = argparse.ArgumentParser( description="Create a secret on the Nillion network with set read/retrieve permissions" @@ -49,8 +50,8 @@ # Bob and Charlie store their votes in the network async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") # start a list of store ids to keep track of stored secrets store_ids = [] @@ -65,16 +66,17 @@ async def main(): ############################# # 1.2 Voters initialization # ############################# + seed = party_info["seed"] client_n = create_nillion_client( - getUserKeyFromFile(party_info["userkey_file"]), - getNodeKeyFromFile(party_info["nodekey_file"]) + UserKey.from_seed(seed), + NodeKey.from_seed(seed) ) # Create payments config and set up Nillion wallet with a private key to pay for operations payments_config_n = create_payments_config(chain_id, grpc_endpoint) payments_client_n = LedgerClient(payments_config_n) payments_wallet_n = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -107,14 +109,14 @@ async def main(): # Get cost quote, then pay for operation to store the secret receipt_store = await pay( client_n, - nillion.Operation.store_secrets(stored_secret), + nillion.Operation.store_values(stored_secret), payments_wallet_n, payments_client_n, cluster_id, ) # Store the permissioned secret - store_id = await client_n.store_secrets( + store_id = await client_n.store_values( cluster_id, stored_secret, permissions, receipt_store ) diff --git a/examples_and_tutorials/voting_tutorial/03_multi_party_compute.py b/examples_and_tutorials/voting_tutorial/03_multi_party_compute.py index 7d9155b6..72290695 100644 --- a/examples_and_tutorials/voting_tutorial/03_multi_party_compute.py +++ b/examples_and_tutorials/voting_tutorial/03_multi_party_compute.py @@ -9,6 +9,7 @@ import py_nillion_client as nillion import os import sys +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from config import ( CONFIG, @@ -27,12 +28,11 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile from digest_result import digest_plurality_vote_honest_result, digest_plurality_vote_dishonest_with_abort_result, digest_plurality_vote_robust_result - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") parser = argparse.ArgumentParser( description="Create a secret on the Nillion network with set read/retrieve permissions" @@ -57,8 +57,8 @@ async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") ##################################### # 1. Parties initialization # @@ -68,14 +68,15 @@ async def main(): # 1.1 Owner initialization # ############################# # Alice initializes a client + seed = CONFIG_PARTY_1["seed"] client_alice = create_nillion_client( - getUserKeyFromFile(CONFIG_PARTY_1["userkey_file"]), - getNodeKeyFromFile(CONFIG_PARTY_1["nodekey_file"]) + UserKey.from_seed(seed), + NodeKey.from_seed(seed) ) payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) party_id_alice = client_alice.party_id diff --git a/examples_and_tutorials/voting_tutorial/client_voting.py b/examples_and_tutorials/voting_tutorial/client_voting.py index 5c15970b..dca13880 100644 --- a/examples_and_tutorials/voting_tutorial/client_voting.py +++ b/examples_and_tutorials/voting_tutorial/client_voting.py @@ -9,6 +9,7 @@ import py_nillion_client as nillion import os import sys +from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv from config import ( CONFIG @@ -24,17 +25,17 @@ pay, create_payments_config, ) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile from digest_result import digest_plurality_vote_honest_result, digest_plurality_vote_dishonest_with_abort_result, digest_plurality_vote_robust_result -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_GRPC") - chain_id = os.getenv("NILLION_CHAIN_ID") + grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") + chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") while True: @@ -107,13 +108,14 @@ async def main(): ###################################### # 1.0 General client initialization # ###################################### - userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1")) - nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1")) + seed = "my_seed" + userkey = UserKey.from_seed(seed) + nodekey = NodeKey.from_seed(seed) general_client = create_nillion_client(userkey, nodekey) general_payments_config = create_payments_config(chain_id, grpc_endpoint) general_payments_client = LedgerClient(general_payments_config) general_payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_WALLET_PRIVATE_KEY"))), + PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion", ) @@ -221,7 +223,7 @@ async def main(): v_c_vote = nillion.SecretUnsignedInteger(v_c_vote_value) v_vote_dic["v"+str(v)+"_c"+str(c)] = v_c_vote c += 1 - v_to_be_store_secrets = nillion.Secrets(v_vote_dic) + v_to_be_store_values = nillion.Secrets(v_vote_dic) ########################################### # 4.2 Set compute permissions to owner # @@ -236,16 +238,16 @@ async def main(): # Get cost quote, then pay for operation to store the secret receipt_store = await pay( voter_v, - nillion.Operation.store_secrets(v_to_be_store_secrets), + nillion.Operation.store_values(v_to_be_store_values), general_payments_wallet, general_payments_client, cluster_id, ) # Store in the network - print("Storing vote by voter "+str(v)+f": {v_to_be_store_secrets}") - store_id = await voter_v.store_secrets( - cluster_id, v_to_be_store_secrets, v_permissions, receipt_store + print("Storing vote by voter "+str(v)+f": {v_to_be_store_values}") + store_id = await voter_v.store_values( + cluster_id, v_to_be_store_values, v_permissions, receipt_store ) store_ids.append(store_id) print(f"Stored vote by voter "+str(v)+f" with store_id ={store_id}") diff --git a/examples_and_tutorials/voting_tutorial/config.py b/examples_and_tutorials/voting_tutorial/config.py index e6cb7386..7a0cfb34 100644 --- a/examples_and_tutorials/voting_tutorial/config.py +++ b/examples_and_tutorials/voting_tutorial/config.py @@ -1,6 +1,7 @@ import os from dotenv import load_dotenv -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") CONFIG = { "nr_candidates": 2, @@ -15,8 +16,7 @@ # Alice CONFIG_PARTY_1={ - "userkey_file": os.getenv("NILLION_USERKEY_PATH_PARTY_1"), - "nodekey_file": os.getenv("NILLION_NODEKEY_PATH_PARTY_1"), + "seed": "alice_seed", "party_name": "Alice", "party_role": "Voter0", "secret_votes": { @@ -28,8 +28,7 @@ # Bob and Charlie CONFIG_N_PARTIES=[ { - "userkey_file": os.getenv("NILLION_USERKEY_PATH_PARTY_2"), - "nodekey_file": os.getenv("NILLION_NODEKEY_PATH_PARTY_2"), + "seed": "bob_seed", "party_name": "Bob", "party_role": "Voter1", "secret_votes": { @@ -38,8 +37,7 @@ }, }, { - "userkey_file": os.getenv("NILLION_USERKEY_PATH_PARTY_3"), - "nodekey_file": os.getenv("NILLION_NODEKEY_PATH_PARTY_3"), + "seed": "charlie_seed", "party_name": "Charlie", "party_role": "Voter2", "secret_votes": { diff --git a/programs/input_integer.py b/programs/input_integer.py deleted file mode 100644 index 917eb6af..00000000 --- a/programs/input_integer.py +++ /dev/null @@ -1,7 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - a = SecretInteger(Input(name="a", party=party1)) - return [Output(a, "my_output", party1)] diff --git a/programs/input_single.py b/programs/input_single.py deleted file mode 100644 index 19f27340..00000000 --- a/programs/input_single.py +++ /dev/null @@ -1,10 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - my_int1 = SecretInteger(Input(name="my_int1", party=party1)) - x = my_int1 - y = x - z = y - return [Output(z, "my_output", party1)] From 1e0d872a9f8476efec44ca5a3adfa7d16d5ffff2 Mon Sep 17 00:00:00 2001 From: davetbutler Date: Fri, 21 Jun 2024 23:49:24 +0100 Subject: [PATCH 18/43] update helpers --- helpers/nillion_client_helper.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/helpers/nillion_client_helper.py b/helpers/nillion_client_helper.py index 53f8ebbb..8b841122 100644 --- a/helpers/nillion_client_helper.py +++ b/helpers/nillion_client_helper.py @@ -1,9 +1,7 @@ import os import py_nillion_client as nillion -from cosmpy.aerial.client import LedgerClient, NetworkConfig -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey +from cosmpy.aerial.client import NetworkConfig from cosmpy.aerial.tx import Transaction from cosmpy.aerial.client.utils import prepare_and_broadcast_basic_transaction from cosmpy.crypto.address import Address @@ -26,7 +24,7 @@ async def pay( ) -> nillion.PaymentReceipt: print("Getting quote for operation...") quote = await client.request_price_quote(cluster_id, operation) - print(f"Quote cost is {quote.cost} unil") + print(f"Quote cost is {quote.cost.total} unil") address = str(Address(payments_wallet.public_key(), "nillion")) message = nillion.create_payments_message(quote, address) tx = Transaction() @@ -36,7 +34,7 @@ async def pay( ) submitted_tx.wait_to_complete() print( - f"Submitting payment receipt {quote.cost} unil, tx hash {submitted_tx.tx_hash}" + f"Submitting payment receipt {quote.cost.total} unil, tx hash {submitted_tx.tx_hash}" ) return nillion.PaymentReceipt(quote, submitted_tx.tx_hash) From 34104a75faf3865e14aa2ad3eefbc42d28565e3d Mon Sep 17 00:00:00 2001 From: davetbutler Date: Mon, 24 Jun 2024 19:41:44 +0100 Subject: [PATCH 19/43] commit before removing examples to new repo --- compile_programs.sh | 35 ----- create_venv.sh | 24 --- docker-compose.yml | 38 ----- .../01_store_secret_party1.py | 2 +- .../02_store_secret_party_n.py | 2 +- .../03_multi_party_compute.py | 2 +- .../01_fetch_reader_userid.py | 2 +- .../02_store_permissioned_secret.py | 2 +- .../03_retrieve_secret.py | 2 +- .../04_revoke_read_permissions.py | 2 +- .../05_test_revoked_permissions.py | 4 +- .../addition_simple.py | 2 +- .../circuit_simple.py | 2 +- .../circuit_simple_2.py | 2 +- .../complex.py | 2 +- .../correlation_coefficient.py | 2 +- .../division_simple.py | 2 +- .../modulo_simple.py | 2 +- .../multiplication_simple.py | 2 +- .../nada_fn_composition.py | 2 +- .../reuse.py | 2 +- .../simple.py | 2 +- .../simple_literals.py | 2 +- .../simple_public_variables.py | 2 +- .../simple_public_variables_only.py | 2 +- .../simple_sub.py | 2 +- .../subtraction_simple.py | 2 +- .../subtraction_simple_neg.py | 2 +- .../tiny_secret_addition.py | 69 --------- .../store_and_retrieve_blob.py | 2 +- .../store_and_retrieve_integer.py | 2 +- .../load_tests/load_test_large.py | 146 ------------------ .../load_tests/load_test_medium.py | 146 ------------------ .../load_tests/load_tests_small.py | 146 ------------------ .../01_store_secret_party1.py | 6 +- .../02_store_secret_party_n.py | 2 +- .../03_multi_party_compute.py | 2 +- .../nada_programs/nada-project.toml | 99 ++++++++++++ .../nada_programs/src}/addition_simple.py | 0 .../src}/addition_simple_multi_party.py | 0 .../nada_programs/src}/circuit_simple_2.py | 0 .../src}/correlation_coefficient.py | 0 .../nada_programs/src}/division_simple.py | 0 .../src}/greater_or_equal_than.py | 0 .../nada_programs/src}/greater_than.py | 0 .../nada_programs/src}/less_than.py | 0 .../nada_programs/src/literals.py | 0 .../nada_programs/src}/millionaires.py | 0 .../nada_programs/src}/modulo_simple.py | 0 .../src}/multiplication_simple.py | 0 .../nada_programs/src}/nada_fn_max.py | 0 .../nada_programs/src/public_variables.py | 0 .../nada_programs/src}/reuse.py | 0 .../src}/reuse_simple_sub_multi_party.py | 0 .../nada_programs/src}/simple_sub.py | 0 .../src}/single_addition_multi_party.py | 0 .../nada_programs/src}/subtraction_simple.py | 0 .../src}/subtraction_simple_neg.py | 0 .../src}/voting_dishonest_abort_5.py | 0 .../src}/voting_dishonest_robust_6.py | 0 .../nada_programs/src}/voting_honest_1.py | 0 .../nada_programs/src}/voting_honest_2.py | 0 .../nada_programs/tests/addition_simple.yaml | 12 ++ .../tests/addition_simple_multi_party.yaml | 12 ++ .../nada_programs/tests/circuit_simple_2.yaml | 26 ++++ .../tests/correlation_coefficient.yaml | 90 +++++++++++ .../nada_programs/tests/division_simple.yaml | 12 ++ .../tests/greater_or_equal_than.yaml | 16 ++ .../nada_programs/tests/greater_than.yaml | 16 ++ .../nada_programs/tests/less_than.yaml | 16 ++ .../nada_programs/tests/literals.yaml | 12 ++ .../nada_programs/tests/millionaires.yaml | 14 ++ .../nada_programs/tests/modulo_simple.yaml | 12 ++ .../tests/multiplication_simple.yaml | 12 ++ .../nada_programs/tests/nada_fn_max.yaml | 12 ++ .../nada_programs/tests/public_variables.yaml | 16 ++ .../nada_programs/tests/reuse.yaml | 14 ++ .../tests/reuse_simple_sub_multi_party.yaml | 12 ++ .../nada_programs/tests/simple_sub.yaml | 16 ++ .../tests/subtraction_simple.yaml | 12 ++ .../tests/subtraction_simple_neg.yaml | 12 ++ .../tests/voting_dishonest_abort_5.yaml | 40 +++++ .../tests/voting_dishonest_robust_6.yaml | 64 ++++++++ .../nada_programs/tests/voting_honest_1.yaml | 22 +++ .../nada_programs/tests/voting_honest_2.yaml | 22 +++ .../01_store_program_party1.py | 4 +- .../02_store_secret_party_n.py | 2 +- .../voting_tutorial/03_multi_party_compute.py | 2 +- .../voting_tutorial/README.md | 4 +- .../voting_tutorial/client_voting.py | 6 +- .../voting_tutorial/config.py | 2 +- .../voting_tutorial/tutorial.md | 8 +- programs/addition_simple_multi_party_3.py | 13 -- programs/array_complex.py | 22 --- programs/circuit_simple.py | 15 -- programs/circuit_simple_multi_party.py | 16 -- programs/complex.py | 21 --- programs/complex_operation_mix.py | 21 --- programs/import_file.py | 13 -- programs/incubation/array2dimensional.py | 27 ---- programs/input_2_dimensional_array.py | 7 - programs/input_n_dimensional_array.py | 7 - programs/less_or_equal_than.py | 14 -- programs/lib/library.py | 3 - programs/map_simple.py | 17 -- programs/multiplication_simple_multi_party.py | 12 -- programs/nada_fn_composition.py | 14 -- programs/nada_fn_simple.py | 14 -- programs/reduce_simple.py | 17 -- programs/reuse_flipped1.py | 14 -- programs/reuse_flipped2.py | 14 -- programs/reuse_simple_1.py | 11 -- programs/reuse_simple_1_multi_party.py | 12 -- programs/reuse_simple_2.py | 11 -- programs/reuse_simple_sub.py | 11 -- programs/simple.py | 15 -- programs/simple_public_variables_only.py | 11 -- programs/single_addition.py | 11 -- programs/subtraction_simple_multi_party.py | 12 -- .../subtraction_simple_neg_multi_party.py | 12 -- programs/tuple_new_unzip.py | 16 -- pyvenv.cfg | 8 - quickstart/client_code/secret_addition.py | 0 .../client_code/secret_addition_complete.py | 44 +++--- .../nada_programs/nada-project.toml | 7 + .../src/secret_addition_complete.py | 0 .../tests/secret_addition_complete.yaml | 12 ++ requirements.txt | 2 + store_program.sh | 33 ---- utils.sh | 37 ----- 130 files changed, 680 insertions(+), 1155 deletions(-) delete mode 100755 compile_programs.sh delete mode 100755 create_venv.sh delete mode 100644 docker-compose.yml delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition.py delete mode 100644 examples_and_tutorials/load_tests/load_test_large.py delete mode 100644 examples_and_tutorials/load_tests/load_test_medium.py delete mode 100644 examples_and_tutorials/load_tests/load_tests_small.py create mode 100644 examples_and_tutorials/nada_programs/nada-project.toml rename {programs => examples_and_tutorials/nada_programs/src}/addition_simple.py (100%) rename {programs => examples_and_tutorials/nada_programs/src}/addition_simple_multi_party.py (100%) rename {programs => examples_and_tutorials/nada_programs/src}/circuit_simple_2.py (100%) rename {programs => examples_and_tutorials/nada_programs/src}/correlation_coefficient.py (100%) rename {programs => examples_and_tutorials/nada_programs/src}/division_simple.py (100%) rename {programs => examples_and_tutorials/nada_programs/src}/greater_or_equal_than.py (100%) rename {programs => examples_and_tutorials/nada_programs/src}/greater_than.py (100%) rename {programs => examples_and_tutorials/nada_programs/src}/less_than.py (100%) rename programs/simple_literals.py => examples_and_tutorials/nada_programs/src/literals.py (100%) rename {programs => examples_and_tutorials/nada_programs/src}/millionaires.py (100%) rename {programs => examples_and_tutorials/nada_programs/src}/modulo_simple.py (100%) rename {programs => examples_and_tutorials/nada_programs/src}/multiplication_simple.py (100%) rename {programs => examples_and_tutorials/nada_programs/src}/nada_fn_max.py (100%) rename programs/simple_public_variables.py => examples_and_tutorials/nada_programs/src/public_variables.py (100%) rename {programs => examples_and_tutorials/nada_programs/src}/reuse.py (100%) rename {programs => examples_and_tutorials/nada_programs/src}/reuse_simple_sub_multi_party.py (100%) rename {programs => examples_and_tutorials/nada_programs/src}/simple_sub.py (100%) rename {programs => examples_and_tutorials/nada_programs/src}/single_addition_multi_party.py (100%) rename {programs => examples_and_tutorials/nada_programs/src}/subtraction_simple.py (100%) rename {programs => examples_and_tutorials/nada_programs/src}/subtraction_simple_neg.py (100%) rename {programs => examples_and_tutorials/nada_programs/src}/voting_dishonest_abort_5.py (100%) rename {programs => examples_and_tutorials/nada_programs/src}/voting_dishonest_robust_6.py (100%) rename {programs => examples_and_tutorials/nada_programs/src}/voting_honest_1.py (100%) rename {programs => examples_and_tutorials/nada_programs/src}/voting_honest_2.py (100%) create mode 100644 examples_and_tutorials/nada_programs/tests/addition_simple.yaml create mode 100644 examples_and_tutorials/nada_programs/tests/addition_simple_multi_party.yaml create mode 100644 examples_and_tutorials/nada_programs/tests/circuit_simple_2.yaml create mode 100644 examples_and_tutorials/nada_programs/tests/correlation_coefficient.yaml create mode 100644 examples_and_tutorials/nada_programs/tests/division_simple.yaml create mode 100644 examples_and_tutorials/nada_programs/tests/greater_or_equal_than.yaml create mode 100644 examples_and_tutorials/nada_programs/tests/greater_than.yaml create mode 100644 examples_and_tutorials/nada_programs/tests/less_than.yaml create mode 100644 examples_and_tutorials/nada_programs/tests/literals.yaml create mode 100644 examples_and_tutorials/nada_programs/tests/millionaires.yaml create mode 100644 examples_and_tutorials/nada_programs/tests/modulo_simple.yaml create mode 100644 examples_and_tutorials/nada_programs/tests/multiplication_simple.yaml create mode 100644 examples_and_tutorials/nada_programs/tests/nada_fn_max.yaml create mode 100644 examples_and_tutorials/nada_programs/tests/public_variables.yaml create mode 100644 examples_and_tutorials/nada_programs/tests/reuse.yaml create mode 100644 examples_and_tutorials/nada_programs/tests/reuse_simple_sub_multi_party.yaml create mode 100644 examples_and_tutorials/nada_programs/tests/simple_sub.yaml create mode 100644 examples_and_tutorials/nada_programs/tests/subtraction_simple.yaml create mode 100644 examples_and_tutorials/nada_programs/tests/subtraction_simple_neg.yaml create mode 100644 examples_and_tutorials/nada_programs/tests/voting_dishonest_abort_5.yaml create mode 100644 examples_and_tutorials/nada_programs/tests/voting_dishonest_robust_6.yaml create mode 100644 examples_and_tutorials/nada_programs/tests/voting_honest_1.yaml create mode 100644 examples_and_tutorials/nada_programs/tests/voting_honest_2.yaml delete mode 100644 programs/addition_simple_multi_party_3.py delete mode 100644 programs/array_complex.py delete mode 100644 programs/circuit_simple.py delete mode 100644 programs/circuit_simple_multi_party.py delete mode 100644 programs/complex.py delete mode 100644 programs/complex_operation_mix.py delete mode 100644 programs/import_file.py delete mode 100644 programs/incubation/array2dimensional.py delete mode 100644 programs/input_2_dimensional_array.py delete mode 100644 programs/input_n_dimensional_array.py delete mode 100644 programs/less_or_equal_than.py delete mode 100644 programs/lib/library.py delete mode 100644 programs/map_simple.py delete mode 100644 programs/multiplication_simple_multi_party.py delete mode 100644 programs/nada_fn_composition.py delete mode 100644 programs/nada_fn_simple.py delete mode 100644 programs/reduce_simple.py delete mode 100644 programs/reuse_flipped1.py delete mode 100644 programs/reuse_flipped2.py delete mode 100644 programs/reuse_simple_1.py delete mode 100644 programs/reuse_simple_1_multi_party.py delete mode 100644 programs/reuse_simple_2.py delete mode 100644 programs/reuse_simple_sub.py delete mode 100644 programs/simple.py delete mode 100644 programs/simple_public_variables_only.py delete mode 100644 programs/single_addition.py delete mode 100644 programs/subtraction_simple_multi_party.py delete mode 100644 programs/subtraction_simple_neg_multi_party.py delete mode 100644 programs/tuple_new_unzip.py delete mode 100644 pyvenv.cfg create mode 100644 quickstart/client_code/secret_addition.py rename examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition_complete.py => quickstart_complete/client_code/secret_addition_complete.py (73%) create mode 100644 quickstart_complete/nada_programs/nada-project.toml rename programs/tiny_secret_addition_complete.py => quickstart_complete/nada_programs/src/secret_addition_complete.py (100%) create mode 100644 quickstart_complete/nada_programs/tests/secret_addition_complete.yaml delete mode 100755 store_program.sh delete mode 100644 utils.sh diff --git a/compile_programs.sh b/compile_programs.sh deleted file mode 100755 index 5c7d647e..00000000 --- a/compile_programs.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash - -# This script compiles all $PROGRAMS_FOLDER programs to mir -PROGRAMS_FOLDER="programs" -COMPILED_PROGRAMS_FOLDER="programs-compiled" - -SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}" 2>/dev/null)" && pwd -P)" -TARGET_PATH="${SCRIPT_PATH}/${COMPILED_PROGRAMS_FOLDER}" -PROGRAMS_PATH="${SCRIPT_PATH}/${PROGRAMS_FOLDER}" - -PYNADAC="pynadac" - -cd "${PROGRAMS_PATH}" || exit 1 - -for file in *.py ; do - echo "Compiling ${file}" - "$PYNADAC" --target-dir "${TARGET_PATH}" \ - --generate-mir-json \ - "${file}" -done - -echo "------------------------" -echo "Compiled programs: all files in the programs directory were compiled to mir: [$TARGET_PATH]" - -echo "Now try running an example:" - -echo "----------single party compute --------------" - -echo "Code for single party compute lives in the examples_and_tutorials/core_concept_client_single_party_compute folder" -echo "📋 to run single party compute - addition_simple program: 'cd examples_and_tutorials/core_concept_client_single_party_compute && python3 addition_simple.py'" - -echo "----------multi party compute --------------" - -echo "Code for multi party compute lives in the examples_and_tutorials/core_concept_multi_party_compute folder" -echo "📋 to run multi party compute in 3 steps - addition_simple_multi_party_3: 'cd examples_and_tutorials/core_concept_multi_party_compute && python3 01_store_secret_party1.py'" \ No newline at end of file diff --git a/create_venv.sh b/create_venv.sh deleted file mode 100755 index 8e5bb12e..00000000 --- a/create_venv.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash - -function create_venv () { - if [[ ! -z "${VIRTUAL_ENV:-}" ]]; then - echo "Virtualenv is already active! Run 'deactivate' to deactivate the virtualenv." - return 0 - fi - - echo "Creating virtualenv" - python3 -m pip install --user virtualenv==20.24.6 - - NILLION_VENV=".venv" - mkdir -p "$NILLION_VENV" - python3 -m virtualenv -p python3 "$NILLION_VENV" - source "$NILLION_VENV/bin/activate" - python3 -m pip install -r requirements.txt - - echo "Virtualenv: $NILLION_VENV" - echo "Check the $NILLION_VENV/lib/python3.1X/site-packages folder to make sure you have py_nillion_client and nada_dsl packages" - echo "📋 Copy and run the following command to activate your environment:" - echo "source $NILLION_VENV/bin/activate" -} - -create_venv diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index a5fa73dc..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,38 +0,0 @@ -version: '3.8' -services: - demo: - container_name: nillion - build: - context: . - dockerfile_inline: | - FROM python:3.10-slim - ARG DEBIAN_FRONTEND="noninteractive" - SHELL ["/bin/bash", "-o", "pipefail", "-xe", "-c"] - RUN apt update; \ - apt install -y --no-install-recommends \ - jq; \ - apt clean; \ - rm -rf /var/lib/apt/lists/*; \ - pip install --upgrade pip; \ - groupadd -r nillion; \ - useradd -rm -g nillion nillion; - USER nillion - network_mode: 'bridge' - environment: - - PUID=1000 - - PGID=1000 - - TZ=Europe/London - - NILLION_SDK_ROOT=/sdk - volumes: - - '$PWD/..:/host' - - $NILLION_SDK_ROOT:/sdk - working_dir: /host - command: - - /bin/bash - - '-c' - - | - source ./utils.sh - check_for_sdk_root - install_nada_dsl - pushd permissions - ./bootstrap-local-environment.sh diff --git a/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py b/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py index e1513730..87c7f38f 100644 --- a/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py +++ b/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py @@ -18,7 +18,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") from config import ( CONFIG_PROGRAM_NAME, diff --git a/examples_and_tutorials/core_concept_multi_party_compute/02_store_secret_party_n.py b/examples_and_tutorials/core_concept_multi_party_compute/02_store_secret_party_n.py index cfe05b88..a98ae8c5 100644 --- a/examples_and_tutorials/core_concept_multi_party_compute/02_store_secret_party_n.py +++ b/examples_and_tutorials/core_concept_multi_party_compute/02_store_secret_party_n.py @@ -19,7 +19,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") from config import ( CONFIG_PROGRAM_NAME, diff --git a/examples_and_tutorials/core_concept_multi_party_compute/03_multi_party_compute.py b/examples_and_tutorials/core_concept_multi_party_compute/03_multi_party_compute.py index d984c57a..cd3c6332 100644 --- a/examples_and_tutorials/core_concept_multi_party_compute/03_multi_party_compute.py +++ b/examples_and_tutorials/core_concept_multi_party_compute/03_multi_party_compute.py @@ -19,7 +19,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") from config import ( CONFIG_PROGRAM_NAME, diff --git a/examples_and_tutorials/core_concept_permissions/01_fetch_reader_userid.py b/examples_and_tutorials/core_concept_permissions/01_fetch_reader_userid.py index dc6b8ad9..c3607f1d 100644 --- a/examples_and_tutorials/core_concept_permissions/01_fetch_reader_userid.py +++ b/examples_and_tutorials/core_concept_permissions/01_fetch_reader_userid.py @@ -10,7 +10,7 @@ from helpers.nillion_client_helper import create_nillion_client home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") async def main(): seed_1 = "seed_1" diff --git a/examples_and_tutorials/core_concept_permissions/02_store_permissioned_secret.py b/examples_and_tutorials/core_concept_permissions/02_store_permissioned_secret.py index ac653d23..e3dcdfa5 100644 --- a/examples_and_tutorials/core_concept_permissions/02_store_permissioned_secret.py +++ b/examples_and_tutorials/core_concept_permissions/02_store_permissioned_secret.py @@ -19,7 +19,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") async def main(args = None): parser = argparse.ArgumentParser( diff --git a/examples_and_tutorials/core_concept_permissions/03_retrieve_secret.py b/examples_and_tutorials/core_concept_permissions/03_retrieve_secret.py index 39164cff..b1ed617a 100644 --- a/examples_and_tutorials/core_concept_permissions/03_retrieve_secret.py +++ b/examples_and_tutorials/core_concept_permissions/03_retrieve_secret.py @@ -19,7 +19,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") async def main(args = None): parser = argparse.ArgumentParser( diff --git a/examples_and_tutorials/core_concept_permissions/04_revoke_read_permissions.py b/examples_and_tutorials/core_concept_permissions/04_revoke_read_permissions.py index efc33b93..a5f09bf9 100644 --- a/examples_and_tutorials/core_concept_permissions/04_revoke_read_permissions.py +++ b/examples_and_tutorials/core_concept_permissions/04_revoke_read_permissions.py @@ -19,7 +19,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") async def main(args = None): parser = argparse.ArgumentParser( diff --git a/examples_and_tutorials/core_concept_permissions/05_test_revoked_permissions.py b/examples_and_tutorials/core_concept_permissions/05_test_revoked_permissions.py index ed1ec2cd..2800e0b2 100644 --- a/examples_and_tutorials/core_concept_permissions/05_test_revoked_permissions.py +++ b/examples_and_tutorials/core_concept_permissions/05_test_revoked_permissions.py @@ -20,7 +20,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") fetch_reader_userid = importlib.import_module("01_fetch_reader_userid") @@ -32,7 +32,7 @@ from helpers.nillion_client_helper import create_nillion_client home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") async def main(args = None): parser = argparse.ArgumentParser( diff --git a/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py b/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py index f70b1d30..0c4cacb7 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py @@ -18,7 +18,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") # 1 Party running simple addition on 1 stored secret and 1 compute time secret async def main(): diff --git a/examples_and_tutorials/core_concept_single_party_compute/circuit_simple.py b/examples_and_tutorials/core_concept_single_party_compute/circuit_simple.py index b44ccb8b..2b4192d0 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/circuit_simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/circuit_simple.py @@ -19,7 +19,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") # 1 Party running a simple circuit on 2 stored secrets and 2 compute time secrets async def main(): diff --git a/examples_and_tutorials/core_concept_single_party_compute/circuit_simple_2.py b/examples_and_tutorials/core_concept_single_party_compute/circuit_simple_2.py index f874d967..92e4bfe5 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/circuit_simple_2.py +++ b/examples_and_tutorials/core_concept_single_party_compute/circuit_simple_2.py @@ -19,7 +19,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") # 1 Party running a simple circuit on 6 stored secrets and 3 compute time secrets async def main(): diff --git a/examples_and_tutorials/core_concept_single_party_compute/complex.py b/examples_and_tutorials/core_concept_single_party_compute/complex.py index 038948ab..f8d2da07 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/complex.py +++ b/examples_and_tutorials/core_concept_single_party_compute/complex.py @@ -19,7 +19,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") # 1 Party running complex program on 5 stored secrets and 1 runtime secret async def main(): diff --git a/examples_and_tutorials/core_concept_single_party_compute/correlation_coefficient.py b/examples_and_tutorials/core_concept_single_party_compute/correlation_coefficient.py index d09bce72..fdf5f6bb 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/correlation_coefficient.py +++ b/examples_and_tutorials/core_concept_single_party_compute/correlation_coefficient.py @@ -30,7 +30,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") # 1 Party running simple addition on 1 stored secret and 1 compute time secret async def main(): diff --git a/examples_and_tutorials/core_concept_single_party_compute/division_simple.py b/examples_and_tutorials/core_concept_single_party_compute/division_simple.py index 3d8aa072..f71893a7 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/division_simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/division_simple.py @@ -19,7 +19,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") # 1 Party running simple division on 2 stored secrets diff --git a/examples_and_tutorials/core_concept_single_party_compute/modulo_simple.py b/examples_and_tutorials/core_concept_single_party_compute/modulo_simple.py index 0a1acba9..c4a4e55a 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/modulo_simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/modulo_simple.py @@ -19,7 +19,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") # 1 Party running modulo on 1 stored secrets and a public variable async def main(): diff --git a/examples_and_tutorials/core_concept_single_party_compute/multiplication_simple.py b/examples_and_tutorials/core_concept_single_party_compute/multiplication_simple.py index 58208870..1deddaaf 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/multiplication_simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/multiplication_simple.py @@ -19,7 +19,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") # 1 Party running simple multiplication on 1 stored secret and 1 compute time secret async def main(): diff --git a/examples_and_tutorials/core_concept_single_party_compute/nada_fn_composition.py b/examples_and_tutorials/core_concept_single_party_compute/nada_fn_composition.py index ae98c54c..7bfd0839 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/nada_fn_composition.py +++ b/examples_and_tutorials/core_concept_single_party_compute/nada_fn_composition.py @@ -19,7 +19,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") diff --git a/examples_and_tutorials/core_concept_single_party_compute/reuse.py b/examples_and_tutorials/core_concept_single_party_compute/reuse.py index f90fc36c..d4237abf 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/reuse.py +++ b/examples_and_tutorials/core_concept_single_party_compute/reuse.py @@ -19,7 +19,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") diff --git a/examples_and_tutorials/core_concept_single_party_compute/simple.py b/examples_and_tutorials/core_concept_single_party_compute/simple.py index 55f14dad..db57846b 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/simple.py @@ -19,7 +19,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") diff --git a/examples_and_tutorials/core_concept_single_party_compute/simple_literals.py b/examples_and_tutorials/core_concept_single_party_compute/simple_literals.py index a04c2ac3..cb08eb32 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/simple_literals.py +++ b/examples_and_tutorials/core_concept_single_party_compute/simple_literals.py @@ -19,7 +19,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") diff --git a/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables.py b/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables.py index ee24d7d6..f9d03d76 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables.py +++ b/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables.py @@ -19,7 +19,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") # 1 Party running compute with only public variables async def main(): diff --git a/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables_only.py b/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables_only.py index 14d46bf8..ebeb60c1 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables_only.py +++ b/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables_only.py @@ -19,7 +19,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") # 1 Party running compute with only public variables async def main(): diff --git a/examples_and_tutorials/core_concept_single_party_compute/simple_sub.py b/examples_and_tutorials/core_concept_single_party_compute/simple_sub.py index 82a9df94..d509ec3a 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/simple_sub.py +++ b/examples_and_tutorials/core_concept_single_party_compute/simple_sub.py @@ -19,7 +19,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") diff --git a/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple.py b/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple.py index 3471e716..6c2e1cf4 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple.py +++ b/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple.py @@ -19,7 +19,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") # 1 Party running a simple circuit on 6 stored secrets and 3 compute time secrets async def main(): diff --git a/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple_neg.py b/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple_neg.py index 3244e4d3..885a2f25 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple_neg.py +++ b/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple_neg.py @@ -19,7 +19,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") diff --git a/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition.py b/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition.py deleted file mode 100644 index dd68ebc8..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition.py +++ /dev/null @@ -1,69 +0,0 @@ -import asyncio -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import create_nillion_client - -home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") - -# Complete the 🎯 TODOs to store the tiny_secret_addition program in the network, store secrets, and compute -async def main(): - # 0. The bootstrap-local-environment.sh script put nillion-devnet config variables into the .env file - # Get cluster_id, user_key, and node_key from the .env file - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - - # 🎯 TODO 1. Initialize NillionClient against nillion-devnet - # Create Nillion Client for user - client = create_nillion_client(userkey, nodekey) - - # 🎯 TODO 2. Get the user id and party id from NillionClient - - # 🎯 TODO 3. Create a payments config, payments client and payments wallet then pay for and store a compiled Nada program in the network - # Set the program name - program_name = "tiny_secret_addition" - # Set the path to the compiled program - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - # Create payments config - # Pay to store the program - # Store program - # Create a variable for the program_id, which is the {user_id}/{program_name} - - # 🎯 TODO 4. Create the 1st secret with bindings to the program - # Create a secret named "my_int1" with any value, ex: 500 - # Create secret bindings object to bind the secret to the program and set the input party - # Set the input party for the secret - # The party name needs to match the party name that is storing "my_int1" in the program - # Set permissions for the client to compute on the program - - # 🎯 TODO 5. Store the secret in the network and print the returned store_id - # Pay to store the secret - # Store a secret, passing in the receipt from payment - - # 🎯 TODO 6. Create compute bindings to set input and output parties - - # 🎯 TODO 7. Pay for and compute on the program with 1st secret from the network, and the 2nd secret, provided at compute time - # Add my_int2, the 2nd secret at computation time - # Pay for the compute - # Compute on the secret - - # 🎯 TODO 8. Print the computation result - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - pass diff --git a/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_blob.py b/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_blob.py index fd8f17cb..6075a842 100644 --- a/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_blob.py +++ b/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_blob.py @@ -14,7 +14,7 @@ from helpers.nillion_client_helper import create_nillion_client, pay, create_payments_config home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") # Store and retrieve a SecretBlob using the Python Client async def main(): diff --git a/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_integer.py b/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_integer.py index 103adb51..3bd17b03 100644 --- a/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_integer.py +++ b/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_integer.py @@ -14,7 +14,7 @@ from helpers.nillion_client_helper import create_nillion_client, pay, create_payments_config home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") # Store and retrieve a SecretInteger using the Python Client async def main(): diff --git a/examples_and_tutorials/load_tests/load_test_large.py b/examples_and_tutorials/load_tests/load_test_large.py deleted file mode 100644 index 579deedc..00000000 --- a/examples_and_tutorials/load_tests/load_test_large.py +++ /dev/null @@ -1,146 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv("/Users/davidtbutler/Library/Application Support/nillion.nillion/nillion-devnet.env") - - -# 1 Party running simple addition on 1 stored secret and 1 compute time secret -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - client = create_nillion_client(userkey, nodekey) - party_id = client.party_id - user_id = client.user_id - party_name = "Party1" - program_name = "load_test_large" - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - - # Create payments config and set up Nillion wallet with a private key to pay for operations - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - ##### STORE PROGRAM - print("-----STORE PROGRAM") - - # Get cost quote, then pay for operation to store program - receipt_store_program = await pay( - client, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - # Store program, passing in the receipt that shows proof of payment - action_id = await client.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) - - # Print details about stored program - program_id = f"{user_id}/{program_name}" - print("Stored program. action_id:", action_id) - print("Stored program_id:", program_id) - - ##### STORE SECRETS - print("-----STORE SECRETS") - - # Create a secret - stored_secret = nillion.Secrets( - { - "my_int1": nillion.SecretInteger(500), - } - ) - - # Create a permissions object to attach to the stored secret - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) - - # Get cost quote, then pay for operation to store the secret - receipt_store = await pay( - client, - nillion.Operation.store_values(stored_secret), - payments_wallet, - payments_client, - cluster_id, - ) - - # Store a secret, passing in the receipt that shows proof of payment - store_id = await client.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - ##### COMPUTE - print("-----COMPUTE") - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - # Create a computation time secret to use - computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) - - # Get cost quote, then pay for operation to compute - receipt_compute = await pay( - client, - nillion.Operation.compute(program_id, computation_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - # Compute, passing all params including the receipt that shows proof of payment - uuid = await client.compute( - cluster_id=cluster_id, - bindings=compute_bindings, - store_ids=[store_id], - secrets=computation_time_secrets, - receipt=receipt_compute, - public_variables=nillion.PublicVariables({"public_int1": nillion.PublicInteger(10)}), - ) - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - # Print compute result - print(f"The computation was sent to the network. compute_id: {uuid}") - 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 - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {"my_output": 510} diff --git a/examples_and_tutorials/load_tests/load_test_medium.py b/examples_and_tutorials/load_tests/load_test_medium.py deleted file mode 100644 index 3fdbe135..00000000 --- a/examples_and_tutorials/load_tests/load_test_medium.py +++ /dev/null @@ -1,146 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv("/Users/davidtbutler/Library/Application Support/nillion.nillion/nillion-devnet.env") - - -# 1 Party running simple addition on 1 stored secret and 1 compute time secret -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - client = create_nillion_client(userkey, nodekey) - party_id = client.party_id - user_id = client.user_id - party_name = "Party1" - program_name = "load_test_medium" - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - - # Create payments config and set up Nillion wallet with a private key to pay for operations - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - ##### STORE PROGRAM - print("-----STORE PROGRAM") - - # Get cost quote, then pay for operation to store program - receipt_store_program = await pay( - client, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - # Store program, passing in the receipt that shows proof of payment - action_id = await client.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) - - # Print details about stored program - program_id = f"{user_id}/{program_name}" - print("Stored program. action_id:", action_id) - print("Stored program_id:", program_id) - - ##### STORE SECRETS - print("-----STORE SECRETS") - - # Create a secret - stored_secret = nillion.Secrets( - { - "my_int1": nillion.SecretInteger(500), - } - ) - - # Create a permissions object to attach to the stored secret - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) - - # Get cost quote, then pay for operation to store the secret - receipt_store = await pay( - client, - nillion.Operation.store_values(stored_secret), - payments_wallet, - payments_client, - cluster_id, - ) - - # Store a secret, passing in the receipt that shows proof of payment - store_id = await client.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - ##### COMPUTE - print("-----COMPUTE") - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - # Create a computation time secret to use - computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) - - # Get cost quote, then pay for operation to compute - receipt_compute = await pay( - client, - nillion.Operation.compute(program_id, computation_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - # Compute, passing all params including the receipt that shows proof of payment - uuid = await client.compute( - cluster_id=cluster_id, - bindings=compute_bindings, - store_ids=[store_id], - secrets=computation_time_secrets, - receipt=receipt_compute, - public_variables=nillion.PublicVariables({"public_int1": nillion.PublicInteger(10)}), - ) - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - # Print compute result - print(f"The computation was sent to the network. compute_id: {uuid}") - 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 - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {"my_output": 510} diff --git a/examples_and_tutorials/load_tests/load_tests_small.py b/examples_and_tutorials/load_tests/load_tests_small.py deleted file mode 100644 index c6f8d0a8..00000000 --- a/examples_and_tutorials/load_tests/load_tests_small.py +++ /dev/null @@ -1,146 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -load_dotenv("/Users/davidtbutler/Library/Application Support/nillion.nillion/nillion-devnet.env") - - -# 1 Party running simple addition on 1 stored secret and 1 compute time secret -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - client = create_nillion_client(userkey, nodekey) - party_id = client.party_id - user_id = client.user_id - party_name = "Party1" - program_name = "load_test_small" - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - - # Create payments config and set up Nillion wallet with a private key to pay for operations - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - ##### STORE PROGRAM - print("-----STORE PROGRAM") - - # Get cost quote, then pay for operation to store program - receipt_store_program = await pay( - client, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - # Store program, passing in the receipt that shows proof of payment - action_id = await client.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) - - # Print details about stored program - program_id = f"{user_id}/{program_name}" - print("Stored program. action_id:", action_id) - print("Stored program_id:", program_id) - - ##### STORE SECRETS - print("-----STORE SECRETS") - - # Create a secret - stored_secret = nillion.Secrets( - { - "my_int1": nillion.SecretInteger(500), - } - ) - - # Create a permissions object to attach to the stored secret - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) - - # Get cost quote, then pay for operation to store the secret - receipt_store = await pay( - client, - nillion.Operation.store_values(stored_secret), - payments_wallet, - payments_client, - cluster_id, - ) - - # Store a secret, passing in the receipt that shows proof of payment - store_id = await client.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - ##### COMPUTE - print("-----COMPUTE") - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - # Create a computation time secret to use - computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) - - # Get cost quote, then pay for operation to compute - receipt_compute = await pay( - client, - nillion.Operation.compute(program_id, computation_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - # Compute, passing all params including the receipt that shows proof of payment - uuid = await client.compute( - cluster_id=cluster_id, - bindings=compute_bindings, - store_ids=[store_id], - secrets=computation_time_secrets, - receipt=receipt_compute, - public_variables=nillion.PublicVariables({"public_int1": nillion.PublicInteger(10)}), - ) - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - # Print compute result - print(f"The computation was sent to the network. compute_id: {uuid}") - 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 - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {"my_output": 510} diff --git a/examples_and_tutorials/millionaires_problem_example/01_store_secret_party1.py b/examples_and_tutorials/millionaires_problem_example/01_store_secret_party1.py index 5a901e2e..4649be8b 100644 --- a/examples_and_tutorials/millionaires_problem_example/01_store_secret_party1.py +++ b/examples_and_tutorials/millionaires_problem_example/01_store_secret_party1.py @@ -20,7 +20,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") # Alice stores the millionaires program in the network async def main(): @@ -35,8 +35,8 @@ async def main(): millionaires_program_name = "millionaires" - # Note: check out the code for the full millionaires program in the programs folder - program_mir_path = "../../programs-compiled/millionaires.nada.bin" + # Note: check out the code for the full millionaires program in the nada_programs folder + program_mir_path = "../../nada_programs-compiled/millionaires.nada.bin" payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) diff --git a/examples_and_tutorials/millionaires_problem_example/02_store_secret_party_n.py b/examples_and_tutorials/millionaires_problem_example/02_store_secret_party_n.py index 1d393fe5..6572bd04 100644 --- a/examples_and_tutorials/millionaires_problem_example/02_store_secret_party_n.py +++ b/examples_and_tutorials/millionaires_problem_example/02_store_secret_party_n.py @@ -22,7 +22,7 @@ from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") # Bob and Charlie store their salaries in the network async def main(args=None): diff --git a/examples_and_tutorials/millionaires_problem_example/03_multi_party_compute.py b/examples_and_tutorials/millionaires_problem_example/03_multi_party_compute.py index 5fb8da30..e1b9a9f9 100644 --- a/examples_and_tutorials/millionaires_problem_example/03_multi_party_compute.py +++ b/examples_and_tutorials/millionaires_problem_example/03_multi_party_compute.py @@ -26,7 +26,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") async def main(args=None): parser = argparse.ArgumentParser( diff --git a/examples_and_tutorials/nada_programs/nada-project.toml b/examples_and_tutorials/nada_programs/nada-project.toml new file mode 100644 index 00000000..44324b20 --- /dev/null +++ b/examples_and_tutorials/nada_programs/nada-project.toml @@ -0,0 +1,99 @@ +name = "quickstart-example-programs" +version = "0.1.0" +authors = [""] + +[[programs]] +path = "src/addition_simple.py" +prime_size = 128 + +[[programs]] +path = "src/addition_simple_multi_party.py" +prime_size = 128 + +[[programs]] +path = "src/circuit_simple_2.py" +prime_size = 128 + +[[programs]] +path = "src/correlation_coefficient.py" +prime_size = 128 + +[[programs]] +path = "src/division_simple.py" +prime_size = 128 + +[[programs]] +path = "src/greater_or_equal_than.py" +prime_size = 128 + +[[programs]] +path = "src/greater_than.py" +prime_size = 128 + +[[programs]] +path = "src/less_than.py" +prime_size = 128 + +[[programs]] +path = "src/literals.py" +prime_size = 128 + +[[programs]] +path = "src/millionaires.py" +prime_size = 128 + +[[programs]] +path = "src/modulo_simple.py" +prime_size = 128 + +[[programs]] +path = "src/multiplication_simple.py" +prime_size = 128 + +[[programs]] +path = "src/nada_fn_max.py" +prime_size = 128 + +[[programs]] +path = "src/public_variables.py" +prime_size = 128 + +[[programs]] +path = "src/reuse.py" +prime_size = 128 + +[[programs]] +path = "src/reuse_simple_sub_multi_party.py" +prime_size = 128 + +[[programs]] +path = "src/simple_sub.py" +prime_size = 128 + +[[programs]] +path = "src/single_addition_multi_party.py" +prime_size = 128 + +[[programs]] +path = "src/subtraction_simple.py" +prime_size = 128 + +[[programs]] +path = "src/subtraction_simple_neg.py" +prime_size = 128 + +[[programs]] +path = "src/voting_dishonest_abort_5.py" +prime_size = 128 + +[[programs]] +path = "src/voting_dishonest_robust_6.py" +prime_size = 128 + +[[programs]] +path = "src/voting_honest_1.py" +prime_size = 128 + +[[programs]] +path = "src/voting_honest_2.py" +prime_size = 128 diff --git a/programs/addition_simple.py b/examples_and_tutorials/nada_programs/src/addition_simple.py similarity index 100% rename from programs/addition_simple.py rename to examples_and_tutorials/nada_programs/src/addition_simple.py diff --git a/programs/addition_simple_multi_party.py b/examples_and_tutorials/nada_programs/src/addition_simple_multi_party.py similarity index 100% rename from programs/addition_simple_multi_party.py rename to examples_and_tutorials/nada_programs/src/addition_simple_multi_party.py diff --git a/programs/circuit_simple_2.py b/examples_and_tutorials/nada_programs/src/circuit_simple_2.py similarity index 100% rename from programs/circuit_simple_2.py rename to examples_and_tutorials/nada_programs/src/circuit_simple_2.py diff --git a/programs/correlation_coefficient.py b/examples_and_tutorials/nada_programs/src/correlation_coefficient.py similarity index 100% rename from programs/correlation_coefficient.py rename to examples_and_tutorials/nada_programs/src/correlation_coefficient.py diff --git a/programs/division_simple.py b/examples_and_tutorials/nada_programs/src/division_simple.py similarity index 100% rename from programs/division_simple.py rename to examples_and_tutorials/nada_programs/src/division_simple.py diff --git a/programs/greater_or_equal_than.py b/examples_and_tutorials/nada_programs/src/greater_or_equal_than.py similarity index 100% rename from programs/greater_or_equal_than.py rename to examples_and_tutorials/nada_programs/src/greater_or_equal_than.py diff --git a/programs/greater_than.py b/examples_and_tutorials/nada_programs/src/greater_than.py similarity index 100% rename from programs/greater_than.py rename to examples_and_tutorials/nada_programs/src/greater_than.py diff --git a/programs/less_than.py b/examples_and_tutorials/nada_programs/src/less_than.py similarity index 100% rename from programs/less_than.py rename to examples_and_tutorials/nada_programs/src/less_than.py diff --git a/programs/simple_literals.py b/examples_and_tutorials/nada_programs/src/literals.py similarity index 100% rename from programs/simple_literals.py rename to examples_and_tutorials/nada_programs/src/literals.py diff --git a/programs/millionaires.py b/examples_and_tutorials/nada_programs/src/millionaires.py similarity index 100% rename from programs/millionaires.py rename to examples_and_tutorials/nada_programs/src/millionaires.py diff --git a/programs/modulo_simple.py b/examples_and_tutorials/nada_programs/src/modulo_simple.py similarity index 100% rename from programs/modulo_simple.py rename to examples_and_tutorials/nada_programs/src/modulo_simple.py diff --git a/programs/multiplication_simple.py b/examples_and_tutorials/nada_programs/src/multiplication_simple.py similarity index 100% rename from programs/multiplication_simple.py rename to examples_and_tutorials/nada_programs/src/multiplication_simple.py diff --git a/programs/nada_fn_max.py b/examples_and_tutorials/nada_programs/src/nada_fn_max.py similarity index 100% rename from programs/nada_fn_max.py rename to examples_and_tutorials/nada_programs/src/nada_fn_max.py diff --git a/programs/simple_public_variables.py b/examples_and_tutorials/nada_programs/src/public_variables.py similarity index 100% rename from programs/simple_public_variables.py rename to examples_and_tutorials/nada_programs/src/public_variables.py diff --git a/programs/reuse.py b/examples_and_tutorials/nada_programs/src/reuse.py similarity index 100% rename from programs/reuse.py rename to examples_and_tutorials/nada_programs/src/reuse.py diff --git a/programs/reuse_simple_sub_multi_party.py b/examples_and_tutorials/nada_programs/src/reuse_simple_sub_multi_party.py similarity index 100% rename from programs/reuse_simple_sub_multi_party.py rename to examples_and_tutorials/nada_programs/src/reuse_simple_sub_multi_party.py diff --git a/programs/simple_sub.py b/examples_and_tutorials/nada_programs/src/simple_sub.py similarity index 100% rename from programs/simple_sub.py rename to examples_and_tutorials/nada_programs/src/simple_sub.py diff --git a/programs/single_addition_multi_party.py b/examples_and_tutorials/nada_programs/src/single_addition_multi_party.py similarity index 100% rename from programs/single_addition_multi_party.py rename to examples_and_tutorials/nada_programs/src/single_addition_multi_party.py diff --git a/programs/subtraction_simple.py b/examples_and_tutorials/nada_programs/src/subtraction_simple.py similarity index 100% rename from programs/subtraction_simple.py rename to examples_and_tutorials/nada_programs/src/subtraction_simple.py diff --git a/programs/subtraction_simple_neg.py b/examples_and_tutorials/nada_programs/src/subtraction_simple_neg.py similarity index 100% rename from programs/subtraction_simple_neg.py rename to examples_and_tutorials/nada_programs/src/subtraction_simple_neg.py diff --git a/programs/voting_dishonest_abort_5.py b/examples_and_tutorials/nada_programs/src/voting_dishonest_abort_5.py similarity index 100% rename from programs/voting_dishonest_abort_5.py rename to examples_and_tutorials/nada_programs/src/voting_dishonest_abort_5.py diff --git a/programs/voting_dishonest_robust_6.py b/examples_and_tutorials/nada_programs/src/voting_dishonest_robust_6.py similarity index 100% rename from programs/voting_dishonest_robust_6.py rename to examples_and_tutorials/nada_programs/src/voting_dishonest_robust_6.py diff --git a/programs/voting_honest_1.py b/examples_and_tutorials/nada_programs/src/voting_honest_1.py similarity index 100% rename from programs/voting_honest_1.py rename to examples_and_tutorials/nada_programs/src/voting_honest_1.py diff --git a/programs/voting_honest_2.py b/examples_and_tutorials/nada_programs/src/voting_honest_2.py similarity index 100% rename from programs/voting_honest_2.py rename to examples_and_tutorials/nada_programs/src/voting_honest_2.py diff --git a/examples_and_tutorials/nada_programs/tests/addition_simple.yaml b/examples_and_tutorials/nada_programs/tests/addition_simple.yaml new file mode 100644 index 00000000..a90ecb49 --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/addition_simple.yaml @@ -0,0 +1,12 @@ +--- +program: addition_simple +inputs: + secrets: + my_int2: + SecretInteger: "3" + my_int1: + SecretInteger: "3" + public_variables: {} +expected_outputs: + my_output: + SecretInteger: "6" diff --git a/examples_and_tutorials/nada_programs/tests/addition_simple_multi_party.yaml b/examples_and_tutorials/nada_programs/tests/addition_simple_multi_party.yaml new file mode 100644 index 00000000..f344822a --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/addition_simple_multi_party.yaml @@ -0,0 +1,12 @@ +--- +program: addition_simple_multi_party +inputs: + secrets: + my_int1: + SecretInteger: "3" + my_int2: + SecretInteger: "3" + public_variables: {} +expected_outputs: + my_output: + SecretInteger: "6" diff --git a/examples_and_tutorials/nada_programs/tests/circuit_simple_2.yaml b/examples_and_tutorials/nada_programs/tests/circuit_simple_2.yaml new file mode 100644 index 00000000..286d8e1e --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/circuit_simple_2.yaml @@ -0,0 +1,26 @@ +--- +program: circuit_simple_2 +inputs: + secrets: + D: + SecretInteger: "3" + A: + SecretInteger: "3" + F: + SecretInteger: "3" + G: + SecretInteger: "3" + E: + SecretInteger: "3" + B: + SecretInteger: "3" + H: + SecretInteger: "3" + C: + SecretInteger: "3" + I: + SecretInteger: "3" + public_variables: {} +expected_outputs: + my_output: + SecretInteger: "66" diff --git a/examples_and_tutorials/nada_programs/tests/correlation_coefficient.yaml b/examples_and_tutorials/nada_programs/tests/correlation_coefficient.yaml new file mode 100644 index 00000000..6ac18a15 --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/correlation_coefficient.yaml @@ -0,0 +1,90 @@ +--- +program: correlation_coefficient +inputs: + secrets: + x7: + SecretInteger: "3" + y5: + SecretInteger: "3" + y2: + SecretInteger: "1" + y6: + SecretInteger: "3" + x14: + SecretInteger: "4" + y14: + SecretInteger: "3" + x16: + SecretInteger: "6" + x6: + SecretInteger: "3" + x13: + SecretInteger: "2" + y3: + SecretInteger: "3" + y8: + SecretInteger: "3" + x9: + SecretInteger: "45" + x15: + SecretInteger: "3" + y16: + SecretInteger: "8" + y1: + SecretInteger: "42" + y7: + SecretInteger: "3" + x12: + SecretInteger: "3" + x4: + SecretInteger: "3" + x5: + SecretInteger: "3" + x19: + SecretInteger: "3" + y13: + SecretInteger: "3" + y17: + SecretInteger: "3" + x2: + SecretInteger: "3" + x8: + SecretInteger: "3" + y9: + SecretInteger: "3" + x10: + SecretInteger: "3" + y12: + SecretInteger: "3" + x1: + SecretInteger: "3" + y4: + SecretInteger: "3" + x18: + SecretInteger: "3" + y15: + SecretInteger: "67" + y19: + SecretInteger: "3" + x3: + SecretInteger: "3" + x0: + SecretInteger: "3" + y10: + SecretInteger: "3" + x11: + SecretInteger: "3" + y0: + SecretInteger: "4" + y11: + SecretInteger: "3" + x17: + SecretInteger: "3" + y18: + SecretInteger: "3" + public_variables: {} +expected_outputs: + correlation_coefficient_squared: + SecretInteger: "52437031009678720685790412" + sign: + SecretBoolean: false diff --git a/examples_and_tutorials/nada_programs/tests/division_simple.yaml b/examples_and_tutorials/nada_programs/tests/division_simple.yaml new file mode 100644 index 00000000..cc2591f1 --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/division_simple.yaml @@ -0,0 +1,12 @@ +--- +program: division_simple +inputs: + secrets: + my_int1: + SecretInteger: "18" + public_variables: + my_int3: + Integer: "6" +expected_outputs: + my_output: + SecretInteger: "3" diff --git a/examples_and_tutorials/nada_programs/tests/greater_or_equal_than.yaml b/examples_and_tutorials/nada_programs/tests/greater_or_equal_than.yaml new file mode 100644 index 00000000..394dd720 --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/greater_or_equal_than.yaml @@ -0,0 +1,16 @@ +--- +program: greater_or_equal_than +inputs: + secrets: + C: + SecretInteger: "3" + D: + SecretInteger: "3" + B: + SecretInteger: "3" + A: + SecretInteger: "3" + public_variables: {} +expected_outputs: + my_output: + SecretBoolean: true diff --git a/examples_and_tutorials/nada_programs/tests/greater_than.yaml b/examples_and_tutorials/nada_programs/tests/greater_than.yaml new file mode 100644 index 00000000..ec8c0e9d --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/greater_than.yaml @@ -0,0 +1,16 @@ +--- +program: greater_than +inputs: + secrets: + A: + SecretInteger: "3" + B: + SecretInteger: "3" + D: + SecretInteger: "3" + C: + SecretInteger: "3" + public_variables: {} +expected_outputs: + my_output: + SecretBoolean: true diff --git a/examples_and_tutorials/nada_programs/tests/less_than.yaml b/examples_and_tutorials/nada_programs/tests/less_than.yaml new file mode 100644 index 00000000..08533fa2 --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/less_than.yaml @@ -0,0 +1,16 @@ +--- +program: less_than +inputs: + secrets: + D: + SecretInteger: "3" + B: + SecretInteger: "3" + C: + SecretInteger: "3" + A: + SecretInteger: "3" + public_variables: {} +expected_outputs: + my_output: + SecretBoolean: false diff --git a/examples_and_tutorials/nada_programs/tests/literals.yaml b/examples_and_tutorials/nada_programs/tests/literals.yaml new file mode 100644 index 00000000..a443f7ab --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/literals.yaml @@ -0,0 +1,12 @@ +--- +program: literals +inputs: + secrets: + A: + SecretInteger: "3" + C: + SecretInteger: "3" + public_variables: {} +expected_outputs: + O: + SecretInteger: "915" diff --git a/examples_and_tutorials/nada_programs/tests/millionaires.yaml b/examples_and_tutorials/nada_programs/tests/millionaires.yaml new file mode 100644 index 00000000..5d83527f --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/millionaires.yaml @@ -0,0 +1,14 @@ +--- +program: millionaires +inputs: + secrets: + alice_salary: + SecretInteger: "3" + bob_salary: + SecretInteger: "5" + charlie_salary: + SecretInteger: "10" + public_variables: {} +expected_outputs: + largest_position: + SecretInteger: "2" diff --git a/examples_and_tutorials/nada_programs/tests/modulo_simple.yaml b/examples_and_tutorials/nada_programs/tests/modulo_simple.yaml new file mode 100644 index 00000000..c538aab3 --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/modulo_simple.yaml @@ -0,0 +1,12 @@ +--- +program: modulo_simple +inputs: + secrets: + my_int1: + SecretInteger: "3" + public_variables: + public_my_int2: + Integer: "6" +expected_outputs: + my_output: + SecretInteger: "3" diff --git a/examples_and_tutorials/nada_programs/tests/multiplication_simple.yaml b/examples_and_tutorials/nada_programs/tests/multiplication_simple.yaml new file mode 100644 index 00000000..2b233982 --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/multiplication_simple.yaml @@ -0,0 +1,12 @@ +--- +program: multiplication_simple +inputs: + secrets: + my_int1: + SecretInteger: "3" + my_int2: + SecretInteger: "3" + public_variables: {} +expected_outputs: + my_output: + SecretInteger: "9" diff --git a/examples_and_tutorials/nada_programs/tests/nada_fn_max.yaml b/examples_and_tutorials/nada_programs/tests/nada_fn_max.yaml new file mode 100644 index 00000000..4f74ff89 --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/nada_fn_max.yaml @@ -0,0 +1,12 @@ +--- +program: nada_fn_max +inputs: + secrets: + my_int1: + SecretInteger: "10" + my_int2: + SecretInteger: "3" + public_variables: {} +expected_outputs: + my_output: + SecretInteger: "10" diff --git a/examples_and_tutorials/nada_programs/tests/public_variables.yaml b/examples_and_tutorials/nada_programs/tests/public_variables.yaml new file mode 100644 index 00000000..f764700c --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/public_variables.yaml @@ -0,0 +1,16 @@ +--- +program: public_variables +inputs: + secrets: + A: + SecretInteger: "3" + C: + SecretInteger: "3" + public_variables: + B: + Integer: "3" + D: + Integer: "3" +expected_outputs: + O: + SecretInteger: "30" diff --git a/examples_and_tutorials/nada_programs/tests/reuse.yaml b/examples_and_tutorials/nada_programs/tests/reuse.yaml new file mode 100644 index 00000000..ae2dc486 --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/reuse.yaml @@ -0,0 +1,14 @@ +--- +program: reuse +inputs: + secrets: + B: + SecretInteger: "3" + A: + SecretInteger: "3" + C: + SecretInteger: "3" + public_variables: {} +expected_outputs: + R: + SecretInteger: "18" diff --git a/examples_and_tutorials/nada_programs/tests/reuse_simple_sub_multi_party.yaml b/examples_and_tutorials/nada_programs/tests/reuse_simple_sub_multi_party.yaml new file mode 100644 index 00000000..8039b752 --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/reuse_simple_sub_multi_party.yaml @@ -0,0 +1,12 @@ +--- +program: reuse_simple_sub_multi_party +inputs: + secrets: + my_int1: + SecretInteger: "3" + my_int2: + SecretInteger: "10" + public_variables: {} +expected_outputs: + my_output: + SecretInteger: "-91" diff --git a/examples_and_tutorials/nada_programs/tests/simple_sub.yaml b/examples_and_tutorials/nada_programs/tests/simple_sub.yaml new file mode 100644 index 00000000..2fedba5c --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/simple_sub.yaml @@ -0,0 +1,16 @@ +--- +program: simple_sub +inputs: + secrets: + D: + SecretInteger: "3" + A: + SecretInteger: "3" + B: + SecretInteger: "3" + C: + SecretInteger: "10" + public_variables: {} +expected_outputs: + O: + SecretInteger: "-21" diff --git a/examples_and_tutorials/nada_programs/tests/subtraction_simple.yaml b/examples_and_tutorials/nada_programs/tests/subtraction_simple.yaml new file mode 100644 index 00000000..4dd1825e --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/subtraction_simple.yaml @@ -0,0 +1,12 @@ +--- +program: subtraction_simple +inputs: + secrets: + my_int1: + SecretInteger: "3" + my_int2: + SecretInteger: "3" + public_variables: {} +expected_outputs: + my_output: + SecretInteger: "0" diff --git a/examples_and_tutorials/nada_programs/tests/subtraction_simple_neg.yaml b/examples_and_tutorials/nada_programs/tests/subtraction_simple_neg.yaml new file mode 100644 index 00000000..6ba9e630 --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/subtraction_simple_neg.yaml @@ -0,0 +1,12 @@ +--- +program: subtraction_simple_neg +inputs: + secrets: + my_int2: + SecretInteger: "3" + my_int4: + SecretInteger: "10" + public_variables: {} +expected_outputs: + my_output: + SecretInteger: "-7" diff --git a/examples_and_tutorials/nada_programs/tests/voting_dishonest_abort_5.yaml b/examples_and_tutorials/nada_programs/tests/voting_dishonest_abort_5.yaml new file mode 100644 index 00000000..7e3fee18 --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/voting_dishonest_abort_5.yaml @@ -0,0 +1,40 @@ +--- +program: voting_dishonest_abort_5 +inputs: + secrets: + v0_c0: + SecretUnsignedInteger: "3" + v2_c1: + SecretUnsignedInteger: "3" + v2_c0: + SecretUnsignedInteger: "3" + v0_c1: + SecretUnsignedInteger: "3" + v1_c0: + SecretUnsignedInteger: "10" + v1_c1: + SecretUnsignedInteger: "3" + public_variables: {} +expected_outputs: + final_vote_count_c1: + SecretUnsignedInteger: "9" + check_sum_v0: + SecretUnsignedInteger: "6" + check_prod_v1_c0: + SecretUnsignedInteger: "72" + check_sum_v1: + SecretUnsignedInteger: "13" + check_prod_v0_c1: + SecretUnsignedInteger: "2" + final_vote_count_c0: + SecretUnsignedInteger: "16" + check_prod_v2_c1: + SecretUnsignedInteger: "2" + check_prod_v2_c0: + SecretUnsignedInteger: "2" + check_prod_v0_c0: + SecretUnsignedInteger: "2" + check_sum_v2: + SecretUnsignedInteger: "6" + check_prod_v1_c1: + SecretUnsignedInteger: "2" diff --git a/examples_and_tutorials/nada_programs/tests/voting_dishonest_robust_6.yaml b/examples_and_tutorials/nada_programs/tests/voting_dishonest_robust_6.yaml new file mode 100644 index 00000000..2f05691f --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/voting_dishonest_robust_6.yaml @@ -0,0 +1,64 @@ +--- +program: voting_dishonest_robust_6 +inputs: + secrets: + v2_c0: + SecretUnsignedInteger: "3" + v2_c1: + SecretUnsignedInteger: "3" + v0_c1: + SecretUnsignedInteger: "3" + v0_c0: + SecretUnsignedInteger: "3" + v1_c1: + SecretUnsignedInteger: "3" + v1_c0: + SecretUnsignedInteger: "3" + public_variables: {} +expected_outputs: + if_prod_cheat_open_v1_c0: + SecretUnsignedInteger: "3" + check_prod_v2_c1: + SecretUnsignedInteger: "2" + if_prod_cheat_open_v2_c0: + SecretUnsignedInteger: "3" + final_vote_count_c0: + SecretUnsignedInteger: "9" + final_vote_count_c1: + SecretUnsignedInteger: "9" + check_sum_v0: + SecretUnsignedInteger: "6" + check_prod_v0_c0: + SecretUnsignedInteger: "2" + if_prod_cheat_open_v2_c1: + SecretUnsignedInteger: "3" + check_prod_v0_c1: + SecretUnsignedInteger: "2" + check_prod_v1_c1: + SecretUnsignedInteger: "2" + if_sum_cheat_open_v2_c0: + SecretUnsignedInteger: "3" + check_prod_v2_c0: + SecretUnsignedInteger: "2" + if_sum_cheat_open_v2_c1: + SecretUnsignedInteger: "3" + if_prod_cheat_open_v0_c0: + SecretUnsignedInteger: "3" + if_prod_cheat_open_v0_c1: + SecretUnsignedInteger: "3" + if_sum_cheat_open_v1_c1: + SecretUnsignedInteger: "3" + if_sum_cheat_open_v0_c1: + SecretUnsignedInteger: "3" + check_sum_v2: + SecretUnsignedInteger: "6" + check_sum_v1: + SecretUnsignedInteger: "6" + if_prod_cheat_open_v1_c1: + SecretUnsignedInteger: "3" + check_prod_v1_c0: + SecretUnsignedInteger: "2" + if_sum_cheat_open_v0_c0: + SecretUnsignedInteger: "3" + if_sum_cheat_open_v1_c0: + SecretUnsignedInteger: "3" diff --git a/examples_and_tutorials/nada_programs/tests/voting_honest_1.yaml b/examples_and_tutorials/nada_programs/tests/voting_honest_1.yaml new file mode 100644 index 00000000..504d8c4a --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/voting_honest_1.yaml @@ -0,0 +1,22 @@ +--- +program: voting_honest_1 +inputs: + secrets: + v1_c0: + SecretUnsignedInteger: "3" + v2_c0: + SecretUnsignedInteger: "3" + v2_c1: + SecretUnsignedInteger: "3" + v0_c0: + SecretUnsignedInteger: "3" + v0_c1: + SecretUnsignedInteger: "3" + v1_c1: + SecretUnsignedInteger: "3" + public_variables: {} +expected_outputs: + final_vote_count_c0: + SecretUnsignedInteger: "9" + final_vote_count_c1: + SecretUnsignedInteger: "9" diff --git a/examples_and_tutorials/nada_programs/tests/voting_honest_2.yaml b/examples_and_tutorials/nada_programs/tests/voting_honest_2.yaml new file mode 100644 index 00000000..867caea5 --- /dev/null +++ b/examples_and_tutorials/nada_programs/tests/voting_honest_2.yaml @@ -0,0 +1,22 @@ +--- +program: voting_honest_2 +inputs: + secrets: + v0_c1: + SecretUnsignedInteger: "3" + v1_c1: + SecretUnsignedInteger: "3" + v2_c0: + SecretUnsignedInteger: "3" + v1_c0: + SecretUnsignedInteger: "3" + v0_c0: + SecretUnsignedInteger: "3" + v2_c1: + SecretUnsignedInteger: "3" + public_variables: {} +expected_outputs: + final_vote_count_c1: + SecretUnsignedInteger: "9" + final_vote_count_c0: + SecretUnsignedInteger: "9" diff --git a/examples_and_tutorials/voting_tutorial/01_store_program_party1.py b/examples_and_tutorials/voting_tutorial/01_store_program_party1.py index a4fd53c9..dd139ce4 100644 --- a/examples_and_tutorials/voting_tutorial/01_store_program_party1.py +++ b/examples_and_tutorials/voting_tutorial/01_store_program_party1.py @@ -25,7 +25,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") # Alice stores the voting program in the network async def main(): @@ -40,7 +40,7 @@ async def main(): # either 'digest_plurality_vote_honest_result()' or 'digest_plurality_vote_dishonest_with_abort_result()' # functions to digest the result. # - # Existing voting programs: + # Existing voting nada_programs: # # program_name = "voting_honest_1" # program_name = "voting_honest_2" diff --git a/examples_and_tutorials/voting_tutorial/02_store_secret_party_n.py b/examples_and_tutorials/voting_tutorial/02_store_secret_party_n.py index aa0617bf..cc895fd0 100644 --- a/examples_and_tutorials/voting_tutorial/02_store_secret_party_n.py +++ b/examples_and_tutorials/voting_tutorial/02_store_secret_party_n.py @@ -27,7 +27,7 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") parser = argparse.ArgumentParser( description="Create a secret on the Nillion network with set read/retrieve permissions" diff --git a/examples_and_tutorials/voting_tutorial/03_multi_party_compute.py b/examples_and_tutorials/voting_tutorial/03_multi_party_compute.py index 72290695..d7afaede 100644 --- a/examples_and_tutorials/voting_tutorial/03_multi_party_compute.py +++ b/examples_and_tutorials/voting_tutorial/03_multi_party_compute.py @@ -32,7 +32,7 @@ from digest_result import digest_plurality_vote_honest_result, digest_plurality_vote_dishonest_with_abort_result, digest_plurality_vote_robust_result home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") parser = argparse.ArgumentParser( description="Create a secret on the Nillion network with set read/retrieve permissions" diff --git a/examples_and_tutorials/voting_tutorial/README.md b/examples_and_tutorials/voting_tutorial/README.md index f4ea3380..33808dcc 100644 --- a/examples_and_tutorials/voting_tutorial/README.md +++ b/examples_and_tutorials/voting_tutorial/README.md @@ -30,7 +30,7 @@ In the single file version, the different roles are played by a single entity (` 5. (Real environment:) Voters send their their party IDs and store IDs to the owner. 6. Owner compute voting system using votes from voters. -Also, we present a function [`digest_plurality_vote_robust_result()`](digest_result.py) that digests the result output by the [voting_dishonest_robust_6.py](../../programs/voting_dishonest_robust_6.py). +Also, we present a function [`digest_plurality_vote_robust_result()`](digest_result.py) that digests the result output by the [voting_dishonest_robust_6.py](../nada_programs/src/voting_dishonest_robust_6.py). ### Run @@ -84,4 +84,4 @@ The script will provide the command to perform step 3. python3 03_multi_party_compute.py --program_id {program_id} --party_ids_to_store_ids {party_ids_to_store_ids} ``` -Also, we present a function [`digest_plurality_vote_robust_result()`](digest_result.py) that digests the result output by the [voting_dishonest_robust_6.py](../../programs/voting_dishonest_robust_6.py). +Also, we present a function [`digest_plurality_vote_robust_result()`](digest_result.py) that digests the result output by the [voting_dishonest_robust_6.py](../nada_programs/src/voting_dishonest_robust_6.py). diff --git a/examples_and_tutorials/voting_tutorial/client_voting.py b/examples_and_tutorials/voting_tutorial/client_voting.py index dca13880..995a217d 100644 --- a/examples_and_tutorials/voting_tutorial/client_voting.py +++ b/examples_and_tutorials/voting_tutorial/client_voting.py @@ -29,7 +29,7 @@ from digest_result import digest_plurality_vote_honest_result, digest_plurality_vote_dishonest_with_abort_result, digest_plurality_vote_robust_result home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") async def main(): @@ -44,7 +44,7 @@ async def main(): # either 'digest_plurality_vote_honest_result()' or 'digest_plurality_vote_dishonest_with_abort_result()' # functions above to digest the result. # - # Existing voting programs: + # Existing voting nada_programs: # # program_name = "voting_honest_1" # program_name = "voting_honest_2" @@ -160,7 +160,7 @@ async def main(): # 2. Storing program # ##################################### - # Note: do not forget to compile the programs and store the corresponding .nada.bin file. + # Note: do not forget to compile the nada_programs and store the corresponding .nada.bin file. program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" if os.path.exists(program_mir_path): None diff --git a/examples_and_tutorials/voting_tutorial/config.py b/examples_and_tutorials/voting_tutorial/config.py index 7a0cfb34..1822cffc 100644 --- a/examples_and_tutorials/voting_tutorial/config.py +++ b/examples_and_tutorials/voting_tutorial/config.py @@ -1,7 +1,7 @@ import os from dotenv import load_dotenv home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") CONFIG = { "nr_candidates": 2, diff --git a/examples_and_tutorials/voting_tutorial/tutorial.md b/examples_and_tutorials/voting_tutorial/tutorial.md index 0fd3674b..56d8a839 100644 --- a/examples_and_tutorials/voting_tutorial/tutorial.md +++ b/examples_and_tutorials/voting_tutorial/tutorial.md @@ -67,7 +67,7 @@ Under this assumption, **all** the above voting systems can be reduced to **one* Let us start with a simple program with 3 voters and 2 candidates. Recall that the following program can be applied to any type of voting system described in the previous [chapter](#voting-systems). -Program: [voting_honest_1.py](../../programs/voting_honest_1.py) +Program: [voting_honest_1.py](../nada_programs/src/voting_honest_1.py) ```python """ PROGRAM 1 @@ -166,7 +166,7 @@ We can make use of the Python run-time `for` loop to unroll the variables and cr Below, program 2 shows how we could use compiled-time `for` loops to assist us creating all parties, input votes and building the computation section. -Program: [voting_honest_2.py](../../programs/voting_honest_2.py) +Program: [voting_honest_2.py](../nada_programs/src/voting_honest_2.py) ```python """ PROGRAM 2 @@ -492,7 +492,7 @@ Similarly, we can utilize compiled-time `for` loops and **Python functions** to >💡 In addition to compiled-time `for` loops, leveraging **Python functions** can lead to more organized and cleaner code. Python functions are executed by the interpreter during compilation and cannot rely on runtime variables. In contrast, **Nada functions** rely on run-time variables and are only executed during run-time. This distinction becomes apparent when comparing with the example provided in program 3. -Program: [voting_dishonest_abort_5.py](../../programs/voting_dishonest_abort_5.py) +Program: [voting_dishonest_abort_5.py](../nada_programs/src/voting_dishonest_abort_5.py) ```python """ PROGRAM 5 @@ -709,7 +709,7 @@ Then, we output the cheating conditions `comp_v1_sum`, `comp_v1_c0_prod` and `co Below, we put together the above two checks with our protocol. For readability purposes, we use a function `return_val_if_any_true(list_of_bool, val)` which returns the value `val` if at least on boolean in the list is false. -Program: [voting_dishonest_robust_6.py](../../programs/voting_dishonest_robust_6.py) +Program: [voting_dishonest_robust_6.py](../nada_programs/src/voting_dishonest_robust_6.py) ```python """ PROGRAM 6 diff --git a/programs/addition_simple_multi_party_3.py b/programs/addition_simple_multi_party_3.py deleted file mode 100644 index 932a214b..00000000 --- a/programs/addition_simple_multi_party_3.py +++ /dev/null @@ -1,13 +0,0 @@ -from nada_dsl import * - -def nada_main(): - party1 = Party(name="Party1") - party2 = Party(name="Party2") - party3 = Party(name="Party3") - my_int1 = SecretInteger(Input(name="my_int1", party=party1)) - my_int2 = SecretInteger(Input(name="my_int2", party=party2)) - my_int3 = SecretInteger(Input(name="my_int3", party=party3)) - - new_int = my_int1 + my_int2 + my_int3 - - return [Output(new_int, "my_output", party1)] diff --git a/programs/array_complex.py b/programs/array_complex.py deleted file mode 100644 index 25585e95..00000000 --- a/programs/array_complex.py +++ /dev/null @@ -1,22 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - party2 = Party(name="Party2") - my_int1 = SecretInteger(Input(name="my_int1", party=party1)) - my_array_1 = Array(SecretInteger(Input(name="my_array_1", party=party1)), size=10) - my_array_2 = Array(SecretInteger(Input(name="my_array_2", party=party2)), size=10) - - unzipped = unzip(my_array_2.zip(my_array_1)) - - @nada_fn - def add(a: SecretInteger, b: SecretInteger) -> SecretInteger: - return a + b - - new_array = my_array_1.zip(my_array_2).map(add).reduce(add, my_int1) - - out1 = Output(unzipped, "zip.unzip.tuple", party1) - out2 = Output(new_array, "zip.map.reduce.array", party1) - - return [out1, out2] diff --git a/programs/circuit_simple.py b/programs/circuit_simple.py deleted file mode 100644 index 1592ab9b..00000000 --- a/programs/circuit_simple.py +++ /dev/null @@ -1,15 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - my_int1 = SecretInteger(Input(name="my_int1", party=party1)) - my_int2 = SecretInteger(Input(name="my_int2", party=party1)) - my_int3 = SecretInteger(Input(name="my_int3", party=party1)) - my_int4 = SecretInteger(Input(name="my_int4", party=party1)) - - new_int1 = my_int1 * my_int2 - new_int2 = my_int3 * my_int4 - new_int3 = new_int1 + new_int2 - - return [Output(new_int3, "my_output", party1)] diff --git a/programs/circuit_simple_multi_party.py b/programs/circuit_simple_multi_party.py deleted file mode 100644 index 0fc41472..00000000 --- a/programs/circuit_simple_multi_party.py +++ /dev/null @@ -1,16 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - party2 = Party(name="Party2") - my_int1 = SecretInteger(Input(name="my_int1", party=party1)) - my_int2 = SecretInteger(Input(name="my_int2", party=party2)) - my_int3 = SecretInteger(Input(name="my_int3", party=party1)) - my_int4 = SecretInteger(Input(name="my_int4", party=party2)) - - new_int1 = my_int1 * my_int2 - new_int2 = my_int3 * my_int4 - new_int3 = new_int1 + new_int2 - - return [Output(new_int3, "my_output", party1)] diff --git a/programs/complex.py b/programs/complex.py deleted file mode 100644 index eff03864..00000000 --- a/programs/complex.py +++ /dev/null @@ -1,21 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - A = SecretInteger(Input(name="A", party=party1)) - B = SecretInteger(Input(name="B", party=party1)) - C = SecretInteger(Input(name="C", party=party1)) - D = SecretInteger(Input(name="D", party=party1)) - E = SecretInteger(Input(name="E", party=party1)) - F = SecretInteger(Input(name="F", party=party1)) - - TMP1 = A * B - PRODUCT1 = TMP1 * C - TMP2 = C * D - PRODUCT2 = TMP2 * E - PRODUCT3 = E * F - PARTIAL = PRODUCT1 + PRODUCT2 - FINAL = PARTIAL + PRODUCT3 - - return [Output(FINAL, "FINAL", party1)] diff --git a/programs/complex_operation_mix.py b/programs/complex_operation_mix.py deleted file mode 100644 index 66f93580..00000000 --- a/programs/complex_operation_mix.py +++ /dev/null @@ -1,21 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - party2 = Party(name="Party2") - A = SecretInteger(Input(name="A", party=party1)) - B = SecretInteger(Input(name="B", party=party2)) - C = SecretInteger(Input(name="C", party=party1)) - D = SecretInteger(Input(name="D", party=party2)) - E = SecretInteger(Input(name="E", party=party2)) - F = SecretInteger(Input(name="F", party=party2)) - G = SecretInteger(Input(name="G", party=party2)) - - result = ( - ((A * B) + C + D) * (E * (F + G)) - + (A * B * (C + D) + E) * F - + (A + (B * (C + (D * (E + F))))) - ) - - return [Output(result, "my_output", party1)] diff --git a/programs/import_file.py b/programs/import_file.py deleted file mode 100644 index db0ed4a5..00000000 --- a/programs/import_file.py +++ /dev/null @@ -1,13 +0,0 @@ -from nada_dsl import * -from lib.library import add - - -def nada_main(): - party1 = Party(name="Party1") - party2 = Party(name="Party2") - my_int1 = SecretInteger(Input(name="my_int1", party=party1)) - my_int2 = SecretInteger(Input(name="my_int2", party=party2)) - - new_int1 = add(my_int1, my_int2) - - return [Output(new_int1, "my_output", party1)] diff --git a/programs/incubation/array2dimensional.py b/programs/incubation/array2dimensional.py deleted file mode 100644 index e0158522..00000000 --- a/programs/incubation/array2dimensional.py +++ /dev/null @@ -1,27 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - @nada_fn - def add(a: SecretInteger, b: SecretInteger) -> SecretInteger: - return a + b - - @nada_fn - def matrix_addition( - a: Array[SecretInteger], b: Array[SecretInteger] - ) -> SecretInteger: - return a.zip(b).map(add).reduce(add) - - party1 = Party(name="Party1") - party2 = Party(name="Party2") - - my_array_1 = Array( - Array(SecretInteger(Input(name="my_array_1", party=party1)), size=10), size=10 - ) - my_array_2 = Array( - Array(SecretInteger(Input(name="my_array_2", party=party2)), size=10), size=10 - ) - - output = my_array_1.zip(my_array_2).map(matrix_addition) - - return [Output(output, "output", party1)] diff --git a/programs/input_2_dimensional_array.py b/programs/input_2_dimensional_array.py deleted file mode 100644 index ef364a52..00000000 --- a/programs/input_2_dimensional_array.py +++ /dev/null @@ -1,7 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - a = Array(Array(SecretInteger(Input(name="a", party=party1)), size=5), size=5) - return [Output(a, "my_output", party1)] diff --git a/programs/input_n_dimensional_array.py b/programs/input_n_dimensional_array.py deleted file mode 100644 index 7f81b972..00000000 --- a/programs/input_n_dimensional_array.py +++ /dev/null @@ -1,7 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - a = Array(Array(Array(SecretInteger(Input(name="a", party=party1)), size=5), size=5), size=5) - return [Output(a, "my_output", party1)] diff --git a/programs/less_or_equal_than.py b/programs/less_or_equal_than.py deleted file mode 100644 index 88707fda..00000000 --- a/programs/less_or_equal_than.py +++ /dev/null @@ -1,14 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - party2 = Party(name="Party2") - A = SecretInteger(Input(name="A", party=party1)) - B = SecretInteger(Input(name="B", party=party2)) - C = SecretInteger(Input(name="C", party=party1)) - D = SecretInteger(Input(name="D", party=party2)) - - result = A * B + C <= B * D - - return [Output(result, "my_output", party1)] diff --git a/programs/lib/library.py b/programs/lib/library.py deleted file mode 100644 index a2ff07b5..00000000 --- a/programs/lib/library.py +++ /dev/null @@ -1,3 +0,0 @@ -def add(int1, int2): - result = int1 + int2 - return result diff --git a/programs/map_simple.py b/programs/map_simple.py deleted file mode 100644 index 9ae283da..00000000 --- a/programs/map_simple.py +++ /dev/null @@ -1,17 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - my_array_1 = Array(SecretInteger(Input(name="my_array_1", party=party1)), size=10) - my_int = SecretInteger(Input(name="my_int", party=party1)) - - @nada_fn - def inc(a: SecretInteger) -> SecretInteger: - return a + my_int - - new_array = my_array_1.map(inc) - - out = Output(new_array, "out", party1) - - return [out] diff --git a/programs/multiplication_simple_multi_party.py b/programs/multiplication_simple_multi_party.py deleted file mode 100644 index 49d18aed..00000000 --- a/programs/multiplication_simple_multi_party.py +++ /dev/null @@ -1,12 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - party2 = Party(name="Party2") - my_int1 = SecretInteger(Input(name="my_int1", party=party1)) - my_int2 = SecretInteger(Input(name="my_int2", party=party2)) - - new_int = my_int1 * my_int2 - - return [Output(new_int, "my_output", party1)] diff --git a/programs/nada_fn_composition.py b/programs/nada_fn_composition.py deleted file mode 100644 index 50381077..00000000 --- a/programs/nada_fn_composition.py +++ /dev/null @@ -1,14 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - my_int1 = SecretInteger(Input(name="my_int1", party=party1)) - my_int2 = SecretInteger(Input(name="my_int2", party=party1)) - - @nada_fn - def add_times(a: SecretInteger, b: SecretInteger) -> SecretInteger: - return a * (a + b) - - new_int = add_times(my_int1, my_int2) - return [Output(new_int, "my_output", party1)] diff --git a/programs/nada_fn_simple.py b/programs/nada_fn_simple.py deleted file mode 100644 index cf53bbf8..00000000 --- a/programs/nada_fn_simple.py +++ /dev/null @@ -1,14 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - my_int1 = SecretInteger(Input(name="my_int1", party=party1)) - my_int2 = SecretInteger(Input(name="my_int2", party=party1)) - - @nada_fn - def add(a: SecretInteger, b: SecretInteger) -> SecretInteger: - return a + b - - new_int = add(my_int1, my_int2) - return [Output(new_int, "my_output", party1)] diff --git a/programs/reduce_simple.py b/programs/reduce_simple.py deleted file mode 100644 index e0043b2e..00000000 --- a/programs/reduce_simple.py +++ /dev/null @@ -1,17 +0,0 @@ -from nada_dsl import Array, Party, SecretInteger, Output, Input, nada_fn - - -def nada_main(): - party1 = Party(name="Party1") - my_array_1 = Array(SecretInteger(Input(name="my_array_1", party=party1)), size=4) - my_int1 = SecretInteger(Input(name="my_int1", party=party1)) - - @nada_fn - def add(a: SecretInteger, b: SecretInteger) -> SecretInteger: - return a + b - - addition = my_array_1.reduce(add, my_int1) - - out2 = Output(addition, "reduce.addition", party1) - - return [out2] diff --git a/programs/reuse_flipped1.py b/programs/reuse_flipped1.py deleted file mode 100644 index 038f2540..00000000 --- a/programs/reuse_flipped1.py +++ /dev/null @@ -1,14 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - A = SecretInteger(Input(name="A", party=party1)) - B = SecretInteger(Input(name="B", party=party1)) - C = SecretInteger(Input(name="C", party=party1)) - - TMP1 = A * B - TMP2 = C * A - R = TMP1 + TMP2 - - return [Output(R, "R", party1)] diff --git a/programs/reuse_flipped2.py b/programs/reuse_flipped2.py deleted file mode 100644 index d42ea1b2..00000000 --- a/programs/reuse_flipped2.py +++ /dev/null @@ -1,14 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - A = SecretInteger(Input(name="A", party=party1)) - B = SecretInteger(Input(name="B", party=party1)) - C = SecretInteger(Input(name="C", party=party1)) - - TMP1 = A * B - TMP2 = C * B - R = TMP1 + TMP2 - - return [Output(R, "R", party1)] diff --git a/programs/reuse_simple_1.py b/programs/reuse_simple_1.py deleted file mode 100644 index 251b1132..00000000 --- a/programs/reuse_simple_1.py +++ /dev/null @@ -1,11 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - a = SecretInteger(Input(name="A", party=party1)) - b = SecretInteger(Input(name="B", party=party1)) - - result = a * b + a * b - - return [Output(result, "my_output", party1)] diff --git a/programs/reuse_simple_1_multi_party.py b/programs/reuse_simple_1_multi_party.py deleted file mode 100644 index a1aeb85f..00000000 --- a/programs/reuse_simple_1_multi_party.py +++ /dev/null @@ -1,12 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - party2 = Party(name="Party2") - a = SecretInteger(Input(name="my_int1", party=party1)) - b = SecretInteger(Input(name="my_int2", party=party2)) - - result = a * b + a * b - - return [Output(result, "my_output", party1)] diff --git a/programs/reuse_simple_2.py b/programs/reuse_simple_2.py deleted file mode 100644 index 753470d3..00000000 --- a/programs/reuse_simple_2.py +++ /dev/null @@ -1,11 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - a = SecretInteger(Input(name="A", party=party1)) - b = SecretInteger(Input(name="B", party=party1)) - - result = (a + b) * (a + b) - - return [Output(result, "my_output", party1)] diff --git a/programs/reuse_simple_sub.py b/programs/reuse_simple_sub.py deleted file mode 100644 index b2431ac7..00000000 --- a/programs/reuse_simple_sub.py +++ /dev/null @@ -1,11 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - a = SecretInteger(Input(name="A", party=party1)) - b = SecretInteger(Input(name="B", party=party1)) - - result = (a + b) * (a - b) - - return [Output(result, "my_output", party1)] diff --git a/programs/simple.py b/programs/simple.py deleted file mode 100644 index 1cd684f1..00000000 --- a/programs/simple.py +++ /dev/null @@ -1,15 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - A = SecretInteger(Input(name="A", party=party1)) - B = SecretInteger(Input(name="B", party=party1)) - C = SecretInteger(Input(name="C", party=party1)) - D = SecretInteger(Input(name="D", party=party1)) - - TMP1 = A * B - TMP2 = C * D - O = TMP1 + TMP2 - - return [Output(O, "O", party1)] diff --git a/programs/simple_public_variables_only.py b/programs/simple_public_variables_only.py deleted file mode 100644 index 6a7e477e..00000000 --- a/programs/simple_public_variables_only.py +++ /dev/null @@ -1,11 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - A = PublicInteger(Input(name="A", party=party1)) - B = PublicInteger(Input(name="B", party=party1)) - - O = A * B - - return [Output(O, "O", party1)] diff --git a/programs/single_addition.py b/programs/single_addition.py deleted file mode 100644 index a627ec53..00000000 --- a/programs/single_addition.py +++ /dev/null @@ -1,11 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - A = SecretInteger(Input(name="A", party=party1)) - B = SecretInteger(Input(name="B", party=party1)) - - R = A + B - - return [Output(R, "R", party1)] diff --git a/programs/subtraction_simple_multi_party.py b/programs/subtraction_simple_multi_party.py deleted file mode 100644 index 93c635b3..00000000 --- a/programs/subtraction_simple_multi_party.py +++ /dev/null @@ -1,12 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - party2 = Party(name="Party2") - my_int1 = SecretInteger(Input(name="my_int1", party=party1)) # 32 - my_int2 = SecretInteger(Input(name="my_int2", party=party2)) # 81 - - new_int = my_int2 - my_int1 - - return [Output(new_int, "my_output", party1)] diff --git a/programs/subtraction_simple_neg_multi_party.py b/programs/subtraction_simple_neg_multi_party.py deleted file mode 100644 index 77f76262..00000000 --- a/programs/subtraction_simple_neg_multi_party.py +++ /dev/null @@ -1,12 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - party2 = Party(name="Party2") - my_int1 = SecretInteger(Input(name="my_int1", party=party1)) # 102 - my_int2 = SecretInteger(Input(name="my_int2", party=party2)) # 81 - - new_int = my_int2 - my_int1 # 81 - 102 = -21 - - return [Output(new_int, "my_output", party1)] diff --git a/programs/tuple_new_unzip.py b/programs/tuple_new_unzip.py deleted file mode 100644 index 7ea01fec..00000000 --- a/programs/tuple_new_unzip.py +++ /dev/null @@ -1,16 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - a = SecretInteger(Input(name="a", party=party1)) - b = SecretInteger(Input(name="b", party=party1)) - c = SecretInteger(Input(name="c", party=party1)) - d = SecretInteger(Input(name="d", party=party1)) - - my_tuple_1 = Tuple.new(a, b) - my_tuple_2 = Tuple.new(c, d) - my_array_1 = Array.new(my_tuple_1, my_tuple_2) - result = unzip(my_array_1) - - return [Output(result, "my_output", party1)] diff --git a/pyvenv.cfg b/pyvenv.cfg deleted file mode 100644 index 8177f1e4..00000000 --- a/pyvenv.cfg +++ /dev/null @@ -1,8 +0,0 @@ -home = /opt/homebrew/opt/python@3.11/bin -implementation = CPython -version_info = 3.11.6.final.0 -virtualenv = 20.24.5 -include-system-site-packages = false -base-prefix = /opt/homebrew/opt/python@3.11/Frameworks/Python.framework/Versions/3.11 -base-exec-prefix = /opt/homebrew/opt/python@3.11/Frameworks/Python.framework/Versions/3.11 -base-executable = /opt/homebrew/opt/python@3.11/bin/python3.11 diff --git a/quickstart/client_code/secret_addition.py b/quickstart/client_code/secret_addition.py new file mode 100644 index 00000000..e69de29b diff --git a/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition_complete.py b/quickstart_complete/client_code/secret_addition_complete.py similarity index 73% rename from examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition_complete.py rename to quickstart_complete/client_code/secret_addition_complete.py index f9fe6d4e..35b4b469 100644 --- a/examples_and_tutorials/core_concept_single_party_compute/tiny_secret_addition_complete.py +++ b/quickstart_complete/client_code/secret_addition_complete.py @@ -19,33 +19,33 @@ ) home = os.getenv("HOME") -load_dotenv(f"{home}/Library/Application Support/nillion.nillion/nillion-devnet.env") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") -# This python script stores the tiny_secret_addition_complete program in the network, store secrets, and compute async def main(): - # 0. The bootstrap-local-environment.sh script put nillion-devnet config variables into the .env file - # Get cluster_id, gprc endpoint, chain id, user_key,node_key from the .env file + # 1. Initial setup + # 1.1. Get cluster_id, grpc_endpoint, & chain_id from the .env file cluster_id = os.getenv("NILLION_CLUSTER_ID") grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") + + # 1.2 pick a seed and generate user and node keys seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) + userkey = UserKey.from_seed(seed) + nodekey = NodeKey.from_seed(seed) - # ✅ 1. Initialize NillionClient against nillion-devnet + # 2. Initialize NillionClient against nillion-devnet # Create Nillion Client for user client = create_nillion_client(userkey, nodekey) - # ✅ 2. Get the user id and party id from NillionClient party_id = client.party_id user_id = client.user_id - # ✅ 3. Create a payments config, payments client and payments wallet then pay for and store a compiled Nada program in the network - # Set the program name - program_name = "tiny_secret_addition_complete" - # Set the path to the compiled program - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - # Create payments config + # 3. Pay for and store the program + # Set the program name and path to the compiled program + program_name = "secret_addition_complete" + program_mir_path = f"../nada_programs/target/{program_name}.nada.bin" + + # Create payments config, client and wallet payments_config = create_payments_config(chain_id, grpc_endpoint) payments_client = LedgerClient(payments_config) payments_wallet = LocalWallet( @@ -53,7 +53,7 @@ async def main(): prefix="nillion", ) - # Pay to store the program + # Pay to store the program and obtain a receipt of the payment receipt_store_program = await pay( client, nillion.Operation.store_program(), @@ -62,17 +62,17 @@ async def main(): cluster_id, ) - # Store program + # Store the program action_id = await client.store_program( cluster_id, program_name, program_mir_path, receipt_store_program ) - # Create a variable for the program_id, which is the {user_id}/{program_name} + # Create a variable for the program_id, which is the {user_id}/{program_name}. We will need this later program_id = f"{user_id}/{program_name}" print("Stored program. action_id:", action_id) print("Stored program_id:", program_id) - # ✅ 4. Create the 1st secret with bindings to the program + # 4. Create the 1st secret, add permissions, pay for and store it in the network # Create a secret named "my_int1" with any value, ex: 500 new_secret = nillion.Secrets( { @@ -88,7 +88,7 @@ async def main(): permissions = nillion.Permissions.default_for_user(client.user_id) permissions.add_compute_permissions({client.user_id: {program_id}}) - # ✅ 5. Pay for and store the secret in the network and print the returned store_id + # Pay for and store the secret in the network and print the returned store_id receipt_store = await pay( client, nillion.Operation.store_values(new_secret), @@ -103,13 +103,11 @@ async def main(): print(f"Computing using program {program_id}") print(f"Use secret store_id: {store_id}") - # ✅ 6. Create compute bindings to set input and output parties + # 5. Create compute bindings to set input and output parties, add a computation time secret and pay for & run the computation compute_bindings = nillion.ProgramBindings(program_id) compute_bindings.add_input_party(party_name, party_id) compute_bindings.add_output_party(party_name, party_id) - # ✅ 7. Pay for and compute on the program with 1st secret from the network, and the 2nd secret, provided at compute time - # Add my_int2, the 2nd secret at computation time computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) @@ -132,7 +130,7 @@ async def main(): receipt_compute, ) - # ✅ 8. Print the computation result + # 8. Return 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() diff --git a/quickstart_complete/nada_programs/nada-project.toml b/quickstart_complete/nada_programs/nada-project.toml new file mode 100644 index 00000000..9d002b88 --- /dev/null +++ b/quickstart_complete/nada_programs/nada-project.toml @@ -0,0 +1,7 @@ +name = "quick_start_programs" +version = "0.1.0" +authors = [""] + +[[programs]] +path = "src/secret_addition_complete.py" +prime_size = 128 \ No newline at end of file diff --git a/programs/tiny_secret_addition_complete.py b/quickstart_complete/nada_programs/src/secret_addition_complete.py similarity index 100% rename from programs/tiny_secret_addition_complete.py rename to quickstart_complete/nada_programs/src/secret_addition_complete.py diff --git a/quickstart_complete/nada_programs/tests/secret_addition_complete.yaml b/quickstart_complete/nada_programs/tests/secret_addition_complete.yaml new file mode 100644 index 00000000..46541fe8 --- /dev/null +++ b/quickstart_complete/nada_programs/tests/secret_addition_complete.yaml @@ -0,0 +1,12 @@ +--- +program: secret_addition_complete +inputs: + secrets: + my_int1: + SecretInteger: "3" + my_int2: + SecretInteger: "3" + public_variables: {} +expected_outputs: + my_output: + SecretInteger: "6" diff --git a/requirements.txt b/requirements.txt index d980720d..a6a9a7ab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ +py-nillion-client +nada-dsl python-dotenv==1.0.0 pytest-asyncio>=0.23.6 cosmpy>=0.9.2 \ No newline at end of file diff --git a/store_program.sh b/store_program.sh deleted file mode 100755 index 7500d933..00000000 --- a/store_program.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -# This script stores a compiled program as NILLION_USERKEY_PATH_PARTY_1 -SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}" 2>/dev/null)" && pwd -P)" - -set -a # Automatically export all variables -source "$SCRIPT_PATH/.env" -set +a # Stop automatically exporting - -# Check for 1 argument (RELATIVE_PROGRAM_PATH) -if [ "$#" -ne 1 ]; then - echo "Usage: $0 " - exit 1 -fi - -RELATIVE_PROGRAM_PATH="$SCRIPT_PATH/$1" -# Extract the program name from the path (e.g., addition_simple from programs-compiled/addition_simple.nada.bin) -PROGRAM_NAME=$(basename "$RELATIVE_PROGRAM_PATH" .nada.bin) - -# Execute the command -nillion \ - --user-key-path $NILLION_USERKEY_PATH_PARTY_1 \ - --node-key-path $NILLION_NODEKEY_PATH_PARTY_1 \ - -b $NILLION_BOOTNODE_MULTIADDRESS \ - --payments-private-key $NILLION_WALLET_PRIVATE_KEY \ - --payments-chain-id $NILLION_CHAIN_ID \ - --payments-rpc-endpoint $NILLION_BLOCKCHAIN_RPC_ENDPOINT \ - --payments-sc-address $NILLION_PAYMENTS_SC_ADDRESS \ - --blinding-factors-manager-sc-address $NILLION_BLINDING_FACTORS_MANAGER_SC_ADDRESS \ - store-program \ - --cluster-id $NILLION_CLUSTER_ID \ - $RELATIVE_PROGRAM_PATH \ - $PROGRAM_NAME diff --git a/utils.sh b/utils.sh deleted file mode 100644 index d12d880c..00000000 --- a/utils.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash -function __echo_red_bold { - echo -e "\033[1;31m${1}\033[0m" -} - -function __nillion_pip_install() { - WHLPATH=$(find "$NILLION_WHL_ROOT" -iname "$1" -type f -print | head -n1) - echo $WHLPATH - pip install --force-reinstall "${WHLPATH:?could not find $1 in $NILLION_WHL_ROOT}" -} - -function install_py_nillion_client() { - __nillion_pip_install "${NILLION_PYCLIENT_WHL_FILE_NAME}" -} - -function install_nada_dsl() { - __nillion_pip_install "nada_dsl-*-any.whl" -} - -function discover_sdk_bin_path() { - - BINPATH=$(find "$NILLION_SDK_ROOT" -name "$1" -type f -print | head -n1) - - if ! command -v "$BINPATH" > /dev/null; then - echo "${1} was not discovered. Check NILLION_SDK_ROOT $NILLION_SDK_ROOT $BINPATH" 1>&2 - exit 1 - fi - echo "$BINPATH" -} - -function check_for_sdk_root() { - echo "NILLION_SDK_ROOT is set to: '$NILLION_SDK_ROOT'" - if [ -z "$NILLION_SDK_ROOT" -a ! -d "$NILLION_SDK_ROOT" ]; then - echo "Error: NILLION_SDK_ROOT is not set to a directory" - exit 1 - fi -} From 340bf0810c795007ba3ad10e1d6ec33535cece90 Mon Sep 17 00:00:00 2001 From: davetbutler Date: Mon, 24 Jun 2024 20:14:40 +0100 Subject: [PATCH 20/43] remove examples and simplify directory structure --- .gitignore | 4 +- CONTRIBUTING.md | 77 -- README.md | 118 +-- .../01_store_secret_party1.py | 107 --- .../02_store_secret_party_n.py | 124 --- .../03_multi_party_compute.py | 124 --- .../README.md | 52 - .../config.py | 33 - .../01_fetch_reader_userid.py | 35 - .../02_store_permissioned_secret.py | 104 -- .../03_retrieve_secret.py | 85 -- .../04_revoke_read_permissions.py | 93 -- .../05_test_revoked_permissions.py | 100 -- .../core_concept_permissions/README.md | 19 - .../README.md | 22 - .../addition_simple.py | 145 --- .../circuit_simple.py | 136 --- .../circuit_simple_2.py | 145 --- .../complex.py | 144 --- .../correlation_coefficient.py | 204 ---- .../division_simple.py | 138 --- .../modulo_simple.py | 138 --- .../multiplication_simple.py | 135 --- .../nada_fn_composition.py | 132 --- .../reuse.py | 140 --- .../simple.py | 135 --- .../simple_literals.py | 133 --- .../simple_public_variables.py | 141 --- .../simple_public_variables_only.py | 120 --- .../simple_sub.py | 135 --- .../subtraction_simple.py | 137 --- .../subtraction_simple_neg.py | 132 --- .../store_and_retrieve_blob.py | 86 -- .../store_and_retrieve_integer.py | 80 -- .../01_store_secret_party1.py | 88 -- .../02_store_secret_party_n.py | 130 --- .../03_multi_party_compute.py | 164 ---- .../millionaires_problem_example/README.md | 51 - .../millionaires_problem_example/config.py | 34 - .../nada_programs/nada-project.toml | 99 -- .../nada_programs/src/addition_simple.py | 11 - .../src/addition_simple_multi_party.py | 12 - .../nada_programs/src/circuit_simple_2.py | 27 - .../src/correlation_coefficient.py | 73 -- .../nada_programs/src/division_simple.py | 11 - .../src/greater_or_equal_than.py | 14 - .../nada_programs/src/greater_than.py | 14 - .../nada_programs/src/less_than.py | 14 - .../nada_programs/src/literals.py | 16 - .../nada_programs/src/millionaires.py | 21 - .../nada_programs/src/modulo_simple.py | 11 - .../src/multiplication_simple.py | 11 - .../nada_programs/src/nada_fn_max.py | 13 - .../nada_programs/src/public_variables.py | 18 - .../nada_programs/src/reuse.py | 14 - .../src/reuse_simple_sub_multi_party.py | 12 - .../nada_programs/src/simple_sub.py | 15 - .../src/single_addition_multi_party.py | 12 - .../nada_programs/src/subtraction_simple.py | 11 - .../src/subtraction_simple_neg.py | 11 - .../src/voting_dishonest_abort_5.py | 131 --- .../src/voting_dishonest_robust_6.py | 176 ---- .../nada_programs/src/voting_honest_1.py | 38 - .../nada_programs/src/voting_honest_2.py | 38 - .../nada_programs/tests/addition_simple.yaml | 12 - .../tests/addition_simple_multi_party.yaml | 12 - .../nada_programs/tests/circuit_simple_2.yaml | 26 - .../tests/correlation_coefficient.yaml | 90 -- .../nada_programs/tests/division_simple.yaml | 12 - .../tests/greater_or_equal_than.yaml | 16 - .../nada_programs/tests/greater_than.yaml | 16 - .../nada_programs/tests/less_than.yaml | 16 - .../nada_programs/tests/literals.yaml | 12 - .../nada_programs/tests/millionaires.yaml | 14 - .../nada_programs/tests/modulo_simple.yaml | 12 - .../tests/multiplication_simple.yaml | 12 - .../nada_programs/tests/nada_fn_max.yaml | 12 - .../nada_programs/tests/public_variables.yaml | 16 - .../nada_programs/tests/reuse.yaml | 14 - .../tests/reuse_simple_sub_multi_party.yaml | 12 - .../nada_programs/tests/simple_sub.yaml | 16 - .../tests/subtraction_simple.yaml | 12 - .../tests/subtraction_simple_neg.yaml | 12 - .../tests/voting_dishonest_abort_5.yaml | 40 - .../tests/voting_dishonest_robust_6.yaml | 64 -- .../nada_programs/tests/voting_honest_1.yaml | 22 - .../nada_programs/tests/voting_honest_2.yaml | 22 - .../01_store_program_party1.py | 144 --- .../02_store_secret_party_n.py | 138 --- .../voting_tutorial/03_multi_party_compute.py | 181 ---- .../voting_tutorial/README.md | 87 -- .../voting_tutorial/client_voting.py | 334 ------- .../voting_tutorial/config.py | 48 - .../voting_tutorial/digest_result.py | 70 -- .../voting_tutorial/image/architecture.png | Bin 116180 -> 0 bytes .../voting_tutorial/inputs/v0_input.txt | 2 - .../voting_tutorial/inputs/v1_input.txt | 2 - .../voting_tutorial/inputs/v2_input.txt | 2 - .../voting_tutorial/tutorial.md | 909 ------------------ testing/Dockerfile | 19 - testing/docker_main.sh | 41 - testing/run_tests.sh | 5 - 102 files changed, 5 insertions(+), 7412 deletions(-) delete mode 100644 CONTRIBUTING.md delete mode 100644 examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py delete mode 100644 examples_and_tutorials/core_concept_multi_party_compute/02_store_secret_party_n.py delete mode 100644 examples_and_tutorials/core_concept_multi_party_compute/03_multi_party_compute.py delete mode 100644 examples_and_tutorials/core_concept_multi_party_compute/README.md delete mode 100644 examples_and_tutorials/core_concept_multi_party_compute/config.py delete mode 100644 examples_and_tutorials/core_concept_permissions/01_fetch_reader_userid.py delete mode 100644 examples_and_tutorials/core_concept_permissions/02_store_permissioned_secret.py delete mode 100644 examples_and_tutorials/core_concept_permissions/03_retrieve_secret.py delete mode 100644 examples_and_tutorials/core_concept_permissions/04_revoke_read_permissions.py delete mode 100644 examples_and_tutorials/core_concept_permissions/05_test_revoked_permissions.py delete mode 100644 examples_and_tutorials/core_concept_permissions/README.md delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/README.md delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/addition_simple.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/circuit_simple.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/circuit_simple_2.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/complex.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/correlation_coefficient.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/division_simple.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/modulo_simple.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/multiplication_simple.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/nada_fn_composition.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/reuse.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/simple.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/simple_literals.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/simple_public_variables.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/simple_public_variables_only.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/simple_sub.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/subtraction_simple.py delete mode 100644 examples_and_tutorials/core_concept_single_party_compute/subtraction_simple_neg.py delete mode 100644 examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_blob.py delete mode 100644 examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_integer.py delete mode 100644 examples_and_tutorials/millionaires_problem_example/01_store_secret_party1.py delete mode 100644 examples_and_tutorials/millionaires_problem_example/02_store_secret_party_n.py delete mode 100644 examples_and_tutorials/millionaires_problem_example/03_multi_party_compute.py delete mode 100644 examples_and_tutorials/millionaires_problem_example/README.md delete mode 100644 examples_and_tutorials/millionaires_problem_example/config.py delete mode 100644 examples_and_tutorials/nada_programs/nada-project.toml delete mode 100644 examples_and_tutorials/nada_programs/src/addition_simple.py delete mode 100644 examples_and_tutorials/nada_programs/src/addition_simple_multi_party.py delete mode 100644 examples_and_tutorials/nada_programs/src/circuit_simple_2.py delete mode 100644 examples_and_tutorials/nada_programs/src/correlation_coefficient.py delete mode 100644 examples_and_tutorials/nada_programs/src/division_simple.py delete mode 100644 examples_and_tutorials/nada_programs/src/greater_or_equal_than.py delete mode 100644 examples_and_tutorials/nada_programs/src/greater_than.py delete mode 100644 examples_and_tutorials/nada_programs/src/less_than.py delete mode 100644 examples_and_tutorials/nada_programs/src/literals.py delete mode 100644 examples_and_tutorials/nada_programs/src/millionaires.py delete mode 100644 examples_and_tutorials/nada_programs/src/modulo_simple.py delete mode 100644 examples_and_tutorials/nada_programs/src/multiplication_simple.py delete mode 100644 examples_and_tutorials/nada_programs/src/nada_fn_max.py delete mode 100644 examples_and_tutorials/nada_programs/src/public_variables.py delete mode 100644 examples_and_tutorials/nada_programs/src/reuse.py delete mode 100644 examples_and_tutorials/nada_programs/src/reuse_simple_sub_multi_party.py delete mode 100644 examples_and_tutorials/nada_programs/src/simple_sub.py delete mode 100644 examples_and_tutorials/nada_programs/src/single_addition_multi_party.py delete mode 100644 examples_and_tutorials/nada_programs/src/subtraction_simple.py delete mode 100644 examples_and_tutorials/nada_programs/src/subtraction_simple_neg.py delete mode 100644 examples_and_tutorials/nada_programs/src/voting_dishonest_abort_5.py delete mode 100644 examples_and_tutorials/nada_programs/src/voting_dishonest_robust_6.py delete mode 100644 examples_and_tutorials/nada_programs/src/voting_honest_1.py delete mode 100644 examples_and_tutorials/nada_programs/src/voting_honest_2.py delete mode 100644 examples_and_tutorials/nada_programs/tests/addition_simple.yaml delete mode 100644 examples_and_tutorials/nada_programs/tests/addition_simple_multi_party.yaml delete mode 100644 examples_and_tutorials/nada_programs/tests/circuit_simple_2.yaml delete mode 100644 examples_and_tutorials/nada_programs/tests/correlation_coefficient.yaml delete mode 100644 examples_and_tutorials/nada_programs/tests/division_simple.yaml delete mode 100644 examples_and_tutorials/nada_programs/tests/greater_or_equal_than.yaml delete mode 100644 examples_and_tutorials/nada_programs/tests/greater_than.yaml delete mode 100644 examples_and_tutorials/nada_programs/tests/less_than.yaml delete mode 100644 examples_and_tutorials/nada_programs/tests/literals.yaml delete mode 100644 examples_and_tutorials/nada_programs/tests/millionaires.yaml delete mode 100644 examples_and_tutorials/nada_programs/tests/modulo_simple.yaml delete mode 100644 examples_and_tutorials/nada_programs/tests/multiplication_simple.yaml delete mode 100644 examples_and_tutorials/nada_programs/tests/nada_fn_max.yaml delete mode 100644 examples_and_tutorials/nada_programs/tests/public_variables.yaml delete mode 100644 examples_and_tutorials/nada_programs/tests/reuse.yaml delete mode 100644 examples_and_tutorials/nada_programs/tests/reuse_simple_sub_multi_party.yaml delete mode 100644 examples_and_tutorials/nada_programs/tests/simple_sub.yaml delete mode 100644 examples_and_tutorials/nada_programs/tests/subtraction_simple.yaml delete mode 100644 examples_and_tutorials/nada_programs/tests/subtraction_simple_neg.yaml delete mode 100644 examples_and_tutorials/nada_programs/tests/voting_dishonest_abort_5.yaml delete mode 100644 examples_and_tutorials/nada_programs/tests/voting_dishonest_robust_6.yaml delete mode 100644 examples_and_tutorials/nada_programs/tests/voting_honest_1.yaml delete mode 100644 examples_and_tutorials/nada_programs/tests/voting_honest_2.yaml delete mode 100644 examples_and_tutorials/voting_tutorial/01_store_program_party1.py delete mode 100644 examples_and_tutorials/voting_tutorial/02_store_secret_party_n.py delete mode 100644 examples_and_tutorials/voting_tutorial/03_multi_party_compute.py delete mode 100644 examples_and_tutorials/voting_tutorial/README.md delete mode 100644 examples_and_tutorials/voting_tutorial/client_voting.py delete mode 100644 examples_and_tutorials/voting_tutorial/config.py delete mode 100644 examples_and_tutorials/voting_tutorial/digest_result.py delete mode 100644 examples_and_tutorials/voting_tutorial/image/architecture.png delete mode 100644 examples_and_tutorials/voting_tutorial/inputs/v0_input.txt delete mode 100644 examples_and_tutorials/voting_tutorial/inputs/v1_input.txt delete mode 100644 examples_and_tutorials/voting_tutorial/inputs/v2_input.txt delete mode 100644 examples_and_tutorials/voting_tutorial/tutorial.md delete mode 100644 testing/Dockerfile delete mode 100644 testing/docker_main.sh delete mode 100755 testing/run_tests.sh diff --git a/.gitignore b/.gitignore index 23729864..e1f2b9f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,11 @@ **/*.pyc **/__pycache__ -programs-compiled/ nillion-venv/ permissions/.nillion-config.json +examples_and_tutorials/nada_programs/target +quickstart_complete/nada_programs/target .env .DS_Store *.key + .venv \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index ed99c932..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,77 +0,0 @@ -# Contributing Guide - -Thank you for investing your time in contributing. - -This guide aims to provide an overview of the contribution workflow to help us make the contribution process effective for everyone involved. - -Read the [README](README.md) to get an overview of the project. - -### Project Status - -The project is under active development. - -You can view the open Issues, follow the development process and contribute to the project. - -## Getting started - -You can contribute to this repo in many ways: - -- Solve open issues -- Report bugs or feature requests -- Improve the documentation - -Contributions are made via Issues and Pull Requests (PRs). A few general guidelines for contributions: - -- Search for existing Issues and PRs before creating your own. -- Contributions should only fix/add the functionality in the issue OR address style issues, not both. -- If you're running into an error, please give context. Explain what you're trying to do and how to reproduce the error. -- Please use the same formatting in the code repository. You can configure your IDE to do it by using the prettier / linting config files included in each package. -- If applicable, please edit the README.md file to reflect the changes. - -### Issues - -Issues should be used to report problems, request a new feature, or discuss potential changes before a PR is created. - -#### Solve an issue - -Scan through our [existing issues](https://github.com/NillionNetwork/nillion-python-starter/issues) to find one that interests you. - -If a contributor is working on the issue, they will be assigned to the individual. If you find an issue to work on, you are welcome to assign it to yourself and open a PR with a fix for it. - -#### Create a new issue - -If a related issue doesn't exist, you can open a new issue. - -Some tips to follow when you are creating an issue: - -- Provide as much context as possible. Over-communicate to give the most details to the reader. -- Include the steps to reproduce the issue or the reason for adding the feature. -- Screenshots, videos etc., are highly appreciated. - -### Pull Requests - -#### Pull Request Process - -We follow the ["fork-and-pull" Git workflow](https://github.com/susam/gitpr) - -1. Fork the repo -2. Clone the project -3. Create a new branch with a descriptive name -4. Commit your changes to the new branch -5. Push changes to your fork -6. Open a PR in our repository by [creating a pull request from your fork](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork). Use the "main" branch as the base branch, the branch in the repository that you want to merge your changes into. -7. Tag one of the maintainers to review your PR - -Here are some tips for a high-quality pull request: - -- Create a title for the PR that accurately defines the work done. -- Structure the description neatly to make it easy to consume by the readers. For example, you can include bullet points and screenshots instead of having one large paragraph. -- Add the link to the issue if applicable. -- Have a good commit message that summarises the work done. - -Once you submit your PR: - -- We may ask questions, request additional information or ask for changes to be made before a PR can be merged. Please note that these are to make the PR clear for everyone involved and aims to create a frictionless interaction process. -- As you update your PR and apply changes, mark each conversation resolved. - -Once the PR is approved, we'll "squash-and-merge" to keep the git commit history clean. diff --git a/README.md b/README.md index d2d6bd25..616ba56e 100644 --- a/README.md +++ b/README.md @@ -2,120 +2,6 @@ This is an EXPERIMENTAL PAYMENTS ENABLED BRANCH of python starter repo for building on the Nillion Network. In order to use this branch, you'll need the latest experimental version of the Nillion SDK, nada library, and python client library. -## Installing the latest experimental versions of Nillion +Welcome to the start of your Nillion developer journey. -#### Prerequisites: Install the CLI Dependencies - -The [`bootstrap-local-environment.sh`](./bootstrap-local-environment.sh) file uses `pidof` and `grep`. - -- [Install `pidof`](https://command-not-found.com/pidof) -- [Install `grep`](https://command-not-found.com/grep) - -### Setup - -1. Install the specific version of nillion with flags to also download nada and the python client at the same specific version by running - - ```bash - nilup install {version} --nada-dsl --python-client - ``` - - This results in a message that shows you the downloaded paths to the versions of nada_dsl and the python client you’ll need to use, for example if I was downloading a version `vABCD`, I would see: - - ```bash - Using pip to install /Users/steph/.nilup/sdks/vABCD/nada_dsl-0.1.0-py3-none-any.whl - nada_dsl version vABCD installed - Installing python client version vABCD - Downloading vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl to /Users/steph/.nilup/sdks/vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl - Using pip to install /Users/steph/.nilup/sdks/vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl - python client version vABCD installed - ``` - - Use this message to figure out your local paths to the latest experimental versions of python client and nada. You'll need to know these paths in steps 5 and 6 to manually install these 2 packages. - - - python client path: `/Users/steph/.nilup/sdks/vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl` - - nada path: `/Users/steph/.nilup/sdks/vABCD/py_nillion_client-0.1.1-cp37-abi3-macosx_11_0_arm64.whl` - -2. Set nillion version and initialise it (you only need to initialise on your first time installing/using) - -```bash -nilup use {version} -nilup init -``` - -3. Change directories into this branch of this repo - - ``` - cd nillion-python-starter - git branch payments-flow-update - git checkout payments-flow-update - git pull origin payments-flow-update - ``` - -4. Create venv - - ```bash - bash ./create_venv.sh && source .venv/bin/activate - ``` - -5. Manually install nada using the path printed by nilup installation in step 1 - - ```bash - python3 -m pip install {your/nada/path} - ``` - -6. Manually install the Python client using the path printed by nilup installation in step 1 - - ```bash - python3 -m pip install {your/python/client/path} - ``` - -7. Bootstrap local environment to connect to the nillion-devnet and add the configuration to your .env file - - ```bash - ./bootstrap-local-environment.sh - ``` - -8. Compile all programs - - Nada programs need to be compiled ahead of being stored. Compile all programs in the [programs](./programs/) folder with the script [`compile_programs.sh`](./compile_programs.sh): - - ```shell - bash compile_programs.sh - ``` - - This generates a `programs-compiled` folder containing the compiled programs. - -## Usage - -After completing environment setup, run the examples: - -- To run multi party examples, go to the [multi party compute](examples_and_tutorials/core_concept_multi_party_compute) folder. - -- To run single party examples, go to the [single party compute](examples_and_tutorials/core_concept_single_party_compute) folder. - -- To run permissions examples (storing and retrieving permissioned secrets, revoking permissions, etc.), go to the [permissions](examples_and_tutorials/core_concept_permissions) folder. - -## Store a Compiled Program - -Store a compiled program in the network with this script: - -```shell -bash store_program.sh {RELATIVE_COMPILED_PROGRAM_PATH} -``` - -To store the compiled [`addition_simple`](./programs/addition_simple.py) program you can run: - -```shell -bash store_program.sh programs-compiled/addition_simple.nada.bin -``` - -Storing a program results in the stored `program_id`, the network's reference to the program. The `program_id` is the `{user_id}/{program_name}`. - -## Testing - -Most examples and tutorials within this repository can be tested. Docker is required to run the tests. - -```shell -cd testing -bash run_tests.sh -``` +This repo corresponds to the Nillion Python quickstart. To get started with Nillion head over to the [Python QuickStart docs](https://docs.nillion.com/python-quickstart). diff --git a/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py b/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py deleted file mode 100644 index 87c7f38f..00000000 --- a/examples_and_tutorials/core_concept_multi_party_compute/01_store_secret_party1.py +++ /dev/null @@ -1,107 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -from config import ( - CONFIG_PROGRAM_NAME, - CONFIG_PARTY_1 -) - -# The 1st Party stores a secret -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed_party_1 = CONFIG_PARTY_1["seed"] - client_1 = create_nillion_client( - UserKey.from_seed(seed_party_1), NodeKey.from_seed(seed_party_1) - ) - user_id_1 = client_1.user_id - program_mir_path=f"../../programs-compiled/{CONFIG_PROGRAM_NAME}.nada.bin" - - # Create payments config and set up Nillion wallet with a private key to pay for operations - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - ##### STORE PROGRAM - print("-----STORE PROGRAM") - - # Get cost quote, then pay for operation to store program - receipt_store_program = await pay( - client_1, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - # 1st Party stores program - action_id = await client_1.store_program( - cluster_id, CONFIG_PROGRAM_NAME, program_mir_path, receipt_store_program - ) - - program_id=f"{user_id_1}/{CONFIG_PROGRAM_NAME}" - print('Stored program. action_id:', action_id) - print('Stored program_id:', program_id) - - # Create a permissions object to attach to the stored secret - permissions = nillion.Permissions.default_for_user(client_1.user_id) - permissions.add_compute_permissions({client_1.user_id: {program_id}}) - - - ##### STORE SECRETS - print("-----STORE SECRETS") - - # 1st Party creates a secret - stored_secret_1 = nillion.Secrets({ - key: nillion.SecretInteger(value) - for key, value in CONFIG_PARTY_1["secrets"].items() - }) - - # Get cost quote, then pay for operation to store the secret - receipt_store = await pay( - client_1, - nillion.Operation.store_values(stored_secret_1), - payments_wallet, - payments_client, - cluster_id, - ) - - # 1st Party stores a secret - store_id_1 = await client_1.store_values( - cluster_id, stored_secret_1, permissions, receipt_store - ) - secrets_string = ", ".join(f"{key}: {value}" for key, value in CONFIG_PARTY_1["secrets"].items()) - print(f"\n🎉1️⃣ Party {CONFIG_PARTY_1['party_name']} stored {secrets_string} at store id: {store_id_1}") - print("\n📋⬇️ Copy and run the following command to store N other party secrets") - print(f"\npython3 02_store_secret_party_n.py --user_id_1 {user_id_1} --store_id_1 {store_id_1}") - return [user_id_1, store_id_1] - -if __name__ == "__main__": - asyncio.run(main()) - -@pytest.mark.asyncio -async def test_main(): - pass \ No newline at end of file diff --git a/examples_and_tutorials/core_concept_multi_party_compute/02_store_secret_party_n.py b/examples_and_tutorials/core_concept_multi_party_compute/02_store_secret_party_n.py deleted file mode 100644 index a98ae8c5..00000000 --- a/examples_and_tutorials/core_concept_multi_party_compute/02_store_secret_party_n.py +++ /dev/null @@ -1,124 +0,0 @@ -import argparse -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -from config import ( - CONFIG_PROGRAM_NAME, - CONFIG_N_PARTIES -) - -# N other parties store a secret -async def main(args = None): - parser = argparse.ArgumentParser( - description="Create a secret on the Nillion network with set read/retrieve permissions" - ) - parser.add_argument( - "--user_id_1", - required=True, - type=str, - help="User ID of the user who will compute with the secret being stored", - ) - parser.add_argument( - "--store_id_1", - required=True, - type=str, - help="Store ID of the 1st secret", - ) - - args = parser.parse_args(args) - - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - program_id=f"{args.user_id_1}/{CONFIG_PROGRAM_NAME}" - - # start a list of store ids to keep track of stored secrets - store_ids = [] - party_ids = [] - - for party_info in CONFIG_N_PARTIES: - seed = party_info["seed"] - client_n = create_nillion_client( - UserKey.from_seed(seed), - NodeKey.from_seed(seed) - ) - party_id_n = client_n.party_id - user_id_n = client_n.user_id - party_name = party_info["party_name"] - secret_name = party_info["secret_name"] - secret_value = party_info["secret_value"] - - # Create payments config and set up Nillion wallet with a private key to pay for operations - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # Create a secret for the current party - stored_secret = nillion.Secrets({ - secret_name: nillion.SecretInteger(secret_value) - }) - - # Get cost quote, then pay for operation to store the secret - receipt_store = await pay( - client_n, - nillion.Operation.store_values(stored_secret), - payments_wallet, - payments_client, - cluster_id, - ) - - # Create permissions object - permissions = nillion.Permissions.default_for_user(user_id_n) - - # Give compute permissions to the first party - print(program_id) - compute_permissions = { - args.user_id_1: {program_id}, - } - permissions.add_compute_permissions(compute_permissions) - - # Store the permissioned secret - store_id = await client_n.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - store_ids.append(store_id) - party_ids.append(party_id_n) - - print(f"\n🎉N Party {party_name} stored {secret_name}: {secret_value} at store id: {store_id}") - print(f"\n🎉Compute permission on the secret granted to user_id: {args.user_id_1}") - - party_ids_to_store_ids = ' '.join([f'{party_id}:{store_id}' for party_id, store_id in zip(party_ids, store_ids)]) - - print("\n📋⬇️ Copy and run the following command to run multi party computation using the secrets") - print(f"\npython3 03_multi_party_compute.py --store_id_1 {args.store_id_1} --party_ids_to_store_ids {party_ids_to_store_ids}") - return [args.store_id_1, party_ids_to_store_ids] - -if __name__ == "__main__": - asyncio.run(main()) - -@pytest.mark.asyncio -async def test_main(): - pass diff --git a/examples_and_tutorials/core_concept_multi_party_compute/03_multi_party_compute.py b/examples_and_tutorials/core_concept_multi_party_compute/03_multi_party_compute.py deleted file mode 100644 index cd3c6332..00000000 --- a/examples_and_tutorials/core_concept_multi_party_compute/03_multi_party_compute.py +++ /dev/null @@ -1,124 +0,0 @@ -import argparse -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -from config import ( - CONFIG_PROGRAM_NAME, - CONFIG_PARTY_1, - CONFIG_N_PARTIES -) - -async def main(args = None): - parser = argparse.ArgumentParser( - description="Create a secret on the Nillion network with set read/retrieve permissions" - ) - - parser.add_argument( - "--store_id_1", - required=True, - type=str, - help="Store ID of the 1st secret", - ) - - parser.add_argument( - "--party_ids_to_store_ids", - required=True, - nargs='+', - type=str, - help="List of partyid:storeid pairs of the secrets, with each pair separated by a space", - ) - - args = parser.parse_args(args) - - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - - # 1st party computes on secrets - seed = CONFIG_PARTY_1["seed"] - client_1 = create_nillion_client( - UserKey.from_seed(seed), - NodeKey.from_seed(seed) - ) - user_id_1 = client_1.user_id - party_id_1 = client_1.party_id - - # Create payments config and set up Nillion wallet with a private key to pay for operations - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - program_id=f"{user_id_1}/{CONFIG_PROGRAM_NAME}" - - # Bind the parties in the computation to the client to set inputs and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(CONFIG_PARTY_1["party_name"], party_id_1) - compute_bindings.add_output_party(CONFIG_PARTY_1["party_name"], party_id_1) - store_id_1 = args.store_id_1 - - print(f"Computing using program {program_id}") - print(f"Party 1 secret store_id: {store_id_1}") - - party_ids_to_store_ids = {} - i=0 - for pair in args.party_ids_to_store_ids: - party_id, store_id = pair.split(':') - party_name = CONFIG_N_PARTIES[i]['party_name'] - compute_bindings.add_input_party(party_name, party_id) - party_ids_to_store_ids[party_id] = store_id - i=i+1 - - compute_time_secrets = nillion.Secrets({}) - - # Get cost quote, then pay for operation to compute - receipt_compute = await pay( - client_1, - nillion.Operation.compute(program_id, compute_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - # Compute on the secret with all store ids. Note that there are no compute time secrets or public variables - compute_id = await client_1.compute( - cluster_id, - compute_bindings, - [store_id_1] + list(party_ids_to_store_ids.values()), - compute_time_secrets, - nillion.PublicVariables({}), - receipt_compute, - ) - - # Print compute result - print(f"The computation was sent to the network. compute_id: {compute_id}") - while True: - compute_event = await client_1.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 - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/examples_and_tutorials/core_concept_multi_party_compute/README.md b/examples_and_tutorials/core_concept_multi_party_compute/README.md deleted file mode 100644 index 856a997c..00000000 --- a/examples_and_tutorials/core_concept_multi_party_compute/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Multi Party Example - -This is an example using the Nillion Python Client to store a program, and run multi party compute, computation involving secret inputs from multiple parties. - -## Assumptions - -The 3-part Multi Party example assumes there is a stored program that meets the following criteria: - -- there are N+1 parties -- each party contributes secret inputs of type SecretInteger, and each party stores at least one secret an input -- the 1st party stores a secret -- N other parties store secrets and give the 1st party permission to compute on their secrets -- the 1st party runs compute -- the 1st party reads the output - -## Run the example - -1. Set up requirements following the repo README to populate the .env file with environment variables. - -2. Follow README instructions to compile a program that meets the above assumptions. Take note of the program name. Here are some programs that meet the multi party example program assumptions: - -- addition_simple_multi_party.nada.bin -- circuit_simple_multi_party.nada.bin -- complex_operation_mix -- greater_or_equal_than -- greater_than -- import_file -- less_or_equal_than -- less_than -- multiplication_simple_multi_party.nada.bin -- single_addition_multi_party.nada.bin -- subtraction_simple_multi_party.nada.bin -- subtraction_simple_neg_multi_party.nada.bin -- reuse_simple_1_multi_party.nada.bin -- reuse_simple_sub_multi_party.nada.bin - -For example before running addition_simple_multi_party.py, compile all programs and check that addition_simple_multi_party.nada.bin exists in the compiled-programs folder. - -```bash -cd ../.. -./compile_programs.sh -``` - -3. Update values in config.py to set the stored program name, party names, secret names, and secret values. - -4. Start the 3 part example by running - -```bash -python3 01_store_secret_party1.py -``` - -5. Follow the cli directions to run part 2 and 3 of the example. diff --git a/examples_and_tutorials/core_concept_multi_party_compute/config.py b/examples_and_tutorials/core_concept_multi_party_compute/config.py deleted file mode 100644 index 58348783..00000000 --- a/examples_and_tutorials/core_concept_multi_party_compute/config.py +++ /dev/null @@ -1,33 +0,0 @@ -import os -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv -load_dotenv("/Users/davidtbutler/Library/Application Support/nillion.nillion/nillion-devnet.env") - -# replace this with your program_id -CONFIG_PROGRAM_NAME="addition_simple_multi_party_3" - -# 1st party -CONFIG_PARTY_1={ - "seed": "party_1_seed", - "nodekey_alternate_file": os.getenv("NILLION_NODEKEY_PATH_PARTY_4"), - "party_name": "Party1", - "secrets": { - "my_int1": 1, - } -} - -# N other parties -CONFIG_N_PARTIES=[ - { - "seed": "party_2_seed", - "party_name": "Party2", - "secret_name": "my_int2", - "secret_value": 5, - }, - { - "seed": "party_3_seed", - "party_name": "Party3", - "secret_name": "my_int3", - "secret_value": 2, - }, -] \ No newline at end of file diff --git a/examples_and_tutorials/core_concept_permissions/01_fetch_reader_userid.py b/examples_and_tutorials/core_concept_permissions/01_fetch_reader_userid.py deleted file mode 100644 index c3607f1d..00000000 --- a/examples_and_tutorials/core_concept_permissions/01_fetch_reader_userid.py +++ /dev/null @@ -1,35 +0,0 @@ -import asyncio -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -async def main(): - seed_1 = "seed_1" - userkey = UserKey.from_seed((seed_1)) - nodekey = NodeKey.from_seed((seed_1)) - - # Reader Nillion client - reader = create_nillion_client(userkey, nodekey) - # Get the reader's user id - reader_user_id = reader.user_id - - print("ℹ️ Fetched the reader's USER ID:", reader_user_id) - print("\n\nRun the following command to store a secret and give read/retrieve permissions to the READER USER ID") - print(f"\n📋 python3 02_store_permissioned_secret.py --retriever_user_id {reader_user_id}") - return reader_user_id - -if __name__ == "__main__": - asyncio.run(main()) - -@pytest.mark.asyncio -async def test_main(): - pass diff --git a/examples_and_tutorials/core_concept_permissions/02_store_permissioned_secret.py b/examples_and_tutorials/core_concept_permissions/02_store_permissioned_secret.py deleted file mode 100644 index e3dcdfa5..00000000 --- a/examples_and_tutorials/core_concept_permissions/02_store_permissioned_secret.py +++ /dev/null @@ -1,104 +0,0 @@ -import argparse -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -async def main(args = None): - parser = argparse.ArgumentParser( - description="Create a secret on the Nillion network with set read/retrieve permissions" - ) - parser.add_argument( - "--retriever_user_id", - required=True, - type=str, - help="User ID of the reader python client (derived from user key)", - ) - args = parser.parse_args(args) - - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed_2 = "seed_2" - userkey = UserKey.from_seed(seed_2) - nodekey = NodeKey.from_seed(seed_2) - - # Writer Nillion client - writer = create_nillion_client(userkey, nodekey) - writer_user_id = writer.user_id - print(writer_user_id, args.retriever_user_id) - - # Create payments config and set up Nillion wallet with a private key to pay for operations - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # Create secret - secret_name_1 = "my_int1" - secret_1 = nillion.SecretInteger(10) - - secret_name_2 = "my_int2" - secret_2 = nillion.SecretInteger(32) - secrets_object = nillion.Secrets({secret_name_1: secret_1, secret_name_2: secret_2}) - - # Writer gives themself default core_concept_permissions - permissions = nillion.Permissions.default_for_user(writer_user_id) - # Writer gives the reader permission to read/retrieve secret - permissions.add_retrieve_permissions(set([args.retriever_user_id, writer_user_id])) - - result = ( - "allowed" - if permissions.is_retrieve_allowed(args.retriever_user_id) - else "not allowed" - ) - if result == "not allowed": - raise Exception("failed to set core_concept_permissions") - - print(f"ℹ️ Permissions set: Reader {args.retriever_user_id} is {result} to retrieve the secret") - - # Get cost quote, then pay for operation to store the secret - receipt_store = await pay( - writer, - nillion.Operation.store_values(secrets_object), - payments_wallet, - payments_client, - cluster_id, - ) - - # Writer stores the permissioned secret, resulting in the secret's store id - print(f"ℹ️ Storing permissioned secret: {secrets_object}") - store_id = await writer.store_values( - cluster_id, secrets_object, permissions, receipt_store - ) - - print("ℹ️ STORE ID:", store_id) - print("\n\nRun the following command to retrieve the secret by store id as the reader") - print(f"\n📋 python3 03_retrieve_secret.py --store_id {store_id} --secret_name {secret_name_1}") - return store_id - -if __name__ == "__main__": - asyncio.run(main()) - -@pytest.mark.asyncio -async def test_main(): - pass diff --git a/examples_and_tutorials/core_concept_permissions/03_retrieve_secret.py b/examples_and_tutorials/core_concept_permissions/03_retrieve_secret.py deleted file mode 100644 index b1ed617a..00000000 --- a/examples_and_tutorials/core_concept_permissions/03_retrieve_secret.py +++ /dev/null @@ -1,85 +0,0 @@ -import argparse -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -async def main(args = None): - parser = argparse.ArgumentParser( - description="Use read permissions to retrieve a secret owned by another user on the Nillion network" - ) - parser.add_argument( - "--store_id", - required=True, - type=str, - help="Store ID from the writer client store operation", - ) - parser.add_argument( - "--secret_name", - required=True, - type=str, - help="Secret name from the writer client store operation", - ) - - args = parser.parse_args(args) - - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed_1 = "seed_1" - userkey = UserKey.from_seed(seed_1) - nodekey = NodeKey.from_seed(seed_1) - - # Reader Nillion client - reader = create_nillion_client(userkey, nodekey) - reader_user_id = reader.user_id - - # Create payments config and set up Nillion wallet with a private key to pay for operations - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # Get cost quote, then pay for operation to retrieve the secret - receipt_store = await pay( - reader, - nillion.Operation.retrieve_value(), - payments_wallet, - payments_client, - cluster_id, - ) - - # Reader retrieves the named secret by store id - print(f"Retrieving secret as reader: {reader_user_id}") - result = await reader.retrieve_value(cluster_id, args.store_id, args.secret_name, receipt_store) - - print(f"🦄 Retrieved {args.secret_name} secret, value = {result[1].value}", file=sys.stderr) - print("\n\nRun the following command to revoke the reader's retrieve permissions to the secret") - print(f"\n📋 python3 04_revoke_read_permissions.py --store_id {args.store_id} --revoked_user_id {reader_user_id}") - return [args.store_id, reader_user_id] - -if __name__ == "__main__": - asyncio.run(main()) - -@pytest.mark.asyncio -async def test_main(): - pass diff --git a/examples_and_tutorials/core_concept_permissions/04_revoke_read_permissions.py b/examples_and_tutorials/core_concept_permissions/04_revoke_read_permissions.py deleted file mode 100644 index a5f09bf9..00000000 --- a/examples_and_tutorials/core_concept_permissions/04_revoke_read_permissions.py +++ /dev/null @@ -1,93 +0,0 @@ -import argparse -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -async def main(args = None): - parser = argparse.ArgumentParser( - description="Revoke user read/retrieve permissions from a secret on the Nillion network" - ) - parser.add_argument( - "--store_id", - required=True, - type=str, - help="Store ID from the writer client store operation", - ) - parser.add_argument( - "--revoked_user_id", - required=True, - type=str, - help="User ID of the reader python client (derived from user key)", - ) - args = parser.parse_args(args) - - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed_2 = "seed_2" - userkey = UserKey.from_seed(seed_2) - nodekey = NodeKey.from_seed(seed_2) - - # Writer Nillion client - writer = create_nillion_client(userkey, nodekey) - - # Create payments config and set up Nillion wallet with a private key to pay for operations - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # Create new permissions object to rewrite permissions (reader no longer has retrieve permission) - new_permissions = nillion.Permissions.default_for_user(writer.user_id) - result = ( - "allowed" - if new_permissions.is_retrieve_allowed(args.revoked_user_id) - else "not allowed" - ) - if result != "not allowed": - raise Exception("failed to create valid permissions object") - - # Get cost quote, then pay for operation to update permissions - receipt_update_permissions = await pay( - writer, - nillion.Operation.update_permissions(), - payments_wallet, - payments_client, - cluster_id, - ) - - # Update the permission - print(f"ℹ️ Updating permissions for secret: {args.store_id}.") - print(f"ℹ️ Reset permissions so that user id {args.revoked_user_id} is {result} to retrieve object.", file=sys.stderr) - await writer.update_permissions( cluster_id, args.store_id , new_permissions, receipt_update_permissions) - - print("\n\nRun the following command to test that permissions have been properly revoked") - print(f"\n📋 python3 05_test_revoked_permissions.py --store_id {args.store_id}") - return args.store_id - -if __name__ == "__main__": - asyncio.run(main()) - -@pytest.mark.asyncio -async def test_main(): - pass diff --git a/examples_and_tutorials/core_concept_permissions/05_test_revoked_permissions.py b/examples_and_tutorials/core_concept_permissions/05_test_revoked_permissions.py deleted file mode 100644 index 2800e0b2..00000000 --- a/examples_and_tutorials/core_concept_permissions/05_test_revoked_permissions.py +++ /dev/null @@ -1,100 +0,0 @@ -import importlib -import argparse -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - - -fetch_reader_userid = importlib.import_module("01_fetch_reader_userid") -store_permissioned_secret = importlib.import_module("02_store_permissioned_secret") -retrieve_secret = importlib.import_module("03_retrieve_secret") -revoke_read_permissions = importlib.import_module("04_revoke_read_permissions") - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -async def main(args = None): - parser = argparse.ArgumentParser( - description="Check that retrieval permissions on a Secret have been revoked" - ) - parser.add_argument( - "--store_id", - required=True, - type=str, - help="Store ID from the writer client store operation", - ) - args = parser.parse_args(args) - - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed_1 = "seed_1" - userkey = UserKey.from_seed(seed_1) - nodekey = NodeKey.from_seed(seed_1) - - # Reader Nillion client - reader = create_nillion_client(userkey, nodekey) - reader_user_id = reader.user_id - - # Create payments config and set up Nillion wallet with a private key to pay for operations - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # Get cost quote, then pay for operation to retrieve the secret - receipt_store = await pay( - reader, - nillion.Operation.retrieve_value(), - payments_wallet, - payments_client, - cluster_id, - ) - - try: - secret_name = "my_int1" - await reader.retrieve_value(cluster_id, args.store_id, secret_name, receipt_store) - print(f"⛔ FAIL: {reader_user_id} user id with revoked permissions was allowed to access secret", file=sys.stderr) - except Exception as e: - if str(e) == "retrieving value: the user is not authorized to access the secret": - print(f"🦄 Success: After user permissions were revoked, {reader_user_id} was not allowed to access secret", file=sys.stderr) - else: - raise(e) - -if __name__ == "__main__": - asyncio.run(main()) - -@pytest.mark.asyncio -async def test_main(): - result = await fetch_reader_userid.main() - args = ['--retriever_user_id', result] - result = await store_permissioned_secret.main(args) - args = ['--store_id', result] - result = await retrieve_secret.main(args) - args = ['--store_id', result[0], '--revoked_user_id', result[1]] - result = await revoke_read_permissions.main(args) - args = ['--store_id', result] - await main(args) diff --git a/examples_and_tutorials/core_concept_permissions/README.md b/examples_and_tutorials/core_concept_permissions/README.md deleted file mode 100644 index 89453611..00000000 --- a/examples_and_tutorials/core_concept_permissions/README.md +++ /dev/null @@ -1,19 +0,0 @@ -### Run permissions examples (storing and retrieving permissioned secrets, revoking permissions) - -Before running through examples, `./bootstrap-local-environment.sh` creates user keys for the secret writer and for the reader who the writer will allow to read the secret. Permissions examples are labeled 1-5: - -1. The reader fetches their user id -2. The writer stores a secret and gives the reader retrieve permissions on the secret based on the reader's user id, resulting in a store id for the secret -3. The reader retrieves the secret with the store id -4. The writer revokes secret permissions by rewriting them -5. The reader tries to retrieve the secret, but no longer has access to it - -To run through the example flow, simply run the python scripts in order. The output of a script will show you what to run next. Below gives the structure of the commands needed. - -```shell -python3 01_fetch_reader_userid.py -python3 02_store_permissioned_secret.py --retriever_user_id {READER_USER_ID} -python3 03_retrieve_secret.py --store_id {STORE_ID} --secret_name {SECRET_NAME} -python3 04_revoke_read_permissions.py --store_id {STORE_ID} --revoked_user_id {READER_USER_ID} -python3 05_test_revoked_permissions.py --store_id {STORE_ID} -``` diff --git a/examples_and_tutorials/core_concept_single_party_compute/README.md b/examples_and_tutorials/core_concept_single_party_compute/README.md deleted file mode 100644 index a719c9e1..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Single Party Compute Examples - -These compute examples only involve one party. - -## Run an example - -1. Set up requirements following the repo README to populate the .env file with environment variables. - -2. Follow README instructions to compile and store the program of the same name as the file - -For example before running addition_simple.py, compile all programs and check that addition_simple.nada.bin exists in the compiled-programs folder. - -```bash -cd ../.. -./compile_programs.sh -``` - -3. Run the python example - -```bash -python3 addition_simple.py -``` diff --git a/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py b/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py deleted file mode 100644 index 0c4cacb7..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/addition_simple.py +++ /dev/null @@ -1,145 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -# 1 Party running simple addition on 1 stored secret and 1 compute time secret -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - client = create_nillion_client(userkey, nodekey) - party_id = client.party_id - user_id = client.user_id - party_name = "Party1" - program_name = "addition_simple" - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - - # Create payments config and set up Nillion wallet with a private key to pay for operations - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - ##### STORE PROGRAM - print("-----STORE PROGRAM") - - # Get cost quote, then pay for operation to store program - receipt_store_program = await pay( - client, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - # Store program, passing in the receipt that shows proof of payment - action_id = await client.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) - - # Print details about stored program - program_id = f"{user_id}/{program_name}" - print("Stored program. action_id:", action_id) - print("Stored program_id:", program_id) - - ##### STORE SECRETS - print("-----STORE SECRETS") - - # Create a secret - stored_secret = nillion.Secrets( - { - "my_int1": nillion.SecretInteger(500), - } - ) - - # Create a permissions object to attach to the stored secret - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) - - # Get cost quote, then pay for operation to store the secret - receipt_store = await pay( - client, - nillion.Operation.store_values(stored_secret), - payments_wallet, - payments_client, - cluster_id, - ) - - # Store a secret, passing in the receipt that shows proof of payment - store_id = await client.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - ##### COMPUTE - print("-----COMPUTE") - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - # Create a computation time secret to use - computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) - - # Get cost quote, then pay for operation to compute - receipt_compute = await pay( - client, - nillion.Operation.compute(program_id, computation_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - # Compute, passing all params including the receipt that shows proof of payment - uuid = await client.compute( - cluster_id=cluster_id, - bindings=compute_bindings, - store_ids=[store_id], - secrets=computation_time_secrets, - receipt=receipt_compute, - public_variables=nillion.PublicVariables({}), - ) - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - # Print compute result - print(f"The computation was sent to the network. compute_id: {uuid}") - 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 - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {"my_output": 510} diff --git a/examples_and_tutorials/core_concept_single_party_compute/circuit_simple.py b/examples_and_tutorials/core_concept_single_party_compute/circuit_simple.py deleted file mode 100644 index 2b4192d0..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/circuit_simple.py +++ /dev/null @@ -1,136 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -# 1 Party running a simple circuit on 2 stored secrets and 2 compute time secrets -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - client = create_nillion_client(userkey, nodekey) - party_id = client.party_id - user_id = client.user_id - party_name = "Party1" - program_name = "circuit_simple" - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - receipt_store_program = await pay( - client, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - # store program - action_id = await client.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) - - program_id = f"{user_id}/{program_name}" - print("Stored program. action_id:", action_id) - print("Stored program_id:", program_id) - - # Set permissions for the client to compute on the program - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) - - # Create a secret - stored_secret = nillion.Secrets( - { - "my_int1": nillion.SecretInteger(3), - "my_int2": nillion.SecretInteger(4), - } - ) - - receipt_store = await pay( - client, - nillion.Operation.store_values(stored_secret), - payments_wallet, - payments_client, - cluster_id, - ) - - # Store a secret - store_id = await client.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - computation_time_secrets = nillion.Secrets( - {"my_int3": nillion.SecretInteger(2), "my_int4": nillion.SecretInteger(5)} - ) - - # Pay for the compute - receipt_compute = await pay( - client, - nillion.Operation.compute(program_id, computation_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - # Compute on the secret - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - nillion.PublicVariables({}), - receipt_compute, - ) - - # Print compute 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 - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {"my_output": 22} diff --git a/examples_and_tutorials/core_concept_single_party_compute/circuit_simple_2.py b/examples_and_tutorials/core_concept_single_party_compute/circuit_simple_2.py deleted file mode 100644 index 92e4bfe5..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/circuit_simple_2.py +++ /dev/null @@ -1,145 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -# 1 Party running a simple circuit on 6 stored secrets and 3 compute time secrets -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - client = create_nillion_client(userkey, nodekey) - party_id = client.party_id - user_id = client.user_id - party_name = "Party1" - program_name = "circuit_simple_2" - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # Pay to store the program - receipt_store_program = await pay( - client, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - # store program - action_id = await client.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) - - program_id = f"{user_id}/{program_name}" - print("Stored program. action_id:", action_id) - print("Stored program_id:", program_id) - - # Set permissions for the client to compute on the program - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) - - # Create a secret - stored_secret = nillion.Secrets( - { - "A": nillion.SecretInteger(3), - "B": nillion.SecretInteger(14), - "C": nillion.SecretInteger(5), - "D": nillion.SecretInteger(6), - "E": nillion.SecretInteger(2), - "F": nillion.SecretInteger(1), - } - ) - - receipt_store = await pay( - client, - nillion.Operation.store_values(stored_secret), - payments_wallet, - payments_client, - cluster_id, - ) - - # Store a secret - store_id = await client.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - computation_time_secrets = nillion.Secrets( - { - "G": nillion.SecretInteger(9), - "H": nillion.SecretInteger(10), - "I": nillion.SecretInteger(7), - } - ) - - # Pay for the compute - receipt_compute = await pay( - client, - nillion.Operation.compute(program_id, computation_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - # Compute on the secrets - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - nillion.PublicVariables({}), - receipt_compute, - ) - - # Print compute 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 - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {"my_output": 319} diff --git a/examples_and_tutorials/core_concept_single_party_compute/complex.py b/examples_and_tutorials/core_concept_single_party_compute/complex.py deleted file mode 100644 index f8d2da07..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/complex.py +++ /dev/null @@ -1,144 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -# 1 Party running complex program on 5 stored secrets and 1 runtime secret -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - client = create_nillion_client(userkey, nodekey) - party_id = client.party_id - user_id = client.user_id - party_name = "Party1" - program_name = "complex" - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # Pay to store the program - receipt_store_program = await pay( - client, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - # store program - action_id = await client.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) - - program_id = f"{user_id}/{program_name}" - print("Stored program. action_id:", action_id) - print("Stored program_id:", program_id) - - # Set permissions for the client to compute on the program - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) - - # Create a secret - stored_secret = nillion.Secrets( - { - "A": nillion.SecretInteger(10), - "B": nillion.SecretInteger(2), - "C": nillion.SecretInteger(1), - "D": nillion.SecretInteger(5), - "E": nillion.SecretInteger(4), - } - ) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) - - receipt_store = await pay( - client, - nillion.Operation.store_values(stored_secret), - payments_wallet, - payments_client, - cluster_id, - ) - - # Store a secret - store_id = await client.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - computation_time_secrets = nillion.Secrets( - { - "F": nillion.SecretInteger(3), - } - ) - - # Pay for the compute - receipt_compute = await pay( - client, - nillion.Operation.compute(program_id, computation_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - # Compute on the secret - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - nillion.PublicVariables({}), - receipt_compute, - ) - - # Print compute 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 - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {"FINAL": 52} diff --git a/examples_and_tutorials/core_concept_single_party_compute/correlation_coefficient.py b/examples_and_tutorials/core_concept_single_party_compute/correlation_coefficient.py deleted file mode 100644 index fdf5f6bb..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/correlation_coefficient.py +++ /dev/null @@ -1,204 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest -from pdb import set_trace as bp -import argparse -import asyncio -import py_nillion_client as nillion -import os -import sys -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv -import random -from math import sqrt -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -# 1 Party running simple addition on 1 stored secret and 1 compute time secret -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - client = create_nillion_client(userkey, nodekey) - party_id = client.party_id - user_id = client.user_id - party_0_name = "Party0" - party_1_name = "Party1" - out_party_name = "OutParty" - program_name = "correlation_coefficient" - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # Pay to store the program - receipt_store_program = await pay( - client, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - # store program - action_id = await client.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) - - program_id = f"{user_id}/{program_name}" - print("Stored program. action_id:", action_id) - print("Stored program_id:", program_id) - - # Set permissions for the client to compute on the program - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) - - # Config elements - linear = lambda x: 30 * x + 21 - p0_points = 10 - p1_points = 10 - precision = 5 - - random.seed(42) - - # Create inputs for both parties - party_0_dict = {} - for i in range(p0_points): - party_0_dict[f"x{i}"] = nillion.SecretInteger(i + 1) - party_0_dict[f"y{i}"] = nillion.SecretInteger( - linear(i + 1) + random.randint(0, 10) - ) - - party_1_dict = {} - for i in range(p0_points, p0_points + p1_points): - party_1_dict[f"x{i}"] = nillion.SecretInteger(i + 1) - party_1_dict[f"y{i}"] = nillion.SecretInteger( - linear(i + 1) + random.randint(0, 10) - ) - - # Parties store the secrets - party_0_secrets = nillion.Secrets(party_0_dict) - party_1_secrets = nillion.Secrets(party_1_dict) - - # Give core_concept_permissions to owner to compute with my vote - secret_permissions = nillion.Permissions.default_for_user(user_id) - secret_permissions.add_compute_permissions( - { - user_id: {program_id}, - } - ) - - store_ids = [] - # Store in the network - print(f"Storing party 0: {party_0_secrets}") - - # Get cost quote, then pay for operation to store the secret - receipt_store_0 = await pay( - client, - nillion.Operation.store_values(party_0_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - store_id = await client.store_values( - cluster_id, party_0_secrets, secret_permissions, receipt_store_0 - ) - store_ids.append(store_id) - print(f"Stored party 0 with store_id ={store_id}") - - receipt_store_1 = await pay( - client, - nillion.Operation.store_values(party_1_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - print(f"Storing party 1: {party_1_secrets}") - store_id = await client.store_values( - cluster_id, party_1_secrets, secret_permissions, receipt_store_1 - ) - store_ids.append(store_id) - print(f"Stored party 1 with store_id ={store_id}") - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_0_name, party_id) - compute_bindings.add_input_party(party_1_name, party_id) - compute_bindings.add_output_party(out_party_name, party_id) - computation_time_secrets = nillion.Secrets({}) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - # Get cost quote, then pay for operation to compute - receipt_compute = await pay( - client, - nillion.Operation.compute(program_id, computation_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - # Compute on the secret - compute_id = await client.compute( - cluster_id, - compute_bindings, - store_ids, - computation_time_secrets, - nillion.PublicVariables({}), - receipt_compute - ) - - # Print compute 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}") - corr_coeff_squared = ( - compute_event.result.value["correlation_coefficient_squared"] - / 10**precision - ) - sign = 1 if compute_event.result.value["sign"] else -1 - corr_coeff = round(sign * sqrt(corr_coeff_squared), precision) - print( - f"📈 Correlation coefficient = {corr_coeff} with precision {precision}." - ) - return compute_event.result.value - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {"correlation_coefficient_squared": 99958, "sign": 1} diff --git a/examples_and_tutorials/core_concept_single_party_compute/division_simple.py b/examples_and_tutorials/core_concept_single_party_compute/division_simple.py deleted file mode 100644 index f71893a7..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/division_simple.py +++ /dev/null @@ -1,138 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - - -# 1 Party running simple division on 2 stored secrets -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - client = create_nillion_client(userkey, nodekey) - party_id = client.party_id - user_id = client.user_id - party_name = "Party1" - program_name = "division_simple" - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # Pay to store the program - receipt_store_program = await pay( - client, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - # store program - action_id = await client.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) - - program_id = f"{user_id}/{program_name}" - print("Stored program. action_id:", action_id) - print("Stored program_id:", program_id) - - # Set permissions for the client to compute on the program - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) - - # Create a secret - stored_secret = nillion.Secrets( - { - "my_int1": nillion.SecretInteger(10), - } - ) - - receipt_store = await pay( - client, - nillion.Operation.store_values(stored_secret), - payments_wallet, - payments_client, - cluster_id, - ) - # Store a secret - store_id = await client.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - computation_time_secrets = nillion.Secrets({}) - - # Pay for the compute - receipt_compute = await pay( - client, - nillion.Operation.compute(program_id, computation_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - # Compute on the secret - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - nillion.PublicVariables( - { - "my_int3": nillion.PublicInteger(2), - } - ), - receipt_compute, - ) - - # Print compute 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 - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {"my_output": 5} diff --git a/examples_and_tutorials/core_concept_single_party_compute/modulo_simple.py b/examples_and_tutorials/core_concept_single_party_compute/modulo_simple.py deleted file mode 100644 index c4a4e55a..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/modulo_simple.py +++ /dev/null @@ -1,138 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -# 1 Party running modulo on 1 stored secrets and a public variable -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - client = create_nillion_client(userkey, nodekey) - party_id = client.party_id - user_id = client.user_id - party_name = "Party1" - program_name = "modulo_simple" - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # Pay to store the program - receipt_store_program = await pay( - client, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - # store program - action_id = await client.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) - - program_id = f"{user_id}/{program_name}" - print("Stored program. action_id:", action_id) - print("Stored program_id:", program_id) - - # Set permissions for the client to compute on the program - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) - - # Create a secret - stored_secret = nillion.Secrets( - { - "my_int1": nillion.SecretInteger(37), - } - ) - - receipt_store = await pay( - client, - nillion.Operation.store_values(stored_secret), - payments_wallet, - payments_client, - cluster_id, - ) - - # Store a secret - store_id = await client.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - computation_time_secrets = nillion.Secrets({}) - - # Pay for the compute - receipt_compute = await pay( - client, - nillion.Operation.compute(program_id, computation_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - public_variables = nillion.PublicVariables( - {"public_my_int2": nillion.PublicInteger(12)} - ) - - # Compute on the secret - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - public_variables, - receipt_compute, - ) - - # Print compute 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 - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {"my_output": 1} diff --git a/examples_and_tutorials/core_concept_single_party_compute/multiplication_simple.py b/examples_and_tutorials/core_concept_single_party_compute/multiplication_simple.py deleted file mode 100644 index 1deddaaf..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/multiplication_simple.py +++ /dev/null @@ -1,135 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -# 1 Party running simple multiplication on 1 stored secret and 1 compute time secret -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - client = create_nillion_client(userkey, nodekey) - party_id = client.party_id - user_id = client.user_id - user_id = client.user_id - party_name = "Party1" - program_name = "multiplication_simple" - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # Pay to store the program - receipt_store_program = await pay( - client, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - # store program - action_id = await client.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) - - program_id = f"{user_id}/{program_name}" - print("Stored program. action_id:", action_id) - print("Stored program_id:", program_id) - - # Set permissions for the client to compute on the program - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) - - # Create a secret - stored_secret = nillion.Secrets( - { - "my_int1": nillion.SecretInteger(500), - } - ) - - receipt_store = await pay( - client, - nillion.Operation.store_values(stored_secret), - payments_wallet, - payments_client, - cluster_id, - ) - - # Store a secret - store_id = await client.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) - - # Pay for the compute - receipt_compute = await pay( - client, - nillion.Operation.compute(program_id, computation_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - # Compute on the secret - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - nillion.PublicVariables({}), - receipt_compute, - ) - - # Print compute 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 - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {"my_output": 5000} diff --git a/examples_and_tutorials/core_concept_single_party_compute/nada_fn_composition.py b/examples_and_tutorials/core_concept_single_party_compute/nada_fn_composition.py deleted file mode 100644 index 7bfd0839..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/nada_fn_composition.py +++ /dev/null @@ -1,132 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - client = create_nillion_client(userkey, nodekey) - party_id = client.party_id - user_id = client.user_id - party_name = "Party1" - program_name = "nada_fn_composition" - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # Pay to store the program - receipt_store_program = await pay( - client, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - # store program - action_id = await client.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) - - program_id = f"{user_id}/{program_name}" - print("Stored program. action_id:", action_id) - print("Stored program_id:", program_id) - - # Set permissions for the client to compute on the program - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) - - # Create a secret - stored_secret = nillion.Secrets( - { - "my_int1": nillion.SecretInteger(500), - } - ) - - receipt_store = await pay( - client, - nillion.Operation.store_values(stored_secret), - payments_wallet, - payments_client, - cluster_id, - ) - - # Store a secret - store_id = await client.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - - computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) - - # Pay for the compute - receipt_compute = await pay( - client, - nillion.Operation.compute(program_id, computation_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - # Compute on the secret - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - nillion.PublicVariables({}), - receipt_compute, - ) - - # Print compute 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 - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {"my_output": 255000} diff --git a/examples_and_tutorials/core_concept_single_party_compute/reuse.py b/examples_and_tutorials/core_concept_single_party_compute/reuse.py deleted file mode 100644 index d4237abf..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/reuse.py +++ /dev/null @@ -1,140 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - client = create_nillion_client(userkey, nodekey) - party_id = client.party_id - user_id = client.user_id - party_name = "Party1" - program_name = "reuse" - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # Pay to store the program - receipt_store_program = await pay( - client, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - # store program - action_id = await client.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) - - program_id = f"{user_id}/{program_name}" - print("Stored program. action_id:", action_id) - print("Stored program_id:", program_id) - - # Set permissions for the client to compute on the program - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) - - # Create a secret - stored_secret = nillion.Secrets( - { - "A": nillion.SecretInteger(3), - "B": nillion.SecretInteger(4), - } - ) - secret_bindings = nillion.ProgramBindings(program_id) - secret_bindings.add_input_party(party_name, party_id) - - receipt_store = await pay( - client, - nillion.Operation.store_values(stored_secret), - payments_wallet, - payments_client, - cluster_id, - ) - - # Store a secret - store_id = await client.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - computation_time_secrets = nillion.Secrets( - { - "C": nillion.SecretInteger(5), - } - ) - - # Pay for the compute - receipt_compute = await pay( - client, - nillion.Operation.compute(program_id, computation_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - # Compute on the secrets - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - nillion.PublicVariables({}), - receipt_compute, - ) - - # Print compute 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 - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {"R": 27} diff --git a/examples_and_tutorials/core_concept_single_party_compute/simple.py b/examples_and_tutorials/core_concept_single_party_compute/simple.py deleted file mode 100644 index db57846b..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/simple.py +++ /dev/null @@ -1,135 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - client = create_nillion_client(userkey, nodekey) - party_id = client.party_id - user_id = client.user_id - party_name = "Party1" - program_name = "simple" - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # Pay to store the program - receipt_store_program = await pay( - client, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - # store program - action_id = await client.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) - - program_id = f"{user_id}/{program_name}" - print("Stored program. action_id:", action_id) - print("Stored program_id:", program_id) - - # Set permissions for the client to compute on the program - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) - - # Create a secret - stored_secret = nillion.Secrets( - { - "A": nillion.SecretInteger(3), - "B": nillion.SecretInteger(14), - "C": nillion.SecretInteger(5), - "D": nillion.SecretInteger(6), - } - ) - - receipt_store = await pay( - client, - nillion.Operation.store_values(stored_secret), - payments_wallet, - payments_client, - cluster_id, - ) - # Store a secret - store_id = await client.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - computation_time_secrets = nillion.Secrets({}) - - # Pay for the compute - receipt_compute = await pay( - client, - nillion.Operation.compute(program_id, computation_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - # Compute on the secrets - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - nillion.PublicVariables({}), - receipt_compute, - ) - - # Print compute 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 - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {"O": 72} diff --git a/examples_and_tutorials/core_concept_single_party_compute/simple_literals.py b/examples_and_tutorials/core_concept_single_party_compute/simple_literals.py deleted file mode 100644 index cb08eb32..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/simple_literals.py +++ /dev/null @@ -1,133 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - client = create_nillion_client(userkey, nodekey) - party_id = client.party_id - user_id = client.user_id - party_name = "Party1" - program_name = "simple_literals" - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # Pay to store the program - receipt_store_program = await pay( - client, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - # store program - action_id = await client.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) - - program_id = f"{user_id}/{program_name}" - print("Stored program. action_id:", action_id) - print("Stored program_id:", program_id) - - # Set permissions for the client to compute on the program - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) - - # Create a secret - stored_secret = nillion.Secrets( - { - "A": nillion.SecretInteger(3), - "C": nillion.SecretInteger(5), - } - ) - - receipt_store = await pay( - client, - nillion.Operation.store_values(stored_secret), - payments_wallet, - payments_client, - cluster_id, - ) - # Store a secret - store_id = await client.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - computation_time_secrets = nillion.Secrets({}) - - # Pay for the compute - receipt_compute = await pay( - client, - nillion.Operation.compute(program_id, computation_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - # Compute on the secrets - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - nillion.PublicVariables({}), - receipt_compute, - ) - - # Print compute 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 - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {"O": 1015} diff --git a/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables.py b/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables.py deleted file mode 100644 index f9d03d76..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables.py +++ /dev/null @@ -1,141 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -# 1 Party running compute with only public variables -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - client = create_nillion_client(userkey, nodekey) - party_id = client.party_id - user_id = client.user_id - party_name = "Party1" - program_name = "simple_public_variables" - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # Pay to store the program - receipt_store_program = await pay( - client, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - # store program - action_id = await client.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) - - program_id = f"{user_id}/{program_name}" - print("Stored program. action_id:", action_id) - print("Stored program_id:", program_id) - - # Set permissions for the client to compute on the program - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) - - # Create a secret - stored_secret = nillion.Secrets( - { - "A": nillion.SecretInteger(3), - "C": nillion.SecretInteger(5), - } - ) - - receipt_store = await pay( - client, - nillion.Operation.store_values(stored_secret), - payments_wallet, - payments_client, - cluster_id, - ) - # Store a secret - store_id = await client.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - public_variables = nillion.PublicVariables( - { - "B": nillion.PublicInteger(10), - "D": nillion.PublicInteger(6), - } - ) - - computation_time_secrets = nillion.Secrets({}) - - # Pay for the compute - receipt_compute = await pay( - client, - nillion.Operation.compute(program_id, computation_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - # Compute on the secrets - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - public_variables, - receipt_compute, - ) - - # Print compute 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 - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {"O": 119} diff --git a/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables_only.py b/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables_only.py deleted file mode 100644 index ebeb60c1..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/simple_public_variables_only.py +++ /dev/null @@ -1,120 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -# 1 Party running compute with only public variables -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - client = create_nillion_client(userkey, nodekey) - party_id = client.party_id - user_id = client.user_id - party_name = "Party1" - program_name = "simple_public_variables_only" - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # Pay to store the program - receipt_store_program = await pay( - client, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - # store program - action_id = await client.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) - - program_id = f"{user_id}/{program_name}" - print("Stored program. action_id:", action_id) - print("Stored program_id:", program_id) - - # Set permissions for the client to compute on the program - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - - public_variables = nillion.PublicVariables( - { - "A": nillion.PublicInteger(10), - "B": nillion.PublicInteger(6), - } - ) - - computation_time_secrets = nillion.Secrets({}) - - # Pay for the compute - receipt_compute = await pay( - client, - nillion.Operation.compute(program_id, computation_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - # Compute on the secrets - compute_id = await client.compute( - cluster_id, - compute_bindings, - [], - nillion.Secrets({}), - public_variables, - receipt_compute, - ) - - # Print compute 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 - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {"O": 60} diff --git a/examples_and_tutorials/core_concept_single_party_compute/simple_sub.py b/examples_and_tutorials/core_concept_single_party_compute/simple_sub.py deleted file mode 100644 index d509ec3a..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/simple_sub.py +++ /dev/null @@ -1,135 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - client = create_nillion_client(userkey, nodekey) - party_id = client.party_id - user_id = client.user_id - party_name = "Party1" - program_name = "simple_sub" - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # Pay to store the program - receipt_store_program = await pay( - client, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - # store program - action_id = await client.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) - - program_id = f"{user_id}/{program_name}" - print("Stored program. action_id:", action_id) - print("Stored program_id:", program_id) - - # Set permissions for the client to compute on the program - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) - - # Create a secret - stored_secret = nillion.Secrets( - { - "A": nillion.SecretInteger(3), - "B": nillion.SecretInteger(14), - "C": nillion.SecretInteger(5), - "D": nillion.SecretInteger(6), - } - ) - - receipt_store = await pay( - client, - nillion.Operation.store_values(stored_secret), - payments_wallet, - payments_client, - cluster_id, - ) - # Store a secret - store_id = await client.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - computation_time_secrets = nillion.Secrets({}) - - # Pay for the compute - receipt_compute = await pay( - client, - nillion.Operation.compute(program_id, computation_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - # Compute on the secrets - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - nillion.PublicVariables({}), - receipt_compute, - ) - - # Print compute 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 - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {"O": 12} diff --git a/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple.py b/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple.py deleted file mode 100644 index 6c2e1cf4..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple.py +++ /dev/null @@ -1,137 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -# 1 Party running a simple circuit on 6 stored secrets and 3 compute time secrets -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - client = create_nillion_client(userkey, nodekey) - party_id = client.party_id - user_id = client.user_id - party_name = "Party1" - program_name = "subtraction_simple" - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # Pay to store the program - receipt_store_program = await pay( - client, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - # store program - action_id = await client.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) - - program_id = f"{user_id}/{program_name}" - print("Stored program. action_id:", action_id) - print("Stored program_id:", program_id) - - # Set permissions for the client to compute on the program - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) - - # Create a secret - stored_secret = nillion.Secrets( - { - "my_int1": nillion.SecretInteger(30), - } - ) - - receipt_store = await pay( - client, - nillion.Operation.store_values(stored_secret), - payments_wallet, - payments_client, - cluster_id, - ) - # Store a secret - store_id = await client.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - computation_time_secrets = nillion.Secrets( - { - "my_int2": nillion.SecretInteger(90), - } - ) - - # Pay for the compute - receipt_compute = await pay( - client, - nillion.Operation.compute(program_id, computation_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - # Compute on the secrets - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - nillion.PublicVariables({}), - receipt_compute, - ) - - # Print compute 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 - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {"my_output": 60} diff --git a/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple_neg.py b/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple_neg.py deleted file mode 100644 index 885a2f25..00000000 --- a/examples_and_tutorials/core_concept_single_party_compute/subtraction_simple_neg.py +++ /dev/null @@ -1,132 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - client = create_nillion_client(userkey, nodekey) - party_id = client.party_id - user_id = client.user_id - party_name = "Party1" - program_name = "subtraction_simple_neg" - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # Pay to store the program - receipt_store_program = await pay( - client, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - # store program - action_id = await client.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) - - program_id = f"{user_id}/{program_name}" - print("Stored program. action_id:", action_id) - print("Stored program_id:", program_id) - - # Set permissions for the client to compute on the program - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) - - # Create a secret - stored_secret = nillion.Secrets( - { - "my_int4": nillion.SecretInteger(3), - } - ) - - receipt_store = await pay( - client, - nillion.Operation.store_values(stored_secret), - payments_wallet, - payments_client, - cluster_id, - ) - # Store a secret - store_id = await client.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - # Bind the parties in the computation to the client to set input and output parties - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") - - computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(9)}) - - # Pay for the compute - receipt_compute = await pay( - client, - nillion.Operation.compute(program_id, computation_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - # Compute on the secrets - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - nillion.PublicVariables({}), - receipt_compute, - ) - - # Print compute 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 - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {"my_output": 6} diff --git a/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_blob.py b/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_blob.py deleted file mode 100644 index 6075a842..00000000 --- a/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_blob.py +++ /dev/null @@ -1,86 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client, pay, create_payments_config - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -# Store and retrieve a SecretBlob using the Python Client -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - client = create_nillion_client(userkey, nodekey) - - # Create payments config and set up Nillion wallet with a private key to pay for operations - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion" - ) - - ##### STORE SECRET - print('-----STORE SECRET') - - # Create a SecretBlob - secret_name = "my_blob" - - # create a bytearray from the string using UTF-8 encoding - secret_value = bytearray("gm, builder!", "utf-8") - - # Create a secret - stored_secret = nillion.Secrets({ - secret_name: nillion.SecretBlob(secret_value), - }) - - # Create a permissions object to attach to the stored secret - permissions = nillion.Permissions.default_for_user(client.user_id) - - # Get cost quote, then pay for operation to store the secret - receipt_store = await pay( - client, nillion.Operation.store_values(stored_secret), payments_wallet, payments_client, cluster_id - ) - - # Store a secret, passing in the receipt that shows proof of payment - store_id = await client.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - print(f"The secret is stored at store_id: {store_id}") - - ##### RETRIEVE SECRET - print('-----RETRIEVE SECRET') - - # Get cost quote, then pay for operation to retrieve the secret - receipt_retrieve = await pay( - client, nillion.Operation.retrieve_value(), payments_wallet, payments_client, cluster_id - ) - - result_tuple = await client.retrieve_value(cluster_id, store_id, secret_name, receipt_retrieve) - print(f"The secret name as a uuid is {result_tuple[0]}") - - decoded_secret_value = result_tuple[1].value.decode('utf-8') - print(f"The secret value is '{decoded_secret_value}'") - return decoded_secret_value - -if __name__ == "__main__": - asyncio.run(main()) - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == 'gm, builder!' diff --git a/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_integer.py b/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_integer.py deleted file mode 100644 index 3bd17b03..00000000 --- a/examples_and_tutorials/core_concept_store_and_retrieve_secrets/store_and_retrieve_integer.py +++ /dev/null @@ -1,80 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) -from helpers.nillion_client_helper import create_nillion_client, pay, create_payments_config - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -# Store and retrieve a SecretInteger using the Python Client -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "my_seed" - userkey = UserKey.from_seed((seed)) - nodekey = NodeKey.from_seed((seed)) - client = create_nillion_client(userkey, nodekey) - - # Create payments config and set up Nillion wallet with a private key to pay for operations - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), prefix="nillion" - ) - - ##### STORE SECRET - print('-----STORE SECRET') - - # Create a SecretInteger - secret_name = "my_int1" - secret_value = 100 - stored_secret = nillion.Secrets({ - secret_name: nillion.SecretInteger(secret_value), - }) - - # Create a permissions object to attach to the stored secret - permissions = nillion.Permissions.default_for_user(client.user_id) - - # Get cost quote, then pay for operation to store the secret - receipt_store = await pay( - client, nillion.Operation.store_values(stored_secret), payments_wallet, payments_client, cluster_id - ) - - # Store a secret, passing in the receipt that shows proof of payment - store_id = await client.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - print(f"The secret is stored at store_id: {store_id}") - - ##### RETRIEVE SECRET - print('-----RETRIEVE SECRET') - - # Get cost quote, then pay for operation to retrieve the secret - receipt_retrieve = await pay( - client, nillion.Operation.retrieve_value(), payments_wallet, payments_client, cluster_id - ) - - result_tuple = await client.retrieve_value(cluster_id, store_id, secret_name, receipt_retrieve) - print(f"The secret name as a uuid is {result_tuple[0]}") - print(f"The secret value is {result_tuple[1].value}") - return result_tuple[1].value - -if __name__ == "__main__": - asyncio.run(main()) - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == 'gm, builder!' diff --git a/examples_and_tutorials/millionaires_problem_example/01_store_secret_party1.py b/examples_and_tutorials/millionaires_problem_example/01_store_secret_party1.py deleted file mode 100644 index 4649be8b..00000000 --- a/examples_and_tutorials/millionaires_problem_example/01_store_secret_party1.py +++ /dev/null @@ -1,88 +0,0 @@ -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv -from config import CONFIG_PARTY_1 - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -# Alice stores the millionaires program in the network -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - seed = "alice_seed" - client_alice = create_nillion_client( - UserKey.from_seed(seed), - NodeKey.from_seed(seed), - ) - - millionaires_program_name = "millionaires" - - # Note: check out the code for the full millionaires program in the nada_programs folder - program_mir_path = "../../nada_programs-compiled/millionaires.nada.bin" - - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # Pay to store the program - receipt_store_program = await pay( - client_alice, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - # Store millionaires program in the network - print(f"Storing program in the network: {millionaires_program_name}") - program_id = await client_alice.store_program( - cluster_id, millionaires_program_name, program_mir_path, receipt_store_program - ) - - # Set permissions for the client to compute on the program - permissions = nillion.Permissions.default_for_user(client_alice.user_id) - permissions.add_compute_permissions({client_alice.user_id: {program_id}}) - - user_id_alice = client_alice.user_id - program_id = f"{user_id_alice}/{millionaires_program_name}" - - print(f"Alice stores millionaires program at program_id: {program_id}") - print(f"Alice tells Bob and Charlie her user_id and the millionaires program_id") - - print( - "\n📋⬇️ Copy and run the following command to store Bob and Charlie's salaries in the network" - ) - print( - f"\npython3 02_store_secret_party_n.py --user_id_1 {user_id_alice} --program_id {program_id}" - ) - return [user_id_alice, program_id] - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - pass diff --git a/examples_and_tutorials/millionaires_problem_example/02_store_secret_party_n.py b/examples_and_tutorials/millionaires_problem_example/02_store_secret_party_n.py deleted file mode 100644 index 6572bd04..00000000 --- a/examples_and_tutorials/millionaires_problem_example/02_store_secret_party_n.py +++ /dev/null @@ -1,130 +0,0 @@ -import argparse -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv -from config import CONFIG_N_PARTIES - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) -from helpers.nillion_keypath_helper import getUserKeyFromFile, getNodeKeyFromFile - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -# Bob and Charlie store their salaries in the network -async def main(args=None): - parser = argparse.ArgumentParser( - description="Create a secret on the Nillion network with set read/retrieve permissions" - ) - parser.add_argument( - "--user_id_1", - required=True, - type=str, - help="User ID of the user who will compute with the secret being stored", - ) - parser.add_argument( - "--program_id", - required=True, - type=str, - help="Program ID of the millionaires program", - ) - - args = parser.parse_args(args) - - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - - # start a list of store ids to keep track of stored secrets - store_ids = [] - party_ids = [] - - for party_info in CONFIG_N_PARTIES: - party_seed = party_info["party_name"] + "_seed" - client_n = create_nillion_client( - UserKey.from_seed(party_seed), - NodeKey.from_seed(party_seed), - ) - party_id_n = client_n.party_id - user_id_n = client_n.user_id - - payments_config_n = create_payments_config(chain_id, grpc_endpoint) - payments_client_n = LedgerClient(payments_config_n) - payments_wallet_n = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - party_name = party_info["party_name"] - secret_name = party_info["secret_name"] - secret_value = party_info["secret_value"] - - # Create a secret for the current party - stored_secret = nillion.Secrets( - {secret_name: nillion.SecretInteger(secret_value)} - ) - - # Create permissions object with default permissions for the current user - permissions = nillion.Permissions.default_for_user(user_id_n) - - # Give compute permissions to Alice so she can use the secret in the specific millionionaires program by program id - compute_permissions = { - args.user_id_1: {args.program_id}, - } - permissions.add_compute_permissions(compute_permissions) - print( - f"\n👍 {party_name} gives compute permissions on their secret to Alice's user_id: {args.user_id_1}" - ) - - receipt_store = await pay( - client_n, - nillion.Operation.store_values(stored_secret), - payments_wallet_n, - payments_client_n, - cluster_id, - ) - # Store the permissioned secret - store_id = await client_n.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - store_ids.append(store_id) - party_ids.append(party_id_n) - - print( - f"\n🎉 {party_name} stored {secret_name}: {secret_value} at store id: {store_id}" - ) - - party_ids_to_store_ids = " ".join( - [f"{party_id}:{store_id}" for party_id, store_id in zip(party_ids, store_ids)] - ) - - print( - "\n📋⬇️ Copy and run the following command to run multi party computation using the secrets" - ) - print( - f"\npython3 03_multi_party_compute.py --program_id {args.program_id} --party_ids_to_store_ids {party_ids_to_store_ids}" - ) - return [args.program_id, party_ids_to_store_ids] - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - pass diff --git a/examples_and_tutorials/millionaires_problem_example/03_multi_party_compute.py b/examples_and_tutorials/millionaires_problem_example/03_multi_party_compute.py deleted file mode 100644 index e1b9a9f9..00000000 --- a/examples_and_tutorials/millionaires_problem_example/03_multi_party_compute.py +++ /dev/null @@ -1,164 +0,0 @@ -import argparse -import asyncio -import py_nillion_client as nillion -import os -import sys -import pytest -import importlib - -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -from config import CONFIG_PARTY_1, CONFIG_N_PARTIES - -store_secret_party_1 = importlib.import_module("01_store_secret_party1") -store_secret_party_n = importlib.import_module("02_store_secret_party_n") - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -async def main(args=None): - parser = argparse.ArgumentParser( - description="Create a secret on the Nillion network with set read/retrieve permissions" - ) - - parser.add_argument( - "--program_id", - required=True, - type=str, - help="Program ID of the millionaires program", - ) - - parser.add_argument( - "--party_ids_to_store_ids", - required=True, - nargs="+", - type=str, - help="List of partyid:storeid pairs of the secrets, with each pair separated by a space", - ) - - args = parser.parse_args(args) - - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - - alice_seed = "alice_seed" - # Alice initializes a client - client_alice = create_nillion_client( - UserKey.from_seed(alice_seed), - NodeKey.from_seed(alice_seed), - ) - party_id_alice = client_alice.party_id - - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # Create computation bindings for millionaires program - compute_bindings = nillion.ProgramBindings(args.program_id) - - # Add Alice as an input party - compute_bindings.add_input_party(CONFIG_PARTY_1["party_name"], party_id_alice) - - # Add an output party (Alice). - # The output party reads the result of the blind computation - compute_bindings.add_output_party(CONFIG_PARTY_1["party_name"], party_id_alice) - - print(f"Computing using program {args.program_id}") - - # Also add Bob and Charlie as input parties - party_ids_to_store_ids = {} - i = 0 - for pair in args.party_ids_to_store_ids: - party_id, store_id = pair.split(":") - party_name = CONFIG_N_PARTIES[i]["party_name"] - compute_bindings.add_input_party(party_name, party_id) - party_ids_to_store_ids[party_id] = store_id - i = i + 1 - - # Add any computation time secrets - # Alice provides her salary at compute time - party_name_alice = CONFIG_PARTY_1["party_name"] - secret_name_alice = CONFIG_PARTY_1["secret_name"] - secret_value_alice = CONFIG_PARTY_1["secret_value"] - compute_time_secrets = nillion.Secrets( - {secret_name_alice: nillion.SecretInteger(secret_value_alice)} - ) - - # Pay for the compute - receipt_compute = await pay( - client_alice, - nillion.Operation.compute(args.program_id, compute_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - print( - f"\n🎉 {party_name_alice} provided {secret_name_alice}: {secret_value_alice} as a compute time secret" - ) - - # Compute on the secret with all store ids. Note that there are no compute time secrets or public variables - compute_id = await client_alice.compute( - cluster_id, - compute_bindings, - list(party_ids_to_store_ids.values()), # Bob and Charlie's stored secrets - compute_time_secrets, # Alice's computation time secret - nillion.PublicVariables({}), - receipt_compute, - ) - - # Print compute result - print(f"The computation was sent to the network. compute_id: {compute_id}") - while True: - compute_event = await client_alice.next_compute_event() - if isinstance(compute_event, nillion.ComputeFinishedEvent): - print(f"✅ Compute complete for compute_id {compute_event.uuid}") - print(f"🖥️ The output result is {compute_event.result.value}") - - # The compute result is an index - # Map it to the corresponding party name who should pay for lunch - # ['Alice', 'Bob', 'Charlie'] - my_parties = CONFIG_N_PARTIES - my_parties.insert(0, CONFIG_PARTY_1) - richest_party = my_parties[compute_event.result.value["largest_position"]][ - "party_name" - ] - print(f"The richest friend is {richest_party}") - return richest_party - - -if __name__ == "__main__": - asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await store_secret_party_1.main() - args = ["--user_id_1", result[0], "--program_id", result[1]] - result = await store_secret_party_n.main(args) - store_ids = result[1].split(" ", 1) - args = [ - "--program_id", - result[0], - "--party_ids_to_store_ids", - store_ids[0], - store_ids[1], - ] - result = await main(args) - assert result == "Charlie" diff --git a/examples_and_tutorials/millionaires_problem_example/README.md b/examples_and_tutorials/millionaires_problem_example/README.md deleted file mode 100644 index 181ec1a1..00000000 --- a/examples_and_tutorials/millionaires_problem_example/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# MPC Millionaires Problem Example - -In our version of the [Millionaires Problem](https://docs.nillion.com/multi-party-computation#classic-scenario-the-millionaires-problem), three friends, Alice, Bob & Charlie finish lunch and decide that the richest friend should pay the bill. - -They want to figure out who has the highest salary without telling each other how much they earn. - -Alice suggests that this is the perfect opportunity for the friends to use Nillion for blind computation on high value data to determine who should pay for lunch. She offers to create a millionaires problem program, compile it, and store it in Nillion so that the friends can run multi party blind computation to figure out who should pay for lunch. - -- Alice's millionaires program in NADA: [../../programs/millionaires.py](https://github.com/NillionNetwork/nillion-python-starter/blob/main/programs/millionaires.py) -- Compiled program: [./millionaires.nada.bin](https://github.com/NillionNetwork/nillion-python-starter/blob/main/millionaires_problem_example/millionaires.nada.bin) - -## Setup - -Before running the example, [follow the repo README](https://github.com/NillionNetwork/nillion-python-starter/blob/main/README.md) to install cli dependencies, complete environment setup, and activate your .venv environment. - -## Run the example - -### Step 1 - -- Alice creates and stores program in the network -- Alice shares her user id and the resulting program id with Bob and Charlie - -Run the command to perform step 1. - -```bash -python3 01_store_secret_party1.py -``` - -### Step 2 - -- Bob and Charlie store their salaries in the network as secrets. Each secret is stored with - - **bindings** to the millionaires program id - - **permissions** so Alice can compute using the secret -- Bob and Charlie each share their party_id and secret store_id with Alice. - -The script will provide the command to perform step 2. - -```bash -python3 02_store_secret_party_n.py --user_id_1 {user_id} --program_id {program_id} -``` - -### Step 3 - -- Alice runs millionaires program computation using Bob and Charlie's secret salaries. She provides her own salary as a secret at computation time. -- Alice receives the output result of the program, and the friends know that Charlie should pay for lunch. - -The script will provide the command to perform step 3. - -```bash -python3 03_multi_party_compute.py --program_id {program_id} --party_ids_to_store_ids {party_ids_to_store_ids} -``` diff --git a/examples_and_tutorials/millionaires_problem_example/config.py b/examples_and_tutorials/millionaires_problem_example/config.py deleted file mode 100644 index c506193d..00000000 --- a/examples_and_tutorials/millionaires_problem_example/config.py +++ /dev/null @@ -1,34 +0,0 @@ -import os -import py_nillion_client as nillion -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv - -load_dotenv("/Users/davidtbutler/Library/Application Support/nillion.nillion/nillion-devnet.env") - -# Alice -CONFIG_PARTY_1 = { - "userkey_file": os.getenv("NILLION_USERKEY_PATH_PARTY_1"), - "nodekey_file": os.getenv("NILLION_NODEKEY_PATH_PARTY_1"), - "nodekey_alternate_file": os.getenv("NILLION_NODEKEY_PATH_PARTY_4"), - "party_name": "Alice", - "secret_name": "alice_salary", - "secret_value": 10000, -} - -# Bob and Charlie -CONFIG_N_PARTIES = [ - { - "userkey_file": os.getenv("NILLION_USERKEY_PATH_PARTY_2"), - "nodekey_file": os.getenv("NILLION_NODEKEY_PATH_PARTY_2"), - "party_name": "Bob", - "secret_name": "bob_salary", - "secret_value": 8000, - }, - { - "userkey_file": os.getenv("NILLION_USERKEY_PATH_PARTY_3"), - "nodekey_file": os.getenv("NILLION_NODEKEY_PATH_PARTY_3"), - "party_name": "Charlie", - "secret_name": "charlie_salary", - "secret_value": 12000, - }, -] diff --git a/examples_and_tutorials/nada_programs/nada-project.toml b/examples_and_tutorials/nada_programs/nada-project.toml deleted file mode 100644 index 44324b20..00000000 --- a/examples_and_tutorials/nada_programs/nada-project.toml +++ /dev/null @@ -1,99 +0,0 @@ -name = "quickstart-example-programs" -version = "0.1.0" -authors = [""] - -[[programs]] -path = "src/addition_simple.py" -prime_size = 128 - -[[programs]] -path = "src/addition_simple_multi_party.py" -prime_size = 128 - -[[programs]] -path = "src/circuit_simple_2.py" -prime_size = 128 - -[[programs]] -path = "src/correlation_coefficient.py" -prime_size = 128 - -[[programs]] -path = "src/division_simple.py" -prime_size = 128 - -[[programs]] -path = "src/greater_or_equal_than.py" -prime_size = 128 - -[[programs]] -path = "src/greater_than.py" -prime_size = 128 - -[[programs]] -path = "src/less_than.py" -prime_size = 128 - -[[programs]] -path = "src/literals.py" -prime_size = 128 - -[[programs]] -path = "src/millionaires.py" -prime_size = 128 - -[[programs]] -path = "src/modulo_simple.py" -prime_size = 128 - -[[programs]] -path = "src/multiplication_simple.py" -prime_size = 128 - -[[programs]] -path = "src/nada_fn_max.py" -prime_size = 128 - -[[programs]] -path = "src/public_variables.py" -prime_size = 128 - -[[programs]] -path = "src/reuse.py" -prime_size = 128 - -[[programs]] -path = "src/reuse_simple_sub_multi_party.py" -prime_size = 128 - -[[programs]] -path = "src/simple_sub.py" -prime_size = 128 - -[[programs]] -path = "src/single_addition_multi_party.py" -prime_size = 128 - -[[programs]] -path = "src/subtraction_simple.py" -prime_size = 128 - -[[programs]] -path = "src/subtraction_simple_neg.py" -prime_size = 128 - -[[programs]] -path = "src/voting_dishonest_abort_5.py" -prime_size = 128 - -[[programs]] -path = "src/voting_dishonest_robust_6.py" -prime_size = 128 - -[[programs]] -path = "src/voting_honest_1.py" -prime_size = 128 - -[[programs]] -path = "src/voting_honest_2.py" -prime_size = 128 diff --git a/examples_and_tutorials/nada_programs/src/addition_simple.py b/examples_and_tutorials/nada_programs/src/addition_simple.py deleted file mode 100644 index 8da7dcc3..00000000 --- a/examples_and_tutorials/nada_programs/src/addition_simple.py +++ /dev/null @@ -1,11 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - my_int1 = SecretInteger(Input(name="my_int1", party=party1)) - my_int2 = SecretInteger(Input(name="my_int2", party=party1)) - - new_int = my_int1 + my_int2 - - return [Output(new_int, "my_output", party1)] diff --git a/examples_and_tutorials/nada_programs/src/addition_simple_multi_party.py b/examples_and_tutorials/nada_programs/src/addition_simple_multi_party.py deleted file mode 100644 index 66cc0738..00000000 --- a/examples_and_tutorials/nada_programs/src/addition_simple_multi_party.py +++ /dev/null @@ -1,12 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - party2 = Party(name="Party2") - my_int1 = SecretInteger(Input(name="my_int1", party=party1)) - my_int2 = SecretInteger(Input(name="my_int2", party=party2)) - - new_int = my_int1 + my_int2 - - return [Output(new_int, "my_output", party1)] diff --git a/examples_and_tutorials/nada_programs/src/circuit_simple_2.py b/examples_and_tutorials/nada_programs/src/circuit_simple_2.py deleted file mode 100644 index fd398127..00000000 --- a/examples_and_tutorials/nada_programs/src/circuit_simple_2.py +++ /dev/null @@ -1,27 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - A = SecretInteger(Input(name="A", party=party1)) - B = SecretInteger(Input(name="B", party=party1)) - C = SecretInteger(Input(name="C", party=party1)) - D = SecretInteger(Input(name="D", party=party1)) - E = SecretInteger(Input(name="E", party=party1)) - F = SecretInteger(Input(name="F", party=party1)) - G = SecretInteger(Input(name="G", party=party1)) - H = SecretInteger(Input(name="H", party=party1)) - I = SecretInteger(Input(name="I", party=party1)) - - # A * B * C + D * E + F * G * H + I - - Mul0 = A * B - Mul1 = Mul0 * C - Mul2 = D * E - Add0 = Mul1 + Mul2 - Mul3 = F * G - Mul4 = Mul3 * H - Add1 = Add0 + Mul4 - Add2 = Add1 + I - - return [Output(Add2, "my_output", party1)] diff --git a/examples_and_tutorials/nada_programs/src/correlation_coefficient.py b/examples_and_tutorials/nada_programs/src/correlation_coefficient.py deleted file mode 100644 index 71c8c013..00000000 --- a/examples_and_tutorials/nada_programs/src/correlation_coefficient.py +++ /dev/null @@ -1,73 +0,0 @@ -from nada_dsl import * - -def nada_main(): - """ - This program computes the correlation coefficient between two vectors. - - In this example we assume there are two parties P0 and P1. - P0 provides 10 points (x_i, y_i) and P1 provides 10 pointss (x_j, y_j). - """ - - nr_parties = 2 - p0_points = 10 - p1_points = 10 - precision = 5 - total_points = p0_points + p1_points - - # Create parties - parties = [] - for i in range(nr_parties): - parties.append(Party(name="Party" + str(i))) - outparty = Party(name="OutParty") - - - # Build x and y vector - xi_vector = [] - yi_vector = [] - # Party 0 input - for i in range(p0_points): - xi_vector.append(SecretInteger(Input(name="x" + str(i), party=parties[0]))) - yi_vector.append(SecretInteger(Input(name="y" + str(i), party=parties[0]))) - # Party 1 input - for i in range(p1_points): - xi_vector.append(SecretInteger(Input(name="x" + str(i + p0_points), party=parties[1]))) - yi_vector.append(SecretInteger(Input(name="y" + str(i + p0_points), party=parties[1]))) - - # Compute the following values: - # sum_x = Σ x_i - # sum_y = Σ y_i - # sum_xy = Σ x_i.y_i - # sum_xx = Σ x_i.x_i - # sum_yy = Σ y_i.y_i - sum_x = xi_vector[0] - sum_y = yi_vector[0] - sum_xy = xi_vector[0] * yi_vector[0] - sum_xx = xi_vector[0] * xi_vector[0] - sum_yy = yi_vector[0] * yi_vector[0] - for i in range(1, total_points): - sum_x += xi_vector[i] - sum_y += yi_vector[i] - sum_xy += xi_vector[i] * yi_vector[i] - sum_xx += xi_vector[i] * xi_vector[i] - sum_yy += yi_vector[i] * yi_vector[i] - - # Build the formula: - # (n * sum_xy - sum_x * sum_y) - # (r_xy)^2 = ------------------------------------------------------------- - # (n * sum_xx - sum_x * sum_x) * (n * sum_yy - sum_y * sum_y) - n = Integer(total_points) - n_times_sum_xy = n * sum_xy - sum_x_times_sum_y = sum_x * sum_y - ld = n * sum_xx - sum_x * sum_x - rd = n * sum_yy - sum_y * sum_y - - numerator = n_times_sum_xy - sum_x_times_sum_y - denominator = ld * rd - sq_numerator = numerator * numerator * Integer(10**precision) - r2 = sq_numerator / denominator - - # If sign == true, sign is positive - # else, sign is negative - sign = n_times_sum_xy > sum_x_times_sum_y - - return [(Output(r2, "correlation_coefficient_squared", outparty)), (Output(sign, "sign", outparty))] \ No newline at end of file diff --git a/examples_and_tutorials/nada_programs/src/division_simple.py b/examples_and_tutorials/nada_programs/src/division_simple.py deleted file mode 100644 index 2ec9da1c..00000000 --- a/examples_and_tutorials/nada_programs/src/division_simple.py +++ /dev/null @@ -1,11 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - my_int1 = SecretInteger(Input(name="my_int1", party=party1)) - my_int3 = PublicInteger(Input(name="my_int3", party=party1)) - - new_int = my_int1 / my_int3 - - return [Output(new_int, "my_output", party1)] diff --git a/examples_and_tutorials/nada_programs/src/greater_or_equal_than.py b/examples_and_tutorials/nada_programs/src/greater_or_equal_than.py deleted file mode 100644 index a06ebf21..00000000 --- a/examples_and_tutorials/nada_programs/src/greater_or_equal_than.py +++ /dev/null @@ -1,14 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - party2 = Party(name="Party2") - A = SecretInteger(Input(name="A", party=party1)) - B = SecretInteger(Input(name="B", party=party2)) - C = SecretInteger(Input(name="C", party=party1)) - D = SecretInteger(Input(name="D", party=party2)) - - result = A * B + C >= B * D - - return [Output(result, "my_output", party1)] diff --git a/examples_and_tutorials/nada_programs/src/greater_than.py b/examples_and_tutorials/nada_programs/src/greater_than.py deleted file mode 100644 index d82e13d1..00000000 --- a/examples_and_tutorials/nada_programs/src/greater_than.py +++ /dev/null @@ -1,14 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - party2 = Party(name="Party2") - A = SecretInteger(Input(name="A", party=party1)) - B = SecretInteger(Input(name="B", party=party2)) - C = SecretInteger(Input(name="C", party=party1)) - D = SecretInteger(Input(name="D", party=party2)) - - result = A * B + C > B * D - - return [Output(result, "my_output", party1)] diff --git a/examples_and_tutorials/nada_programs/src/less_than.py b/examples_and_tutorials/nada_programs/src/less_than.py deleted file mode 100644 index 1ea64bfd..00000000 --- a/examples_and_tutorials/nada_programs/src/less_than.py +++ /dev/null @@ -1,14 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - party2 = Party(name="Party2") - A = SecretInteger(Input(name="A", party=party1)) - B = SecretInteger(Input(name="B", party=party2)) - C = SecretInteger(Input(name="C", party=party1)) - D = SecretInteger(Input(name="D", party=party2)) - - result = A * B + C < B * D - - return [Output(result, "my_output", party1)] diff --git a/examples_and_tutorials/nada_programs/src/literals.py b/examples_and_tutorials/nada_programs/src/literals.py deleted file mode 100644 index 6b476d3f..00000000 --- a/examples_and_tutorials/nada_programs/src/literals.py +++ /dev/null @@ -1,16 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - A = SecretInteger(Input(name="A", party=party1)) - C = SecretInteger(Input(name="C", party=party1)) - - TMP0 = A * Integer(13) + Integer(13) # secret * literal + literal (checks literal re-use) - TMP1 = C * Integer(50) # secret * literal - TMP2 = Integer(13) + Integer(50) # literal + literal - TMP3 = Integer(13) * Integer(50) # literal * literal - - O = TMP0 + TMP1 + TMP2 + TMP3 - - return [Output(O, "O", party1)] diff --git a/examples_and_tutorials/nada_programs/src/millionaires.py b/examples_and_tutorials/nada_programs/src/millionaires.py deleted file mode 100644 index 5db81266..00000000 --- a/examples_and_tutorials/nada_programs/src/millionaires.py +++ /dev/null @@ -1,21 +0,0 @@ -from nada_dsl import * - -def nada_main(): - alice = Party(name="Alice") # party 0 - bob = Party(name="Bob") # party 1 - charlie = Party(name="Charlie") # party 2 - - alice_salary = SecretInteger(Input(name="alice_salary", party=alice)) - bob_salary = SecretInteger(Input(name="bob_salary", party=bob)) - charlie_salary = SecretInteger(Input(name="charlie_salary", party=charlie)) - - largest_position = ( - (alice_salary > bob_salary).if_else( - (alice_salary > charlie_salary).if_else(Integer(0), Integer(2)), - (bob_salary > charlie_salary).if_else(Integer(1), Integer(2)) - ) - ) - - out = Output(largest_position, "largest_position", alice) - - return [out] \ No newline at end of file diff --git a/examples_and_tutorials/nada_programs/src/modulo_simple.py b/examples_and_tutorials/nada_programs/src/modulo_simple.py deleted file mode 100644 index 33ff5160..00000000 --- a/examples_and_tutorials/nada_programs/src/modulo_simple.py +++ /dev/null @@ -1,11 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - my_int1 = SecretInteger(Input(name="my_int1", party=party1)) - public_my_int2 = PublicInteger(Input(name="public_my_int2", party=party1)) - - new_int = my_int1 % public_my_int2 - - return [Output(new_int, "my_output", party1)] diff --git a/examples_and_tutorials/nada_programs/src/multiplication_simple.py b/examples_and_tutorials/nada_programs/src/multiplication_simple.py deleted file mode 100644 index 3694ab00..00000000 --- a/examples_and_tutorials/nada_programs/src/multiplication_simple.py +++ /dev/null @@ -1,11 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - my_int1 = SecretInteger(Input(name="my_int1", party=party1)) - my_int2 = SecretInteger(Input(name="my_int2", party=party1)) - - new_int = my_int1 * my_int2 - - return [Output(new_int, "my_output", party1)] diff --git a/examples_and_tutorials/nada_programs/src/nada_fn_max.py b/examples_and_tutorials/nada_programs/src/nada_fn_max.py deleted file mode 100644 index f312cb16..00000000 --- a/examples_and_tutorials/nada_programs/src/nada_fn_max.py +++ /dev/null @@ -1,13 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - my_int1 = SecretInteger(Input(name="my_int1", party=party1)) - my_int2 = SecretInteger(Input(name="my_int2", party=party1)) - - def max(a: SecretInteger, b: SecretInteger) -> SecretInteger: - return (a < b).if_else(b, a) - - new_int = max(my_int1, my_int2) - return [Output(new_int, "my_output", party1)] diff --git a/examples_and_tutorials/nada_programs/src/public_variables.py b/examples_and_tutorials/nada_programs/src/public_variables.py deleted file mode 100644 index c499b839..00000000 --- a/examples_and_tutorials/nada_programs/src/public_variables.py +++ /dev/null @@ -1,18 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - A = SecretInteger(Input(name="A", party=party1)) - B = PublicInteger(Input(name="B", party=party1)) - C = SecretInteger(Input(name="C", party=party1)) - D = PublicInteger(Input(name="D", party=party1)) - - TMP0 = A + B # secret + public - TMP1 = C * D # secret * public - TMP2 = B + D # public + public - TMP3 = B * D # public * public - - O = TMP0 + TMP1 + TMP2 + TMP3 - - return [Output(O, "O", party1)] diff --git a/examples_and_tutorials/nada_programs/src/reuse.py b/examples_and_tutorials/nada_programs/src/reuse.py deleted file mode 100644 index 21e1bafc..00000000 --- a/examples_and_tutorials/nada_programs/src/reuse.py +++ /dev/null @@ -1,14 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - A = SecretInteger(Input(name="A", party=party1)) - B = SecretInteger(Input(name="B", party=party1)) - C = SecretInteger(Input(name="C", party=party1)) - - TMP1 = A * B - TMP2 = A * C - R = TMP1 + TMP2 - - return [Output(R, "R", party1)] diff --git a/examples_and_tutorials/nada_programs/src/reuse_simple_sub_multi_party.py b/examples_and_tutorials/nada_programs/src/reuse_simple_sub_multi_party.py deleted file mode 100644 index 03033436..00000000 --- a/examples_and_tutorials/nada_programs/src/reuse_simple_sub_multi_party.py +++ /dev/null @@ -1,12 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - party2 = Party(name="Party2") - a = SecretInteger(Input(name="my_int1", party=party1)) - b = SecretInteger(Input(name="my_int2", party=party2)) - - result = (a + b) * (a - b) - - return [Output(result, "my_output", party1)] diff --git a/examples_and_tutorials/nada_programs/src/simple_sub.py b/examples_and_tutorials/nada_programs/src/simple_sub.py deleted file mode 100644 index d15b4091..00000000 --- a/examples_and_tutorials/nada_programs/src/simple_sub.py +++ /dev/null @@ -1,15 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - A = SecretInteger(Input(name="A", party=party1)) - B = SecretInteger(Input(name="B", party=party1)) - C = SecretInteger(Input(name="C", party=party1)) - D = SecretInteger(Input(name="D", party=party1)) - - TMP1 = A * B - TMP2 = C * D - O = TMP1 - TMP2 - - return [Output(O, "O", party1)] diff --git a/examples_and_tutorials/nada_programs/src/single_addition_multi_party.py b/examples_and_tutorials/nada_programs/src/single_addition_multi_party.py deleted file mode 100644 index 393e469d..00000000 --- a/examples_and_tutorials/nada_programs/src/single_addition_multi_party.py +++ /dev/null @@ -1,12 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - party2 = Party(name="Party2") - A = SecretInteger(Input(name="A", party=party2)) - B = SecretInteger(Input(name="B", party=party2)) - - R = A + B - - return [Output(R, "R", party1)] diff --git a/examples_and_tutorials/nada_programs/src/subtraction_simple.py b/examples_and_tutorials/nada_programs/src/subtraction_simple.py deleted file mode 100644 index 75fc21d3..00000000 --- a/examples_and_tutorials/nada_programs/src/subtraction_simple.py +++ /dev/null @@ -1,11 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - my_int1 = SecretInteger(Input(name="my_int1", party=party1)) # 32 - my_int2 = SecretInteger(Input(name="my_int2", party=party1)) # 81 - - new_int = my_int2 - my_int1 - - return [Output(new_int, "my_output", party1)] diff --git a/examples_and_tutorials/nada_programs/src/subtraction_simple_neg.py b/examples_and_tutorials/nada_programs/src/subtraction_simple_neg.py deleted file mode 100644 index 59f79b51..00000000 --- a/examples_and_tutorials/nada_programs/src/subtraction_simple_neg.py +++ /dev/null @@ -1,11 +0,0 @@ -from nada_dsl import * - - -def nada_main(): - party1 = Party(name="Party1") - my_int4 = SecretInteger(Input(name="my_int4", party=party1)) # 102 - my_int2 = SecretInteger(Input(name="my_int2", party=party1)) # 81 - - new_int = my_int2 - my_int4 # 81 - 102 = -21 - - return [Output(new_int, "my_output", party1)] diff --git a/examples_and_tutorials/nada_programs/src/voting_dishonest_abort_5.py b/examples_and_tutorials/nada_programs/src/voting_dishonest_abort_5.py deleted file mode 100644 index 93c05da3..00000000 --- a/examples_and_tutorials/nada_programs/src/voting_dishonest_abort_5.py +++ /dev/null @@ -1,131 +0,0 @@ -""" -PROGRAM 5 - -nr of voters: m = 3 -nr of candidates: n = 2 -""" -from nada_dsl import * - -def initialize_voters(nr_voters): - """ - Initializes the list of voters with unique identifiers. - - Args: - nr_voters (int): Number of voters. - - Returns: - list: List of Party objects representing each voter. - """ - voters = [] - for i in range(nr_voters): - voters.append(Party(name="Voter" + str(i))) - - return voters - -def inputs_initialization(nr_voters, nr_candidates, voters): - """ - Initializes the input for each candidate, collecting votes from each voter securely. - - Args: - nr_voters (int): Number of voters. - nr_candidates (int): Number of candidates. - - Returns: - list: List of lists containing SecretUnsignedInteger objects representing votes per candidate. - """ - votes_per_candidate = [] - for c in range(nr_candidates): - votes_per_candidate.append([]) - for v in range(nr_voters): - votes_per_candidate[c].append(SecretUnsignedInteger(Input(name="v" + str(v) + "_c" + str(c), party=voters[v]))) - - return votes_per_candidate - -def count_votes(nr_voters, nr_candidates, votes_per_candidate, outparty): - """ - Counts the votes for each candidate. - - Args: - nr_voters (int): Number of voters. - nr_candidates (int): Number of candidates. - votes_per_candidate (list): List of lists containing SecretUnsignedInteger objects representing votes per candidate. - - Returns: - list: List of Output objects representing the final vote count for each candidate. - """ - votes = [] - for c in range(nr_candidates): - result = votes_per_candidate[c][0] - for v in range(1, nr_voters): - result += votes_per_candidate[c][v] - votes.append(Output(result, "final_vote_count_c" + str(c), outparty)) - - return votes - -def fn_check_sum(nr_voters, nr_candidates, votes_per_candidate, outparty): - """ - Verifies the sum of votes for each voter to ensure correctness. - - Args: - nr_voters (int): Number of voters. - nr_candidates (int): Number of candidates. - votes_per_candidate (list): List of lists containing SecretUnsignedInteger objects representing votes per candidate. - - Returns: - list: List of Output objects representing the sum verification for each voter. - """ - check_sum = [] - for v in range(nr_voters): - check = votes_per_candidate[0][v] - for c in range(1, nr_candidates): - vote_v_c = votes_per_candidate[c][v] - check += vote_v_c - check_sum.append(Output(check, "check_sum_v" + str(v), outparty)) - - return check_sum - -def fn_check_prod(nr_voters, nr_candidates, votes_per_candidate, outparty): - """ - Verifies the product of vote values for each voter and candidate. - - Args: - nr_voters (int): Number of voters. - nr_candidates (int): Number of candidates. - votes_per_candidate (list): List of lists containing SecretUnsignedInteger objects representing votes per candidate. - - Returns: - list: List of Output objects representing the product verification for each voter and candidate. - """ - check_prod = [] - for v in range(nr_voters): - for c in range(nr_candidates): - vote_v_c = votes_per_candidate[c][v] - check_v_c_product = (UnsignedInteger(1) - vote_v_c)*(UnsignedInteger(2) - vote_v_c) - check_prod.append(Output(check_v_c_product, "check_prod_v" + str(v) + "_c" + str(c), outparty)) - - return check_prod - -def nada_main(): - - # 0. Compiled-time constants - nr_voters = 3 - nr_candidates = 2 - - # 1. Parties initialization - voters = initialize_voters(nr_voters) - outparty = Party(name="OutParty") - - # 2. Inputs initialization - votes_per_candidate = inputs_initialization(nr_voters, nr_candidates, voters) - - # 3. Computation - # Count the votes - votes = count_votes(nr_voters, nr_candidates, votes_per_candidate, outparty) - # Check input soundness - check_sum = fn_check_sum(nr_voters, nr_candidates, votes_per_candidate, outparty) - check_prod = fn_check_prod(nr_voters, nr_candidates, votes_per_candidate, outparty) - - # 4. Output - # Concatenate lists - results = votes + check_sum + check_prod - return results \ No newline at end of file diff --git a/examples_and_tutorials/nada_programs/src/voting_dishonest_robust_6.py b/examples_and_tutorials/nada_programs/src/voting_dishonest_robust_6.py deleted file mode 100644 index 90fc7784..00000000 --- a/examples_and_tutorials/nada_programs/src/voting_dishonest_robust_6.py +++ /dev/null @@ -1,176 +0,0 @@ -""" -PROGRAM 6 - -nr of voters: m = 3 -nr of candidates: n = 2 -""" -from nada_dsl import * - -def return_val_if_any_false(list_of_bool, val): - """ - Returns val if any boolean inside list_of_bool is false. - - Parameters: - - list_of_bool (list of bool): List of boolean values to be checked. - - val: Value to be returned if any boolean in the list is false. - - Returns: - - val: If any boolean in the list is false. - - 0: If none of the booleans in the list are false. - """ - - final_value = UnsignedInteger(0) - for bool in list_of_bool: - # Use if_else method to check if the current boolean is true, - # if true, return vfinal_valueal, otherwise return the current val - final_value = bool.if_else(final_value, val) - - return final_value - -def initialize_voters(nr_voters): - """ - Initialize voters with unique identifiers. - - Parameters: - - nr_voters (int): Number of voters. - - Returns: - - voters (list): List of Party objects representing voters. - """ - voters = [] - for i in range(nr_voters): - voters.append(Party(name="Voter" + str(i))) - - return voters - -def inputs_initialization(nr_voters, nr_candidates, voters): - """ - Initialize inputs for votes per candidate. - - Parameters: - - nr_voters (int): Number of voters. - - nr_candidates (int): Number of candidates. - - Returns: - - votes_per_candidate (list): List of lists representing votes per candidate. - """ - votes_per_candidate = [] - for c in range(nr_candidates): - votes_per_candidate.append([]) - for v in range(nr_voters): - votes_per_candidate[c].append(SecretUnsignedInteger(Input(name="v" + str(v) + "_c" + str(c), party=voters[v]))) - - return votes_per_candidate - -def count_votes(nr_voters, nr_candidates, votes_per_candidate, outparty): - """ - Count votes for each candidate. - - Parameters: - - nr_voters (int): Number of voters. - - nr_candidates (int): Number of candidates. - - votes_per_candidate (list): List of lists representing votes per candidate. - - Returns: - - votes (list): List of Output objects representing vote counts for each candidate. - """ - votes = [] - for c in range(nr_candidates): - result = votes_per_candidate[c][0] - for v in range(1, nr_voters): - result += votes_per_candidate[c][v] - votes.append(Output(result, "final_vote_count_c" + str(c), outparty)) - - return votes - -def fn_check_sum(nr_voters, nr_candidates, votes_per_candidate, outparty): - """ - Check the sum of votes for each voter. - - Parameters: - - nr_voters (int): Number of voters. - - nr_candidates (int): Number of candidates. - - votes_per_candidate (list): List of lists representing votes per candidate. - - Returns: - - check_sum (list): List of Output objects representing the sum checks for each voter. - - if_sum_cheat_open (list): List of Output objects representing revealed votes of cheating voters. - """ - check_sum = [] - if_sum_cheat_open = [] - for v in range(nr_voters): - check = votes_per_candidate[0][v] - for c in range(1, nr_candidates): - vote_v_c = votes_per_candidate[c][v] - check += vote_v_c - check_sum.append(Output(check, "check_sum_v" + str(v), outparty)) - # Reveal if cheat - comp_v_sum = check <= UnsignedInteger(nr_candidates + 1) - for c in range(nr_candidates): - vote_v_c = votes_per_candidate[c][v] - if_sum_cheat_open_v_c = comp_v_sum.if_else(UnsignedInteger(0), vote_v_c) - if_sum_cheat_open.append(Output(if_sum_cheat_open_v_c, "if_sum_cheat_open_v" + str(v) + "_c" + str(c), outparty)) - - return check_sum, if_sum_cheat_open - -def fn_check_prod(nr_voters, nr_candidates, votes_per_candidate, outparty): - """ - Check the product of votes for each voter. - - Parameters: - - nr_voters (int): Number of voters. - - nr_candidates (int): Number of candidates. - - votes_per_candidate (list): List of lists representing votes per candidate. - - Returns: - - check_prod (list): List of Output objects representing the product checks for each voter. - - if_prod_cheat_open (list): List of Output objects representing revealed votes of cheating voters. - """ - check_prod = [] - if_prod_cheat_open = [] - all_comp_prod = [] - for v in range(nr_voters): - all_comp_v_prod = [] - for c in range(nr_candidates): - vote_v_c = votes_per_candidate[c][v] - check_v_c_product = (UnsignedInteger(1) - vote_v_c)*(UnsignedInteger(2) - vote_v_c) - check_prod.append(Output(check_v_c_product, "check_prod_v" + str(v) + "_c" + str(c), outparty)) - # collect all reveal conditions - comp_v_c_prod = check_v_c_product < UnsignedInteger(1) - all_comp_v_prod.append(comp_v_c_prod) - all_comp_prod.append(all_comp_v_prod) - # reveal all votes from voter v if - for v in range(nr_voters): - all_comp_v_prod = all_comp_prod[v] - for c in range(nr_candidates): - vote_v_c = votes_per_candidate[c][v] - if_prod_cheat_open_v_c = return_val_if_any_false(all_comp_v_prod, vote_v_c) - if_prod_cheat_open.append(Output(if_prod_cheat_open_v_c, "if_prod_cheat_open_v" + str(v) + "_c" + str(c), outparty)) - - return check_prod, if_prod_cheat_open - - -def nada_main(): - - # 0. Compiled-time constants - nr_voters = 3 - nr_candidates = 2 - - # 1. Parties initialization - voters = initialize_voters(nr_voters) - outparty = Party(name="OutParty") - - # 2. Inputs initialization - votes_per_candidate = inputs_initialization(nr_voters, nr_candidates, voters) - - # 3. Computation - # Count the votes - votes = count_votes(nr_voters, nr_candidates, votes_per_candidate, outparty) - # Check input soundness - check_sum, if_sum_cheat_open = fn_check_sum(nr_voters, nr_candidates, votes_per_candidate, outparty) - check_prod, if_prod_cheat_open = fn_check_prod(nr_voters, nr_candidates, votes_per_candidate, outparty) - - # 4. Output - # Concatenate lists - results = votes + check_sum + if_sum_cheat_open + check_prod + if_prod_cheat_open - return results \ No newline at end of file diff --git a/examples_and_tutorials/nada_programs/src/voting_honest_1.py b/examples_and_tutorials/nada_programs/src/voting_honest_1.py deleted file mode 100644 index fe3a9df2..00000000 --- a/examples_and_tutorials/nada_programs/src/voting_honest_1.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -PROGRAM 1 - -nr of voters: m = 3 -nr of candidates: n = 2 -""" -from nada_dsl import * - -def nada_main(): - - # 1. Parties initialization - voter0 = Party(name="Voter0") - voter1 = Party(name="Voter1") - voter2 = Party(name="Voter2") - outparty = Party(name="OutParty") - - # 2. Inputs initialization - ## Votes from voter 0 - v0_c0 = SecretUnsignedInteger(Input(name="v0_c0", party=voter0)) - v0_c1 = SecretUnsignedInteger(Input(name="v0_c1", party=voter0)) - ## Votes from voter 1 - v1_c0 = SecretUnsignedInteger(Input(name="v1_c0", party=voter1)) - v1_c1 = SecretUnsignedInteger(Input(name="v1_c1", party=voter1)) - ## Votes from voter 2 - v2_c0 = SecretUnsignedInteger(Input(name="v2_c0", party=voter2)) - v2_c1 = SecretUnsignedInteger(Input(name="v2_c1", party=voter2)) - - # 3. Computation - ## Add votes for candidate 0 - result_c0 = v0_c0 + v1_c0 + v2_c0 - ## Add votes for candidate 1 - result_c1 = v0_c1 + v1_c1 + v2_c1 - - # 4. Output - result_c0 = Output(result_c0, "final_vote_count_c0", outparty) - result_c1 = Output(result_c1, "final_vote_count_c1", outparty) - - return [result_c0, result_c1] \ No newline at end of file diff --git a/examples_and_tutorials/nada_programs/src/voting_honest_2.py b/examples_and_tutorials/nada_programs/src/voting_honest_2.py deleted file mode 100644 index 617007be..00000000 --- a/examples_and_tutorials/nada_programs/src/voting_honest_2.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -PROGRAM 2 - -nr of voters: m = 5 -nr of candidates: n = 3 -""" -from nada_dsl import * - -def nada_main(): - - # 0. Compiled-time constants - nr_voters = 3 - nr_candidates = 2 - - # 1. Parties initialization - voters = [] - for i in range(nr_voters): - voters.append(Party(name="Voter" + str(i))) - outparty = Party(name="OutParty") - - # 2. Inputs initialization - votes_per_candidate = [] - for c in range(nr_candidates): - votes_per_candidate.append([]) - for v in range(nr_voters): - votes_per_candidate[c].append(SecretUnsignedInteger(Input(name="v" + str(v) + "_c" + str(c), party=voters[v]))) - - # 3. Computation - results = [] - for c in range(nr_candidates): - result = votes_per_candidate[c][0] - for v in range(1, nr_voters): - ## Add votes for candidate c - result += votes_per_candidate[c][v] - # 4. Output - results.append(Output(result, "final_vote_count_c" + str(c), outparty)) - - return results \ No newline at end of file diff --git a/examples_and_tutorials/nada_programs/tests/addition_simple.yaml b/examples_and_tutorials/nada_programs/tests/addition_simple.yaml deleted file mode 100644 index a90ecb49..00000000 --- a/examples_and_tutorials/nada_programs/tests/addition_simple.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -program: addition_simple -inputs: - secrets: - my_int2: - SecretInteger: "3" - my_int1: - SecretInteger: "3" - public_variables: {} -expected_outputs: - my_output: - SecretInteger: "6" diff --git a/examples_and_tutorials/nada_programs/tests/addition_simple_multi_party.yaml b/examples_and_tutorials/nada_programs/tests/addition_simple_multi_party.yaml deleted file mode 100644 index f344822a..00000000 --- a/examples_and_tutorials/nada_programs/tests/addition_simple_multi_party.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -program: addition_simple_multi_party -inputs: - secrets: - my_int1: - SecretInteger: "3" - my_int2: - SecretInteger: "3" - public_variables: {} -expected_outputs: - my_output: - SecretInteger: "6" diff --git a/examples_and_tutorials/nada_programs/tests/circuit_simple_2.yaml b/examples_and_tutorials/nada_programs/tests/circuit_simple_2.yaml deleted file mode 100644 index 286d8e1e..00000000 --- a/examples_and_tutorials/nada_programs/tests/circuit_simple_2.yaml +++ /dev/null @@ -1,26 +0,0 @@ ---- -program: circuit_simple_2 -inputs: - secrets: - D: - SecretInteger: "3" - A: - SecretInteger: "3" - F: - SecretInteger: "3" - G: - SecretInteger: "3" - E: - SecretInteger: "3" - B: - SecretInteger: "3" - H: - SecretInteger: "3" - C: - SecretInteger: "3" - I: - SecretInteger: "3" - public_variables: {} -expected_outputs: - my_output: - SecretInteger: "66" diff --git a/examples_and_tutorials/nada_programs/tests/correlation_coefficient.yaml b/examples_and_tutorials/nada_programs/tests/correlation_coefficient.yaml deleted file mode 100644 index 6ac18a15..00000000 --- a/examples_and_tutorials/nada_programs/tests/correlation_coefficient.yaml +++ /dev/null @@ -1,90 +0,0 @@ ---- -program: correlation_coefficient -inputs: - secrets: - x7: - SecretInteger: "3" - y5: - SecretInteger: "3" - y2: - SecretInteger: "1" - y6: - SecretInteger: "3" - x14: - SecretInteger: "4" - y14: - SecretInteger: "3" - x16: - SecretInteger: "6" - x6: - SecretInteger: "3" - x13: - SecretInteger: "2" - y3: - SecretInteger: "3" - y8: - SecretInteger: "3" - x9: - SecretInteger: "45" - x15: - SecretInteger: "3" - y16: - SecretInteger: "8" - y1: - SecretInteger: "42" - y7: - SecretInteger: "3" - x12: - SecretInteger: "3" - x4: - SecretInteger: "3" - x5: - SecretInteger: "3" - x19: - SecretInteger: "3" - y13: - SecretInteger: "3" - y17: - SecretInteger: "3" - x2: - SecretInteger: "3" - x8: - SecretInteger: "3" - y9: - SecretInteger: "3" - x10: - SecretInteger: "3" - y12: - SecretInteger: "3" - x1: - SecretInteger: "3" - y4: - SecretInteger: "3" - x18: - SecretInteger: "3" - y15: - SecretInteger: "67" - y19: - SecretInteger: "3" - x3: - SecretInteger: "3" - x0: - SecretInteger: "3" - y10: - SecretInteger: "3" - x11: - SecretInteger: "3" - y0: - SecretInteger: "4" - y11: - SecretInteger: "3" - x17: - SecretInteger: "3" - y18: - SecretInteger: "3" - public_variables: {} -expected_outputs: - correlation_coefficient_squared: - SecretInteger: "52437031009678720685790412" - sign: - SecretBoolean: false diff --git a/examples_and_tutorials/nada_programs/tests/division_simple.yaml b/examples_and_tutorials/nada_programs/tests/division_simple.yaml deleted file mode 100644 index cc2591f1..00000000 --- a/examples_and_tutorials/nada_programs/tests/division_simple.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -program: division_simple -inputs: - secrets: - my_int1: - SecretInteger: "18" - public_variables: - my_int3: - Integer: "6" -expected_outputs: - my_output: - SecretInteger: "3" diff --git a/examples_and_tutorials/nada_programs/tests/greater_or_equal_than.yaml b/examples_and_tutorials/nada_programs/tests/greater_or_equal_than.yaml deleted file mode 100644 index 394dd720..00000000 --- a/examples_and_tutorials/nada_programs/tests/greater_or_equal_than.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -program: greater_or_equal_than -inputs: - secrets: - C: - SecretInteger: "3" - D: - SecretInteger: "3" - B: - SecretInteger: "3" - A: - SecretInteger: "3" - public_variables: {} -expected_outputs: - my_output: - SecretBoolean: true diff --git a/examples_and_tutorials/nada_programs/tests/greater_than.yaml b/examples_and_tutorials/nada_programs/tests/greater_than.yaml deleted file mode 100644 index ec8c0e9d..00000000 --- a/examples_and_tutorials/nada_programs/tests/greater_than.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -program: greater_than -inputs: - secrets: - A: - SecretInteger: "3" - B: - SecretInteger: "3" - D: - SecretInteger: "3" - C: - SecretInteger: "3" - public_variables: {} -expected_outputs: - my_output: - SecretBoolean: true diff --git a/examples_and_tutorials/nada_programs/tests/less_than.yaml b/examples_and_tutorials/nada_programs/tests/less_than.yaml deleted file mode 100644 index 08533fa2..00000000 --- a/examples_and_tutorials/nada_programs/tests/less_than.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -program: less_than -inputs: - secrets: - D: - SecretInteger: "3" - B: - SecretInteger: "3" - C: - SecretInteger: "3" - A: - SecretInteger: "3" - public_variables: {} -expected_outputs: - my_output: - SecretBoolean: false diff --git a/examples_and_tutorials/nada_programs/tests/literals.yaml b/examples_and_tutorials/nada_programs/tests/literals.yaml deleted file mode 100644 index a443f7ab..00000000 --- a/examples_and_tutorials/nada_programs/tests/literals.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -program: literals -inputs: - secrets: - A: - SecretInteger: "3" - C: - SecretInteger: "3" - public_variables: {} -expected_outputs: - O: - SecretInteger: "915" diff --git a/examples_and_tutorials/nada_programs/tests/millionaires.yaml b/examples_and_tutorials/nada_programs/tests/millionaires.yaml deleted file mode 100644 index 5d83527f..00000000 --- a/examples_and_tutorials/nada_programs/tests/millionaires.yaml +++ /dev/null @@ -1,14 +0,0 @@ ---- -program: millionaires -inputs: - secrets: - alice_salary: - SecretInteger: "3" - bob_salary: - SecretInteger: "5" - charlie_salary: - SecretInteger: "10" - public_variables: {} -expected_outputs: - largest_position: - SecretInteger: "2" diff --git a/examples_and_tutorials/nada_programs/tests/modulo_simple.yaml b/examples_and_tutorials/nada_programs/tests/modulo_simple.yaml deleted file mode 100644 index c538aab3..00000000 --- a/examples_and_tutorials/nada_programs/tests/modulo_simple.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -program: modulo_simple -inputs: - secrets: - my_int1: - SecretInteger: "3" - public_variables: - public_my_int2: - Integer: "6" -expected_outputs: - my_output: - SecretInteger: "3" diff --git a/examples_and_tutorials/nada_programs/tests/multiplication_simple.yaml b/examples_and_tutorials/nada_programs/tests/multiplication_simple.yaml deleted file mode 100644 index 2b233982..00000000 --- a/examples_and_tutorials/nada_programs/tests/multiplication_simple.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -program: multiplication_simple -inputs: - secrets: - my_int1: - SecretInteger: "3" - my_int2: - SecretInteger: "3" - public_variables: {} -expected_outputs: - my_output: - SecretInteger: "9" diff --git a/examples_and_tutorials/nada_programs/tests/nada_fn_max.yaml b/examples_and_tutorials/nada_programs/tests/nada_fn_max.yaml deleted file mode 100644 index 4f74ff89..00000000 --- a/examples_and_tutorials/nada_programs/tests/nada_fn_max.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -program: nada_fn_max -inputs: - secrets: - my_int1: - SecretInteger: "10" - my_int2: - SecretInteger: "3" - public_variables: {} -expected_outputs: - my_output: - SecretInteger: "10" diff --git a/examples_and_tutorials/nada_programs/tests/public_variables.yaml b/examples_and_tutorials/nada_programs/tests/public_variables.yaml deleted file mode 100644 index f764700c..00000000 --- a/examples_and_tutorials/nada_programs/tests/public_variables.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -program: public_variables -inputs: - secrets: - A: - SecretInteger: "3" - C: - SecretInteger: "3" - public_variables: - B: - Integer: "3" - D: - Integer: "3" -expected_outputs: - O: - SecretInteger: "30" diff --git a/examples_and_tutorials/nada_programs/tests/reuse.yaml b/examples_and_tutorials/nada_programs/tests/reuse.yaml deleted file mode 100644 index ae2dc486..00000000 --- a/examples_and_tutorials/nada_programs/tests/reuse.yaml +++ /dev/null @@ -1,14 +0,0 @@ ---- -program: reuse -inputs: - secrets: - B: - SecretInteger: "3" - A: - SecretInteger: "3" - C: - SecretInteger: "3" - public_variables: {} -expected_outputs: - R: - SecretInteger: "18" diff --git a/examples_and_tutorials/nada_programs/tests/reuse_simple_sub_multi_party.yaml b/examples_and_tutorials/nada_programs/tests/reuse_simple_sub_multi_party.yaml deleted file mode 100644 index 8039b752..00000000 --- a/examples_and_tutorials/nada_programs/tests/reuse_simple_sub_multi_party.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -program: reuse_simple_sub_multi_party -inputs: - secrets: - my_int1: - SecretInteger: "3" - my_int2: - SecretInteger: "10" - public_variables: {} -expected_outputs: - my_output: - SecretInteger: "-91" diff --git a/examples_and_tutorials/nada_programs/tests/simple_sub.yaml b/examples_and_tutorials/nada_programs/tests/simple_sub.yaml deleted file mode 100644 index 2fedba5c..00000000 --- a/examples_and_tutorials/nada_programs/tests/simple_sub.yaml +++ /dev/null @@ -1,16 +0,0 @@ ---- -program: simple_sub -inputs: - secrets: - D: - SecretInteger: "3" - A: - SecretInteger: "3" - B: - SecretInteger: "3" - C: - SecretInteger: "10" - public_variables: {} -expected_outputs: - O: - SecretInteger: "-21" diff --git a/examples_and_tutorials/nada_programs/tests/subtraction_simple.yaml b/examples_and_tutorials/nada_programs/tests/subtraction_simple.yaml deleted file mode 100644 index 4dd1825e..00000000 --- a/examples_and_tutorials/nada_programs/tests/subtraction_simple.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -program: subtraction_simple -inputs: - secrets: - my_int1: - SecretInteger: "3" - my_int2: - SecretInteger: "3" - public_variables: {} -expected_outputs: - my_output: - SecretInteger: "0" diff --git a/examples_and_tutorials/nada_programs/tests/subtraction_simple_neg.yaml b/examples_and_tutorials/nada_programs/tests/subtraction_simple_neg.yaml deleted file mode 100644 index 6ba9e630..00000000 --- a/examples_and_tutorials/nada_programs/tests/subtraction_simple_neg.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -program: subtraction_simple_neg -inputs: - secrets: - my_int2: - SecretInteger: "3" - my_int4: - SecretInteger: "10" - public_variables: {} -expected_outputs: - my_output: - SecretInteger: "-7" diff --git a/examples_and_tutorials/nada_programs/tests/voting_dishonest_abort_5.yaml b/examples_and_tutorials/nada_programs/tests/voting_dishonest_abort_5.yaml deleted file mode 100644 index 7e3fee18..00000000 --- a/examples_and_tutorials/nada_programs/tests/voting_dishonest_abort_5.yaml +++ /dev/null @@ -1,40 +0,0 @@ ---- -program: voting_dishonest_abort_5 -inputs: - secrets: - v0_c0: - SecretUnsignedInteger: "3" - v2_c1: - SecretUnsignedInteger: "3" - v2_c0: - SecretUnsignedInteger: "3" - v0_c1: - SecretUnsignedInteger: "3" - v1_c0: - SecretUnsignedInteger: "10" - v1_c1: - SecretUnsignedInteger: "3" - public_variables: {} -expected_outputs: - final_vote_count_c1: - SecretUnsignedInteger: "9" - check_sum_v0: - SecretUnsignedInteger: "6" - check_prod_v1_c0: - SecretUnsignedInteger: "72" - check_sum_v1: - SecretUnsignedInteger: "13" - check_prod_v0_c1: - SecretUnsignedInteger: "2" - final_vote_count_c0: - SecretUnsignedInteger: "16" - check_prod_v2_c1: - SecretUnsignedInteger: "2" - check_prod_v2_c0: - SecretUnsignedInteger: "2" - check_prod_v0_c0: - SecretUnsignedInteger: "2" - check_sum_v2: - SecretUnsignedInteger: "6" - check_prod_v1_c1: - SecretUnsignedInteger: "2" diff --git a/examples_and_tutorials/nada_programs/tests/voting_dishonest_robust_6.yaml b/examples_and_tutorials/nada_programs/tests/voting_dishonest_robust_6.yaml deleted file mode 100644 index 2f05691f..00000000 --- a/examples_and_tutorials/nada_programs/tests/voting_dishonest_robust_6.yaml +++ /dev/null @@ -1,64 +0,0 @@ ---- -program: voting_dishonest_robust_6 -inputs: - secrets: - v2_c0: - SecretUnsignedInteger: "3" - v2_c1: - SecretUnsignedInteger: "3" - v0_c1: - SecretUnsignedInteger: "3" - v0_c0: - SecretUnsignedInteger: "3" - v1_c1: - SecretUnsignedInteger: "3" - v1_c0: - SecretUnsignedInteger: "3" - public_variables: {} -expected_outputs: - if_prod_cheat_open_v1_c0: - SecretUnsignedInteger: "3" - check_prod_v2_c1: - SecretUnsignedInteger: "2" - if_prod_cheat_open_v2_c0: - SecretUnsignedInteger: "3" - final_vote_count_c0: - SecretUnsignedInteger: "9" - final_vote_count_c1: - SecretUnsignedInteger: "9" - check_sum_v0: - SecretUnsignedInteger: "6" - check_prod_v0_c0: - SecretUnsignedInteger: "2" - if_prod_cheat_open_v2_c1: - SecretUnsignedInteger: "3" - check_prod_v0_c1: - SecretUnsignedInteger: "2" - check_prod_v1_c1: - SecretUnsignedInteger: "2" - if_sum_cheat_open_v2_c0: - SecretUnsignedInteger: "3" - check_prod_v2_c0: - SecretUnsignedInteger: "2" - if_sum_cheat_open_v2_c1: - SecretUnsignedInteger: "3" - if_prod_cheat_open_v0_c0: - SecretUnsignedInteger: "3" - if_prod_cheat_open_v0_c1: - SecretUnsignedInteger: "3" - if_sum_cheat_open_v1_c1: - SecretUnsignedInteger: "3" - if_sum_cheat_open_v0_c1: - SecretUnsignedInteger: "3" - check_sum_v2: - SecretUnsignedInteger: "6" - check_sum_v1: - SecretUnsignedInteger: "6" - if_prod_cheat_open_v1_c1: - SecretUnsignedInteger: "3" - check_prod_v1_c0: - SecretUnsignedInteger: "2" - if_sum_cheat_open_v0_c0: - SecretUnsignedInteger: "3" - if_sum_cheat_open_v1_c0: - SecretUnsignedInteger: "3" diff --git a/examples_and_tutorials/nada_programs/tests/voting_honest_1.yaml b/examples_and_tutorials/nada_programs/tests/voting_honest_1.yaml deleted file mode 100644 index 504d8c4a..00000000 --- a/examples_and_tutorials/nada_programs/tests/voting_honest_1.yaml +++ /dev/null @@ -1,22 +0,0 @@ ---- -program: voting_honest_1 -inputs: - secrets: - v1_c0: - SecretUnsignedInteger: "3" - v2_c0: - SecretUnsignedInteger: "3" - v2_c1: - SecretUnsignedInteger: "3" - v0_c0: - SecretUnsignedInteger: "3" - v0_c1: - SecretUnsignedInteger: "3" - v1_c1: - SecretUnsignedInteger: "3" - public_variables: {} -expected_outputs: - final_vote_count_c0: - SecretUnsignedInteger: "9" - final_vote_count_c1: - SecretUnsignedInteger: "9" diff --git a/examples_and_tutorials/nada_programs/tests/voting_honest_2.yaml b/examples_and_tutorials/nada_programs/tests/voting_honest_2.yaml deleted file mode 100644 index 867caea5..00000000 --- a/examples_and_tutorials/nada_programs/tests/voting_honest_2.yaml +++ /dev/null @@ -1,22 +0,0 @@ ---- -program: voting_honest_2 -inputs: - secrets: - v0_c1: - SecretUnsignedInteger: "3" - v1_c1: - SecretUnsignedInteger: "3" - v2_c0: - SecretUnsignedInteger: "3" - v1_c0: - SecretUnsignedInteger: "3" - v0_c0: - SecretUnsignedInteger: "3" - v2_c1: - SecretUnsignedInteger: "3" - public_variables: {} -expected_outputs: - final_vote_count_c1: - SecretUnsignedInteger: "9" - final_vote_count_c0: - SecretUnsignedInteger: "9" diff --git a/examples_and_tutorials/voting_tutorial/01_store_program_party1.py b/examples_and_tutorials/voting_tutorial/01_store_program_party1.py deleted file mode 100644 index dd139ce4..00000000 --- a/examples_and_tutorials/voting_tutorial/01_store_program_party1.py +++ /dev/null @@ -1,144 +0,0 @@ -########################################################################################## - -# VOTING -- PART 1 - -########################################################################################## - - -import asyncio -import py_nillion_client as nillion -import os -import sys -from dotenv import load_dotenv - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -from py_nillion_client import NodeKey, UserKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -# Alice stores the voting program in the network -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - - while True: - - # Below, you can choose which voting program to use. In case you choose a voting program - # different from the robust version ('voting_dishonest_robust_6'), you can complete - # either 'digest_plurality_vote_honest_result()' or 'digest_plurality_vote_dishonest_with_abort_result()' - # functions to digest the result. - # - # Existing voting nada_programs: - # - # program_name = "voting_honest_1" - # program_name = "voting_honest_2" - # program_name = "voting_dishonest_abort_5" - # program_name = "voting_dishonest_robust_6" - - print("Choose a program to test:") - print("1. voting_honest_1") - print("2. voting_honest_2") - print("3. voting_dishonest_abort_5") - print("4. voting_dishonest_robust_6") - - choice = input("Enter the number corresponding to your choice: ") - - programs = { - "1": "voting_honest_1", - "2": "voting_honest_2", - "3": "voting_dishonest_abort_5", - "4": "voting_dishonest_robust_6" - } - - if choice in programs: - program_name = programs[choice] - print("You have chosen:", program_name) - print(" _ _ _ _ _ _ _ ") - print("| | ___| |_( )___ __ _____ | |_ ___ __ _(_) |_| |__ ") - print("| | / _ \\ __|// __| \\ \\ / / _ \\| __/ _ \\ \\ \\ /\\ / / | __| '_ \\ ") - print("| |__| __/ |_ \\__ \\ \\ V / (_) | || __/ \\ V V /| | |_| | | |") - print("|_____\\___|\\__| |___/ \\_/_\\___/ \\__\\___| \\_/\\_/ |_|\\__|_| |_|") - print(" _____(_) | (_) ___ _____| | ") - print(" | _ | | | | |/ _ \\| _ | | ") - print(" | | | | | | | | (_) | | | |_| ") - print(" |_| |_|_|_|_|_|\\___/|_| |_(_) ") - print(" ") - break # Exit the loop if a valid choice is made - else: - print("Invalid choice. Please enter a number between 1 and 4.") - - ##################################### - # 1. Parties initialization # - ##################################### - - ############################# - # 1.1 Owner initialization # - ############################# - - # Create client - alice_seed = "alice_seed" - client_alice = create_nillion_client( - UserKey.from_seed(alice_seed), NodeKey.from_seed(alice_seed) - ) - - # Create payments config and set up Nillion wallet with a private key to pay for operations - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - ##################################### - # 2. Storing program # - ##################################### - - # Get cost quote, then pay for operation to store program - receipt_store_program = await pay( - client_alice, - nillion.Operation.store_program(), - payments_wallet, - payments_client, - cluster_id, - ) - - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - if os.path.exists(program_mir_path): - None - else: - raise FileNotFoundError(f"The file '{program_mir_path}' does not exist.\nMake sure you compiled the PyNada programs with './compile_programs.sh'.\nCheck README.md for more details.") - - # Store program in the Network - print(f"Storing program in the network: {program_name}") - action_id = await client_alice.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) - print("action_id is: ", action_id) - user_id_alice = client_alice.user_id - program_id = f"{user_id_alice}/{program_name}" - print("program_id is: ", program_id) - - ##################################### - # 3. Send program ID # - ##################################### - - # This requires its own mechanism in a real environment. - print(f"Alice stored {program_name} program at program_id: {program_id}") - print(f"Alice tells Bob and Charlie her user_id and the voting program_id") - - print("\n📋⬇️ Copy and run the following command to store Bob and Charlie's votes in the network") - print(f"\npython3 02_store_secret_party_n.py --user_id_1 {user_id_alice} --program_id {program_id}") - -asyncio.run(main()) \ No newline at end of file diff --git a/examples_and_tutorials/voting_tutorial/02_store_secret_party_n.py b/examples_and_tutorials/voting_tutorial/02_store_secret_party_n.py deleted file mode 100644 index cc895fd0..00000000 --- a/examples_and_tutorials/voting_tutorial/02_store_secret_party_n.py +++ /dev/null @@ -1,138 +0,0 @@ -########################################################################################## - -# VOTING -- PART 2 - -########################################################################################## - -import argparse -import asyncio -import py_nillion_client as nillion -import os -import sys -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv -from config import ( - CONFIG_N_PARTIES -) - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -parser = argparse.ArgumentParser( - description="Create a secret on the Nillion network with set read/retrieve permissions" -) -parser.add_argument( - "--user_id_1", - required=True, - type=str, - help="User ID of the user who will compute with the secret being stored", -) -parser.add_argument( - "--program_id", - required=True, - type=str, - help="Program ID of the voting program", -) - -args = parser.parse_args() - -# Bob and Charlie store their votes in the network -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - - # start a list of store ids to keep track of stored secrets - store_ids = [] - party_ids = [] - - for party_info in CONFIG_N_PARTIES: - - ##################################### - # 1. Parties initialization # - ##################################### - - ############################# - # 1.2 Voters initialization # - ############################# - seed = party_info["seed"] - client_n = create_nillion_client( - UserKey.from_seed(seed), - NodeKey.from_seed(seed) - ) - - # Create payments config and set up Nillion wallet with a private key to pay for operations - payments_config_n = create_payments_config(chain_id, grpc_endpoint) - payments_client_n = LedgerClient(payments_config_n) - payments_wallet_n = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - party_id_n = client_n.party_id - user_id_n = client_n.user_id - party_name = party_info["party_name"] - party_role = party_info["party_role"] - secret_votes = party_info["secret_votes"] - - ##################################### - # 4. Storing votes # - ##################################### - # Create a secret for the current party - secret_votes = {key: nillion.SecretUnsignedInteger(value) for key, value in secret_votes.items()} - stored_secret = nillion.Secrets(secret_votes) - - ########################################### - # 4.2 Set compute permissions to owner # - ########################################### - # Create permissions object with default permissions for the current user - permissions = nillion.Permissions.default_for_user(user_id_n) - - # Give compute permissions to Alice so she can use the secret in the specific voting program by program id - compute_permissions = { - args.user_id_1: {args.program_id}, - } - permissions.add_compute_permissions(compute_permissions) - print(f"\n👍 {party_name} gives compute permissions on their secret to Alice's user_id: {args.user_id_1}") - - # Get cost quote, then pay for operation to store the secret - receipt_store = await pay( - client_n, - nillion.Operation.store_values(stored_secret), - payments_wallet_n, - payments_client_n, - cluster_id, - ) - - # Store the permissioned secret - store_id = await client_n.store_values( - cluster_id, stored_secret, permissions, receipt_store - ) - - store_ids.append(store_id) - party_ids.append(party_id_n) - - print(f"\n🎉 {party_name} stored its vote at store id: {store_id}") - - ##################################### - # 5. Send party IDs and store IDs # - ##################################### - - # This requires its own mechanism in a real environment. - party_ids_to_store_ids = ' '.join([f'{party_id}:{store_id}' for party_id, store_id in zip(party_ids, store_ids)]) - - print("\n📋⬇️ Copy and run the following command to run multi party computation using the secrets") - print(f"\npython3 03_multi_party_compute.py --program_id {args.program_id} --party_ids_to_store_ids {party_ids_to_store_ids}") - -asyncio.run(main()) diff --git a/examples_and_tutorials/voting_tutorial/03_multi_party_compute.py b/examples_and_tutorials/voting_tutorial/03_multi_party_compute.py deleted file mode 100644 index d7afaede..00000000 --- a/examples_and_tutorials/voting_tutorial/03_multi_party_compute.py +++ /dev/null @@ -1,181 +0,0 @@ -########################################################################################## - -# VOTING -- PART 3 - -########################################################################################## - -import argparse -import asyncio -import py_nillion_client as nillion -import os -import sys -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv -from config import ( - CONFIG, - CONFIG_CANDIDATES, - CONFIG_PARTY_1, - CONFIG_N_PARTIES -) - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -from digest_result import digest_plurality_vote_honest_result, digest_plurality_vote_dishonest_with_abort_result, digest_plurality_vote_robust_result - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -parser = argparse.ArgumentParser( - description="Create a secret on the Nillion network with set read/retrieve permissions" -) - -parser.add_argument( - "--program_id", - required=True, - type=str, - help="Program ID of the voting program", -) - -parser.add_argument( - "--party_ids_to_store_ids", - required=True, - nargs='+', - type=str, - help="List of partyid:storeid pairs of the secrets, with each pair separated by a space", -) - -args = parser.parse_args() - -async def main(): - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - - ##################################### - # 1. Parties initialization # - ##################################### - - ############################# - # 1.1 Owner initialization # - ############################# - # Alice initializes a client - seed = CONFIG_PARTY_1["seed"] - client_alice = create_nillion_client( - UserKey.from_seed(seed), - NodeKey.from_seed(seed) - ) - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - party_id_alice = client_alice.party_id - - ##################################### - # 4. Storing votes # - ##################################### - # Add any computation time secrets - # Alice provides her vote at compute time - - party_name_alice = CONFIG_PARTY_1["party_name"] - secret_votes = CONFIG_PARTY_1["secret_votes"] - secret_votes = {key: nillion.SecretUnsignedInteger(value) for key, value in secret_votes.items()} - compute_time_secrets = nillion.Secrets(secret_votes) - - print(f"\n🎉 {party_name_alice} provided her vote as a compute time secret.") - - ##################################### - # 6. Owner execute computation # - ##################################### - # Create computation bindings for voting program - compute_bindings = nillion.ProgramBindings(args.program_id) - - ################################################# - # 6.1 Bind voter to input party in the program # - ################################################# - # Add Alice as an input party - compute_bindings.add_input_party(CONFIG_PARTY_1["party_role"], party_id_alice) - - # Also add Bob and Charlie as input parties - party_ids_to_store_ids = {} - i=0 - for pair in args.party_ids_to_store_ids: - party_id, store_id = pair.split(':') - party_role = CONFIG_N_PARTIES[i]['party_role'] - compute_bindings.add_input_party(party_role, party_id) - party_ids_to_store_ids[party_id] = store_id - i=i+1 - - ################################################## - # 6.2 Bind owner to output party in the program # - ################################################## - # Add an output party (Alice). - # The output party reads the result of the blind computation - compute_bindings.add_output_party("OutParty", party_id_alice) - - # Get cost quote, then pay for operation to compute - receipt_compute = await pay( - client_alice, - nillion.Operation.compute(args.program_id, compute_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) - - print(f"Computing using program {args.program_id}") - - # Compute on the secret with all store ids. Note that there are no compute time secrets or public variables - compute_id = await client_alice.compute( - cluster_id, - compute_bindings, - list(party_ids_to_store_ids.values()), # Bob and Charlie's stored secrets - compute_time_secrets, # Alice's computation time secret - nillion.PublicVariables({}), - receipt_compute - ) - - # Print compute result - print(f"The computation was sent to the network. compute_id: {compute_id}") - print("Waiting computation response...") - while True: - compute_event = await client_alice.next_compute_event() - if isinstance(compute_event, nillion.ComputeFinishedEvent): - print(f"✅ Compute complete for compute_id {compute_event.uuid}") - dict_result = compute_event.result.value - print(f"🖥️ The output result is {dict_result}\n") - - # Digest the result - program_name = os.path.basename(args.program_id) - nr_candidates = CONFIG["nr_candidates"] - nr_voters = CONFIG["nr_voters"] - voters = CONFIG_N_PARTIES - voters.insert(0, CONFIG_PARTY_1) - candidates = CONFIG_CANDIDATES - if program_name == "voting_dishonest_robust_6": - print("Let use digest the result given by the network:") - winner, total_votes, cheaters = digest_plurality_vote_robust_result(dict_result, nr_candidates, nr_voters) - winner_name = candidates[winner] - cheaters_names = [voters[voter]["party_name"] for voter in cheaters] - print(f"🏆 Winner is {winner_name}") - print(f"🔢 Total number of votes per candidate:") - print(f" Dave: {total_votes[0]}") - print(f" Emma: {total_votes[1]}") - print(f"🕵️‍♂️ List of cheaters: {cheaters_names}") - elif program_name in ["voting_honest_1", "voting_honest_2"]: - digest_plurality_vote_honest_result(dict_result, nr_candidates, nr_voters) - elif program_name == "voting_dishonest_abort_5": - digest_plurality_vote_dishonest_with_abort_result(dict_result, nr_candidates, nr_voters) - - break - -asyncio.run(main()) diff --git a/examples_and_tutorials/voting_tutorial/README.md b/examples_and_tutorials/voting_tutorial/README.md deleted file mode 100644 index 33808dcc..00000000 --- a/examples_and_tutorials/voting_tutorial/README.md +++ /dev/null @@ -1,87 +0,0 @@ -# Voting tutorial - -This tutorial aims to introduce you to the features of PyNada embedded DSL through the use of voting systems. We'll start simple and gradually increase the complexity of the programs to show you some interesting techniques. - -Additionally, we've provided two sets of Python scripts for you to explore the flow of a voting system running in `devnet-nillion`: - -1. Single File: One script emulates the behavior of all parties. -2. Multiple Files: Each party runs in its own separate script. - -In a real-world scenario, the roles should be distributed across multiple machines. - -## Tutorial - -Please go to the [tutorial page](tutorial.md) to dive deeper into PyNada. - -## Setup - -Before running the example, [follow the repo README](../../README.md) to install cli dependencies, complete environment setup, and activate your .venv environment. - -## Single file example - -In the single file version, the different roles are played by a single entity (`general_client`). The script has the following flow: - - 1. Parties initialization. - 2. Owner stores a program. - 3. (Real environment:) Owner sends the program ID to all voters. - 4. Voters store votes: - 4.1 Bind voter to party in the program - 4.2 Set compute permission to owner - 5. (Real environment:) Voters send their their party IDs and store IDs to the owner. - 6. Owner compute voting system using votes from voters. - -Also, we present a function [`digest_plurality_vote_robust_result()`](digest_result.py) that digests the result output by the [voting_dishonest_robust_6.py](../nada_programs/src/voting_dishonest_robust_6.py). - -### Run - -Run the command to start the example and follow the steps described: -```bash -python3 client_voting.py -``` - -## Multiple files example - -In this voting example, we maintain the same structure as in previous multiparty computation examples: - -- Alice assumes the role of the owner and acts as voter 0. -- Bob serves as voter 1. -- Charlie takes on the role of voter 2. - -Furthermore, the voters will cast their votes for two candidates: Dave and Emma. - -### Step 1 - -- Alice creates and stores program in the network -- Alice shares her user id and the resulting program id with Bob and Charlie - -Run the command to perform step 1. - -```bash -python3 01_store_secret_party1.py -``` - -### Step 2 - -- Bob and Charlie store their VOTES in the network as secrets. Each secret is stored with - - **bindings** to the voting program id. - - **permissions** so Alice (the owner) can compute using the secret. -- Bob and Charlie each share their `party_id` and secret `store_id` with Alice. - -The script will provide the command to perform step 2. - -```bash -python3 02_store_secret_party_n.py --user_id_1 {user_id} --program_id {program_id} -``` - -### Step 3 - -- Alice runs voting program computation using Bob and Charlie's secret votes. She provides her own vote as a secret at computation time. -- Alice receives the output result of the program. - -The script will provide the command to perform step 3. - -```bash -python3 03_multi_party_compute.py --program_id {program_id} --party_ids_to_store_ids {party_ids_to_store_ids} -``` - -Also, we present a function [`digest_plurality_vote_robust_result()`](digest_result.py) that digests the result output by the [voting_dishonest_robust_6.py](../nada_programs/src/voting_dishonest_robust_6.py). diff --git a/examples_and_tutorials/voting_tutorial/client_voting.py b/examples_and_tutorials/voting_tutorial/client_voting.py deleted file mode 100644 index 995a217d..00000000 --- a/examples_and_tutorials/voting_tutorial/client_voting.py +++ /dev/null @@ -1,334 +0,0 @@ -########################################################################################## - -# SINGLE FILE VOTING - -########################################################################################## - - -import asyncio -import py_nillion_client as nillion -import os -import sys -from py_nillion_client import NodeKey, UserKey -from dotenv import load_dotenv -from config import ( - CONFIG -) - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey - -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - -from digest_result import digest_plurality_vote_honest_result, digest_plurality_vote_dishonest_with_abort_result, digest_plurality_vote_robust_result - -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -async def main(): - - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - - while True: - - # Below, you can choose which voting program to use. In case you choose a voting program - # different from the robust version ('voting_dishonest_robust_6'), you can complete - # either 'digest_plurality_vote_honest_result()' or 'digest_plurality_vote_dishonest_with_abort_result()' - # functions above to digest the result. - # - # Existing voting nada_programs: - # - # program_name = "voting_honest_1" - # program_name = "voting_honest_2" - # program_name = "voting_dishonest_abort_5" - # program_name = "voting_dishonest_robust_6" - - print("Choose a program to test:") - print("1. voting_honest_1") - print("2. voting_honest_2") - print("3. voting_dishonest_abort_5") - print("4. voting_dishonest_robust_6") - - choice = input("Enter the number corresponding to your choice: ") - - programs = { - "1": "voting_honest_1", - "2": "voting_honest_2", - "3": "voting_dishonest_abort_5", - "4": "voting_dishonest_robust_6" - } - - if choice in programs: - program_name = programs[choice] - print("You have chosen:", program_name) - print(" _ _ _ _ _ _ _ ") - print("| | ___| |_( )___ __ _____ | |_ ___ __ _(_) |_| |__ ") - print("| | / _ \\ __|// __| \\ \\ / / _ \\| __/ _ \\ \\ \\ /\\ / / | __| '_ \\ ") - print("| |__| __/ |_ \\__ \\ \\ V / (_) | || __/ \\ V V /| | |_| | | |") - print("|_____\\___|\\__| |___/ \\_/_\\___/ \\__\\___| \\_/\\_/ |_|\\__|_| |_|") - print(" _____(_) | (_) ___ _____| | ") - print(" | _ | | | | |/ _ \\| _ | | ") - print(" | | | | | | | | (_) | | | |_| ") - print(" |_| |_|_|_|_|_|\\___/|_| |_(_) ") - print(" ") - break # Exit the loop if a valid choice is made - else: - print("Invalid choice. Please enter a number between 1 and 4.") - - # We initialize one party 'general_client' that represents all different parties. - # - # In a real environment, the clients must run in different machines with - # different node and user keys. - # - # The script has the following flow: - # 1. Parties initialization - # 2. Owner stores a program. - # 3. (Real environment:) Owner sends the program ID to all voters. - # 4. Voters store votes: - # 4.1 Bind voter to party in the program - # 4.2 Set compute permission to owner - # 5. (Real environment:) Voters send their their party IDs and store IDs to the owner. - # 6. Owner compute voting system using votes from voters. - - nr_candidates = CONFIG["nr_candidates"] - nr_voters = CONFIG["nr_voters"] - - ##################################### - # 1. Parties initialization # - ##################################### - - ###################################### - # 1.0 General client initialization # - ###################################### - seed = "my_seed" - userkey = UserKey.from_seed(seed) - nodekey = NodeKey.from_seed(seed) - general_client = create_nillion_client(userkey, nodekey) - general_payments_config = create_payments_config(chain_id, grpc_endpoint) - general_payments_client = LedgerClient(general_payments_config) - general_payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - - # ############################# - # # 1.1 Owner initialization # - # ############################# - # # Path to the user and node key generated in previous step - # owner_nodekey_path = "/path/to/owner_node.key" - # owner_nodekey = nillion.NodeKey.from_file(owner_nodekey_path) - # owner_userkey_path = "/path/to/owner_user.key" - # owner_userkey = nillion.UserKey.from_file(owner_userkey_path) - # owner = nillion.NillionClient( - # owner_nodekey, - # bootnodes, - # nillion.ConnectionMode.relay(), - # owner_userkey, - # payments_config=payments_config - # ) - - # ############################# - # # 1.2 Voters initialization # - # ############################# - # # Initialize voters - # voters = [] - # for v in range(nr_voters): - # # Path to the user and node key generated in previous step - # voter_nodekey_path = "/path/to/voter"+str(v)+"_node.key" - # voter_nodekey = nillion.NodeKey.from_file(voter_nodekey_path) - # voter_userkey_path = "/path/to/voter"+str(v)+"_user.key" - # voter_userkey = nillion.UserKey.from_file(voter_userkey_path) - # voter = nillion.NillionClient( - # voter_nodekey, - # bootnodes, - # nillion.ConnectionMode.relay(), - # voter_userkey, - # payments_config=payments_config - # ) - # voters.append(voter) - - - ##################################### - # 2. Storing program # - ##################################### - - # Note: do not forget to compile the nada_programs and store the corresponding .nada.bin file. - program_mir_path = f"../../programs-compiled/{program_name}.nada.bin" - if os.path.exists(program_mir_path): - None - else: - raise FileNotFoundError(f"The file '{program_mir_path}' does not exist.\nMake sure you compiled the PyNada programs with './compile_programs.sh'.\nCheck README.md for more details.") - - # Store program in the Network - print(f"Storing program in the network: {program_name}") - # Get cost quote, then pay for operation to store program - receipt_store_program = await pay( - general_client, - nillion.Operation.store_program(), - general_payments_wallet, - general_payments_client, - cluster_id, - ) - - action_id = await general_client.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) - print("action_id is: ", action_id) - # program_id = owner.user_id + "/" + program_name - program_id = general_client.user_id + "/" + program_name - print("program_id is: ", program_id) - - ##################################### - # 3. Send program ID # - ##################################### - - # This requires its own mechanism in a real environment. - # In this demo, we just reuse the variable 'program_id'. - - ##################################### - # 4. Storing votes # - ##################################### - - # Each voter stores its vote. In this demo, we assume each voter has a file - # containing their votes for each candidate. E.g. voter 0 has a file 'inputs/v0_input.txt' - # with the following format: - # ============== - # 1 - # 2 - # ============== - # This means, voter 0 assigns 1 to candidate 0 and 2 to candidate 1. - - # Voters store the secrets - store_ids = [] - for v in range(nr_voters): - voter_v = general_client - # voter_v = voters[v] - print("voter_v: ", voter_v) - # structure v's votes - v_vote_dic = {} - v_input_file = "inputs/v"+str(v)+"_input.txt" - with open(v_input_file, 'r') as file: - c = 0 - for line in file: - # Remove leading and trailing whitespaces, then convert to integer - v_c_vote_value = int(line.strip()) - v_c_vote = nillion.SecretUnsignedInteger(v_c_vote_value) - v_vote_dic["v"+str(v)+"_c"+str(c)] = v_c_vote - c += 1 - v_to_be_store_values = nillion.Secrets(v_vote_dic) - - ########################################### - # 4.2 Set compute permissions to owner # - ########################################### - # Give permissions to owner to compute with my vote - v_permissions = nillion.Permissions.default_for_user(voter_v.user_id) - v_permissions.add_compute_permissions({ - # owner.user_id: {program_id}, - general_client.user_id: {program_id}, - }) - - # Get cost quote, then pay for operation to store the secret - receipt_store = await pay( - voter_v, - nillion.Operation.store_values(v_to_be_store_values), - general_payments_wallet, - general_payments_client, - cluster_id, - ) - - # Store in the network - print("Storing vote by voter "+str(v)+f": {v_to_be_store_values}") - store_id = await voter_v.store_values( - cluster_id, v_to_be_store_values, v_permissions, receipt_store - ) - store_ids.append(store_id) - print(f"Stored vote by voter "+str(v)+f" with store_id ={store_id}") - - ##################################### - # 5. Send party IDs and store IDs # - ##################################### - - # This requires its own mechanism in a real environment. - # In this demo, we just reuse the variable 'store_ids' and - # the voters' clients to extra their party IDs. - - ##################################### - # 6. Owner execute computation # - ##################################### - - ################################################# - # 6.1 Bind voter to input party in the program # - ################################################# - owner_bindings = nillion.ProgramBindings(program_id) - for v in range(nr_voters): - # owner_bindings_0.add_input_party("Voter"+str(v), voters[v].party_id) - owner_bindings.add_input_party("Voter"+str(v), general_client.party_id) - - ################################################## - # 6.2 Bind owner to output party in the program # - ################################################## - # Bind the "OutParty" party in the computation to the owner's client - # owner_bindings.add_output_party("OutParty", owner.party_id) - owner_bindings.add_output_party("OutParty", general_client.party_id) - - # No secret is directly passed to 'compute'. It only uses - # stored secrets. - to_be_used_in_computation = nillion.Secrets({}) - - print(f"Computing using program {program_id}") - print(f"Stored secrets: {store_ids}") - print(f"Provided secret: {to_be_used_in_computation}") - - # Get cost quote, then pay for operation to compute - receipt_compute = await pay( - general_client, - nillion.Operation.compute(program_id, to_be_used_in_computation), - general_payments_wallet, - general_payments_client, - cluster_id, - ) - - # Owner can execute the computation - # compute_id = await owner.compute( - compute_id = await general_client.compute( - cluster_id, - owner_bindings, - store_ids, - to_be_used_in_computation, - nillion.PublicVariables({}), - receipt_compute - ) - - print(f"Computation sent to the network, compute_id = {compute_id}") - print("Waiting computation response...") - while True: - compute_event = await general_client.next_compute_event() - if isinstance(compute_event, nillion.ComputeFinishedEvent): - print(f"✅ Compute complete for compute_id {compute_event.uuid}") - dict_result = compute_event.result.value - print(f"🖥️ The result is: {dict_result}\n") - - if program_name == "voting_dishonest_robust_6": - print("Let use digest the result given by the network:") - winner, total_votes, cheaters = digest_plurality_vote_robust_result(dict_result, nr_candidates, nr_voters) - print(f"🏆 Winner is candidate with ID={winner}") - print(f"🔢 Total number of votes per candidate: {total_votes}") - print(f"🕵️‍♂️ List of cheaters' IDs: {cheaters}") - elif program_name in ["voting_honest_1", "voting_honest_2"]: - digest_plurality_vote_honest_result(dict_result, nr_candidates, nr_voters) - elif program_name == "voting_dishonest_abort_5": - digest_plurality_vote_dishonest_with_abort_result(dict_result, nr_candidates, nr_voters) - - break - - - -asyncio.run(main()) \ No newline at end of file diff --git a/examples_and_tutorials/voting_tutorial/config.py b/examples_and_tutorials/voting_tutorial/config.py deleted file mode 100644 index 1822cffc..00000000 --- a/examples_and_tutorials/voting_tutorial/config.py +++ /dev/null @@ -1,48 +0,0 @@ -import os -from dotenv import load_dotenv -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -CONFIG = { - "nr_candidates": 2, - "nr_voters": 3, -} - -CONFIG_CANDIDATES=[ - "Dave", - "Emma" -] - - -# Alice -CONFIG_PARTY_1={ - "seed": "alice_seed", - "party_name": "Alice", - "party_role": "Voter0", - "secret_votes": { - "v0_c0": 1, - "v0_c1": 2, - }, -} - -# Bob and Charlie -CONFIG_N_PARTIES=[ - { - "seed": "bob_seed", - "party_name": "Bob", - "party_role": "Voter1", - "secret_votes": { - "v1_c0": 1, - "v1_c1": 2, - }, - }, - { - "seed": "charlie_seed", - "party_name": "Charlie", - "party_role": "Voter2", - "secret_votes": { - "v2_c0": 2, - "v2_c1": 1, - }, - }, -] \ No newline at end of file diff --git a/examples_and_tutorials/voting_tutorial/digest_result.py b/examples_and_tutorials/voting_tutorial/digest_result.py deleted file mode 100644 index 8f85cadb..00000000 --- a/examples_and_tutorials/voting_tutorial/digest_result.py +++ /dev/null @@ -1,70 +0,0 @@ -def digest_plurality_vote_honest_result(dict_result, nr_candidates, nr_voters): - - # TODO: complete there the code to digest the output of the honest version - - print("Update 'digest_plurality_vote_honest_result()' functions to digest the result.") - - return None - - -def digest_plurality_vote_dishonest_with_abort_result(dict_result, nr_candidates, nr_voters): - - # TODO: complete there the code to digest the output of the dishonest with abort version - - print("Update 'digest_plurality_vote_dishonest_with_abort_result()' functions to digest the result.") - - return None - - -def digest_plurality_vote_robust_result(dict_result, nr_candidates, nr_voters): - """ - Digests the robust result of a plurality voting process, identifying the winner, - the votes per candidate, and any potential cheaters. - - Parameters: - - dict_result (dict): A dictionary containing the robust result of the plurality - voting process. It should contain keys for final vote counts - for each candidate, checks for sum and product rules per voter, - and actions if cheating is detected. - - nr_candidates (int): The total number of candidates in the voting process. - - nr_voters (int): The total number of voters in the voting process. - - Returns: - - winner (int): The index of the winning candidate. - - votes_per_candidate (list): A list containing the number of votes received by - each candidate. - - cheaters (list): A list containing the IDs of any potential cheaters. - """ - - # check cheaters - set_of_cheaters = set() - for v in range(nr_voters): - if dict_result["check_sum_v"+str(v)] != nr_candidates + 1: - # add the voter id to set_of_cheaters - set_of_cheaters.add(v) - for c in range(nr_candidates): - if dict_result["check_prod_v"+str(v)+"_c"+str(c)] != 0: - # add the voter id to set_of_cheaters - set_of_cheaters.add(v) - - # collect final votes - votes_per_candidate = [] - for c in range(nr_candidates): - # read nr of votes for candidate 'c' - votes_per_candidate.append(dict_result["final_vote_count_c"+str(c)]) - # revert action of cheaters for all candidates - for cheater in set_of_cheaters: - # if sum rule was broken - if dict_result["check_sum_v"+str(cheater)] != nr_candidates + 1: - for c in range(nr_candidates): - votes_per_candidate[c] -= dict_result["if_sum_cheat_open_v"+str(cheater)+"_c"+str(c)] - # if product rule was broken - else: - for c in range(nr_candidates): - votes_per_candidate[c] -= dict_result["if_prod_cheat_open_v"+str(cheater)+"_c"+str(c)] - - # define winner - winner = max(range(len(votes_per_candidate)), key=lambda i: votes_per_candidate[i]) - - return winner, votes_per_candidate, list(set_of_cheaters) - diff --git a/examples_and_tutorials/voting_tutorial/image/architecture.png b/examples_and_tutorials/voting_tutorial/image/architecture.png deleted file mode 100644 index 033dd5c6ab7ad272d4683098264712cbe1608996..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 116180 zcmeFZWn5KT)Hk{S5k(YHP!tITln_BckldisN{2K`3Ifs%TM$tsWYgVUhY~lfh;)f` zOE=QF_nm8T&U2sp?S6Vcz5IUXu+L(yIp^px{$uR(QBhv%Jn3~(2!hVbNIy}Api^k@ zkKUQn;7MVe95MKH%I=Yj>KX9ke#Ynn`0s1>63^{btWE5lUf3E#rdHOL#vFErw#LR* zc4pT0Yeco8U=bU<=&`Nw3;S2rRt&1IER7*mGY1B)`wWuCFB!PGxOo}w-xuWO7v$k% z(2!8gwP831K@5=06LD4NxaCpPxEsB1S0~GJ>=ZupwonjUdc^SV)~!zlT!dsFi~<6u z{?uJxc0h0I4OcETSRBSUObBwwR~iJ!-1j+|33Pc z6X#rM;ZM4Ayw+i{BRBa=G>S2{`C&Tj%$lrNY@hzjbef`$B-cIPKbU~NtCjLg7Pe=b zW#uMauXWdOfA=tYa@cH!3fkV?Iy?nI@sA0>zZK8sl`hj-H;sLHvJ=EVy{vWbPLZ0G zZRN;o70uKr2S(!^Az72!BJM-FY%#oHngQ6Jjqz7`8LvP2J&W5AwEm5(-$A$VF}PMz zV60_Cwa3S0%fi;N9VY>yv9M~p=>8(XwllpgAILFELaHXCS$*HLtisw3>Wxkrl9Hhs zmVi>Lo=0R&j?JdsPiY+6iCJE;ZW=8A8G2XptDea*i@;%Db9>p=UjMnk`26|k@|HZB z?kW9q5ZYV{A;F~Y{hU#LJiEop$sJYuNdy3DU`l2~utM}Mzm zje>hgsyv~G$9eH)t<>tMUzqL=iNtL$wQt?@D{oG_&2bOLUTGS7sNa`CY#Ta!VZlMN zUC}rA(ECX}$hc2)c2D_#y;}k^fU(ecPg4XQoe>&21@n^k7UfEtrrfSi_*p zDEhdub=m8+#e#}Zm3FPX(<|%}FGe@UX8tVeWskU# zv-7UXMsNEyzZzm#8YKyL+ZETONyOLnpO%WtSPv^?<=rXfPA7)y5?Rnd6DnACSAAOU zRX^(+Y7{!AljOQ>S7$>?WT4dJ1>;h7U)_Lw|lD|jU5Fzom+znHIDvGhv9o`i(_*N=b5AoSxW zu*$999MMr8tR2s3%SDC9jvXiO#(FfeI1dPr28n8Xs~5e+sH*)w3}?Kild)`pQgLk1 z#l*iVf63y!cUK}~q%QRZ1i7C<2=sZAA69IABpKa+3W**1`*!!VRGX^Vaylv9{{3i= z3!Ez77yo29vK;^NVmaK!Ym{CAs@=`wSupXTM@0HGB(`>90*@S<^JQz@H%YFbzJ-{w z@#4&Vf9kau>Kackdnja^;+6U;?tJQdWN6ARxOs){JP|YjQHlXctr(3T<#Th6-_10( zYj!AcjC$0lo4fw5vo()iZPwn5vwVXtJ)u(E%2vBu%V-hBYWr)~_{o9mXTXUuKf*H* zR96({vDDd8Wm^)Lah}=wzJ(vV?RONr@Lw&~i>!8HHv(b@Gq=2B!*pF^)4tPN1U~nY zP&nZC4aOH6ovuC$q{1lyhiwi*Cx`U$!x2-Wc)k1#(f`S!+;h+~Mr^EF2M2z*0v!?}#_uoTn zbLJONArt{FnDZic%3Xt$72M^mzGR!VpXp`W=|-^uyCcUCK#F=Woprm#a#m`KzOhW~ zx?I+M!RkjxwJcmml);$f8*vem%=B30ommOy{`y8w$?g&_W}t<$fJtHGupe3HypuU? zn5b&iTTOEJpQX&juR%cYd0TR*&pMT6gL(A%dlI{>T+TTplMpoTf}9sQ^11{=?*6nd zW1o0)`5@a$ed?2}s5^FK`pX{)p0s&Zd~(xSep&98g!2$O0Wl5t#%Hh9{?yog>Nq^B zP?MmG`W!oyJw2k(thH;@ygkvxLs>gf$Zh^ULf{$%i6A<(=U=i!%e;!uikp45FHnNx zYA_i^6#VJ?vD=%A3gLQh44WS?ib_9-6aj8b##o*hWC}?zKWkp_-&6hb_T03|&di** zUwkT{q4i9^OB7jkc=e1iGyU;bpqCOBl3D712ZLE2ZUk(Q5J5??h!50n%CAspmMUP0 zI;VgYFXHi9P`af`%0HafmW$Vjjx%En(^xs9GL3%;5!k>R1z9bN7Wzf^$47^6igQc8D%Z0H>C!tZ@9;^9HqJvAGF~jd7^6UUmJl z+tN#B`U+KfIm2)6Z+<*&;EZQyq}*iz5}-x=2b=tAwO_dV`k$kR?#rf>LTg@C=T42= z_PB+)FJCV16Vc?Nr{>iJ?7zt<+LQ`Z%AO2XsVtVA*~qZ3yRWh8vKY$iUS)sg#useZ z_H<`wV#D?Js=Wn(x%PVgk+}Bv8(lgfG|>g~9qs>O?Rm}*-OQ%;GHHx=y@FbMc&D66 z*OXD#X`pDbqW0M%TdPMpz|;4-zYGa@6YtjyL3?j9`ax8HF8G$XVK40_gmZck65VDI zJydF;4|@iP>h(0qCVZ^W);TCNuKcni?QQi!aXgp%nDM;T1O9zz4C zhYkp$jm?UdZ9|he)d$SJcRX29ra+lCaGLvGC<16c(D@V@a4_apFF$Fm#@){cBVdf}YUhml0=FIlt~MazD4fY~%AF}t0dY7a zF&DexLM)Xqsmqc?Yf)b!J8~FuG>~;I2Z&ca3138)K?o&L2crRd^qSk3RsJOKoNGzk zYg|n}5-+@T!{N)HNMWXH`6%_uj~uci4mr#DpG1HY6T6Af$IeV&skIZ7zHehR#7_lO zgZwfUs|chOO%$o5BR^XmVW@nR5d9BQUNc!>%eC zr+cR5cQQ85zeL=RY}OtT=w1~5aPc*;>DKxuV*F>{ zQPs|SvQl$IIjRVSe_hkT4&Dy-CV+A%5WoD?YCoQM4o6*t^Ve?@$F7r?j4S!ayI6Er zm6(=cg7s#^Z$V;ZNb)lF507j+-tn92)^B_m1CCt9Kl{5z{9mqhgvhyX9MF6X->+s~9mAMm6uyT3Vwx?3A8e~%P$ZbUKf$$$!y*ASr66(c} zIyzY{_JA}P9|pXfG-gCc&U37t`5dVV$gYWCcFx;G_j^Uu@|nM9;q}k|?=J%YExCNnu5r<1>y!M=}(&mNdjrhNum}^^H6kTrbe(KyRu}Wits~?J$ ze%JVsdlsXidXhxY9^%{Qm7*Ig=i;b~-jdSp5|j5p)e6on{TuyOt_nE8!;Tfb9|X`I zC32Jd@^Y!RxvTBLj6F3s8L0vDyAFX?f)K+Q)0v;3@`exGR~Lzdu1_7CIhh+VOxKdN zd<70kSYEzcq7t0#X*j2@g{5+e1k$gIL~J`4NsMv#P!|;TV*(WOfqNQb=iWcSY+`$N zBL&Vvb%iJEnm7(qL7D=b^H0iX97phFg6c?677&vkN_SV4O$s!3IDb9^P4FX5hU6!ElOB9gn@7mb5u6sN?WyWcg>Qr;VWZ#M%UE{D>JO#f zKa$d2eMOgq0o8{V@7%KMun!Yp*2$_{<%^yGjlg&_%12UIt^*3OpoELmL{$KO>__mw z0O_0 zgE0_vghtq?>A0`EZ&C813Y4$FCXQ}$Ei+yF_T2OJnsZM*FNBsw(w&Mnt;6Ry!H3IC z589)Ye zvds_Ra&SGhOpXx2C@a_WJMW^N(y>&g|2gKzSQVF-<+~-L9n{ZyUUcn^uS89+wC0{& zgv1&Uo7a8$;0u+QFpk)svt%uV`qphw|yS~K5NYw$< zMgr!LVEZdAPEfLW-k3O|PZ}=sB5RwxXaVWej>|L7r?fj4;U#Y6LRrf_uH5wH_tqe7elrD>;Zv%`jAs%NRw z3n-Dt{QYd<=N}qF!}}`m+n>n zxOtyY?+!rYsBw!B@JKogBaZFz?|$W#V#sx#tHSvSX%H^Uo6!UY^O&$84;A^V$vmgW zIH<|_E9F-d;2t{Y1qmFRC>bduW95_~+9$TFnMRgz8eCUJajpIxRT7d}CisBdk&Rlm z?W6vg3`e%|Bl+~U&oAeWvCA$>1Obkf^#ee|=NUX-5&?;C;tUNa#UzSbaakjahnt3s zI)&>$I9VNc3Wvi{7$X*5Txd?U$;Y<$fAb4H4!rj!L0pAWPoTErJe7L25{AQ(3gt5t}bg$0eQossK+V3haO!dlk@w9|*iZe0T z06zyf5noaZaqsz(EPf9U49P4#1ztjZaStZYAtQM1}(Z&^QuTzRbbt)zojTz8GDYwZH3UG0vG{e$Xyf zHX`siS_7wIWn%4Db1%)iIGhJ)JnfHbVgTMy@kgB{v%PCPz8A>U{Q99*cMq=QF(-M@ zLlLi)SuG}+zMpg9nnzy7KS%!^oHM3#FvdaF{8-(O zB%ByftP-DAP->s)Z2GVxv^Tustg=AN4;Sd)+zsZ*8cauTmW&U0yY$Pl?$ zMYc4_uY^@Q6v0>(C@KmkCQB}?9E!hKsAn{Q&=>v`E<=#m+*kP}PW6^4hHAeumynU) z@-oWKf66e|76)ib4~aYtCYYMbK$Uq3r0j~T@j^zH?yH~2*{UZpj%R|6cIXSIM=ZiT zCo_)4dt^tv5|%j%r7_Eva$&AJ^yCM6gXMS)4yUKAY+g*wV;laL$aNU_BUz#pL>w%| z_YM2#GXRbUVl=A^V0(C!-CL0BsbSoj#x>OJmizPv9Y~=0Q@Aw(;5-{V@Z_l&5J|{G zx9F@ke;zp)tZ!YY9UCf5TG=ML-2~&omga8w*PF@Jod=oWnzp%2tRMp*C5vH_DRwFh zV@gk`mfzW_y;^m<{LejsXG@>P*vpR!WkvQ~njMVZnNx{Ondaa(uJn2?s`>uxh+qL3mBC$E0qrhn4T6DHO9zOVt+9j5^9$dR(K| z4X;u+a7+YljyNs>D{X@5W!lspqnthwqE6%RJlM-%Ll7D&Wj+Y+nB~6B7*dqX1pt!PpQvqsqW0j6HQoHWe?obz-`no;q@`D+L1zK@C{XnipOz2%?b>C(Q-%jkgQpKPIxG%NUzmow z@7ZFr<3Zrc!A{@279t9Ob7r9XE&ze&$Synj*y*V%$#TzPWy*G^=8i?{OW$FK2MfOA zt@M{@sky|#LW~7Sc3OwFynmgmjjiQ>Sh|u}TjJJ$YtFjWV=X+t`vc1ydLK1_eQYaiHCtZ92!#&?Dtg8cm9TtO@|s;qvqe}x{OZEm9Y?O5VN*{Y23 z_(2cJ_}Wm*UED{lRe{uPmD;w?e@d((DHeKG6Z#07KAgt@{l>fEAS;=DpEwl-FBxq3 zt~4ekb1bhb>eKMpt!_ujPzrx`T#*|o9V023kOXFWSvxkdwQ7e)rT`?Zy~p*}B{Dp# z_d>@5(BDIGN`HfK*!5xAT0F(t`!11@jPv^zjC@`7aH7-zVm^q-zQkQ_bhj6Dzc+d@ zdqvqTHgh(O9br;P?``Z+l_Zn851Hvp}&k(?23R!0{0&uBmOU* zvyc$;h?d9yo1HxLBW+L z86J;;!&A0sR&m>wBMlOiOExS}T3*&?=dyo)i>D@aNTKEyXi@H+2YMYToVM3M&hqe} z%EvQ)pe?aw+3qW$Pe!Td=w%$%;adlb&6>cz!X+WfZ}pw6UMocO{Ez1-;|PAhK~0`Z z#|hu;4S_H`k+GY#(0_j4=E%E}Qt*I{{PKPzfFYOj3t1mJehE;t9IUbWgWEL9cT-kg zn$izh7+0UqYBlZ){M?o_@-%cgzSYRuRUCkuwvL6LEz(tY2c19@K{xvNCmZ)RjdAOe zDMz^irh4j`jPi2aFlB;se&a`wY`=$d0I^K!ZqNN-;m84k-=QGU;imZ+dA!u8v+-HE z(@Pg%kazgY9)tJ|;;3YqM}@}UamLf{pGH#TC@nMAa#*O>vJ0O=N(b6TKoykK1DdZJ z{gh&&lpk1jLdJ7uiyNtKxR_KDYpk@=m#95wh=8DzSpdhc-vQR~SF=2KC+BLqLFuy6 zaa)-(K!rC9Ly=mf4l&+Wpy|2U5L|tHKDZ|-^ORI#=EVG_7QMvqzT?u(z5@1C&oILC zQ^JGss?RP6OAk*Vg~~~me~wH*yzd~BS!Soh*tVp7pXk#hN>9XJeI@Vb)pI|E2~R`w zWOM{@B6#J7;C%;ok^8~u`L)(Z-50HA#Q-)9t?=A}o|AfW?<$nUe)3mh za#XFsYfEQPhN8Wu)kn3tGT;jmBNr?N?H8lI3!TAsYohZnt|x71f+qBa2@blpu}R+wrUtB{Rwc}<_}xjEa#y*a^$e`Gl8`g264JJ*@1P#{MwtEyNy>tngY$cL0ZX%VK&yT_W0$hc7v4b^DM>4y9qNjy zPdx%nTlg>)E8|HJmrSzSaE;Dkr=f|fNLe=gE*P6J+N5UHE0SPLz=pA4FE3uNjqOs3 zbfW}|?wl+FVVQ-?%+@nbTcoj`S8S#Tw3hjYb+{U*^g%P`ggUivh6XeVbN9KDh>A%> zaZ~}U4>!oH!JXZGkvr#hbtjt9eEkCY`+x6|DZa>aqGzKfAxG(xz7vrpg4Qo1xmZ4E z9FPXa$J5WL|9xdpm)umdZHgVt@D}L66F~M?ku@v89mUpu=|ib)`DsBUZ)lD64tBj+ z8AYSOL0y(7Ogu-HkOCVFiK99Q@h&uH=4Wi#*Ft(qYYgsj&$u>|xdM#*U-(MuiNb*% zPOxNJ9n{g)@wsiS{8#0PZ;{gDsjUA10zSe7Y<&Rv7S#=#7^ddI?I0NSk2^CTZ@VVK z=GH$Fkn6nv6a;?})YICH$K>?@ zHq7|pb$JCJO8_NRB6dng1@Jf4iy=&!7W9$Tn+Rg(Q*HS%FL0A|woO!u#rX&E*eSyQ zPFIpF6`wRLXb$e_TyK^{Go|Z)%{D98UZzhydI1n41c`CgL9j`H*R8hf*`@{NPPlusDE)*09t^!(iYsaS;L%}-d+C$?WDt=zGbJ+5fdEuCNw}X zkOX+&UIbAJlW=YU=?Z^;%J0j|_F@h#caLg7o08ZL)QvE{5B5cyBYM?x%YIsdlq~8q zz4lU~y4AiuK-@L(9DW;=ZT;Z;`)3D+b6Si>f zPtP&+--|j&hLQjrhsCbQfh-<8C6$of6++({Jo@`ydP40aksiUG6qwv-9y?N=Oruad z8bLW<2Fulu<(d+vW4EroJ$eXg5|A6Oz_>gBB0&0Y((S&t#|cWvAs_hO2t?4*vg{Q0 zJ+wxdE7JXl#}*(Z%!ICbuDEglFkEQ^kcNMPZHW}j5TvgYrn@!m6-iOh89e&q3h3f| z2OW-1pbe?Z#eW*BQZkRkBuf(!{(XRzF)W_KQW12CuC#$%UwJkhzr{k)#RHNokj)pT zxB#RMBpka86i|{Dtc;MtlMGK?ljC`B?AG_S=Ann3p~7P2cDAyj&NhFi0!$5NDtx~tjg<8 z%|;rjK)1(RfWJ(xnS?VUE>0>NUC^4Jkh=V9*nie1wL{(Lpj<@y6qMx%yOcVWznV@> zS=W>SF6yS6J!iEJ?*E`;l}c8`^I3Nl1x?bPuUDv17>W`~T0EiPTDPaazH|A7qsKO2 z?f?t}aE`qr6a*7YEPv~KZS9(g)X5q+k75G`AO%|j2+swy2eajB@KFQ{K@DGP4}>B zeKC#xvqV0(SzQ;&VLHTU5MLw|CEkE?EDU&~`>nL6RPW=Vsf_wiszIG!NneMW`53?d zY~Y*tKUYx<(c@K!(G8`%@%P7i+7hM63<+Htpq>|@Wr;fZIsk0~fU}^? z2U%w3bgG7oZ7V(5K<8MI>c_*5@K`y@eBU74yO*Z;79Z9BMyck*LGQQM-94eLB1FOh z8}7uuy%i}eO)TA!&s$KUnhGLxow1DZ6tYgdEB#}|oZpIGam7n^Gz3}0` zHfvYJ*;E<%izGf)nzlT9&8l^dEV^KCxiL*_z{dzU@rNfg5+Bg##$*64fOm{*kId>+mZ;usE?- zs2?i*m78nHqt%;LUC#vua=~@t>);wouMQ3#;cJNlQV%+Lsd0$6)dp~5yS010ovbSj2`mKPhy!a5N<_f-)ddQm?wp@1&} zb^Kkod#d;NvCm*Ns5X4nyxk--t^s@&ryUz;Eg)H(Dqo}W#>Y;8J$7JPCk;jOK~EGD zAQtVD{Oe)mm=c&?0j}f^++8O=PQ1+OkRnQI@iH-74CF zu{Dk~{8srNYgvFrH7gu&f8X{8SOmzIT;M^elNk{AdMzOhFl_QUR_;nZ05AOGcTuE) z*dIPd^Z8(yY{9f9Wm%{^Qip#@DtmS#6U<%aAe5dA{%C-aNS6C^?jRup<2{#avj(DT z!@Rq>?>x3PwDwSImMX-QSU+ptuIsw2#&bvkDo=RCNo*GpPR&#X(DWa3VQy=am?e|? zhYg$+`fqWKUi?32l!Pq6pji-{e{K!$(t zPr>jH#Z?y3r3=768q2@8E$I`^n4anfXd9~@FGv~OpLMI?LmP<*pc*7C%y|R?9Q!dq ziA`et`v5+hgLMu910Gup*IeVm)q%?j^VlqBjs&g${7TRF9V~WS5Y&PU6sevE(;Dz3 zRo{D4xdJH=QGnOE!#JckYc$W=H-wcHaXS_#cMW>FA9E}77ywV|$e7esI7DO#f|4qC z>&Uvbof#Z!hLVs!(2%&T+elDH)%HrO@OO!Rc-d?>;3M#i@SIW;PYVs%f74+()4cgA zFy_sF?=IGAM?eS6^sw;m`~rfCeEuq3e?J+N+B&v_XBwykCKIU=b~qf5!Sov`a`MXp z+g5ze)~E6Cp3;J8+HVv*R3b}}q-4T1Roz`Lz_bL5$Qn&LUNc7AH@H&0TLq^p`PyKx zgaDd&0Gk6Ol-ovAt3De}-|}pQ&BiBry(m$AtCYSu>zdY%^PWLR%>!AH?FOHO70T;E z+TA@~Y@kJgsL2}AoBby-)>7HzZ*aKB@m1S=)$R&_@d&RBgPypOB+IJ!qtdxq3_R$^ zU3eU6WcsO(=&>n_<1FXCJtqVq5>x!~%u3~;VU6vuFOeAp2mY5tl^z!C*zN`5(plhG7^asvvXm( z8*(%1odRixK7NUXT?2!pv<|)j(us8Pb0WEaC6H0vMLmJ`SlKZ{z+(HKPLzVPG2EF% zP+ct|XjgPksW^7VOUbH1x7ex1LAvlPSzK1#L^kNhX4HLt1#seIKb0L}5b=s>uC|O- zNdv54ru6kam&8FXd5|Kr^X@2vP6n%;EGRgU0iPs4P+F_6#`kf9M(cQT|LM~TDhBJ6 z#P6S4bNHQpwwfHK3x-^p#;PBI$u%QOJ*G;V$#3M%>PuMB_RPyj0|UR*>x? zo+K#%AQ9nibB-{U#b2GNvPMDen$lKdXtX2p2!wXj2 z_QZaPiw;Z>^bbAkhr~F+SkPUIo^!=C`k%{ea3l%oQ&fzqxphs^@SOCw$xK$zRWD>D z_|H_bATWAX%-wva{ZsS&9;1x(A zkwge;65AO5vV_i_DjCp{U7Bob546?hE9*#ix?0`wfv@RIzihG=m=w7-*{5HI*I-m- zXZ)49ZLsj(hWn779ZZ@`2!Uj06sdZ_zIAg?3jtOH|0E$TbokRAqV?bdx)Vvy|Ia_# z8wrTLYVo67uSse@*XZ(ClbA5+5=p?NnPay7Ul`z$lK8kYsw~yy|-F ziOV<|*ycJ1Dwiv;Xwc-xE8&(E+tKHV?Vho=!Xp@U7BEZ(FtBsu!)|o}$xnXviOMp7 zn1WzZX!2@o@;*AckEHGkzi}%)SP6nCh|G1SR|+kERSv5#_dD{cfIzPph}fk2?2d%? zP+)*}nG@fg78$~NwXb?SR zPZrgwxev#lp`>^Qud z7)&>;=4q4x>&~Yt;ozZY9u~n^3q+T_$S6ptVgPUrg6zJLpFg5EILAL zC5fa0=m#`fWz%_40io~ zd=`|t)+b*%64!IJPE61)x`V1YK#!u77ZeC`2WyGU7GGG-L4VJHT)P(;ic0c^MQ-yX zzaZv*+I)htpHMCS%)tG6b6&?cI+)T_f!x5#9*JE-2*M-iZPrCV=Q+|O_ROnmxRA72 zp}%r~mUs8U%#3d5UoGe;powdzoc?fA^Vp(!Nu>o23lX1}$y zygeLIx@ZuGp^DC%0XmL#A7YU3PWK99-#+tPc*JpSTIqjo`xdsSBtqeXS^50I5DZhV z1ku6wSM?WMt($4ZR_F+XOr8fmI&4jy(S~Om#2z3CMivyzF&bT4Gn1iV6l~YgAug}4mw_ebQgu@tJ6F1A!#wv{}>3rcrq;B zwW+?m@bVF$bQqy@>!pQL+E(LLzPZMdOtOQz!&sH{b4feFXqjI{_2wJ>=f+1fXD`j2 z22}wbAz{7&=jLgMbKOEmnb7v*iPkvPrla0do}lF)1#mbR6GO_7`Sk0% zM)rp(9Q%KL8_5;xctzJAv@>@=IRapaNya?u>?@9ZgA00#vLJ8eYO2)aC_-!446z>`VV8&zI}meJ@r}+AebS_dhih9V^TnRb=whpa_%wkNT@O!{!#I_N>tcOHg z3P$(7Lp&k^BSE6dwj^ERNH60=x$C54X$f`f84bY1jmSwjc!dnlupru0Clw>-PWs&P z9l0kllr)6MXYWiR?CNC^WnT|@@{=_tcM|l{pd32bTc8zqGc5;$w0ZR}&q{%>f#%Sn z&I7jWJ(2sk>BV&6EqE0nxJGFa@H;2a_6!Igy(iJ&P7Dw=140h6MzlL_7T#GAL4d)| z^vh%nqNnMAvq1pYiOU>7a-=cuZ;5+OgYO><{e?w97 z1p>nf1(I?fdC(7`>kuUU-(4vF8ItaT#{`qgPYC;{>?hX04!eQRKlFjy02E{gzb*VN zRd++9Vz<|ln{1(F!KXTi=)Wnt^^cfD!Yc(;=f?}!b)6bXLEyPSD^i;Ak~Aq?JG8>1 z`wEAvQ=wzobSbBCw!+=4(4ghi!Z?9Pp_08Wv=opcGOZ7}r!?H5C&HIx_AO2FJ37-& zebx-h^>Q-{#2x)H!CFlYbwtF`x!7gW=~k%lw_nev5#|0B`x!7q!^MjkXagD(BNs=Y zN|T>gyXLmIqI)o?LQ@sgeN(^2!xy*pNPC~>t7sm4^rzeBRgZDwUj5;o!cOrCo~Gq~ zG6Ya>Ua{vbz{5K6@F4PLB=D~cR2Fr*3i=4LHq6%kwAv;6g0 z4`1%HP`;JI%(>Fc%DFAx)ONEN-gdL0heGXi9mTYbMN`%12cksUBwsQv?D~l@Ts&=f z^QLoc!KY7aVRSEOKE-x!y0~+zLv;g^u*%i3ys&amA()mT=nB?rQ}c;syX{rP#UYET z<{G_*lg6;#Rm5+WJvC(>`I;RABmMuu4D>WRUlXfm<=4{O7AC zp&sUzx0F1j+W-4zZhOJN2L|G=XJtF0sWRQ-joL7xyX%B0Eih5I0dkP&$3cYT#J8)eXymoO@e~gmy-w}Y9>Mkpe{;vBO?mXbUmxVih8Bzx_c0=dh=_mja;sp zmovY3s-I%-OyL^s(r90~AvX$pPHMm_O^Z9Lu5ha5@WAe3bIj6R3w$3Gv7FfH( zt!&)=vhxtvK^N>24Jf%PH3^<#Zn>)x7?tNL?C}UaP~N;FA&~2uFsxiqZyac~X8qc( ziq1N~OCdypA7@$1bM!Ir%rAe|+ot0QF+UxTUwx44q317O2-FS7q;(}hx?dQ&d3->p zjY$d4Hh}yN7YL?vSq*Akcsj4)0zRGXzRcg=^}Uou=2xSi`PTNG3(`R5+Poe*<1H~i z)uqa(-@2~H<$RC1GP+lO+`+0fj%#r~UjEZedY<%iuSx$5peTrN^RvcU#cnlWS*eg- zD@SG~+kLhHLqig1!ssH5Mg-LF5Ht_6%UG3<-2jxP9(d!eF9M8Q>RHDtVgOQ>Bdoq32g;9TmP06_C{pK=W zwO>9AzcTJc&LCsH^v{B#W@sBnkYxW`NuMA(Ye#v!|EDv*;&TtzTuwoCZx~^hPfdIf zOE~)pBw#zLIiriuLvc~gybMj7S4`C#Q%}x~U}7C2NxlKX z)X9Oa{G1VK6Lk>rwd$_{>Y3g-SLSw#$wG%M9uX3a03G}NyN@>QRE6g0yM@J?g|ATs z2cFmU$<-|?pUrGrV$$|6r&3;;$K~6+e)2is_^#B>r~cqY{8&5c#U7)~elN$ter>S% zf_3>qx+PU6uN4Hn_l72Z_y9?t%mN@v_2#PB*42N=%A?j-Nh8U3G!nOt^W#PAm{|YO zK!@x1=Gm=|XIJ)_B-(zybe(d7%;U=(f7Z}1Pvzan-(MD+J8!6rg3E68oa@T zp~>8)sd~WcG*8kwH?OTgulg9(vjdoW;glF~gsJFCRs$u`7<2pPqbGB+`asVVc5B8* z&$w`D31tR@N%=wsW1zh4mO+jF=g9@rZLX93nu*V~rBW@Qx9}Rw)%EqU-q9KBnjQHv zxWbli(Mvzp-mhy>60b6Wb9RXEU)4?zMCqo{CnnC4&5Vs}cAO!6pP#5;V9N=$@3 zK7%R#>-|lGqpcMC_IVxOyO}96SC3uF)yFGUYP}AlM1`NH7FYCnuEh)8RL(U1DUBJ* zcND3_KDA!X3CriHJ?L1-)+)Ir@e}gl4*l-Xi485CF_p95{=)?Fuojc*CVMUj9oh93 zd?nR@(#&i@$fH?9rH)BzFXSa@w}M`W8nBGJ`Rdh`<+5w5`IzbX6&??rcUPYJ|MHem zIvD!HG*_Cv*R7rQ+5Xe%&$%6>EF zuSWtW-z{nKF|KE4mA)M;Wj6S4C`pq*)NW*sm7}8T#z4T-&G$UtlsjJeDz7hSY7&S0 z8?PyPJ>;=UtXijae%Cquh^050p?Q3})*@@wx8?ZnDcG#y*M#5Yt|8N_IbeaUEMuC@)`9DM!sL;5Dps*r}m7nj&M9%!917af>a?mGERk|XiuuMO7K$oKTp;tromyv?*^-`(-+ zlX7*>`Ze{~q2YVl3lBO7s`9&yvqJX{l9u`NK z)XfLSa5CDH%k%8^pAvjm?h4ktb%OuLC!Phhq)F0j;3cs;|K0!E9^^wQLk>3Y`Un95 z)^$GkdKR6916zumY%JiUQumw?Fm3AzoMlYR-L)cOKsMn&*~HXpg$C-zgT(&76>aTS zt`Ph-n(hoZ4LXV&e0JH)6@HNL?}K4h1H=o9ATrz&*nO6i4LgX@lfOsxnrNiPk}|+2 zbX`X1p)dJ*){7|v4Q{4!!iKhGlY__~YzE-gR3}T8IH8xMAj_RUp(Qvvc6gL7I0@Fw zEivTwjUyZcoH9!P`|SHpa&>aZgMyR8pFg(q8=iU!4@v#YgCble8llJPQwY2Vp-ik6D#?UVGdnA7HU| z(fmiCCh^@{@5-HFg4Td9f{wsdq!JC4ZX%cY_j*y_ywrI(V{p9*vuxea1m+4_d}0*v z2A#P0&tUHel1oxkpJ)PidhqtOJZefM(G#on3*c{c1RY z$)E(SF@^7eR3y1h=rCF+B7E-8Dh*~xTm4eO3yicXb$|;;iW)>_GXCetHzuV4ktifE zu0IxuLxj0(->*1@!eX}Yb_7DLt!arp6h?K#n z09%B=;8W@iSb4xt9Sc%kfKOa42sgo}|KH2r)&QFTeRR>*Y!$;?wLnYt#<`AQ?B&$3 zXv&O$A6)w66tH+;>d+JuHM{4w>MFjA>sGR{OX^2oPxXD(4U$jg$!|Yj&;bovcRF1; zYPGu!Rzp8iWbGtZi1gl^qnugKH33s~VR1n8n@q$c(In-%)MxTd=?V*Sw-mEC>q=zlhIPILd6upjIw(#4_ur{Wz!R8}ueS zpRqSCbo5>hzfSY#@7%DaL0>)n)dip9-g}Ll36V*)qrCM~o#4GA5}1v3Tzi8SQrlY@ z%5%71&D|At+fO`t&z+CK?@M6m4zd)P%kn!6`#GNV5%oW#-I)WH#dzYo{f==fh6k;8 zz}ySIibZ!^U!*(NuqoT@mnHsT-txw3&0~d8VU@YgkGqQx!7)uIzFNG~T`vRgy8Xcp z-!MEk&#q$V&V9w{W|tpID@ijuMD!XtASfQv2frYL(yT9~OV&%3>|OdlrDtv)WNp# zXfE2a)4B`fw!5QfpqC1W+??xbUVg%TNv>Sjtmi)vJzB6mD1AO!WnN{6&lge4`BHlL z<_Wm_FyfAcCqjZh{qB1a%B0K9DV|&G&cEFnzZNedK4o}Au2VyMXuB_gjXoHdpEJUt zI#$8wL-UuiMqDLx$fc&n_M$F&tSz7II4<8+J$M86Bu@5~@3zx!i672X2<%PSGNTX5 z@QUnEP&Gh{V@mg>tNG}&H~)SBH!eqR9On%x!{en-+;g~v9?8Dng$aKceRR~B5BCfL z$)$)u8ia(&3CgN}%5*@*Y=;lXE)u=bUgvsR%&#@p9nOR%o*@dCY4F%_mp8{I!=PO@ zaDCG3G@sL>G`m<}Cg>s`*jOegRe4JV%J1BH4-%w1g?R;P#t!*4bU+2x2@&JISv<6S zkKi*Zg%p10E&Ptb2e$SV@R&&dp*QSUxWJd1fo~#HlJLPlOEiC7f?bY%6exQAEZF4s z$tM1!=eM>m$`2(xDrxm}eUud}3F6NQ83rG?FV$1hbkh>fs0SP^s2<8**WKZ{RPVw2 zzsXpA$1>-bj@4~si}iRI^LB;f%fs@=K7?T}Np~jA{uk%4mv8CrIBmzge1N&ZW}zzK z;WVB?hSL55yxB>9RmU17)cpV&(luI`lN~?YQp|6KZQ|s%e8_E8-gJ+gieyX5ciA>C z4{A%F93N4cDfaqXb&5U^xF%;L?ANhsmAs6zbJH)9Px~=Zb@ZLTns$ zMtJmn(Hp=;{>uQBQWFV(IjXM9G;*ps!>-;!RcnEx;r9;bI)iq*rNR9{QwXroS*(*S z$^ZQq3N_*|?d~*wCxpEbi1&6zSF$CMllSetc``aSpvI~l{N+h`Tu2TxFZwiO@bo05 z_>9ttPhX!{E!RM~?0?ANSP{JuQd-%R=O@Z$yZZm|bd^z2wq17+0YOR$rBy=d2I*3| zl#&MNmPT4BrA7(q?(S|8hHi##iJ`mWyT<2T-&(SmA2M_0Is5Fr&pj#Z%#tn5OzlC` zVDX#(yQ|NSUP^9$w;OQU)flVU>}=UNTl$uXA^b{J3VN6#qRTt}F9SAWvmXq2*W(cuQ4OiX{&{V0z#EL&DqxAd6IzFitsAv&Zrrm z^S})_yF#8TTt$7MB-QJlwsLj1dvl&@CmTXei0d~(r(uYfwGTmnb@_MR+c#jWtCl;z z9Ab>l5(Q*5m=)i9r-0m`Zzz2Ca3o!AX0rH4DkO=WTXNX&3(G`PONkbU zcllvL3}N+5vQW6bZDO{JzX%x8=YK=e+i$zIytxb$T-B&K=5uXJbSvuPq8EBDuM*kL z&Ca5_;&UEnym9qR;i5|Gev%(7`qaTmE!bXXxNd6*Xz`9=J|CbfP zokgT1^d8T`vDC(DPCaxDeA`SAF0R`S33xIQEFc2}*~VrMJ%e9?wfv7m(1Yva8`sF8 zPtp2OPnY$w(6`!KZ6c28?RWaMl=$2IZ z<>;9SUBDLje_C-Q8LK42!C|RlAf6ii4;KK+bo%E)>dpnubi78%Aao4RO-ai$R^23y zi;?@i{i84LMjFuoyhLY>Z#w9HbSiD;r&c5@w^GxZ@bN<=XJ`3aeGZiA&55lPY|k`S z+5e&U1eD(^cvkp1fCjJ=bWEwC$Mx9GMZ8KVl)%%vDPoc zk&^kY(U#^{F@Iy1kylNlnkvTWa7ig9riN-_4lq_%$a^g!EMhZsrlC1qh0LC-a7z{2 zq{9L&bZ32W;~&)6@sEpERpjH@S(Y<9f$q~7^zXy(J21vqQZE$lpoI^;%t=T1PY7fy z@G%&J^H?XJP``I`PpGt5If9UaG`C?eE_b2XS$<@wJ%vRmCIX7Zy;;<-j(1TeyQL-I z@q~|5eI0i{zTuBQh9x*gjGPMm%#4AeAVWU#M}AgnkWqC1^sj|fiVM@7SLQ=)BVTf) z_%8p(^5;m!5gM=^*O|_gq3d3BQ9AeId)y_X@!x&XjmZ3y`>g5HRiX-(yd~L;^NIc$ zAI#&XoN&&^2L6MpBqz|cpn7$}>Z%NV=M|e3c!MgJN%TfmVGe0Potdlsl9A(-m%z;g z0WI&}$uiOcyXk0gx&sEmT88a5SR0F9l(?Mhpvm5mS$RH$vR8LtcXfhT(vlfj0faRG zb$62Zc!fR>)wP_$r}!P+x9LTS1+L^s0rlQg?HXtjz>TUzmVi%|uck@I z97kUC7BPHZ9Hg&D$nf-3Mb=VVpq&y1OHTiSN+qKeMOie;K{1-3Zrb^ZE!8es$%j&3 zKV82iZv9keVq_=z&}WZmqYaN@v~M7hvY@JF;B?X`Mf$Bof>FMXcT@FxLX}X9VXFsV z04t5lWx#QH;152<6nU5pR13;0w|b4jR|gxiopB@qwD_eM4v*-jWA6Q?ZaOmb`#Un< z9R6&|x7*!t+zquTbHC*o#Ndh%tc6|h@3rvAO|Z%xoI7H7!W+?T__T70V5F6+=1p4D zN`f}lowW~``@g{>K$Poevj8kmj3gGMb>m-MB^h0f+t4hiUQ+ZKCwy`JLz5Q914{*6D0YNlL!eFB*?iOpjW`9>(!f8>!3)HLv1m zbckXT>_^V3Di`1H)B)MF^2^)1*%uO_S{!^@asutCStNAOyUzgD#s9+{EsllxZ}r#s z@}-;m3N-Xgj(pGVlj4N8zHAaW{*J|sJM*-6bH-^(qCrh(W_msGrN+kQ;2O1a{pPS& zM5dZ1yE`JBul>?9S@{E~vp`UdRmlP2I&WmwCqUW3sT5iGsb$L)WOtgspKT4R1yk)s zPIce+YM%Ael$}KleM95B{v;JBs1FLQVsQ1VVCEA;BWbMRV<0?+W2MXmtclI>8hQFF z_HtQh24?(zNzdRGD>c2NVfPndM<#noJ}8Yn%D9k}K{yJ$IdwNv?e31QSu_ZQUH{nU zij|-nJ|TpGMF!8>nB~zUSS7n$LDhnlhDpapC6m;%%2z_uAsImK0MFwAYa$Knx0#Ng zNZl@%Tvg|y&q>#uS`G7S_Hmc39Y`k0>0NtiIdFgQIEJJjH=27hg!<(2S~>~fd)Jva zbu~!b@QWlPI*u;sm&Hiq_jh_vE%axO`s(rpi8<9qKL?> z$boI@>uEi*uC?C1ZYbq05z{{0G^widArs){n|*}-Z;+Ac5-{7nR8Usm9(>NDLBQ5? zF5Ez_)PDh3p2hp8F&`>#KNZ2v1oH6(SHJQd)LVT>GF zu#!{wtOJhS%RW;N2^j(t`BydSQqZ3gP;M0B@(VFaMiR6)3cg>sn0T`4?yi!D8_oB1 zb1LsAzt&Wtqge-Hi;U|L2AFf~;i zqnQOy1mbsWh2NI|S*}PxIb)>0*~@^&!rr}d7i3Y&&SM9`uy7!exB$^!dT&O0zl62t1pJ^=q`P?SA?3Cs zI?2!0Xh7TTLPBOyRv;e?w^I>WczEB5ypbKLjCxSi!(m|&hzw)g17kj`-QccF|Ip1j znW~yzUkfTPAbyDmub{*3jaa1ae$NGEM3%qmgo zuy_2$_LFR{?2E+lzT^Z2A-wQu4tJ88IK+1tS>X990z$z3eB}jDV)}M2j{Vt+Vn2>7 zAHM>hOEOZ43AAM&P~n>ivyT2;QD9jIOiKMu=-9i7oQ$lw>_Lak%I7)iWEED`K4$fpKBckBs86iW!!c~S zb4+YD^Y@;}`BuFW(gKB|+Nj%du)x=6kf*xHi1MQ{rz;(3a*Q> zdFt;ASE$?+yvRtce;XrQDHUxxF%b_ykIw?11_HBrN2p@?Y(`ai4)_t)nJX&YB@>lk zD-VH|og}6l1~i#dkGZ#w1Wur+)a7DrGrC&~lJIF8jLKw7;uX*{Q-Vg*bV%N(f6npY z`#UjyRrp3B7ZcF!ZXbsLMdo;So`HhJZu5vdg8D)IP9J9d$EaiIJ>RlmuzPH4Hr-Z3 zNq79)kN<$@M{EcbbRqE?dz;`{J?730z)9H6YO?j1r2Mc=aZ;_HURcP0^ z*WQzjFU|oGnY~ZwHix-Ky#!w@{2MP8z~`<#$uuT3fryBU@AYoBUrn>Z6A^J3vP|_p zx@kaW+;Q+U$(8|f)-ch-$iU;;y}bgsfJedcz&t}$<;-Q`9U=fxzg0jGbUX>VFOZlf z)B+h}&TgZ3%Kx~5%8v4`)2Zb2{^#S!k2kwE6L}IKn-AYiT-r_#85qa#T6nn>!KsRc z4Bx(0(p9lhR|p$E4E;IY`Z?EyIM(^V&nw1D#LF^Nuhz?0z6Ojscl>PEX}9ER_+Cpv zIVp?wA?fjHvTRdDjw}(*Zo&9K5>ZLWIv<0dxZk}-!QZx^@E^X}!Wti1@jzBWH^;w` zW$HnfBcXcD1~^5>lb6fF>z+bMkN%T@eA%~>n$#|LMJmQ^cWxD2KdSynzJ)V^3hMCE zZGK)QOZ(2gYjv_>Aug+AfLv3>`Ts-rgyqrCOR>2MCTcBEgOA3I8gaW4a?R}QTn(CR zjZ?s`$bCSX)rww?qv{b_N>HL4t49sb|2Z>|n5wVGKLw*E)g+A<;Ab?*Kj}^*t(lMy zemLH7MBv=r=+>XGF}Ft`R8UJJrJq8T%qt@zXf{$RxX_uXC3O7mU5d{-vN9EE7P9(~ zOaNi?L|!GM_g;=cEMDeNxxtig_!-HKp92+BNVh8NZF-7Exqar=XZ7POMnKBlsyJ|( z(ls7_8Dt4^(2j3GhBF^CqokR0aVeMMbyuA5ibVo+43RH&cEabqMUr6q(qj#PkBo8E|`T3O%j6=jyo5CKyqgLU-Q<>H!2;@I6 zK>X4Jukwf+E848IOZX_p)$zw}fHk%jQgZyeo>7JMGosyB(|lHjFSNiHlmXvRJ|4_hsuE@ijpJK4lCIRJB|9o>f3yu-3A~p zGf~CKncYnkNFDdBn?z)itWX~xK%ZQ?y}304Y!_0I24tpgvyT&>m3v{aZhWEKx`%C5 zVSP4k|MkU_ft!Dz4|m}>njz0-9JmZlI)-=Y4ejQHltM<*y*MZ#t=&krDwMBA#N&hA zZn4v@l>gI{fw?1YZj-;Ng#eQ{uTpy?^$Os*rYa8)WxMh-lDpr2nn=m(UP?++N+iyL z&QaYKds(M|#OTOZ4KBr9vtu$;lB;w+ZCrv^epD?aZ}~MzP%mi}a0DeUXr*<+n+xWc zlgNv?4pcRg9OBQnYt>$J@PrWY2{ z*-D%raa4Qm8j=3hd3XDDk^rOkAMWha0Itk zxVr2Wd;{exav>1Ze>+5vjBuji-=b|L zQT1t9T7LtlOE0TV;mEsgysl-Bx0h(|OEL!qPXpYRe&V5aZ&gDV$KnZc*d`AjJ1@<; zy$wQ7K0JaFYY5fX3~ovnnEgS(Ip2?unW{M^DD>}C57-nW7$waV!3T^eyuvREfUr$U zm^zdS2|yzwA~oKg-@BeYY6tTaM@4nlo$%6k`{XOXBo>T`7e&uBl%vMhL^NIrI6sb} zIjv$@$AxDb2{~b-y821EgrkcsgU8suw1{NKmq=Lr*|;(JQ*PnAg1tG!?A>-w2p6rz zU)6W_AeYO!lNVvA5VvRZd8 zQrFbtH@2ucdA<9<%KhqGIZSxh>|(#{k7-Q&w6&#Wg-qDXVkWYDP$tBo~q9d zo!arL7T^~tgj3G&H1HRO)0Rq~0!z^q)O<3aFl3ZMyj3JLR}hGF zLESY)6)543ZvxFsvDWhnpbS6-ZO^vYYnDf3Na;b7sFDS`NgzUZCIQWkmu@B81sIS- zK|oEm#*Q=$_DkdWt|w7JGu*Rk2g0gQz9Pgxf$%(e%NY=UUl8m9`CVaH(0$ zwHC|<+(&=KEUapQX&C?XNqXfun?mrz;mmVo@Jr1*^rIQ4 ztBkvyezdT#YKFjI^3YyXaZC+`tF3E*BlSB_i-ReS?21<~Y-(58`h*p0yKIJ)pg`J@ zLJ^y3)=Pg&hp&5N`MBy9x&_lkJZL0JvsZvAE~;R+sgt$5x$_i9eX_~rJdEu#BFGVKyzX&EkaAGb(Hh3J-L-y7Dm2H zM225J!-`_3A~jkBfj}G_7k6-+fU55WZ7wql`xg z6Lqsg*E9N8Y@%Y4Ff1LhBu0BQ4D?rQt&0w`pLIpgPF^+-*y}{}Y|b2C!yRPa8-IIO zll*gv!FS z3wl4(N|=gj8}Q{?cGva-~d7)oL>I<7ZfZG^jEKk9yQ(v>p1x|HC5E_YnU!9bl^y= z6JjRHISa1F3brrcGr~eJNt_}-DwR&{6pOus?j(!7OBknf3{g8#ps+OGRo1Ooar9C- za{&%RQY~^=;HB$OQ_IIcg%r5K{q4@i2Qp5+fp1%4eifFKkh{KbQ_>EaBRTvFn5dfc?z%a{BTwqpHJ*f1BJIkO$)lq{?1=Q= z&h99BK%C!Gtlv^de~Wmpe@j%p8$IAY3E}b*?OYL0UUQu;PQN-3CkmSDBu>C9-l~GMj(ot#P}rG| z+B+sNfMac8p;5K^H$f()aL}=#JqhHqs%lp+*7B{Xev@hUNN{k?0yc%ZneS3G6y?3n8|C$2RlkSSz<0yYA5N*N+cpHI6l^sU z1vd8gCNcCjVFTBv-lPG+Nl3IgMY|Q`S0~k>CdLg8d#Ea)q@ghZMwX?3VrGGv=16Y7d{V35(Ve36nst~2G&x~lW%z9!l@b5DWwJC}>R;}*ot+0?V{BQ=-yj*(?jNKwz> zk^uyA^WTYEalihgvECM)9oTz-K4IDcEn7I9mJ<)(JdJ`amv><6K zDvbl?52$xrgH-BPQq7uRuw(pvLeP)xs1zvB1Oqd1omc?S15Yl3w1FXLesj1>BO*KJ z%|_t6%Gs0870ZbzLm)a`?u&*2Ptf|xy9;Cm5+6@qEVo8mOTGZyxAQLmACn*;g4aAa zzB5QkdEk}JB*gF_GF@^v;snA5{|`+CMAqk26ROsjn|;xII4Zfz(MyE$Qm#9wDXRp+{6=a7%-x$@b-H zHx?H8l$zYjmEU!kmnp7?Nmo?lxpa`jRA7MM29#drZhjeX0~gKc7Xi;eV(GyJa8N}* zCCXd~A1ZZM`q`01@0p&0(fmJ_zcUJu(lIvxA%SP{m`jQI!6`O;oJ%Uq{`45IZOorx zjV))VZFiGCAxH-~Oh-Jqc*v}fp~P-nB|%mKI zb}kJ7($S3g3l1TUt_p1THK*DWp<1a1tiH`T40E%zDPBLa)=qM8-u9pTyWVD>C0!vP zx~9M6iP~r|Q`$estru#k(h+fIpXeu%k{+LYNyuLYEOJQ62$Yunq*+-bpN-?$2qL)k+!IjTo4jY%?!$)oZu(%oM8C?>R4BW8pWV;X~GA@ zzL!9Z3029qKp<>_i#erJhh*v zmsu86^6z{?gAT01w4>I$_&y}`KGGQ5nVA5Jq23t=B!-H}z%o};4;@q&z8nn-0NpfS z^`z#4MDog>BeiDW`{4%^23=>^99@6=>IL&EL%xMGp9|1j!$Jr`vjOredQv)Mxvc#_;A>&1L_1ws41g);|}0d9Lg#>eg8~IS@Sn zY;@6kcV|G4#Q=oialhJ%Zum`0j6+pU?%ir^P;ck97Vi?2QRPEwv2H|RP7Aln%&kWr zWu1L|bD0DA(jZ&uR92OCuWmS~pu_8D%8{F%ub4!4?kU?VIzjXXZ<{ff#O zpmn8)!lh`C(*4L)$$gV^0;{fsIbEL+5{mSm1LBbk7zRDel$>^volH`!Yq{xZ{RJy- zPF@qo70pB<59!__pc-ZtD@sogN)t-sKo@J`uP)ojVr6tHtsGvRJn4;$PG;lzUHvam zoY?-N4RXDYetes3FwvRFpAwptvtAtqlvtdB8%HzPR(=QgShkUW@vkyPH?cUuL-ig7 zlMNuU{<`g4q|B@4hYc)jG_bt&ncsG6I`X?f_IZ3SEL`C(*%90=ry@6hN-!>>>wbz7 zk%>c{xolkc*+;($xKZZT&ySk&M;mumB)D0cdO+52Uf|Y5nyn1}8Jvyg_ev6%4`;TN z@Fq5crA~3L#$o`(M)q#htfz~Ifer;k`{nh9)yu@6ed(_?(sF7~IIYF{hbZ4wEYZ1Jd$O zw#r1hZvOe57Jzlc>b73#tVut$I=qd!#k<`5XpKD@UR5i^YbKug+wa0|dd6MjPMTC@ zTqJ_foJ$$)uhCfvzLDdoGJ^1XzAYE$hpie;y(R zxFr6)TU|Yi5nU6DiA@jJ{M7m@k42EU3oFddO~>DWRtBUrI@I0+L>s`PA-yVzFwkCh zvMBYIf5)d9k^IE*O8yaPsHI%P{Ip6|N@HRHvRg%?`mBhHJ-|gN!`u=)LLJSZOK(P# zK4GqEdTW!`sG@8~mKQV;`JBV{U#pK7iZm~{zHQYWly>&G0g$ZtvRALEqQ-Kv^LR6S zB_4^QRN-?^Z&JWAxeg|O`q}`l-%sF{##7Q206i4D!0dV1vGo~vPIeq^4Yi_>DxTaE zno|VcDBMuR!Ye`6O1o0HL0zBRW+?nRP#F4L#-+h{gX!MoLW#+yAL{skS`6-;cR+gk zJ)x`gMRhNgAoYbBl`Q8uANcw}@C4^VGNS$61>70B18&4_Ps&{bzns24dv$jE=NTgC z_Bg!JJqEaJ<$%jpas1pvc*}k)b-n-{^wf4!;+P;8=1EK85Mas^VaP&!kX3Py0RJB3 z8g-8Wdjz`9d&e3anh2DcJ`Hu#G~IQ2g*x<$HSpTPH%2@_U47N8}m(Xc7y4$a7f*M-i7F^Zv?P1UWTrw z0|c^Az+rKY4e<^|%7+B8Kw9O5Ndaa`deq}8{{+ZYO*JiAMLTl^i>lhwN({${c7q?= zF33A6W2Wm~iV4YZ{U_V6P)SaXM0RfVmQnWg+rVutwfGs( z`|A0%fsN3$q#quHVR=b@cd|+nA3;bb(<6<&_Xr|$XKxr!d7C?FQok|Hyb6Q@xQ|{` z@mq^wKkr*8E_@@yj~nSh002405%hr~ipL3@fwL;oOB1+v0+HhvV?%%XPnZ*A6${`7 z=W05){8HV^y;hy9d`suo+g)bB-uw?^#K{=Cds_wI{&G1RjeSa%o*qx^HC=cAg2GH; z2$;etUX^Xcl&m8rV$9rDXu~!4b_(F(_a^AwCzxbThs`C!HS->6#S&*Fp@q}6hZU*Y zbQO=Qgj+-ERSOkGSN(FNV(jE_tSt|6`F zNM^fLCZ6y~zKnf1RcG_(X*Zb>;Ftz~>s71}4xV;_-qIUMCZ~H^q7#*iA;J_v14KYo zrFlUWq;Ln603-OHQMv5b74z$$YcrjkDKxq$L2^ ziHvOFJv_+(e8NO|XqytEb#a@4>r7J&u}Ry_L3&wd7|I7Uz}suXG{?J03GRHWO=bDl zcF&c@r7*0H!&;~|9Ip@2zF)TSMP8Ec>7uC4OzKuSf~1Aay>}+IK=rXY1{O~MZ1b3IWCrvEVU{${1pGgwBmd#R`BlWN2m zxAO)VZoR|sr0ox{{P|hkyqOfC*>yL`kFk_DmrgZj*x6-{A5}}?h0??EMD$3szSY2R zGThcIrtI_~^_NL3*URzY6(+jG&H)G;@jeUiTeEI9ji_46e{;M7jJd_pPhjDpYK|=s zYSGHZDDEEtDwL&N*;^4WS`D8Di$D#SKz#H>4WcTkxId*tGeW)#-iuH%N<{fVJ;wkCXEIoL0{&Mnc~onnY*op8^qU#?2_~+@SSExkAupMV7WfW zxv(#EnA(cG<;HHW)Z)ZTzJfCjDw1qmB|EQoIl}0eUWrbyWVI;R z-s#%b-#u4iZ@4bY2* zE0I@YH&leRLQ?18!Op~>-hni}q8V~Nb>72DWpPd977OYjJ?S}J8NlDTm|5G{@)1c5 z1BL!q1?a2Q{_524svb6 z{}Ydz0)Uv@ES(ncBY>GXd+(O=32WO#D7a_}pb`NcEJ1YeZ-A$ zpZ(Iza)XJ5{mI(~JIO=>MM%$MVnS^5cKW{4E{2od!qL{u4Y#9?*+%^!3)+3@PTyS^ z9+#}7pO|Du8Yd0Mt08k`K&gpI`TYr%z^%t%jaX% zS5#CkVY&|pi9*~f08fA|{&CIv1JE%yk0hUcp9Gv}b?2B7Y|K;E8^(i0JqCUt;3u0n zf$oj!w+QRDxCtpC8>Et7R1|v}m*|mY%K|4@!tK887a)8JGeJd0oIfZD6OzaUJ|?}W z0Cg$ou~M{B4;_ulYzq)`6-fbRF17z{J5RhQc!vDjCPrUuRC&A4;1clrC^pi7K;S-i zJtlUu(I`{@c^o8&UybhlHy!F~AekStz1#soTkCSh#)#({#l5${8ooj%dmJrdJ%Kdw zpI!$N==qFBVgzwtV?JE75ri!MRR&#Hr9SJxuT?le=L-j zyZ%M@O#QpzNtC9xKFwsioet-t>rdCa^)%9rbs~Re^cwv~)shrpk>?NWSCvAfoR}4o zX3-C+DIz2vJU6~fhkU~qe?TJj@lnlRDKjqWR_&odxXa*NVsI(s(d~hakSx*MhqGBU za!&^yH^Jnhy6v#LI)4$Jn9!>jsj%})C-jHjl|cdu&TmqmcvrJy%9N$$IyL9e8q0EO zKP6PHUQ~TYogbx}=)9NoE?qvaqZw9iGwxMz=T4awY@4$}6L7h>F?uBFNul6niZB1b zeoiD5j0b_&km7bMd>|>?x2gxhl6UK8?Y2WXvo~qr8m;d8` zZV%Vb9W{WF(L#KuS_F1DuH#Z(k*I+nb%-mp&Zh=>1Qvw_%NLB{hdm9k1EX&s0R*L0 zNRGPfC>i^WW`dOd*)naVb3*CXeB6g0e{k=n9p8VM(PQ}&xB0~*s`l`+Si#Ogqy}uM zSnd1knI5UZu0tB}?5H^Vw)3TviZVm7uQrmLkn`$ViUSb=bQ3e^sPIHz#L_6tQPRt{ zP6AEY-1mX^avLKC=5v31DMI9Up0B(r8*DiB@(981=(Hv|y?*wP(eyqFgk&FSJY1lmo{PKcJ%xDRTFIJSGKjb&GkKG#~h zFzwbdBvs6Ae1z8u5K9?MG?Ccy*+oj!5>knei}>S<2SV>X10QTm{ubjqAC8fSJhj!Y z1mfM0IROrviZ5QD^N3n*GqI-FZzrAiGwA0Q2V{{NDmF35`sgP99|2*53D|ymj*8K>mQ2@%--lLo*sfY21bj8B^8I zU)WLD{VrmCg3E8?>>M(1`kHruibX3fv`+RjMmQ1oQhTFAj`K7n0j_Q?Onc#1t3f)u z#h)W}V_2=uZO~T4K}Z;;tpK#mSWG)cC@d`I9jZB4g?1?veB@vTI9}{17>E4LP}1Dh zB@O^8=m>aO~x{#LK+>#zZ9gj_(Cb;0w@?kSMi(>Djp^Q=C*>(s#fyolgNeQ^AABbYy}tO~2Kn~~AJhoh?j6WOjH^q9eumPm2i6mZ zH0ej!$5!M#;!r1V?@4AEg)zEZ_96}T>Z4AxNM?f=%0+ylaGg>4S6buApvu==<^ zFQFM5^9%)YeIrUi=~-}JU$#2iId#_w0)|5xvGq zGBU3|;imSPdO&Ch-RV-}%?EzbABgNFiy0MoFm!kATNEu!+P0)f$ zJlULc*NL3^%8M7ZxMNr1OEUGwnIO5%*hP*tkB3`8u%KqsmWi-;2Swk73GLs02mTw{ zAJlTAd)Cmoag1EnvLDa|TZKPzu36M=(_E)7_o6c@2aokYIQ@9#!?N_gsk6Fo9KZ0V z`KMg@GVJFv8%cA$YOU>2cjE1S(`R9iuy|sN+va=s<^LuquU8m4mEUU4-2$uZH z#Y@fq?(p3wH_pmYk4e1*OlTzd~WrGo2=@;vMoDK|V z-|rJg@vFJ)Y6YlxP=fplKFk+KU2Hh3LW3x)^S;>pbl9Mh>XK|z&*~|#T{mkYuEsygbkEpm_3=FooHc%Z;O%{@DqfWYz9 z!A3E9I&t#_U0}`FDJ>mMG7^m_jN(;0o%7TUn9aO;MsrI!SBB_CqbMwS>Suv z3o99vRj(A8ZxkVDnU8p&&pI(KN{sEzYgMTp$cCM&T34K&t6mD8t}SMbmwr{;{fGPE zWBf(xuaaherZ{NLJUZR=%0iwSG`*!y7akV_yUxK8yQ9V%>@p#7->R_0CU$IM^>WEv zZR;KV-k*SAVo$GZVyd<18TdG_)&A6X26D!zjM{1(6C4IrE|*2m&zuZo0#aVKN{ie^TOpc?51x_jID61k>Be5xXqR>#tk&&<3UB<;wu38{_9CW7DI zD@>vY8RugkGRN5`cDy4*uz&D?U?weOY?h%bF8^;@%Z=5R6KXu%SUjHJamaL47CB91 z8}GcPQi+%ZDW652Ka*X9Zs3dj2}FCwJ>Aq4Bq+ukOg9wXe&i-7nuYOoRy(vK2_=+N zlmI}DfbP2|kCDkmhFR%E9_>fQls$@@nG~I#;Fzxll))MW-|k0Xbx{)k!2S~ri7)Pq zsvzsVPp@0TPZ^X*ZJD%4p``YZFYCHcilX%6hHRRlm)?f+5%&?B%A0xRt4y02TY;ws zr0?=fnnL8DIHbpJRsE!|rt1w+{BrSq{s{O>n#o3^28tXkp-i%|qS!B?+%%tNWbwux z45U3)<;^$b_M{NiqGIS@`8^IJsFg7Td$alL}B{Ey4fdH?!ieWmO_=ddKwiQ6rQeZP@2m->e5e z`^`4!7g{d~?g_|)0llhJEJ@<<-*0uH=)5iPL@$6id=`u(!5A2Se)o~O57&gnG6@Cz z1ILD3P(P@Hc#P!0CQ^-yB;#_hEmp+do?8r zREHaQ6*y5NIMYB@Ni6B3o(j$1y`LwhDc-&y`T}Homg%Q^b7h zp!)2(@ol0qZPxYagN6SAGNP`DhPyq&( zFmxu<5^&Bn-)`+Y$Ddi>?$Tz0aEZpupJ3aCR|?T-pQBFtHMT`@=YOg!1ziW6EKl=y zDX-TnLK24*^2`Hud>#&;D zXIiFs3R?V5=ck?&0~%Z_t#$l;rMO8dRygOd=7)(}heQwUPa55rx+K8SG>%j0?0B9G?x{Y%WJZTH^eoS6sb0{#$G zG|?xF+O2Hc3NEs$&x*$iQsE>+`EUt3LxDxTTm!p5w9R_o@Yc~qv7(7VK7GQVop|q= zUzPvwnYR#QF2KjDLF)L|;y7quUSs0!cQ$DZzA^rmauZAU5X85o1kNfDbKEXMdfgnZ z$oDSPWbiEB{z6qUwYHnar zdcL~T^u1KM@gg?uoHxCS5quOG3PtVn?bhcYoBzHctH3wH5vLK>`w&fGR4`zP*!CfH z{pstKbQ0K>Ky&piVDad4>4y@lSDcZ~PA3LuHCYBvpK%RgX#b*FV)YnwsMd_5@Kbn5 z(CV5ipKpiweXBD5%5J+g2Uf7Sw-PW#pY|%R0?$!fK;Rd_EkrbT45Owv?n}YipR{UC zKiN9R1!mSX2&3s+Lp8;|AMR*;>iP%gg0XXg_qV0v+SjAR{#6UZX%>sf;uP=k;pY!F zO=X<%B;+N>@|+ti)QjsjElaC-LyNUF;wCN*uZrWXsAN?>^yA8L#P+?aM1SHq@;fnn zT;Qg&2hF2(c5aW)1=)^Of#`^=zAOPsU=RKrympLqgQH z*C$MN+Eu2;zBkHQN6X4a5uWKse&6~c!LEDf zw6OQq6p}E@W%42J*ErMs^PTNARKL@+$u9ndyhwyz<{49Cmn3^!ay@k{kaCf*4$3 z)}gjfUfqHE{MqBfpN2SmvRi5(7QM1kO&gi^^_a(ApQtXY>V>Dsw63S6m!YPl`DQrTIo(tO9;FJ;d^JzXkXME7>0OQm z0`g`8_iL5x%AKoS*Q{JCxGxOVR+|&m@V)-2$IxX}G}w?@^Br?Pg+k5c4$wg4j2?! zQ|9w_MEY23)PCB;ruc$@oMYr)7aLtvawzHHLr(Rxl}9yU}^DVkGGu0_&{?Mfuwe;cn4BmQSRS)VX-L!iU6z@~nMA86K z&F`lWQG=pud!pqBg=!R}MMwM^FsH(E3(LLglQu*Fh<*Y=vW6Vmi?oCW&WhN5*nYM_ zN7h=g8-%7E{^c+$wXNYSP6yL>WZx~fkw`ScFxlfdh>amT}{!h)|P*KX$0b+&>Xl%E4u}{6D7N0;sC@jT$|4cej9mba%HR z2ug#rboZgVOAIF#bgASKe$Ee+CrUw;4Z-uulkf@TrEs!@AB^@I0M`0uDt-zRV=FN?yV*|4URiJnbe(VNuw zilbzNQdkS3ONxvycn+^!j3s1bOQZMlJ^-MIMv$pMRvSR9bk;(>Ff#S|>+AML_yO)C zgiC*)2=5+A3X6ST#oF--4l?*Q=p}0MZ2mvwn+i3!K&c4h@E!G|=hfKrHZwxnQ)l6D z#T?u2*x^;lKUc4tpoG1phTbQjtFl$v<8FGmCr!faCq>fTmA zE!`}M3>*zh9Q9@#m5}yyEb^Y6-C$zSY<+0i(hgPq0GCw5G-{O5qwE8^?H{}QZ}{B| zD<^^aGs9Bz1>Su19;;Tllnm|%l{%!~`;b=^NM_J0bG12R!lb1h3n-J+H7_b1-?}^t zzFW}Idy*Oc{7(;0g$P&-;ofSp@6MlJmCJ>hdI?%6Tw}H8rC*`Kwf8iVX!j8SSo){5 zOL=*#;R))*WWTQsW3;t^UFe(BVSGrxPa7siJlh!}U^x$y`LU_dGCQpsfyAt-=e8G@ zVZCQSrT${YY;W!P{&hJa03g6pb}vtH60cUaz81ZVHf41L@b_g*(zntogmhvNLAt}Q zcr>xD<|8l4?P)Y2yi702BJ*F+bn70)>PC<_zd#i8GF$mm(bH*YnIKnayfV6 ze&A}m<-t5Q@kiSF9+sQ?1A{4g10Zlz?BlIa-;-SY0sR&L2OE+ho)y_>?hSB9)Ess8 zB#h^Mi+eopwPx6|$f9aCmB4Skr9G-dQt&&u76Z$OI;R<4)f@lH4aE9ae+Vb(38U6- zll=wk;;_w|@!@_)zOdQb4sLvh72VD#YxkdJH!Rm#q=(CC=FX3aWq=Ox_g7+4yIj6~%dqRMrZ+`Z=L%>pDJB>Y(;j#yShL%A zxmQ&zg2;PYy}}2}ui5a8buZvfy~san@C`fQPKUq9qOyk0mx`dI&t`3(vN2n zVh~#wvy+}Zms|TRHvE4lzqag3vq>Tbj3wXv?cP4c#oLI{Y~p@Gn(sTr!g%FMFZZ6W zoZ?c8IgJ&7Ko2qoE)B7T>!Vg_r!a)k?877{aq#xvIoeDuPV2!JtZHa*XG4a z%zt8oGYR$jXh0bVKDa!lmc>(z7e*v-5N_!up_{Y!$?BR3UpssaA_6E2ksx;OKL+yq z-PF9$&=b4t5{9B7T}@G8gik73JRkkSGG4@QY;Ey6evF>4nr$TCpsA13(gw@niv!h^YuHg8JR)sQNu zbg|eC29z+Ji$9Q~wROb;x(o@Ck~|5l@kt%F`G z2>S2j(2~9*u0iFQ$ThruwSGk(oObsg+|fn&>_JF+Ea`%MPX?wbU;4K7v)#eXZgt+j zic6y7*Aw}+{ts#w29xVuhfA;<395&ENLTRt{^Tzb;)Y=aeJX!{lbvi1+W2}^|IzAO z+b!#ut%MvKAku<}BExttZBT=ZW`aK)iRF@}Is6v-@u)JSDG2I=wsOE<`|Q<=mG$sV zW?6{~W2`I!`sekjOUP^6?Btq1G>w>84thGY^5gb>GC@vv4*Q?<4L>LJI(&WlfhL=z zHr5uh0TpFiYiS>(`H96%ma4Y1qMnT-?}+;;6*u?@gWS%~?FLyAB-QWoD75@~(FYCJ|0;)Q76ukv77A7Bsp;&P9@!eP^Gyv~vk`uo6{O2Wf6-Fy zYzQyHM?kfR^n1lwAYmplC7TTbQSvz&L42suu= z?PF$-i-1~pc%)>lz{PU_WG~95$;d~)OSaK3bFAw!2y(Mu#G6-*jEo#Y!c1t?k7$60 znuTp8JqLJ-u~05m&_NeB-r(8=(kaSB)ad%^`I>Ai>~0~UEUpge4ZsA@!;n**kJLfK zSH^OPw{h3k5de*J+L;(v=@?s(auIh9b8+c|Kl__wg9f`hySXZLN*}o~p%6A>2(q#F zBLV`l)X^i`{tKF053me%+8!?4q?BgvfXu_}LTK6^Ds};f1Hsw%x)$@G@JKy}h+QlS zQC39 z8rc2(zr6q`Qk|YUI8O1Ll@b<@>vQGv7b%Y7Z;k|3g2oWuXnU@Og`8(pOjK(yXZ=Yr zZnK~!g~K627nf+7jsDHZE@ucI83qsasd{8rN;1{e;|hhBP{uT_)_JUv5J+hngDJU2 z5O4oXbN(g=SGLr|l(hbFF!4{opu}B_#UrS_@Mu9Xd$_XNQNe2tZPcPW>c83M?6hOY zf%@9BWi5T-;@I}Tm|$sj`95yl)H{R0%@cqKgv}M!;K5!Xj^^~))rYfTBE+0mrB|nBJ_s# zVQeP-B$mEgoH6o~DsbZ;gVd1ztKtTk%U!Qdhv7lDe4ALLMnEsulN4qm9pMEnfpmVHYX0hx3Vxka_sHQbxf3%wLzXf zw==V@DVxEw6Hk~m-*;7Yv)%wr8W>l02~-j^^@gJLkI=x*Kemci2dQSu$o1`FV$?VR zx6Sp-u7mJ$jV5n|TukrFU}ouhmlvlyehWT!uy#oqiLv&C^Qy|RdehZjPPb*x^1h3d z{{lRj?f)7QK6kEdNZAdd^=*^PQS^gYV>g^+;_Q+WQDNsxRq?k{Jd27Xu+%C5ECY2= z?V5g1W++St3;HH7DzW^v@Yo;<9j7$1JDzC#<;UyTJ$_Yfn@qHbibAJyg@f9|_RY6f z2h-0Z5yf!c&-EVfgb`Z>E;X1yTo#=Qf8krG1mf^_^d;vIYRY_eiUfa#dVGY#S zU5C1zMo*Km1)+bH#I|+h3l2^|!WeC4V1C!?{HWaOrY!=FG2>z}l?j#bn!P#JPusqs=8Q@VG+fP&RI%|3r2htj#VC~cD?(w%aKpc2btavfd1!7Y{R}u8l zj+bN7`q!k1RK)av?&IfkXGq{eBcwEgHteNTDjzL^ZYFi;xh}1c+JIIgC7T1xr{HU& zm-N*$pEp$TaKEDYW@LZYZ-txMI=ont7!RHKxA{WT%6>0x3i5bJl)0bh)Gb74uJdsSc8=L6%JDJ0ARS+jS z|KRjpBTV0j0}=8){B~`Ca|^pU0kpN)Uc2o7OqMMY5GphJN*#)lq-dgod~QTmcAu-1er*3!Dx=q-;au2v>(}lJI~ffu0`iC!Q&S zD|WkIGv<9mhf1riD4mJy*`~MQhQLMIHXaP161ZdZ#K4F-=e%^i6D9Zq<=5#_qfiE&-cDO0Vs3c|ftv0nbW;k;eLEwq~BZS;ZOQKd-Bk8z|y!l-lp! zdqGKq;@(cQv&o=X)*3;b26# zTKjO%t7P{8lfOHOu}S_a(_05Ppi@r==W1Nu0@-Z&D0&xD!uHBw$$F(nzN`Dx&hCyV z=6(q8MZj`WKBDOqS4T3y5g!wuX#1K#$73sUGIArDfutZctLwK!fGwXr21R(?A5IjT zXyf7W7<>=CUJ2TKTfZ%?fbnp9Kk0yKuv`uc6TyN!KL4mtbM$ zitRbg5HcA2=sOcQ^7x{YRgv5z_wB#?cWxIe+caO+a~sNY-WS!dC!i9p2hL_m2$REpO83MRG?%(>KrcInWqo_7$nxnL!M1koAWKT?fPNE z7o$9tD41P1m%6>WYJ8rH1Ty^rQM!|6G4@r=CxogO!nn(X^(NZwl!`+KQ}S`e)MB^R zL=tVkf>E^ORz+-n4V|p&GGs}rMWktoTV9V~bf4svm}Z9a0tGq}=S0Ci-R~CpD>a~p zDMzOD^+o&lyd(8we?Q6i{nv=WQ(g!kiOXYyOb|kXGbPH710;9%*Pl;T^4SWGQ6Lum zNbBubyw1z~sVQVMYqbwjiLP97s3zdzvsH9yHT#=K6ejh`BgJ3oe_RCiXUbYUois?& z-OG+DvL0*=Oe5e-sQn5EFI%;S(IWFpZal>Os~a#S7u5UF0(j`|kAEBNT-~x;-(OVg zexdwZYuAj0U^`8E+%9`Q=I$y(OZ+=~4dB3jocj`4RI<7NfBlGH5eMN%P)-2F1(eq{ z-)`oH$^}uI?~C0=|GDx{xJE)~W!<+wy<}6VTGq*~m|FS4EJC;s3Y2iq@sD92`peU| zBg`HNnw~mNUSeHtmWry)hNnOHkV!t~1oJLfi_G~b1yMZsq4`k3V?v-%cT5R?HVS=L zlyWisy)|2pW~GB^+l;?Ul4Wu=;Rl?X>wN{gPmpyz9&oaOLduH2odMYMjzaVy_s8Hh z+UaH>MjahnOr&N~Mt_~-1}4im6eu2sO6yb%e>0OC&O9TzXXS;$=gkZ8_;Ueq>`KXq z7(;mKNV`|vSewzu$D`OE(S00^Vg&w$ba$|)=eDbSPibyb990pp`RO< z7g3DwACF$og8W-QJo7rRK#*Z2PqC)TlvMV_w+(S1?tGg~eNS14SA|7<2wI^LK%nSGSwpf(`D2&iiNS6D0`Q6O2|iS^hXTEWo}KhGZ* z@lIZl+Peg?<0=PUDPp)WAEW-P zsIG{klr~FiKWHNH5}oSNgm+xp6m;mp-3DaDfRdYE zPjB7|J6v5d_-YyB)B*N(XYPaUvGh4em-_)>Uz}?uE{EySRv^W|%VqwOs}uVP?aPP- zJ(*=>rhDUDYBKR*{kq3q6Om+C+$2im$F6-HytrB8ssk_mWW2Ee-s^~9s**~S&clp* z+vo4PeNO=1t9*ntHQK*Lv|O|#(W~6dCOJu0Emm7zni#`lEbs2;Sa|g2eo7e^4n9U% z^ThHi>#%C)?N0-5v(4-kxP03h1H7}!cjeS!8$NB}#nL}uqhdbUtpHq*Kga`|Y4#L` zVqf|+;C&t0w-YjyfwN!`QeBsLhy1y3=RJV=Pl;wO9C5T7B$VUnlflJ;er_wn6;sqi zrVsMB>yCmy)@o?BgcgU7hHZPd0^WB%p`eLv&pEjd)m+e3W2*Z5J^c3O1q=NwvV#Qe z!_m%V2PJo#aPfCCaAVe_DPgI9G^JYpZCFjuK4P`ZV@Z=g>1pr&^NsHdCRVTXN(qS{gby8h8mBVhjY{o+moYmuAA1{zNX)*#RF;8NpO0 z#%Re53zC_l;HZL0>gRRE3o5Pb^+i-9LnI90Yu(AzUK}aNwot!T|Iz5EOPKT{{(fI7 zRM|~(5jR3zGqe7q7N7-9jy5MpqQAv`&3k5+>}${8L}8xEe8`!3<^%2jSNn`j6{EXr z4UGKD1ck`kF{r#XBk^x-zy$ek<)SZfR%poY>!-Z86ZtT3Y*pLegKK=f(g+hO;;1H; ze_(vhkZmm@haCL;-1EW2Zmj4g0%$q~C#2yf$jh;~JXCd_6yV>$J5oE!C|Ht!GTAggN;7qSL=+3Qz{;YF#HNnio9^F&Y9jk~^RQSH!XfQEejok`ug!B5r zGWxG2S#B+npW9*Yl$Y?7fZ^&)2E%f0r+Ac^2y!6yd%)|>f9>In@W8NB``g(aU{OEc zOxEqZohp`%YW3+nAhDFA>s0;V)Pxi;mlbVWEhcJ)OPh68V~g+9tQR+gwa@1LFP{3T zL)fs9f22oSzzqcI?S2&Kv68}85YhN-20`>p{Ld3TfBIVFdge31F#D_gN-V)K92mW= z=|_?9E=Aj9JWlFj7^@$A6a#Y*)J=s=t03X=Xb`C!KF6j`{jqp(H_vtC>M&Z|NHVAj`jz7(_)Qr42eKY>c;>7smA)YoH>(&R}tP6MW5t9$|`xOfMj!Ii!EMkGVlV zAz3X>?b6|=NA;!;VO6<^9&)WC2V5!-*85C)D^4nVoX9G>bibFEMtc=V1v$U}gq+Ni z$b+Sazn!L($HMVU@HwA|V6nq(o%^n`P9}Bi%c9v3Z2@o5Q;Mn?ldT5el6S|&QI}g! zOkgJpNcB7;tpWWH5jz=#WNY0Bcr?U;vs@m~J`}G_E31QQ`x-_;VhR$**h};Hfu9+q zzRuMEkq&|M@9W9@;C_pVV?dNz2ym6*LpvPgcmj)ncf~Ct+lU|m1h0%$4b=dRpp<9a z)uwmiP;&%VyBsmHh=6lTAj7y4JfD@|#H`pHaxUy$vOVaNJ%_FrQUa3SZ}6H9k9$~m z?28S;gHL0Zb>mF192&I&MT|Q&wm;>F>X)2qU6`&%X)A&&rOOlOka=r`o0tiFZ-|SY$L?g;qG@|_UR!S^XvjFl=vQX$+R@|t) z17-`+3SqcbCZf(@Tt}le2q#0aZiMno5%M{RbTlMGt@&OUvX;RGWRi+df`i=Fcftar9~TGbByQe<==?2iO9Y1i&(7 z-ZqsEOa%+*_dH8m5*pG^)9lmcuoNjorH5fVQp)YX{TkM(@Npydd-F7_A@eD}dd zL}xPo33A&$9*JFKX<@ea5LUBkMrW69W6KKb=k-s}XhQ!+BYW2OFUguO5n={<+k=+% zZOP#-BS&RJg-+@o9TG&jd0swE*85c3k6sMsi9;jj?jX9@=?B4BJ!Ogz~G#r)gzQ z2v3`buf+G^>F{7J{dn97hidTc9qR^(|GnRk!7vSJJ2}fo95YD4AlYllsk>l~0m*fP zD?&y^!ILe%^TNwy_A4h0(AjZQ``<=$rG31&EGMgT77HC>ae%REn&wbs__(CglJo8t66rWj>e_8sst&r zuytaL*}ds~V)H)}62JN((D|xp>^B;s ztP}88Rn{`G4j5V4H4x}g`OQ6&;axyt;xQekS^#n;lV)t`Q48D(@TY8%c&`r0IQOy4r(}2h?=sGe_yO}Pkj@(9?WxWINy7CpOrIfw_L93BjdYi4!eXg4r68I4Aj_F%8}RRF5%3k_`aoi+YAf*Qp;VWkh?~lz@9DVI&fg zDlJ_`zHP(*i+q2IO{{>EyNBZgw6a0E0pqjs{R5BRV%u|kA|)!1&3Vh%V(W?ZodlNS z3xrhM7_rNTk4sk4P48@dBzSH9AyFK@n<;9$#LMV+Ak)0;&(-*1IS2#~4 z>)?Ai)PDqYv$?|8#y`L4wI*@G^kM0CLm9F(D3~}Brf_b4-soXSk(JMwo%HwuJvN^_ zs(fF;Fx7AOAa9fE>cq$VYK&@!x`hK)TnT`P3_|!r8@HwvmPI{^T2=<{m&7pyYCWQm z>nQwvK&PtEeSQpYBe7q#<{&k2Mt9<9$-7z*rVkJ^qKF_##b?jRX|f&lOWyGLKa4le zS{qb7jiRpQwLFu_&;8TJ*S)OjdQ(Gg%5wnsv*z0tB(qtu65{xJ|nmIo| z)S=}5wwJ#0Uy_FMSfw@GmDbR*zU> zKfN-=$tml|MK3lxV-KgMsEregJ>sCM<-S&q#y3CimI#I~YmXC=9DUv}AMfIAQ>;CJ zGY&_>Z14CMtl(%gx_xq7!X5>+(mabSH)B!4xa>QsXD|VCt{Iy?s?1>V{7na@Y-VZU#}X?laA_%gRg^eEhbjWf(+_x zW|q|RoP|R-yk=aw$77CrCx^^Phwmw)T9Op#*o;ljThWh+O-()#qTKSCN$O5Ti82}M z7|rLyF#N*(#j7|*%^3)u$p1JP3>e^h>dj#$${Q5f&#O~Es)7vInH+Bg;TPoe8^l^* zY&WfoZhTl1)-LHvhizOP+nugZgis;Z2rGt1XI~Oygi*lK z06}g;1X?RPPFcKk&e*R<<Q}>UOT;};4(B^EAzYl&!A#TtD|Ze)i9`8Cs6`0}3A5CBx2%rg zyF=kpEsSO<~`PzNzC*{E)9 zFk!ni#x`1zsrsFhEpFi@vGTxj)v5KTq_^_(?_0kmW|Q`-Y>IbIJquj>!=Kk{f{Evw z^8s-@FVSoD`ALoVDjbg|?km!((J5XPDsds*AN{|-MDbG0N_LdFqQA=40Tob0j3NN> zK0ZJnjQnIG6+>V7K`PrAL#cNk)$`QoA6?d0DB{CUOP33^cN7;A!W&h!6UcntuvGF^ z;9v__95FEeETA=Np z4<)eRGMsncC~r&f9coN{s}X;bvN94AQKD=1q=O#!zZKTd+umsgUtj%SF93n%;_lhA zeI2qmP2S)Ik9IFK{NdLhSHitapzKER^w>rdKr(;0Gsn6RLX`G54VoVn|7077aiL() zl2vmjCl3on3w)B49v6*J)Z0Lf5b#bb$1Lcn~el370-tLD9S#u>?j zn3ua)R%_agb8PsqQK#(Y7Gzi@fQ?W`qavZi8$_6sh1`w37TziiHycPD?9C|xp(~%GQ0Q_&Qzm_Tf%6v@+ z^V6B={w`IpWW-c5X|?-T`g?>Wm(Rt7&7`oeBQg}@IKAxE@sHeIaL$ZmPx4K;n?+80 z-;!0GN||D<-Nw9i_pgAsN6eC-vjZo zUO-10_qo~{SY6i;?7CDJAUy#$7y%FdugM(@aV2*57^IXxlF~?sV6Z-1W=u5eb_hqM zb#R2ukRp<}+h3@e@@PX2B#JsIU>h%XCnjD#-&hYaPlOuF*DA_jQR6fQcoCLo6GR(a z+u=?Y)jU$S|DKGqywG$B504|!?%^LHMg~vV=*6gd}lCS2pJ4wD#~K+gVl|JYvMw`>q9c_b+olMnT* z`NCyH33U;emh|khHs~HpSly`DqUV@xqm45|`t@NSt`^i^mxNegbL89o7lNZ0JqWDd zU~Z_3YDm{nKw&s(^micj*y0x%k<*%-4J(i+?R=_~TUtMN$dJ6~vaxC8tTwxIE>czV ztu6*be7`{5@D5}F%Q?THJg3`ls0r^u{|GLR9+01Tur%u~Iybn)o0on)V%@08g0A) z490tPc0xJqsh(&5IZQfFmiPGT6(QsR3=as#^RI&PxZ3oSumC()tDvQ#tES~?1<07- z_myU0JKVGDKCRIk40rPPfSdKWjx>5&Yjnu+ESj6V%Tb58*`V?w#D@Vr6x1^LE$2RM zNZon7{-#(CtaF2j;3EtsbrCiW2Ff=h@vmKwYGoDvN{O!OiXQATR`O*9(5 z*=vUvz@@n?Q+k-Fz4y>9MQkBY^%ts|h9OB;2qc!6#ql$j=**r6j!@F#B8%w;$5Wq6kc{sGnSc z{6AOVKL^WiTNNHlKo{9>vk<5HQnKxz>D(|{$S!wU)ppCf6f$Hm(`#q&X`{BI%dP@~ z-$G2JLbK*ck?RwYgF)n^OAQJ26PXh2Gm3=*W(hi;Bn{rfc9Ip_Yund|b(S>R3G8s;bGw@L-69H zzG6|a2HtFsO!qdbA>Lf)yt3`R6~*`{%u0RT=Pu;)0~_LWd!{2-A`%cXDFHXROeA$c zAsumrli6RrWV)Eh;tTyw&Lom*hX`rMr{$T@7*J9->1uqic5AtOkKkXkQ$0lfS#oJL zcMebO`gP@2CVYeIp6X;y@pz=lRW_uV{t&8uT4qBn*+VsOrn!%hp5 zyx*1^WV}A7sF7tE5hrZ!u(bN+z-LocI14YAHmn?pzOFp9BBD;CibAoJf2Ol^nVg)d ziAijGHo$Bxz56$aqU~X5jI-&SEL~Ip^|94)AyAwN4{zMq3_j8dakmjkd3vz$tQ|Kd zw2|MBPh!y%PZO&QYG$iHF&pKank3q9#%2weJO6z_@C z{DxFn?RG8uGCrjHbpt&<9^N?;fs`Em$0pfjVxw1&_b*bC2;*IUUSd+^4#A18q*9Ee>-j5%LFSv*{QO5zr&TdpO%<%wPUr^Xgo|o5?fZl54{b%?XCRe zjbJ$YF7%PQNehn95Zp3i7|dq|<%|IW!Q$cJ#BBerrzf4qvm;=OJZJ>-_zEJ_8;G zE&hdLcz*kjgSgQ8sYrh|Ei!@ntZC5QSyFYGaVumCNx|KW5M^X_hJ9e@55-!59>SK{ zI}bg?h5R##5@1`)vu@1fLRCs+9*=BPm#zNDOd>lI{~mIcDA{eg$C7(Z2fzPvpeec6 zn!F(BLe?{EBF?5P>1Tr3{3>m5^Tr~OJS@K#)A9WN-?6v4%-9qS)p?6GtsFa8=ndCrfUJ)5XvgYOugMjC-D>cJYv^1C>mTU)Phx ziG@=i`T}sY*L!-t3K?xm^c&FOKNFRWn`dIJvhp1Wgq>csAaP*jKUUJ=;fzpc3`$OV zf=v*14{upOI}=1-wM7tB(r7CBsNIh^fitpuj2Px7yvmwuFqTuyNY5r9+I#;VYj!Cp zCJu0oI(^l2+dFf*50-__V`ol`{>@e1-lST}Z`ThHh@iJpVfyEjO-fW*x<@Y&#l=MN zFGo>34$VfD+>Q`5=1u#%UdJ^GHwE|;+l!ZVz@z^K}K zJRt)eJc=boZ$mPg&eVaI~2rxLD#jh7a#`v_59)@xi zjYqZ-60HEffss=^9{JawW2VHOd%dJp#P_TA_B-q8rA8;Dq=J%c!*Y7s_!Mf6uBc zdRv$Ru?_87S4kh+YY+n-{zcE<0HZNJKdYqh_obtwbL{``!Tyxf+BPO@K(M}FIBu=U zC{8ooz+%#?RyEiKGK<3$Y&}G?@%9qFs&`k48N@+Sw?CPU)AF=FsPT+y?;g7`MZuAa zOb^F&?{iB-trgMgH<}&M5CW4uz8I@Yi&JM11^MJR*rO?_U7IdXA#Ubp=qc~f3I8I1 zMW>x;8dBA7Pp!I?wNKc7z)!6CW9h*(A7AK*Kjm`gY))7Gn=&egbFA@EboNVLS3V_H zJ1-OdawbI-^y^*(UJX9N$r|>|Z3f50x14to8rn|GN_eI;R6fSSyFJeLl9uF`PS#KJ)6C1eG z>}ONdV^oC6_jgRwhkg?>9Hx`SdzmdZtl^#~dtz&?{Gehew0BmOFyv(rfZ&Wqcm8Cy z*%zJ(yj>nZSozykbGqXedniw&CjA3@{>_ZZQImH@>)O(no3ss7q$FkZzGzJTH4XY4 z^xr)Kn^wn1;PYc|7Z!G|rHr`SMFsH_Mq^}hS`TUlg#&~RlUjZJb!93Ff;9P4kMFkV z?1Xlfj&>4Q2~Jkn90VEYz9j|`J*A-0$!XN<$*?q&ObAa}*(j(DU~b1=HWh2xiRp&K z36m4rF0fJq_`4uTPkA|l3QOT?LZSPfxV1W$n8M45%4x^k9p5_oIT0Mab892nQ)~>W zp{ryw2*YBs9XG99`bcO-Q$Qd`4^?m zkEvG)KCrcN=AO$-8a%wWivNMA^X7Whbxtk5Z%tF7kn0t`TPnNcp9Ve(wP|*w4lPUV zV(Jkp!gaII;`)hGG#ew!HJDL9%`Kl3pB^tZiNLC6rF zGxaBy+6S1O%hu)sa`A3`u1T54A5w@(0`-}~s~ZxM-`>1BemsorIr#hq>h4HZpXqUg z^F_V5kKl;N_(ABBz1X4zYdOQwX@_cRyy~09owGhSHc|7V&lA>hEa_iT3t*;f_lc+cdZX812vviLPcIkX147PTLd#P4akGKPRI=h{G34^ z$yoo5KJRd`H&t4#0^$u;#R1;-5V`Tb+6gb=gye{KjM*pcH}=Uy#5MuUTBGhucT?19sv-yd{a61KpE-SDHe|p@$6aCVzCuO%M1TLW z@IwCEACemU5bqPBkAk|w@^O8iWlsJq@w7HKV1`rMk=$+hB9QB|quR`HdG`$J-EB)D z#e4p?(qkiBN%rht^TAL!x7`P5LKf=@KC0=p;?tSXme$t`YFWPCoambo};n zMl}y~QPQv6`GQRGW-6z6aB_$Lv%>XiR!J01)JvfOyx%WROU5`?Gea_cjp{xVawxw0 zp1pqLB>ahvX!MmKXE}LHH@P_eWczGJNmqQU2xa4Jq5an#_mPC>pT=kVX8yP zM(=ox(`8{AN1S)6Wa!adfmLAJrjlPfWYHkj2_;Z|UDjzCFcgs>6=iFuQs47heIz}d z92~}ke?8w{AXzXVUkH=coZ5*?2=AtBOzAU^`|I#96TW6-7G88nnN2i`5Nu~YsxQ!R zi^PPdRr|Knj@FnrS$go}u(Vfdfm=5Ud`(E1dUHk+jsWm9y#GQ>%j=+E&{}gm&b^JY z|E>y^)7O$QwA zO7c3Xx!u5&xP||8OxQ*ucYm*ocw1m^OD*a3h2}7uEa=wKV==OU^C=(!fO&PLbAHZw z*gG651CD_g(>%4gWNi4I;|8r5$ji<7l+(uangpNGOBN=|GtaB7J z)9qdnZF^rV$PJTA);0uf5>K1XkF$<$sQ&wl`xh+t(&nd^V(f2P28b`zJ`Ial@i$We zv61KaJ|Sf7OM9bjab8u;Pa}~?U^bpBru12Qo^a*|pFVXo(-TWJ3VTasd(O;xi`BFr zH`6b*5D>BT42MF7-(Yr+tjp${eSZJ=X2X5A)*s9IVdez}TloN0| zcXw8G`1$Q{^XOfKe4EhbDl$B%hknrAZ63<8scPf=JT)rfU3o0)2wgo@`ja*QJ!a;*eQ(&>cSr@bj%$tnRd?JTCgKjv;?5 zKr2fnbwm8NDXNZweSZ7>dm$-S^$dLod1qPZ|0Bx9zl3ifa8j-%Xk#4#y$5N=k~DIm zEV#sM5jxN1niNpLo{d~J2u%@cJ3cod4|njU>D3J?ZF1zZWOUj~Jg_hATc2@IeqXFe z8QQrX!LB=_mHvp8!$E7Rvm@fbkK@E>wfVp8eS2xR0b~}jplkh;_xo};Uzb(^UTjsU z6}s~0r8VW7{k{A@C=ecX%|=fJ(ycfw>hFIe7s|X3FfWXply8*&mjF!l-goA}PP(_~ zKztkaYUPuUVj_v}9=-G4AE=NuY`VQV-ur)O`U+gyr-kkd@g2`w4lj4ld^v2#{Fi|&J!V;ght#LV>RqciAy@?lXo>ZoJ!mVS&yhy+ z{8EKCAC1?f$MZRnVk;WsFGyxL@}zd&W&9$RI`4|c(?$XJzMFXy0q*Mo;fY49GYI~r zYUQsSDf6R?yXcMg*#7v&@O@n>_X=Eqg^lKN(-wKHZjUE3e&*8pQv4=n&kUDW8<*IL z0G_2&PUV)Geu5ALR_KS@t0xI13V`DNg8Aa#o{-KzeA?GdC5>$Q5ljW35o0y><%H{# zBZ0x@B+Qq{fv1DMKJw_I!R`+=H%s^1-+G?C1z)QgRuv*@rw;=QS+sj>0&wJTyq?F#GIe-OWo4jIOg%kWy zH!P3dY4;O{i^_6^g~q3@fCz-!-p^V~Z6bYbF_*S%u>KaDVEtv3E`@o9)N7^L@ufF8 zUaM6NUmi~&mD96mEAsa&zv;30H5p9V059cm8oSq{`h(BJu>w)Y!K!jTRfCzp@bw!a z4MlYmZBbdZ1lMp+%hvNNk!7+N9fm-6PGVQ+l38hHq()(+tS1~R`Zpqtp8`Pu+*iF0 zP#H9^87hBY=)5z6dEXiMj}(O}i34y^0DG}Xm|wL~+KF5y0(u?J=f06_Y2WxR;#)j{ z?(X=FO9*@oDF%6zEMC~2HXLRYlNQ(VA< z0~=(e<>l&4|6=oUVLqR>{my%uA&s3cwbCIEsATNZE{j?%m4Nh`OEc1|Ar`d4%S$jN zddHX6(ILls__R_r_l2`uzDb;TsZut&E}z+M-=q`+#Ib>?mgaBYMEHru*WpgD{2aE2 z$Lv>80d2+Ub}ypP?x9ry7$I9sC`_U?EYzYGXqTZyA?_az;RL&tDtlGdvv!U2qUq?* z=s&lLuUTufT=o>pksKbch6>j`z9S?5dS7;ab9^1ywfMpAk93D&Emf;-#DmM*T=03& z&E_#ATwFV)VWj8j0_PY)r7M?ST;O-eQU|`H4vd*Ott(r!Hy~o;=dINq`CgY(Jn7$E z>LajAHt(#khcP6h?5cR+Aq~_aCTTzWRR`K27EXTMXdx4S@IOOE0~M9k)Gc}()T+at z;()1No{u0^(Ft-pM$@{r73;@yb*4t{TgEWEN+?mD;!TDZAYC zOZ|T@0Qf{TeH0KaQ6{{PpP8*i)zKg`S(?*8fk=}H1z^dvvoX4#5awtU0WsXJPd!n`_AWj*QS9 z+&7OSj_uINBy!w!_3udD{0s>~o<+8M3TSHC^ONcK=PkyPbW*VhaG)d#|E1-?%k_rO zZU9CI0MbF7r*u&}$Dl>k4rsH6SrMnuoX4^tuJb1arSkKua$IRqqPN3<9BsUVTyA(= zyy-0U#I8K+8pE3o9w#n;S#XJ%$VUV;QOVDlFq;@}pnuS|^PRFF8>ye;NiFA0^($m2 zKEEccX~m0c7Y{|ZyvN1f*?H-DSpNfj#+wtl`HgZ&8WHC33yRRH`;CH06Z_$P^KU)i z)yOmmAVySC&@sBW>l`MK=1RH*qq-_%1e72}S%m#j|NLs+sZhuSvF_B?U_i;*>iZH9 z*uiE=O%klP8zP&pq1Dq+;H%Xp4a+TkORX63Qac}CI3FMUt{qU&QN3Q<#s8)09M04j zWI^QQh*bvMSM=D@%s6czmSSY!)3kQ#*$Dm)%Leuu%KC3>JIGSwTmx!uoxY<=G_#q>VK7rtQ(MM>@e`kd@xwvT;{D?? zE4u$N?ZHGxTYTZIDr={*XAx?AWPy}ej#SERj8q0L(qaG zWg)YxP%~U)`0^)@npRwHHxXtO9X+wdU~nyJ&eOscPb{n{_4Z+vWD6CA{8_&>#?_;a^r>;Y+Hvixcf{P9|ady*G#5F|Hw}j~KMNV*WoOfjRr&e}eq?%^=%hg37z1fq=M+$b5;o_4^Lf>ecsA`lk9@e=Z#v)bbTQJwOEPwAhtEswD+EYHnL+6BBo)W%q!*h^;UcxNFgPEgkgSP>&{<$ z7iGgUjV?3_B^JSbXDph~dzR+N<(bos4#1CRub`!-KGO%>ax~VyR(d;iBdJtMwH$n7 z68wYpva3}h*SsM*=b53OJgq}krM`dw$Lh>Yy)t7i^$8|TdLEI_*{R@{Yo#Aa;m8H0 zX=l}rgZj+UWyR8t0;4u_>SC|ugE1TsyP+$0(=P*uOF7D!dy!!HwUOB1TbX^uS1bIH z?VeJ-=pWtOFuW5*4+TT>gr&_sB1uCtR4}yL3Kk&T+;8S1LjR0)?E!sIJ%a5AU-MB- z6|Z^_m`vM@UaljV=4w&B&3MRMnn4K1@Pttx?eVR?u*O6r*e5W)t-?oPqalDtU zvzdj8f}rx2{_v}Dk%PQOap^r{Ws7v)Q0q0P_ z7aIq|pe_=#{dYYig~_mWQ8#b{S;#o4g*>us@CR9m&(uIz+jrH6EDFoyLdH{?YOFUA z`E^RRpCdFGeCO<7NN~3zz9TC^Xn;Bu0z&1i{tS_Ze`~@RaKDBEHumlJ`H!*V71Oe^ zeb}gFsd?SuPJDl13-t75WyF4ozB-JDFi-8A&JVtJ+ZS0EBTDMnygx=V4)YmJ7a0Qh+V`zA3EfG1P(<#onV(-<%4v86z~CsqpO#{3lZX z;00v1Z&Mc}<7~EQsXXmy^~%ilkdRW{AvAc{1s*Z<*VQ$+PUV-e|p4KB2%Yz9&kz<{Z7&D%`gOIUVcE|NArjj3fNz7ko+I?A4 z0zFi8eliBI9ifGp7>^-5qmtg+Qgz(eMJR=QJ!YGG{q(Sa9%u+7hb^E@umE&GfCWR( zIeV#$OtazdnW|IJt=OC}R+b9r z7Xs~{0X6(5r)eV3<44#7Yk|q_^p_}pnsr}`DN`IGMMe!3?gv%X1YRyq8Blc6s{CsA z4_Ox7f6sNZl#Wv0h1N6v;n+r`ygl6&^;-`It8yESBaIkQT&d=D;%X2ZILZlM!{PG8 zOU8|Y8w1owUOJXkw=>SQY*G(vC0tr!VxDc;DTu>)t(h|34KIB2nJqdnKVX$! zJjhf59q*_9(Cg+Q)tWn~n45qm`&Ez`z>gLvbiE5AYx^G`GFGSn*UH)8zOtX5;G8*h z5uH04U$TNVHC_;*A)S56{4R1$&Yao~iifr&l=Djxi@@3o`Rx3Jn*rWSx>O=Vn{DGW5yChgh5!FY`y1rFv^;T)3|0rYx!kshY%tp&7a(7_BvJ(H^X z+uV5UP)-gmGG-hjiUr?4`ayU{SafOaZSKwbKbfM(0=To9Sl3%93&w4)xIN7qy}6BV zS7>=~zQ+fR$@rAqVoeY8?P{wfc)|HBD_^@258`d`t_Ot63F; zK&m@Ua3)-+2t!R?UD`DQ^__-(;yg+$Y^PhVDJK3MWkMz5f|Q zTu3`A^N=_?22IoOcz7J?KqxFU6p;j!?TO;`F1DHYeTyC4(Ve9SJ{~ZCYIL@lSm6d< z`k|ne+o;DQCJrer45AUyLnux)aRgV)@_f>U!nWcu0@B8bz&*3;LnE;o(|8=PGX~zG zOXuh=#Hfy`*cZNwnAaDb+-;y(B8>uK4n|N+(Q?-Dx(SePEo%B}UqQ6e^E&r2>8|I& zHJjL9;Xsh}7Uxc`S?~OLmJXP|ol$xD6GW5z2buu`|0V;Gb<)N8rEqlF58D~>{zx>| z)MwPUo7%v^(EWrKRb#04ki;r{f>PULM8Rvg`shb!rg{W64Um$?u-9K(Dh49t(R_#VCR2 z^uzpH$7%3W-KXG}WkA)2i zl%sxFim-VNhhj)-l;C{)Yzg`rqOA?cBuZ2DdUY0_$VXWO!N6VuopjKjxXLQ+S^aBv z&@>VXrpw#wfXm1M+7pc#jMGVm=>_LHE>e)mw~XR`S}4!n%TuF6~QLf=#JsBc;qHtbuAk$dti zA2yyyGC_OZpn}UF+@rtQ0iRuL3NT1%XBHqOtvP)}li!aTqF2pk>pxrQ1VLS$Vd%FocEx%c`hO4e~cw*U=oF^}~RqoBb;m+i4{ra6R6@qYy)&yrv2Q&&eQIJ_R76xVxPi^PqzPxK@ zu`js__{hq#2%;NI0uX7OfV}{G5gU@uzT)jX6xNC1P?}ncP$K4oi{@%bD`o~Gk!Hj) zd=7fU>a)qa=WLd;?^z%%D$D^blSH`m6pL3c-EFh%edsA`g|2Kwc=u}IiGCL1hW}hME z&)v-3RO&N=1=u$mpBm@4@Chhi z0}P??Au$z)1Oh3t;9UcTm;Z28^_a=SDR3hKZx0fk%m4jFr7{Syl=U3y%pRKI_y(M@ zL1>Yd6IRb=5GO0CQbPBJ`p)lq{T)VY7 zKi)&6*g|g#*y>88Jd#-u*43MqYugUxOPk-ulpShvf1!ft|F#dTGEk2QSPS;)d$~X@ z9s!)|cbKw&!k;<7%mp{y7?1|GTf;%h(uspZwqz*z(#4)Y$#i@N!7y<#!9_>P@?+XI zm~Pg{K@NGC@_9w-vrQ?(|MPQ{2K3WS>0-EKmb;2bfUFPlfN5a~R^Au2dmyiC`Q#B; z3Dn!2LpvIyUsXZb98`V=(e)&h&EjeOO3l zb5*O55Rq$PkrqD48h^Q9cq56F{~&ivOE~MK6pF%p{&!9JUeQIwyi@{dJ*>6E>CQRD z*^d?etPBGyCSAnv^o2RlmREqQ3X0T;%V!TC2K%U>Ue%9{sK&VvNDhy7^^**S{*qqs zPsD)!OL++t@=S`L$??BAXWK-s0Kmo_LA#=TjcCn{%0QD9V>^NgNhliBh@8MZ!d*#)Q!3up?pGy}eoZ6{=GR4rJQ(be({yq8x+Dth8<3in;AQp5V zO&1Y7J%2YQ@VE%>`86hEllXQ496jHOMO(57hXY(GUkSl72{u(@Hx8%Nzq`Q+^;-4V z7M8=|_IPybrt$GzZL6Fk=;UVn$uCLjq2m)M!iqn+_n(t$Yhq1Wy>$L|!a@SnzG`5r zsq(Ge%M2jQIJxa3E)!W3@Amo}juSHw)#>Cd3``9&dW_`)hMT44D%y5mLc>?O`X7Si&dg_jz3zQpSvNu-v~Pef=-&#ZYr zqEax4V+u@}7Y{WhzNMe?2M)lnb$K;;Lg$8s53+ z=_X<_>JE^aS<~>sL3M6wP6pWr5v(m`=@z;Z1&8nA$dvgTJYg8$dIZ_380m zRa?_^+SgsrJEG$Y(Xl}z9@sMS8avR+41?fZl{D6d@aRB0%t6Q%Sg3#-dnGd<$u>sF z)f$UDlQ2j}@V_{-Z9@uSK5fk={A;#<_sMI{!bh};_XQ6QY%Y(sJ!s2c=~;9FoK&xM z*b>y-Tm{|@OC$2{*@}@ai&y^#zz;rH?a1;c(%%$=E{abwLxIL!npkp=_m8&+1Y+8) zT66pw`i#M%=jY;=gv8+$bY3n0N*zT6c&M;k3mKs%+XsF$90qI8$sYY zL+!)1u9pMz_qbkADSwhuS>fbRSdP;R-Y9eYc{i?a3HL~?`Yi?TJJ zR2>ys^4ekax=|K9a|Ms#I;hFppfqgLQd9jLR|k}$N&Vj9H|V9S0yAEXEk_n;13|mS zA!fjqPWAGn(hr)#v-pnj4Cg-&I#)LNmQF?)W@Fo+1a!jjZE{@|!}*@$&#{<_VT3VQbWW)a?PG2ClZiGJ@kOeNVLt zde>PwR?m{~$*`Cla8q_}Ggwuxnqr`vO@~3fW1t8_g))9-zDnJQO9so+Zh_4z=Np%F z(SMjRLot8D(WC7FnacA^0)*_RrUTT6cBc8p8ZyNm)iF`;%U(H}QP#^cO$Gis&!+Z9 zl8~+ICkHn zmo$F)a5RUZx>431l~3{Uu*} zIgomQ)t2QtYX8#&C>lhmkTVCDUoRS+9gWI`oSk1omV=%H0V2pk={55^iYOR5G=wdd zz`G{*XLRy6U)#<9iI~rG>X@6*9Wq_wB^)g|NnAd3PsZku2@CCNnc30;KdH3Z3BR}%YJXuwaIb#-V`A6$64LK~f>4^TUB2#`s#8l8E8 zi)lOsAV>>eE|`r?r|d=4{-n$yljZp(f2TDX^2lDPx+fQ{@D_LovZwk<(=}G06bgBC ziMjXfEgcBA0D^%1tSi96Sh)WJRWz*HsAB#Pa1oM)Whd}40@(j80wrk`niIdcz;{aj zx5U)Lb1sE1td_**`(9o%=U4$SzBXG>-5a{=u0RCh&E_5CYxR`kET_T4->$@!)`{G2O){oD!aqtfDILkAepcKWz=Bou_#$!WD^ zougwnywTu-+=4KdQ`;J#EYy1uK#U0mVvM^pg-;r?70hs*`P>Wxtwq2B zx=`W;x_wjGMO}BG$WOmD$Wb5vdouHfkye%&kp*z7#%Q$3@)@sM^5v#5+|W=6AdjI< z`eIRQFOvGHP`UP;lWmXhGlk;Z*m=m`bFpNEg$9E&1bFi%gq3>iZ5~n2QprF9S1Xh3$DsSJw+KKUgntF`73XG(vm_yiA!v zQC50YiU8a^-M7qSS-OI#&&NZ8${{iv_@ICV0A8?U<8?G+c+k4?M5XD$gA03lFedmt z`ii3H1j;Tze0+)wG4Neo`88xR`)#?y{EkD6X!LSc`(KWZ0AcNx@pig$cMO%zN&L3| z9-XW~j`nXNT?y$R1%Rz}aFXM1 z)3QcmSrC%^>5adto3k>LrOtrf!uTo749KfmVo{Yq6bcSls=NCEV(b{`8+yu)b!4b0Ja=f%~<5-=KQ&cd}K(Ba9?rHaRGC);t zIcQHq^yx_GTjdyZA3$*wkb9LwUIT{uPN0U+n57a&T294!SJ}Oic@RTp5 zM&ER|Oa~0Eq<-7`-x!S?+%QchtPPx$h~Ajr{3QtvR(stm_i0C3SBS*q0RoT!0UPgq z$)>GXTgn1!{rr}hUiKV=Sygt}=1-&#;Ces#fi-XHnG(UF7z^s=-4?)iz9#}d42)y_ zsJQGmNy!!71aLYcf)XNI-TmcJMECNrvXC1^h8I25`*Gjkd2U=P(lq&}*NB%rlA)uu z1WvF2YMr}8ptmY{beSEzxKwOpU-0cikMBn`NRzH|^+&I7oB!8}3sb{$kR-d&MwoI-*5 zDzVBJ+5!0I3I4aEOIYVuey9&C9p{4TWB%P!M-1)GyzIK|8^WWUZJKiArscxHQsmdV&KDZrf<`X%w7_nvw1Y|_*eBTO#?9;4aD-inUP_Y+iO96ytMmQ zeOJRHsFO<)t5xiyS24MM^ehr!K1JAA7J~3E7Kbo=7MHq;(S&ylN9`oe>4}Gz+hXT; z0cBo=Jz8pr*8W-Uy~T@zH$5D>$TBLd^N>@{lK5&qeSeol-5F;akAI&WpOLdax1JcP zf!Z@s?s>@z?Zwn);T(o=_{47kNDv(DbHv;QjMq(NgQ1w6cND)Bhkwl;YBjBLgYzJH z^F8lx6`;)-7Vdhw0UdRLjFXX!TS2`-=L*G0jIMmNJfo{q*QOjeoMLchC_AS7N z7&6Tw%!jvz(Ok0Q^1>cQ!u6!X0tfmu$wg^_(dp#{KtMNgN6NLg%MDsFG( zzdW+$RIQ*Pi#D-DwZ#42e!q}{+nw&=M?aC0q8IX5Mhjt zTml;pXF~^XW$Q6(7f|BZt)O&PR{34$#Z8+n#qnhGr~2zl(Ql<2$((j)`Iy5{D^f~L zq8nYJU&}~ZQ|oaFf=RVyyH?;$lYsZr@hY?EldY6bu)It_AWDvuslddm>ps{SZTUHel4ycZc%&*w6(tslYG5dGgYjc9$j(!b%Mtk*~b&eU1e1fADKmW%N5W^PBf{qe046W*SbQ2_2T>{W zmY5#Ktz{5vyq2N;>Pv1$>A5^Bc0u96ICMGE+($F2b@~|-8Ty9+S2Cd8*!O7sb=6ds z3;SD~6seXQvf-``7;1G?JeFFxwbJYnZAFVmVj=P^YElLi?LtCxq1xBKRG!--ach% zQaEjEJqOJmXG8x&+vcB5=!1yYT*tWU?)s18qle?hTvnce{c~yD;HDXs+3g+*reDSHXl!>T5P2IM8X z^Xp7HaE5UoFK;`AfwJ;1*I?l~>9}FgeJv+-V~cZ)a_3lL?M7Rrfd0|h+}r$zs-x4l zZl2zWj)x#+#eNv`uk?vq)t%QH@%7Lj?f}L>I9y)m60Q7WCz34YUcwLEOEbS~4;sSc z6rF5-KX0`-{7x?uxTu%mb$g?aXv{flW3lK-$f^loaA*FORNN&3C20f4`>yIb>9|`V zN-P+N^_;_wxE_2{S`=JZXzyyPY{|(sM`e9kIp8j#lEuGDD1UY&SGFnL!D6cpLr+ps z!;(>q*nmd7aa2pc)oW>(tp4yGiDRl5SCd9T~UMJ>_xoPlG6(!I9J;b}Qh_1RXY5fTm`$ zmXjgcu@h4R69)ADBh74B*q73PVzECM5hy*nyT=?S@Xt{^ds$N-rO)v*?XA|G!9dKj zvOx@_8N@(iAlXH%x#*dU#WEu~73*ba_EJ)GqW-(5y)Z7-cJ>||?B(}382DvLD@n@Jb3?VIx5BGNGZ+}0o{xBtw!~+4KqgMoPRss_R%NZG+>vtm z72s(ORsVPLl*`OpBTSsZo<$UX^esD|o-_Rse8gXrg6C=kR;pdmPX4clo7tTO;8)6rf7fq>tz7^x{Ue|LAp zLGA4B-s7JR1}a@Yda$#Q13L%U021RGLg4IJl;qd>{?r`XHy?0ES8hPVG4#Wo@nNv^ zL~W{leHDu1vm5F2wImLz)#c5;i>$*Aa3lcW&@or$)*1Y-;=E`g$t(c8a99eib$h9i zv3}FL3EWy3NT^1fA9E%%as?Wc5%w{qVX6+2bY&23DMeLKoqh3u&z6_X3?c~{&TDEd zsn`^ui5A{ON*kYnKal3bQ`5-tZQQvQQ9OK?UK3SpkQ--7Zo7H+uc{1fL(wZjh_81~ zKKRg5+%5&s1gwnF+zb9R}Si5G-0ka29KqyJT)SL8}fom*iUVS8kku5fY<=vuvlJHN$Sjvj%ti@Y8Mk^y*LT zl7s+*h`1I?{JA45wH+@wVNW3{3{(1}<`Ti(S^4FW7Y(L|PZmsA?`T!2R(_A`GJ6|1 zCnt)(7n>(Ads0_fVffE3W{sjrzDe=PEnk7JT=$!xnC zz@6=so(|2qsgR9+m-$u#TA@3|s%@UQ>-RUICu@7vaw`3&O;860&?Q$q)yef+>`2+C z_c9;-pBf)gQJI~zGG2!4VA-PV@5tEmv^0!_0@t2XvF9JNzTaA%!3YqDj0!f8-LyQuDod#N1~KN zQNTu#71!ECLAq_F&rSKSn`0B)Oo{$5ixcP()5UQq^iTg00&fySE{H|SgX+a_Q5SZf zrN8YXj`?Lwn*g5oHMt)rV|#}musiz9gN_&SmxD5BUSKjI#>yxq++|jcuIYbgz zVP@t&BcsLUDOq)c^F%O^@in;WRip$YjxW@E)xeRpI!(fn|9fdg_kPy~Jn^{7Nc*ss zX@3?mZ*nS8C*wi=uCf~3gJCUF zZm$!gen;)0P3zw=agwyuX7)m)ku@JrS8VZAxV$~Gn}BAby(KohDI1TF{nzS2fy0B9 z^2_v#Oo1G4?~kHDTiAPbA~T_R05c)`pK{#{xEd~2|Aw*zjvSs!*#e4DuOP&?yaB#Z zneeZz^~D!cz&Z^8INOVVNRwcnVY?llv24HMIePSgBjG)W4 zJqPR`(Eav&tHkYH{oRK3A-fiTJ?t7)Wz^zArilC+1bQaD5A-e)&x-)2(8%aN)f<>2 zhvdNR>gzIn+eb@OaMolCoYW=g(zNt%OcrAbSNk&FIJ*z39GC!V1d=&YZE(a$MFwWm zDA%kcpnM1k5|2tNB;WvY<+rHcHkaGE_hmLsWTVgK$1NTCCBMXdeU2}Zm(X@4nf20< z#}E+IWSFjC_ezc{8XGmjkM@pA_A5$ZuihZhwT;0`xpVANMP2CssJnl%@5n@dOX)`| zu<4m)p6z;McQhb&I{9vEVOb~yYs@jTdBOMjh-D>a(t%4q^X1gRAHBEgMb@k|SUhdt z3sETZX?8#S_yLbaCDxwd8nnKQhV(bTI_$oHTa5gH8i6|u`7O(v^KH7U%1(dvJ|&Uh zJO4alT=7MNo!?u9iH48kDD_rOMA3=JcrzG9{IEC zsq`XoBo9q1`-5B_;j4mCA-=tAuzLt0R1Tkoeks~U_wwQG-Cy~}IRJadSi*=+Gg8$! z#a$rQSc2EjN{B|&y7+l1k2Kf!?84cvA_-)!?F>C%sV4eIDW~-4HYrHh_*3Ube7qh1*R(-h-=wk(J%#zUQq++_=4T z6{3ee`HQd!5q33Xya~k;e%Q$}9Lzyde|xSLrD3ubH3k*6zzfAhh$WJT-izN>-M*9d z#jbHW@Vsa-(%*CECWmOR$WSL2!+b-VK9~;9BRob2)msKIgL??c*Gz52Tm)Y3i+hB+ zMtD(j%U{l9tkvI32z57%7MG4SpywvLz+jJ=prcM14#vBQa`XEd==hYNM`-*oeB?oK zI~?NlezX&DI6a8jWm9U~vvJ{P`kINi?#Gcu6q)G$(qSI~y##KM2p2&!DN;h*A#6?r zYb=QoX5!7&9=gSJBtj_%93)itPU^7@hLc&sk7l2QmMS5q(GkPPx^Drwqh%W*68iX7 zl(t7+q7Uf~drv9PUu{u4&4kfohp^t_XG};vQ-M-XIUg}ypp!CcG4k;uVNmNL32^jS z@jJ4&!k}r=b?DKx_tm{Q+kSGWg+B?>Al~-*C;9r- z@tvLckH2ja{NbC^g1@#&$o3f@6^87vIB}%!2;?Dp0nN<#bIx_b@e-)Y?6>PL*}j;1 zWjLHM#J))Fq1bm7N40wLhM@sBMUD}WD5sbxCuE8Hq!W+rH7MR2T5K4T)+d>T^rWl*zFzD-C^*CyTiD$md&yPf61mry&&M)tlBrXHWJl6AtXE@!DlwAZKYg zSM6xqO`A;Ykcp(5;+*mg9o(upph!Q~5Bs_Y;4AxNzsL`w7s45v1)&J5Gy8I9Y7ud& z^Hbi-29k;Mt5*k^lwV8(8R8|KU8y^lP9cz~ya3yAwHd2lmFLnRAf<&ew~0*7XX@KD zhUYVNgT{nqxy>EQWJ|Z*n|Rq^^2sCvF9<%Yi&ld`EbI|+d4AlUhGo4KC-X2M4;DB^ zg@aVFR(V>zc&g|BC7a?`h_|a!;M{3v?%~7zUCDfnLi015VB*^*FrC(kkjC(u6D+5F z246l$pYLaV6s<*vy!yfr><4cjH2qzeDE-? z;_cN@%spU`7T6)?RCd!)dba}4+*EPT(+?{fA91=lWcsJ|h&S2Ghm^%z&`(_?KGuyD z=?C{e74_xQghtC6`XB2NK@(`9u|xC?PfT7Rd_ELTI!Ex<3mi1^Pfk9O|1kHy-skG^ zpqAyLUfp@_@GAznKT&bMuNhf|?jhqz8ZI~5QUb6;bi^3y##TTi&`sfH?M-t-XZCOpUkk8_h%{ck30?eBQt*GAo{C0gPxUSe1=@U*;w809e>sP+&o<1 zvq+ck*gKAjKHm~T+)Uk~NXtsIO$;NfBO#cm)C&+5&h8#M zIDZ;vTVSi^&NbbC7N8E#|o21t>jQrCDg!)>p=y<-L!P9zkJLmZmf7={< zbRy%5bPQ8ZD5pAPQ3)BX-Rp z5@s7mD$CC#o#Fuqk>S5XW?UK7uB`)X;iie^(pxF+GGipcT?d`G(sIgUPIsEVO{5=8 zbU2=2eX*2#UgSi+*Za}+8W~&PI)~UQh1PI(iqzAqhzKHp!J4lYQ{YHlwb{^Kf89z5 zf$S?KQk83k)S1f~zWY93vs9}y!uYKI$9rpkd=}j-~bt%tqe9VpJu{?u)yJ7nBNj!aKOzXFZW)BmhD%-HyY( zy;BUdoI=bwJ@TVFWFW$9rngoB_lV9NxG80R$loch&QkeT@%TetLK)#ZPLEL9r#q#H z7%Xbhu+a9VYnip<^SqpRdz63;YBquj`8V3t1m;WReac9GRN53#n&MgahF}*zDfLWh zqv?Js|F+PM_}Yy|e4+H?*^iQ?&|fM4s;}R*jNhHWwA6oe|8TzeksWm|LsHIsfBZQq zEuq!V=z9HuEB`8F>j@6)>kB+iW(S_{a9dJB)$qZ~PnZxx;ahQ;3aVHP?>8&G;37y} zW<-KE_EC-|Ri%O6tH1C(J06Hc5#Jj**53L@jHVKh<<3 zFALni>4>8)Q?Rw4l?!p|9J-xvTYpdb(&VR$x9FJi_PNyC4ovz;tx0}V)TioWYjMjx z#Z(b-RGibAgl$%|M2-EG!$K$dyy^V`!Ef&)C;BHD%Uzb#`#9SsjHrl+ozGDyOKWZu zc_sMYYe^9)KL`CgAHH#xtk@YCjwYP3zwv7p4Kg{(lxU*`d1)d?Iy=di!wPj|ncHjp{ z2_U0+n#EtVtn?#X?X^=QE$}MMOI%W=^?P$+U%&xmFY|_W1?l{Du+ZFcBYvGK&qw3Y6N1z66cc}2< zNsEW4zXGYqRaM2tiTRm$K{;R)+h)0sr%gHN@LAwHk2Fo-1!W!PMO#~!u0c-cIX=WU z2`T)B$tZUTRrJz^7*e6eTB8+G@T-GMbU?kg>Z@u?G`vbwg%`~M+@A>|g3Sn?lz*xk zi|#z0pz*iNfd>P*yS3mn&dDA>fArKlxq6kax@ai*Hi?{WQW)%%SRIWRa9E!e?b{Hm ztnFtw@+{&gN;}1-wLgE2?Uf+6^(xO4(v8yDGcbS;U@WGOD)muRFLM@|jo8L6MaNm@ zWHljyis3!zcH>Nh`S7-_2t*fe4NC)RA^j%A=JV4#WGXh4FDWLQ9iwiyFJvpzm&dHC zz)m<|t-3DA{bI+8B9f^YgHRfO+A+F+J+O2nACawQIi|(lPouBsRw;*sz01IE7sKFwd>8QNj+Ms4ae&^Zrgag|0@s-^Pq!&VNqB0ObTYL&ToHi_VCzY{(A3V@j1V4yEVY4ye))Kx=`NPW;SKXxdk^Dre_RE9M1{!w zY|ox`{`B=H>qHV9f4j2pzF5-fjgGMbQ|xcs66xP=#$0Wz&r*zm%w_Q4u?q2i%;@OS zs$zb*eMS<9j1O7aW)3Lo8zRat@bby$M#ox=$;fisdfzba2t+TdEAzJQpC>LKjIU-w zlb7-obtkz+MLe+l|L_$}rl0hW9=NoIs8PEJx5O9G1UgmdXF$g6nkWi+o-J!D($9Hh z$R80xLy;9XxFCBC9&Np(t|F{yB)Q8j*}kgS(sIkSJhvr_5^CVd#a+p`7J1KSQ_`%= zjnu2Abz?eE;rxDRhub_0z-L+7P7j2UR?u1xp1#lhyJ^y(I{H{|s%d6~@z1gz6*(Ef zdoIQAC$U&3HDUz4>?;>_*urLjoVS*T=tkI0H3b!^tk8DVMSLF?i?{>8Oz-1JmNmdM zUalMUEleB;ZC+H8eaMx|roAFT1O!>hD!HtD12V~Rl$3K6uCV`hp8$16`bvQvu zE^`ngzoSNAaM=5@HmGwlaH#8Fv}D-TPr|-hp8d<{vys|Y^Yi*Xcoolj!UbN3D%O*Z zC0-17(98;aaasL4JcE-xlW2-KRcH!wY(Jb9@;+-Zh{R~tHWvlA>C zCajZeXJ`7SkeKdOs+r6`qzH(HAof5E@^RFnEsZ)`I~v4`39lUq`lLREz7`; z)8sst)w7ue!Oq7OUE)Zgr=bj0ZIUFNFRxbXkg;^sq(7I{S|||#@@2din|x)if+)#h zJ3Gn+f<4A(%igu1SheMDA@DA+nYm`x(YO^`WpECevzdPSwebI==_{kE;GVB9-QC?G z-Q6W1-O?pUOC#MK(g>1*l$3OLCcQ?}Y9-iO(UyBc}`{8=-iJ3in_RK`u4^fy_ z4TtRgLwLv(+%n+Eq+}duXEwD2q3eC>bXJXUbEbG&B%xCD875!^8r1es;n#EUx5G>m zHIeb6hTy-hYlQ2CpfI5I=?Yg2v6l9(GEyvx=Zn*>r*&dPbxDkHRM)Vu=m?KmYp?q1 zz(Fc?6BMb!q?p1--`nt=y-m&kR>1nc#5Of9#nDWqeH@eT=Gtoc5 zT-^;PFCeK+5=cTokg4|HFi1>AAuGi6f9DyZ07L5G z&)djF8sSrYfaojV>pbB}Z<&OCs^k}%K(oUiH2Y}I#`k4ivAsjkFc#Pkokg;!-9WFl zYI+zvGbx#UlCjNN)8uuWUIGn7Mt> z5KEbla6|`W9-2xlPu&t}dREXJGf60xj-99NQjH!iuqSjccVRzWo>y_?-WT5D#3GjY zG)Qg|Ezvp8MJGUO@Pde{%7+d7s;fb>>e-zTb%ZYWvv(3xP|9O^j;$r_+Y`S#bv=9L z^H!c9htqr6-b9y6QGcE|a6KG2J>R;DqP9{*+y#YXdm8~S6vKvZd*&mD7Qm7m$p~hP zV#P-2M<)=Vc6C8`mu(i$Tp4Te0Rv~&FPb=aF~QN+NJBuPlKNG{Zs57%r;K+Lxzlgt zlRpA%se89M!Z~a`N0F9zu56P)rR_farRUyzte>B?AQ1l>G=`;6UMK* z`QoxZ_z6~ZH%WlSMqH}(O6Pqi1+v{}{6UVB&@K7lnIrw+L!DLc$ybQXPu|ANc{d3RucL2Y|u9EywOmF~3lAPb4;WMU`P!D$_ z!dh|v8R70&s#ogN=d-Cs8$HhgTncu=$k}7mjj_jvMkyA()Y!VD`1=LnqPa6;Gyfy@ z@_n1dsrZ%q>?MOJ%$~g(B+ALC>n>uZtco`l_)>iTZrNgblicZSx_aEUzuM*6H}-rv zCxb|A;zLb;VI{*ySjd3dK+~oWqUma zd?S-T2akpWafV#LDY?#o5*g0Jqz8UopvnguZAmNB6xerO7PVd!mgH3y5 z@DenFqY9jMk39er5Dd&^wc$Lp-7=fr#dU8|_T0L7?Qt~kbWin8DO+bHj}?`r zbHeH1Rpto3j}l;4nmK(m%M+J3dkljcn%72*uHo2u&CqzWf zgoJHE82dJ)S?ue(ODmCUP57r4(UWgg3p|O16|F5tE+-bX!d4@lKLmBVaEF_SZziXz4=LhqM^)ng-6Cpl`*6RVyXiUc$ z3T>rNqScWU9Eq98KVsc)Q&Q^+g-D^a>ncO9+o7fpmDQBN=(<^4Xz;b>|FT{p&G-1^ z2TdByd}>N}7&2_#4>h-o0j-Dgz)>t18NINEA)Q$&h*V2IDT>2iC@!q+e!orulf3?d z=~n_SrTFFKQQd$=zHt>_G6_WJl$W#m{1jeUFJ1wksy`TcS=(HnLE`Ru=IQxu;7J_v z$yh5PTh~tvTwdb@duWGpCC7$u88_^ai$V=p1_nRjS;NgvY(Q@6h7jVbWoTx(o%HI;kpq^=s%si!0V03Qc@d%D4HVmr!n!mdk=7CDXI zZu=C~g-?he;m{S?^*)#iEf@W7UNO}<>D=*wus>$v*OY|%3(@$Nm#uJ_*Zt#=gAM_X zBD9@Nw>kifxh!lfnb6M!Ucca+T}qyxPjndt24+gHgBPJSx%`i5-Fp9J`X_C(7wAPL zBoW8oX!-T~a_plGuROEDz(Q>gXR!j(TLb%!ve@t7^CW#5xm)HB1$8t51;<~@mUrDA z9>=jqQcN-Y|- z=P`beJPxeQv(G~IgW2Ufj>CHx;*O}GrvQxZ>SwrJzLlWxs{6+I#rd22a*n4bCi_$c zQ&yqsZFt~POoc8uiTw$oC-*QQ)vO&2Mm3*FZm!KTDv7XOA!WhKt(UYnSUDXe9f080 zsR4vvyh4Lyjv#-KY#{>sk{GFEA9)kxM4r%3Q7q)~;M>p%N$^jN4mCyN6U_=NjVt<2 zS5Gf;AuY80Y+~G6>CDpFaI3GwO1%_F`CB<`g_Vt9DaA9rGwx8g7mOX*ygOj^$GvJv@@W{jvlIqQlG zvD2ok-gD*Lra$#C{MHS5d;S~M|9HV`{ zdhSeNW{wHFeX#y*;lvo~zaTQl#3oRY17`Y4%Q)Bm}v$4(_E4?5+$$~Z+{PWimYu&y_mq9HDDz4Zq<087n}FA zeKg8m%pIHV=hLNRXD@$6@u$V-2mQd;9ZHfO&H43>t{VD-OQ)Aw!t>;$@>Ng}nP$%^ z)jy)B9}wokGx2kUbC&LXD6pst$v;C{lI)wMhS+f;Vv?S z7_D@?f^GO^xyz@rHvG#nBsXYygXqIYCW3SIDk#$~eY9#G74{8(d6L)&G6nvvDeVt_ z4cM6os*)E&vQ1)|$o+F$riDy;t_{6jn-w1YQ?dP=SV4&%zgaOGs8;rxihf9g9DqZ* z{`cMvo*itf%$KOk#tP%Id!FtrgYfr}@klAhYBzgQ&7CQhaTZayDh25fL*&&r9gC-5 zwDzrkd|jM1Z>QrmN^WT$SI0A8g)+L2kdZa6nwtUrbFzk;#TV6&`J*~#Pyd85UNeB9ep$r>(rYsbTyn3Uj! zlqt(VMRCE0iz60jHIDS2Tm(*Gh!zrf)+pYS)X|U%As$XuIvUDcsQAOMJgiKQqmT!D z4r8$|eBvNTAofyHT*4DJlX%)X;6HO2?AcVuMbU#V7=mZ@nE%+v=tl{@+c4)>$bRMJ zCpwY8R~hIBHg7Rmdn zSQj!wUea@N!TkQdOynB~ANZ|fY?bxSjfkI`4_wMmbpOyo^+9Lg4?a46=hv^<#A&7deZc5a*i75e6)nZ;*`6*8T)2ima{ta>@A0u)A8UVzN;I$TZI~WY%b$;vR+(kH8R5p1# zc+Tn6c=r@L5s{U&o&9G!L%&C#bR&2i4vBI`3*qALvp_}EB8|{v3-DK2M~W^TFfRa! zyo_8hCKyu+0&)5s-_=QgzB%0BFQ5}FTb^vzSgY4c3-uPj!*7=&AhV5*~&R_Rkq6Yw$kxi?OKj#1V29!tC_=uDMpZ1_4fqus8XKQz(1EqqbgRMhwl{dVw^f z2w4+ai`y@VEZJm1K~J`AwX6udLd+fvy8lZ-L|AN7Iz5R}`=t`@R!PZ~k`tiR&-v$L zC&Z!j@kcawHtUiz_WgnhBg9&Z|6}UZ7xA%M_V3x_WF0(8EI!GI6^Rl)v%Lqteh3_k z9>`a)pzQvBX__0^)n!Bz5fMY`Xe+qLdv0}7Y^q$sQaNC7UtTT#yLl&}GYj-OB#e_c zp)+1tFX4$q+Ra>g3jL=I&>t)$X?4MrRRk7NJ!{qL_CPlDdd0Hpa~ACzFH*p+!>ryt zaUin~9_MntG4RVC+PK@8aDeMPOE6Gu;ZQU=4q8HY$>kx&EpN3aJ5@34VBW}cZL0D`XPSR* zDy%9yQ|1>ZZ+CYNf}%!$)hd%c8kz}@v;WU3V}viGBJR0%&a7eH zwB|3+9-472fSyBn{Q4hic@$q+#iRBPy$|^Ei?R3fKXc1e+pO%J0?dskgNM@{VX3vU zl-0-Qtu;0J>m~ERT;z?r>r;Jxsg~xPEJ$=qgen9o@N8(|Q|MUeoKur2r?Y1Dx>>nW zc)L)DN&3H5vZPf2;;ea~OzqgZjeU@+KSP`^Mg(61ixrOSM_35u8!v3?bTA=nYkiLC z*FJMslhA;c3r?TjTvm$XU*rG;NTi5G(_HYNH2yUss6sL;R}7NhLUI1ngzvM$lO5kr zT$U-ivSzQ`@GDyA+T`tIVj~D-zRXlM3xnkC5brd}_us!dzYTnpWF=@kM+QOEM-Zkj zw=*QIpcV4|Jsr>SwM^MRyE;VtZiJnHx8QPXBua7g^R<2yItct;XEb7uDWS@_B%=Fh zG<8-aBWE$FW->84Nl1Q#xyoh^EG~zxN24bk>A4&mCuW)ZaEk3OmW1{O`ZKD=@s-RzR&0G zVZMFCtU1gi52(N$XG1M*6IJ7rLhMRl?HmIuxQM=SQ%K)$uyT;!;B?JXBIQ8(`vMwo zjH%rr&rN$n9y0|If_rZufwY!fosLdl?%-nZO&u03qM(zi_da0+U`=Eo7&!UbyW&P0 zwOf>MSI^Lg*|5_86bc83Tgh6?Lm$Tm+NmXq7m#-ET9eAvmsfo~KPrBI&1?-~x3R0$ z49It*q`-oL|W zFOk#NWzjH$72%_lhg+|1NFSW~EddqiKyVB(FC-pv`Up25U3F%6btkgx$r60GOcvZv z7vqOHuwt_2bH<^_?l0q8>x#e{M0e>k_iLMI9wC}T@} zvlxc75`AwdtQmKu?~0`A?5|0dEnFLI5#aig*;?M2K<@B&(CRmm9UoC(cqJxi;D_V7iMapMql!&^_x;Chf~ zSR(tjohVfw#f^SJd5}Ima&?0`EH{fZ#}=uLm+PkW=W1zM&!8+a1p(26;X6dPbZIIF zzbXmiRII;S-j-?m&ZUM4-??D8hX0lD^Qg)c;qHBM(WYo(4i??KnGJ?&sxM9uq$!Lr z)k|}@pDS^c2e+#_HQPg9bMX#+^Xm#OBeg`Mzlcc=JsAfEV3zbn(|L7z-LAx|jpv~a zw4-U7dm^c>V#8m8%ysghA?B-=+36=9Y!H95iWN6KN)JVJ4pA(WPyRW5RO_;WrkR%T zTXBqp8En|wsWqHO^`lPb&{%4Q2gyPQ@1-~{6GOD#WjYAtch3eULhSyj9gsAHT%H>j z^Y-6#O8XBpllilPcg+0b7aEuhb7B22c_XBH-7)h!2%SluTB{%d3yFgiV?_HQMYS}m z1(+7H=IH(c+fCzwG~F@mNUb?S+@^KV{!FVfDrEM`e9Mutfq;XQVhRR-zV( z1AZal&;M;(;9O@na3roWm3Vx`39E$?w6vD8 z5p6bEB+Y+V+6S7AC7S}x822+Kq3i;&(T{EK>Tt!tdg($W4`Q4Z{Fxr_>y zKQf=qeE$GqRWe}^tdZEd;cFcHquD~1D+~8t;{*{A zY_uXFxB)M8N}>;KA(m&5`n}18??wMK$6-KsX%!$ofA;XLSp6z_$ZHM=$nv1Qgq`UL z`BTxkWb=}g$qXCJy3C*kv2n_D*4a@a_EcuzWV5^ctCHR$>0otZi@+F-U=AldYG2%p z(N&hN0rBS*B`&MFLqH3!j{yWjP?E(f`%q)R}M~?xSSfaSdiXj)%Khn_L+Ve zsTdTFY8G()JC3un|H=%!T`X@|XhaAgRbYCZo_Ff#UC_d|yC+RkmZ4>*x#!9?%tUU* zScndwvYP*1#Ezt9S>kgMP3$6cl{~;k{@ADqSLE^KA1Y1c1v&+*HRnjfJ(zS%7YDd$ zxx+W*-8Eml?S*yymoD0k@Vs_sBpyE6uFM_#)0U%Oh_&w4T`g{!w)tB(NdVznBEk1R+Y2+d` zA6(SA*)=7=$;>7n2i_S+Wv4K>G{IX2WMh{NNgPs<)!y1*H)CHfhwt48tJqCEx#nlK zE96DWds+%TQpF;e00RT$GqjJ61oV_r@{OKCRpi4B+XAUPMyx{yAq9#@?knYu3_++? z!UNBbJkhqKTm90~hSeJh(y(CBQY{Hz-`4B3@DjX|U9=9@gKZOKhE`QZ1cxgGP%FXC zIaw~h7PcJX9^(4D;WSnpp~!ezq|b#U8py$Be0xkq5u(y<4jsUjum=X(oKL`A9X;n5SfxpS|LD z)f7$XVstA)ql3JJYVD&G+)Or$tG>^BCCGqG3lc6+aJaEQir_WTbfeI@TLHY${i7== zmKb+B6DL-R(L5weDbAo*+vuFzttjDolcrYb zq^`lu1NOnt<{SmcHuBSAoM%m)s#(%kp#nFl$>Oc1so3O01_$AKr0M$e-_%#vO6W90 z-A~v8edFtZ&gJXBl8Af0?f=2^YGH^bt34gV4X@oOy8OdyiA_$4uA#_J|2jv4fDH>^ zA-NO5-FRX$l>xjkTd7)%xYJN@yF$MkNKxXe#1HIdK!#`w>^eGZs7tCf5R&^j&CWDR z@txO}5nWG2{btUY;$EaWb6L0<32`pH7s>ZX1VFRlW&t#BSX<>irHkIXBYY_{pKC4+ z;TE+tMEKai5RIiz>SfZvuhx4rV~^9*rq#oPLY1_(xOh{gi(esqOGN+h*tIu^L&f4S(-_6;Y~kj zKci2^Y9ZkR9TH*FauL*SSv4s`mN8q zN1LiU2-T7Qoq8OSFq=-#Ynt9(3m6Wr9y}oLVnU(`tuq*VdQ(fbN%IW7l`?i~;p4#P zmQ@VAEHEl=e1KB`c-xe9kwD-@rE%e3)JFkie=j+tjT((70j}x|dOZa?b0t#J5efor z+ZvYu$%yt&H4i#nUV|*+9Xbixm$iuUQXzs!&mvF!n%^#aIa<78xG%cTaFKW`zCLDy zGoVmB;OVu``B@)-=};9PG9DLIA;K;<5+wvE*oZ;8SO=QUl0W*V;}1`Id<@X$ShHg;Kw7e0 zr8F>Fxc$>h3K8l~4;Y|A&X~LwALamiW^sfg3~96WHr=igd27I$uQA@k(Mqj$>b{!P^iYh&6G9s@F7tW}k*1|U@a`mkiF zcl!bLNrWcFMEH|>2uL(0dUZg@FuQcv;fuuN#59STD5$V*g~>% zU5HeozcM$9jp15_={z<9mD`U!ot z;>Lm*)1c8lIx|abtOA%R@Yxq*nv+m63E)?xsoR`S&9NY>ULIsWnE#bbzg4%ZUI#@6 z_Cnh6-e0Hp-0vrw2J-Tak$o@qSj=Ifh3axN@1q-Nzq$U^m;qnRrlrew0{WuA0$#|w(G56PR%aH}PWE$sXT@Via@7443&ovK6 zI5ipcrnOsD7vfoIzOUcEaRP2s`py>`@x2nXL*}SzG&Cg94sq)GICQAoLhdPPblUuU z=eXii=n|E^X(*;7(FkbUNe}bfs`oelq&!7|GCe9kMIld1KJQaBRk~tcN>sjvyjlIi z3;DuhVD@9q6#S$9uewfoL`}_Nxu{`^MhUJ)%5>c#tv+k|YVpo%(@(iz5W(*!wI-weDMX89$_ACK*zZz(cOiFU{&y+yR88UZ)+y`->^>W2G$twbAnx^ok^&Ixh`5n31o6%Y z;1gk`XzEDu?Y>%rrm3(^dob%$`~4Rhz_ynsfyh=h5m*x-ytIE&3-7b>SNM*mToPI{ zSy>Pbp4)?TtbcFhT*t(RW>bR`{NZvTfzebv$gdtmr%#=Adj^f7Aga8Z6Acb4w@1EI z9$$tW8lDCU5)m(wJZ-Rd+sQuOSegV)&U530fD3+Fi3AjFcLHE|*j7~Y&GrY*PCiDx z5Fp;>pr1ek`Rmis2E!MhqXxZe+fatPJF|j5vuene*W?Zd;%aP0rU~kDtQKX>>|}Az ziV(Lhzn%eb>F{4<7(fffZ{Vpdf4c0K7>Rl&>!OXqZePf&_kpV&*XDM5h_%A$v9H{# znQ3*cATUA>ot>Yq%?R(*S<~YNwni0)ow~7b)W%^ZV4)rS;18U+$zeudh&!Tv8-e#ovPVG(~J z%yAfY`2Ar5UFtt}6rILe)15*FnzIWVfltT=+?` zDx^Odq9GGsNs28vL+6_CX;nB6(Z zi-JQafcAO5zxDNpQQ6@If}@bnA#I#2Q+TEQLRz-0CDR5k?ve#1HqM1Wz#G_vuN5p6 zn9Z^hwWo|SnUuxucB3AEK7q3vuRm9uC_U6+yG?G4G0{+Y#-7NKg@y;$EnW$K2dxs+ zrB#hZH(CAFRpsUySlOF1uczs3BQwZyl=>J#By4aKA;=m}4>C@XpG2lKeA`SR%b8@| zRiL&n1|(HpfHl3GkLl66*CO{wdcf0H(c2d;3wN3@3=t&TWI^2*8g(OL2?1%)&~&q8 z=KmGoOgDgKy*ktZ#Rx(S0pEP#-#%>ox2j4OzShYzP>^M2zWK)h4!HzJ2EM6#PDY}8+No?iSEe**X>88apJ_iUrJa-QE zGeSl=x7VsNXc8K|;QTEP$U# zEP#=?YV?B^uYIdpkSw64uSbZLqOZDIinAD^a&GAH`s?Ybq<|z28@zR_uje7#K`T^O z@t*pPvs+S)9xR8vpP^-E?)N}R+?#FsC%v>&*py^3tQRSv>gvY2JR}a$6>eDGw83Np zPBcY|O;Re9KVUJB+bnX7*Z2>V{`@01?@S4kq}d6;T-sEMBn_jO2rnK_!Lem@%@(k^6_ zH{|_l2L17wJe2O}wZ8(fJAaC%E(#$oRbla|-4B@e_5rG2Dlj=RRQPQ3zM_6tv6ic^ zbZrzmvIpI!UtV2dYZm@aSpA#z+nN&(e6NEY`-gX$__6rHIk}4%E8;{EPs)ltW;(uaNsDL8397j4()%o~Fb9t(nCZ;uUiKzIZl%x(LgN{7 z_w)#aLGJq2Xn21rNH7fE?L4k6@mF&k`a^@%>n$EYkR?_QEdzFlRDEwl#s|bl*$*)-nDlDcL+eqcNw(PBZbCnK6p%^q2PKw)G6a;QKcy-Mp!lu@E!mV= zm|4wIG<|K>+N|SLFn|S}5N=v@p<6!XKUOBHPT2!Oq^u)p4ur7&f=O{0{5joV=_xb2 zFJ7>=_75>j(K z4Liw=?R8DQKeKyY9tM4%E4j>VEmrd0hGeNCF<1O|Pr(CTVyPhoc}xjn!3CSoygJg2 zHpupP1hB~8cR0y?o8O+l7Cq7B&}b=FLbMUD_?ZaddK*Uce7UjgMykRFKkPDC$wWjA zv&bPZfuKmF%hms*Yc%1v-Rs9;|(_H}xX+nw~2-XTBgOW}b$?|-}blQ_2L@UEF!v=d&TIrZVJ zkm~VtyCBia>WWf?hmN?4y#>y~0_J81!;rCU&2BI2;X83rN`QT5(#L%Wh~y5H^Y=^u z49!{s^vcUaG7P9}JNSiL9dPs$0rKE%EAD$QLn^K)1_0&j4#j*x5&y$Xf$TF~`C>$cV34P3T`nQX5;qkUOR~)dCWqcr(&gjg&Z?AEDU$JHbRW zy_DFzaSFjc1G3#?Aq!i2YW^E>W$yH^-=glaLVaHQrVu_ef&u%z90h=
P= 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 P(Tt9EE_;i1AHMKkSyyyh diff --git a/examples_and_tutorials/voting_tutorial/inputs/v0_input.txt b/examples_and_tutorials/voting_tutorial/inputs/v0_input.txt deleted file mode 100644 index 7a754f41..00000000 --- a/examples_and_tutorials/voting_tutorial/inputs/v0_input.txt +++ /dev/null @@ -1,2 +0,0 @@ -1 -2 \ No newline at end of file diff --git a/examples_and_tutorials/voting_tutorial/inputs/v1_input.txt b/examples_and_tutorials/voting_tutorial/inputs/v1_input.txt deleted file mode 100644 index 7a754f41..00000000 --- a/examples_and_tutorials/voting_tutorial/inputs/v1_input.txt +++ /dev/null @@ -1,2 +0,0 @@ -1 -2 \ No newline at end of file diff --git a/examples_and_tutorials/voting_tutorial/inputs/v2_input.txt b/examples_and_tutorials/voting_tutorial/inputs/v2_input.txt deleted file mode 100644 index 6bf8d174..00000000 --- a/examples_and_tutorials/voting_tutorial/inputs/v2_input.txt +++ /dev/null @@ -1,2 +0,0 @@ -2 -1 \ No newline at end of file diff --git a/examples_and_tutorials/voting_tutorial/tutorial.md b/examples_and_tutorials/voting_tutorial/tutorial.md deleted file mode 100644 index 56d8a839..00000000 --- a/examples_and_tutorials/voting_tutorial/tutorial.md +++ /dev/null @@ -1,909 +0,0 @@ -# Voting tutorial - -## Introduction - -This tutorial provides a generic template for creating several voting systems in `PyNada`. We will explore five types of voting systems: Plurality, Range, Veto, Approval and Borda. For a more detailed exposition on the voting systems, we can refer to [[DTY21]](https://www.sciencedirect.com/science/article/abs/pii/S0957417420310964). The tutorial is specifically focused on PyNada programs for these voting systems. If you want to run the voting programs in `nillion-devnet`, check the [README](README.md) file. - -Let us consider the following voting scenario: three voters and two potential candidates. Voters transmit their votes to the Nillion Network in a secret-shared form, which processes the votes for each candidate without having access to their underlying values. The result of the computation is then transmitted to the owner of the voting system, who is able to reconstruct the result and read the winner. - -
- Architecture -

Architecture example.

-
- -## Voting systems - -Let us first define the voting systems we are aiming at. Currently, PyNada does not support the input as zero. For this reason, we shift the input votes by one when compared to the description in [[DTY21]](https://www.sciencedirect.com/science/article/abs/pii/S0957417420310964). - -#### Plurality vote - -**Definition:** Each voter votes for their preferred candidate. They assign a score of 2 to their preferred candidate and a score of 1 to all others. - ->🤓 More specifically, we have the following constraints: ->1. Vote $v_i = (c_1, c_2)$ where $c_j \in \{1,2\}$ and $c_1 + c_2 = 3$, for all $i\in[2]$. ->2. Candidate winner: $\text{arg max} (C_1, C_2) = \text{arg max}(\sum v_i)$. - -#### Range vote - -**Definition:** Each voter gives a score, ranging from 1 to a public predetermined value L, to each candidate. They assign a higher score to their preferred candidates. - ->🤓 More specifically, we have the following constraints: ->1. Vote $v_i = (c_1, c_2)$ where $c_j \in \{1,\ldots, L\}$ for all $i\in[2]$. ->2. Candidate winner: $\text{arg max} (C_1, C_2) = \text{arg max} (\sum v_i)$. - -#### Approval vote - -**Definition:** Each voter approves up to K candidates. They assign a score of 2 to up to K candidates and a score of 1 to all others. - ->🤓 More specifically, we have the following constraints for a scenario where there are 4 candidates and $K=2$: ->1. Vote $v_i = (c_1, c_2, c_3, c_4)$ where $c_j\in\{1,2\}$ and $$c_1 + c_2 + c_3 + c_4 = 6\,\, (= K*2 + (4-K)).$$ ->2. $K$ candidates win: $\text{arg max} (C_1, C_2, C_3, C_4) = \text{arg max} (\sum v_i)$. - -#### Veto vote - -**Definition:** Each voter chooses his least preferred candidate. They assign a score of 1 to their least preferred candidate and a score of 2 to all others. - ->🤓 More specifically, we have the following constraints: ->1. Vote $v_i = (c_1, c_2)$ where $c_j \in \{1,2\}$ and $c_1 + c_2 = 3$, for all $i\in[2]$. Note that this system is only equivalent to the Plurality system in the two candidate setting. ->2. Candidate winner: $\text{arg max} (C_1, C_2) = \text{arg max} (\sum v_i)$. - -#### Borda vote - -**Definition:** Each voter ranks the candidates in preferred order. For M candidates, the most favourite candidate is assigned the score M and the least favourite is assigned 1. - ->🤓 More specifically, we have the following constraints when $M=4$: ->1. Vote $v_i = (c_1, c_2, c_3, c_4)$ where $c_j\in\{1, 2, 3, 4\}$ and $c_k ≠ c_l$ $\forall i≠j$. ->2. Candidate winner: $\text{arg max} (C_1, C_2) = \text{arg max} (\sum v_i)$. - -Each voting system have their constraints and can be applied to different scenarios. In the following sections, we will explore how these can be executed by a PyNada program. - -## Honest voters - -We start with a simple program as a warm-up. We assume voters are honest and provide sound input votes, which simplifies the requirements. - -Under this assumption, **all** the above voting systems can be reduced to **one** simple program where the votes are added for each candidate. The only difference between these voting systems is the input and output interpretation. - -### Hard-coded example - -Let us start with a simple program with 3 voters and 2 candidates. Recall that the following program can be applied to any type of voting system described in the previous [chapter](#voting-systems). - -Program: [voting_honest_1.py](../nada_programs/src/voting_honest_1.py) -```python -""" -PROGRAM 1 - -nr of voters: m = 3 -nr of candidates: n = 2 -""" -from nada_dsl import * - -def nada_main(): - - # 1. Parties initialization - voter0 = Party(name="Voter0") - voter1 = Party(name="Voter1") - voter2 = Party(name="Voter2") - outparty = Party(name="OutParty") - - # 2. Inputs initialization - ## Votes from voter 0 - v0_c0 = SecretUnsignedInteger(Input(name="v0_c0", party=voter0)) - v0_c1 = SecretUnsignedInteger(Input(name="v0_c1", party=voter0)) - ## Votes from voter 1 - v1_c0 = SecretUnsignedInteger(Input(name="v1_c0", party=voter1)) - v1_c1 = SecretUnsignedInteger(Input(name="v1_c1", party=voter1)) - ## Votes from voter 2 - v2_c0 = SecretUnsignedInteger(Input(name="v2_c0", party=voter2)) - v2_c1 = SecretUnsignedInteger(Input(name="v2_c1", party=voter2)) - - # 3. Computation - ## Add votes for candidate 0 - result_c0 = v0_c0 + v1_c0 + v2_c0 - ## Add votes for candidate 1 - result_c1 = v0_c1 + v1_c1 + v2_c1 - - # 4. Output - result_c0 = Output(result_c0, "final_vote_count_c0", outparty) - result_c1 = Output(result_c1, "final_vote_count_c1", outparty) - - return [result_c0, result_c1] -``` - -Let us breakdown the above program 1. All PyNada programs get compiled from the `nada_main()` function which can be divided in four major sections: - -1. **Parties initialization:** all parties involved in the computation should be initialized to allow them to either provide some input or to receive the output of a computation. In our voting example, we have 3 parties that will provide inputs and 1 party receiving the result. - -```python - voter0 = Party(name="Voter0") - voter1 = Party(name="Voter1") - voter2 = Party(name="Voter2") - outparty = Party(name="OutParty") -``` - -> 💡 The parties created within PyNada are just placeholders identified by a name tag. Real input parties are assigned to the placeholders only when a program is run in the network. - - -2. **Inputs initialization:** after specifying the parties, we can use the `Input` function to assign inputs to specific parties. Then, we need to identify the type of the input. In our example, we use `SecretUnsignedInteger`. - -```python - ## Votes from voter 0 - v0_c0 = SecretUnsignedInteger(Input(name="v0_c0", party=voter0)) - v0_c1 = SecretUnsignedInteger(Input(name="v0_c1", party=voter0)) - ## Votes from voter 1 - v1_c0 = SecretUnsignedInteger(Input(name="v1_c0", party=voter1)) - v1_c1 = SecretUnsignedInteger(Input(name="v1_c1", party=voter1)) - ## Votes from voter 2 - v2_c0 = SecretUnsignedInteger(Input(name="v2_c0", party=voter2)) - v2_c1 = SecretUnsignedInteger(Input(name="v2_c1", party=voter2)) -``` - -3. **Computation:** we can use all the initialized inputs and other elements to proceed with some computation. In our case, we simply add all the votes for candidate 0 and all the votes for candidate 1 separately. - -```python - ## Add votes for candidate 0 - result_c0 = v0_c0 + v1_c0 + v2_c0 - ## Add votes for candidate 1 - result_c1 = v0_c1 + v1_c1 + v2_c1 -``` - -4. **Output:** after the logic bulk we can output multiple values using the `Output` function and returning the list of output elements. - -```python - result_c0 = Output(result_c0, "final_vote_count_c0", outparty) - result_c1 = Output(result_c1, "final_vote_count_c1", outparty) - - return [result_c0, result_c1] -``` - -The above program 1 requires hard-coding every voter, vote and candidates’ result. In case we have a big number of voters and candidates, we need to use more handy methods. Next, we show two approaches on how to generalize the above program. - -### Compiled-time `for` loops - -We can make use of the Python run-time `for` loop to unroll the variables and create them for us. - - -> ⚠️ Please note that PyNada does not currently support run-time for loops. In PyNada, Python loops are executed solely at compile time. Consequently, any for loop will not run during program execution and will lack access to input and computed variables, as these are only defined at run-time. - -Below, program 2 shows how we could use compiled-time `for` loops to assist us creating all parties, input votes and building the computation section. - -Program: [voting_honest_2.py](../nada_programs/src/voting_honest_2.py) -```python -""" -PROGRAM 2 - -nr of voters: m = 5 -nr of candidates: n = 3 -""" -from nada_dsl import * - -def nada_main(): - - # 0. Compiled-time constants - nr_voters = 3 - nr_candidates = 2 - - # 1. Parties initialization - voters = [] - for i in range(nr_voters): - voters.append(Party(name="Voter" + str(i))) - outparty = Party(name="OutParty") - - # 2. Inputs initialization - votes_per_candidate = [] - for c in range(nr_candidates): - votes_per_candidate.append([]) - for v in range(nr_voters): - votes_per_candidate[c].append(SecretUnsignedInteger(Input(name="v" + str(v) + "_c" + str(c), party=voters[v]))) - - # 3. Computation - results = [] - for c in range(nr_candidates): - result = votes_per_candidate[c][0] - for v in range(1, nr_voters): - ## Add votes for candidate c - result += votes_per_candidate[c][v] - # 4. Output - results.append(Output(result, "final_vote_count_c" + str(c), outparty)) - - return results -``` -> 🔍 It is interesting to observe that the [MIR](https://docs.nillion.com/concepts#programs) representation of both programs (1 and 2) are exactly the same. You can inspect MIR files in the [`programs-compiled`](/nillion-python-starter/programs-compiled/) folder. - -Let us breakdown the above program 2. - -0. **Compiled-time constants:** we can initialize compiled-time constants and use them for compiled-time related tasks. In our example, both `nr_votes` and `nr_candidates` are used to specify the length of the compiled-time `for` loop unrolling. - -```python - nr_voters = 3 - nr_candidates = 2 -``` - -1. **Parties initialization:** the compiled-time `for` loop is used here to create a python list of parties. -```python - # 1. Parties initialization - voters = [] - for i in range(nr_voters): - voters.append(Party(name="Voter" + str(i))) - outparty = Party(name="OutParty") -``` -After running the above lines, the variable `voters` is a list containing the different voters: - -```python -voters = [Party(name="Voter0"), Party(name="Voter1"), Party(name="Voter0")] -``` -2. **Inputs initialization:** the compiled-time `for` loop is used here to create a python list of lists with all the votes for each candidate. - -```python - # 2. Inputs initialization - votes_per_candidate = [] - for c in range(nr_candidates): - votes_per_candidate.append([]) - for v in range(nr_voters): - votes_per_candidate[c].append(SecretUnsignedInteger(Input(name="v" + str(v) + "_c" + str(c), party=voters[v]))) -``` -After running the above lines, the variable `votes_per_candidate` should have the following structure: - -```python - votes_per_candidate = [[v0_c0, v1_c0, v2_c0], - [v0_c1, v1_c1, v2_c1]] - """ - where vi_cj = SecretUnsignedInteger(Input(name="vi_cj", party=voters[i]))] - """ -``` -3. **Computation:** at this point we use the compiled-time `for` loop to create the expression that represents the votes for each candidate. - -```python - # 3. Computation - results = [] - for c in range(nr_candidates): - result = votes_per_candidate[c][0] - for v in range(1, nr_voters): - ## Add votes for candidate c - result += votes_per_candidate[c][v] -``` - -Let use explicitly show how the `result` variable is updated. We consider the case of the first candidate `c = 0`: - -```python - # Initialization - result = votes_per_candidate[0][0] # = v0_c0 - # for v = 1 - result = votes_per_candidate[0][0] - + votes_per_candidate[0][1] - # for v = 2 - result = votes_per_candidate[0][0] - + votes_per_candidate[0][1] - + votes_per_candidate[0][2] -``` - -> ⚠️ Note that the variable `result` is **not updated** during the execution of the program. In fact, the bytecode generated does not have the concept of variables. This process only builds the intended expression for each candidate. - -4. **Output:** for each candidate, the `result` variable is appended into the list of final results `results`, which is the list to be returned. - -```python - # 3. Computation - results = [] - ... - # 4. Output - results.append(Output(result, "final_vote_count_c" + str(c), outparty)) - - return results -``` - -The `results` list is as follows: - -```python -results = [Output(votes_per_candidate[0][0] - + votes_per_candidate[0][1] - + votes_per_candidate[0][2], "final_vote_count_c0", outparty), - Output(votes_per_candidate[1][0] - + votes_per_candidate[1][1] - + votes_per_candidate[1][2], "final_vote_count_c1", outparty)] -``` - -### Functional programming - -PyNada provides a set of tools to code functional type programs. More specifically, it supports `zip`, `map` and `reduce` over two elements we haven’t come across before: - -- `Array`: a fixed-sized object that holds basic types (e.g. `SecretInteger`, `SecretUnsignedInteger`) -- Nada functions: functions to be executed during run-time. These receive a special annotator `@nada_fn`. - -```python -""" -PROGRAM 3 - -nr of voters: m = 5 -nr of candidates: n = 3 -""" -from nada_dsl import * - -def nada_main(): - - # 0. Compiled-time constants - nr_candidates = 3 - - # 1. Parties initialization - voter1 = Party(name="Voter1") - voter2 = Party(name="Voter2") - voter3 = Party(name="Voter3") - voter4 = Party(name="Voter4") - voter5 = Party(name="Voter5") - outparty = Party(name="OutParty") - - # 2. Inputs initialization - vote1 = Array(SecretInteger(Input(name="Voter1", party=voter1)), size=nr_candidates) - vote2 = Array(SecretInteger(Input(name="Voter2", party=voter2)), size=nr_candidates) - vote3 = Array(SecretInteger(Input(name="Voter3", party=voter3)), size=nr_candidates) - vote4 = Array(SecretInteger(Input(name="Voter4", party=voter4)), size=nr_candidates) - vote5 = Array(SecretInteger(Input(name="Voter5", party=voter5)), size=nr_candidates) - - # 3. Computation - @nada_fn - def add(a: SecretInteger, b: SecretInteger) -> SecretInteger: - return a + b - - final_count = vote1 - .zip(vote2) - .map(add) - .zip(vote3) - .map(add) - .zip(vote4) - .map(add) - .zip(vote5) - .map(add) - - # 4. Output - final_count = Output(final_count, "final_vote_count", outparty) - return [final_count] -``` - -## Dishonest voters - -In the real world, we cannot expect input parties to follow the rules of the voting system. The previous programs (1, 2 and 3) were simply adding the input elements provided by the input parties. This means voters could pump the classification of their preferred candidate. Considering the plurality voting system, in case voter 1 prefers candidate 1, it can input `v1_c0 = 1` and `v1_c1 = 10` instead of providing the (sound) value $2$. - -We can avoid this scenario by checking within the PyNada program that inputs are sound. At this point we can have two types of programs: - -- *Dishonest with abort:* in this scenario voters can provide invalid inputs and the program is able to identify dishonest input parties. However, it does not guarantee correctness of result if there are dishonest input parties. -- *Robust dishonest:* in this scenario voters can also provide invalid inputs and the program is also able to identify dishonest input parties. In this case, the program guarantee correctness of result if there are dishonest input parties. - -Below is a summary of the features for each variant. - - -| Features | Honest | Dishonest with abort | Robust dishonest | -| --- | --- | --- | --- | -| Allow invalid input | ❌ | ✅ | ✅ | -| Identify cheaters | ❌ | ✅ | ✅ | -| Correctness guaranteed | ❌ | ❌ | ✅ | - -Each type of voting system has different ways to identify input soundness. For the sake of brevity, we show two PyNada programs for the **plurality voting system**: dishonest with abort setting and robust dishonest setting. - -### Plurality | Dishonest with abort - -We recall that the plurality voting system requires the following constraint for $M$ candidates: - -- Vote $v_i = (c_1, \dots, c_M)$ where $c_j \in \{1,2\}$ and $c_1 + \ldots + c_M = M + 1$. - -This requires us to check two rules: - -1. Check sum: for every voter $v_i$, the addition of its votes must be exactly $M+1$ -2. Check product: every $c_j\in\{1,2\}$. We can check this by computing the expression $$(1-c_j)\times(2-c_j)$$and check if it is equal to zero. The above expression is equal to zero if and only if $c_j\in\{1,2\}$. - -For three voters and following the notation of Program 1, we code the checks as follows: - -1. Check sum: - -```python - # Check votes: - # 1. Sum - # Voter 0 - check_v0_sum = v0_c0 + v0_c1 - # Voter 1 - check_v1_sum = v1_c1 + v1_c2 - # Voter 2 - check_v2_sum = v2_c1 + v2_c2 -``` - -2. Check product: - -```python - # 2. Product - # Voter 0 - check_v0_c0_product = (UnsignedInteger(1) - v0_c0)*(UnsignedInteger(2) - v0_c0) - check_v0_c1_product = (UnsignedInteger(1) - v0_c1)*(UnsignedInteger(2) - v0_c1) - # Voter 1 - check_v1_c0_product = (UnsignedInteger(1) - v1_c0)*(UnsignedInteger(2) - v1_c0) - check_v1_c1_product = (UnsignedInteger(1) - v1_c1)*(UnsignedInteger(2) - v1_c1) - # Voter 2 - check_v2_c0_product = (UnsignedInteger(1) - v2_c0)*(UnsignedInteger(2) - v2_c0) - check_v2_c1_product = (UnsignedInteger(1) - v2_c1)*(UnsignedInteger(2) - v2_c1) -``` -Below, we put together the above two checks with our protocol: - -```python -""" -PROGRAM 4 - -nr of voters: m = 3 -nr of candidates: n = 2 -""" -from nada_dsl import * - -def nada_main(): - - # 1. Parties initialization - voter0 = Party(name="Voter0") - voter1 = Party(name="Voter1") - voter2 = Party(name="Voter2") - outparty = Party(name="OutParty") - - # 2. Inputs initialization - ## Votes from voter 0 - v0_c0 = SecretUnsignedInteger(Input(name="v0_c0", party=voter0)) - v0_c1 = SecretUnsignedInteger(Input(name="v0_c1", party=voter0)) - ## Votes from voter 1 - v1_c0 = SecretUnsignedInteger(Input(name="v1_c0", party=voter1)) - v1_c1 = SecretUnsignedInteger(Input(name="v1_c1", party=voter1)) - ## Votes from voter 2 - v2_c0 = SecretUnsignedInteger(Input(name="v2_c0", party=voter2)) - v2_c1 = SecretUnsignedInteger(Input(name="v2_c1", party=voter2)) - - # 3. Computation - ## Add votes for candidate 0 - result_c0 = v0_c0 + v1_c0 + v2_c0 - ## Add votes for candidate 1 - result_c1 = v0_c1 + v1_c1 + v2_c1 - - # Check votes: - # 1. Sum - # Voter 0 - check_v0_sum = v0_c0 + v0_c1 - # Voter 1 - check_v1_sum = v1_c1 + v1_c2 - # Voter 2 - check_v2_sum = v2_c1 + v2_c2 - # 2. Product - # Voter 0 - check_v0_c0_product = (UnsignedInteger(1) - v0_c0)*(UnsignedInteger(2) - v0_c0) - check_v0_c1_product = (UnsignedInteger(1) - v0_c1)*(UnsignedInteger(2) - v0_c1) - # Voter 1 - check_v1_c0_product = (UnsignedInteger(1) - v1_c0)*(UnsignedInteger(2) - v1_c0) - check_v1_c1_product = (UnsignedInteger(1) - v1_c1)*(UnsignedInteger(2) - v1_c1) - # Voter 2 - check_v2_c0_product = (UnsignedInteger(1) - v2_c0)*(UnsignedInteger(2) - v2_c0) - check_v2_c1_product = (UnsignedInteger(1) - v2_c1)*(UnsignedInteger(2) - v2_c1) - - # 4. Output - return [ - Output(result_c0, "final_vote_count_c0", outparty), - Output(result_c1, "final_vote_count_c1", outparty), - Output(check_v0_sum, "v0_sum", outparty), - Output(check_v1_sum, "v1_sum", outparty), - Output(check_v2_sum, "v2_sum", outparty), - Output(check_v0_c0_product, "v0_c0_prod", outparty), - Output(check_v0_c1_product, "v0_c1_prod", outparty), - Output(check_v1_c0_product, "v1_c0_prod", outparty), - Output(check_v1_c1_product, "v1_c1_prod", outparty), - Output(check_v2_c0_product, "v2_c0_prod", outparty), - Output(check_v2_c1_product, "v2_c1_prod", outparty), - ] -``` - -Similarly, we can utilize compiled-time `for` loops and **Python functions** to streamline the above code, particularly when scaling the number of voters and candidates. - ->💡 In addition to compiled-time `for` loops, leveraging **Python functions** can lead to more organized and cleaner code. Python functions are executed by the interpreter during compilation and cannot rely on runtime variables. In contrast, **Nada functions** rely on run-time variables and are only executed during run-time. This distinction becomes apparent when comparing with the example provided in program 3. - -Program: [voting_dishonest_abort_5.py](../nada_programs/src/voting_dishonest_abort_5.py) -```python -""" -PROGRAM 5 - -nr of voters: m = 3 -nr of candidates: n = 2 -""" -from nada_dsl import * - -def initialize_voters(nr_voters): - """ - Initializes the list of voters with unique identifiers. - - Args: - nr_voters (int): Number of voters. - - Returns: - list: List of Party objects representing each voter. - """ - voters = [] - for i in range(nr_voters): - voters.append(Party(name="Voter" + str(i))) - - return voters - -def inputs_initialization(nr_voters, nr_candidates, voters): - """ - Initializes the input for each candidate, collecting votes from each voter securely. - - Args: - nr_voters (int): Number of voters. - nr_candidates (int): Number of candidates. - - Returns: - list: List of lists containing SecretUnsignedInteger objects representing votes per candidate. - """ - votes_per_candidate = [] - for c in range(nr_candidates): - votes_per_candidate.append([]) - for v in range(nr_voters): - votes_per_candidate[c].append(SecretUnsignedInteger(Input(name="v" + str(v) + "_c" + str(c), party=voters[v]))) - - return votes_per_candidate - -def count_votes(nr_voters, nr_candidates, votes_per_candidate, outparty): - """ - Counts the votes for each candidate. - - Args: - nr_voters (int): Number of voters. - nr_candidates (int): Number of candidates. - votes_per_candidate (list): List of lists containing SecretUnsignedInteger objects representing votes per candidate. - - Returns: - list: List of Output objects representing the final vote count for each candidate. - """ - votes = [] - for c in range(nr_candidates): - result = votes_per_candidate[c][0] - for v in range(1, nr_voters): - result += votes_per_candidate[c][v] - votes.append(Output(result, "final_vote_count_c" + str(c), outparty)) - - return votes - -def fn_check_sum(nr_voters, nr_candidates, votes_per_candidate, outparty): - """ - Verifies the sum of votes for each voter to ensure correctness. - - Args: - nr_voters (int): Number of voters. - nr_candidates (int): Number of candidates. - votes_per_candidate (list): List of lists containing SecretUnsignedInteger objects representing votes per candidate. - - Returns: - list: List of Output objects representing the sum verification for each voter. - """ - check_sum = [] - for v in range(nr_voters): - check = votes_per_candidate[0][v] - for c in range(1, nr_candidates): - vote_v_c = votes_per_candidate[c][v] - check += vote_v_c - check_sum.append(Output(check, "check_sum_v" + str(v), outparty)) - - return check_sum - -def fn_check_prod(nr_voters, nr_candidates, votes_per_candidate, outparty): - """ - Verifies the product of vote values for each voter and candidate. - - Args: - nr_voters (int): Number of voters. - nr_candidates (int): Number of candidates. - votes_per_candidate (list): List of lists containing SecretUnsignedInteger objects representing votes per candidate. - - Returns: - list: List of Output objects representing the product verification for each voter and candidate. - """ - check_prod = [] - for v in range(nr_voters): - for c in range(nr_candidates): - vote_v_c = votes_per_candidate[c][v] - check_v_c_product = (UnsignedInteger(1) - vote_v_c)*(UnsignedInteger(2) - vote_v_c) - check_prod.append(Output(check_v_c_product, "check_prod_v" + str(v) + "_c" + str(c), outparty)) - - return check_prod - -def nada_main(): - - # 0. Compiled-time constants - nr_voters = 3 - nr_candidates = 2 - - # 1. Parties initialization - voters = initialize_voters(nr_voters) - outparty = Party(name="OutParty") - - # 2. Inputs initialization - votes_per_candidate = inputs_initialization(nr_voters, nr_candidates, voters) - - # 3. Computation - # Count the votes - votes = count_votes(nr_voters, nr_candidates, votes_per_candidate, outparty) - # Check input soundness - check_sum = fn_check_sum(nr_voters, nr_candidates, votes_per_candidate, outparty) - check_prod = fn_check_prod(nr_voters, nr_candidates, votes_per_candidate, outparty) - - # 4. Output - # Concatenate lists - results = votes + check_sum + check_prod - return results -``` - -### Plurality | Robust dishonest - -In the previous section, we were only able to identify cheating voters. Moreover, we were not able to recover the final result without the influence of cheating voters. In this section, we propose a way to achieve that with some features like `if_else` and comparisons `<`. We use unsigned integers (`UnsignedInteger` type) to make the program simpler. - -The 5 types of voting systems rely on **adding** all the votes. So, in case we have access to the votes of cheating voters, we can easily subtract them from the final result and retrieve the result without counting with cheaters’ votes. We can enforce that by adding the following piece of code to output the vote of a cheating voter: - -```python - # Voter 1 check sum - comp_v1_sum = check_v1_sum <= UnsignedInteger(3) # nr_candidates + 1 - - # Candidate 0 check product - comp_v1_c0_prod = check_v1_c0_product < UnsignedInteger(1) - - # Candidate 1 check product - comp_v1_c1_prod = check_v1_c1_product < UnsignedInteger(1) - - # Candidate 0 reveal if voter 1 cheated - if_sum_cheat_open_v1_c0 = comp_v1_sum.if_else(UnsignedInteger(0), v1_c0) - if_prod_cheat_open_v1_c0 = comp_v1_c0_prod.if_else( - comp_v1_c1_prod.if_else( - UnsignedInteger(0), - v1_c0 - ), - v1_c0 - ) - - # Candidate 1 reveal if voter 1 cheated - if_sum_cheat_open_v1_c1 = comp_v1_sum.if_else(UnsignedInteger(0), v1_c1) - if_prod_cheat_open_v1_c1 = comp_v1_c0_prod.if_else( - comp_v1_c1_prod.if_else( - UnsignedInteger(0), - v1_c1 - ), - v1_c1 - ) -``` - -In the code above, we start by checking if the value under `check_v1_sum` follows the required constraint (should be smaller than the number of candidates plus one). The result `comp_v1_sum` is a secret boolean that can then be used in conjunction with the `if_else` function. - -```python - # Voter 1 - comp_v1_sum = check_v1_sum <= UnsignedInteger(3) # nr_candidates + 1 -``` - -Based on the value of `comp_v1_sum`, we can either return the true value of a voter’s vote or a value `0` with no meaning. - -```python - # Candidate 0 reveal if voter 1 cheated - if_sum_cheat_open_v1_c0 = comp_v1_sum.if_else(UnsignedInteger(0), v1_c0) - ... - # Candidate 1 reveal if voter 1 cheated - if_sum_cheat_open_v1_c1 = comp_v1_sum.if_else(UnsignedInteger(0), v1_c1) - ... -``` - -Now, we check if the check product variables `check_v1_c0_product` and `check_v1_c1_product` are zero. Since we are using unsigned integers, than can be achieved by the `. < 1` operation. If that is the case, we use again `if_else` to return a value without meaning `0`, otherwise we return the true value of a voter’s vote. Note that we only reveal the votes in case any of the boolean values (`comp_v1_c0_prod` or `comp_v1_c1_prod`) is false. This is ensured by chaining `if_else` operations - -```python - # Candidate 0 reveal if voter 1 cheated - ... - if_prod_cheat_open_v1_c0 = comp_v1_c0_prod.if_else( - comp_v1_c1_prod.if_else( - UnsignedInteger(0), - v1_c0 - ), - v1_c0 - ) - # Candidate 1 reveal if voter 1 cheated - ... - if_prod_cheat_open_v1_c1 = comp_v1_c0_prod.if_else( - comp_v1_c1_prod.if_else( - UnsignedInteger(0), - v1_c1 - ), - v1_c1 - ) -``` - -Then, we output the cheating conditions `comp_v1_sum`, `comp_v1_c0_prod` and `comp_v1_c1_prod` along with the values `if_sum_cheat_open_v1_c0`, `if_sum_cheat_open_v1_c1`, `if_prod_cheat_open_v1_c0` and `if_prod_cheat_open_v1_c1` . - -Below, we put together the above two checks with our protocol. For readability purposes, we use a function `return_val_if_any_true(list_of_bool, val)` which returns the value `val` if at least on boolean in the list is false. - -Program: [voting_dishonest_robust_6.py](../nada_programs/src/voting_dishonest_robust_6.py) -```python -""" -PROGRAM 6 - -nr of voters: m = 3 -nr of candidates: n = 2 -""" -from nada_dsl import * - -def return_val_if_any_true(list_of_bool, val): - """ - Returns val if any boolean inside list_of_bool is false. - - Parameters: - - list_of_bool (list of bool): List of boolean values to be checked. - - val: Value to be returned if any boolean in the list is false. - - Returns: - - val: If any boolean in the list is false. - - 0: If none of the booleans in the list are false. - """ - - final_value = UnsignedInteger(0) - for bool in list_of_bool: - # Use if_else method to check if the current boolean is true, - # if true, return val, otherwise return the current final_value - final_value = bool.if_else(final_value, val) - - return final_value - -def initialize_voters(nr_voters): - """ - Initialize voters with unique identifiers. - - Parameters: - - nr_voters (int): Number of voters. - - Returns: - - voters (list): List of Party objects representing voters. - """ - voters = [] - for i in range(nr_voters): - voters.append(Party(name="Voter" + str(i))) - - return voters - -def inputs_initialization(nr_voters, nr_candidates, voters): - """ - Initialize inputs for votes per candidate. - - Parameters: - - nr_voters (int): Number of voters. - - nr_candidates (int): Number of candidates. - - Returns: - - votes_per_candidate (list): List of lists representing votes per candidate. - """ - votes_per_candidate = [] - for c in range(nr_candidates): - votes_per_candidate.append([]) - for v in range(nr_voters): - votes_per_candidate[c].append(SecretUnsignedInteger(Input(name="v" + str(v) + "_c" + str(c), party=voters[v]))) - - return votes_per_candidate - -def count_votes(nr_voters, nr_candidates, votes_per_candidate, outparty): - """ - Count votes for each candidate. - - Parameters: - - nr_voters (int): Number of voters. - - nr_candidates (int): Number of candidates. - - votes_per_candidate (list): List of lists representing votes per candidate. - - Returns: - - votes (list): List of Output objects representing vote counts for each candidate. - """ - votes = [] - for c in range(nr_candidates): - result = votes_per_candidate[c][0] - for v in range(1, nr_voters): - result += votes_per_candidate[c][v] - votes.append(Output(result, "final_vote_count_c" + str(c), outparty)) - - return votes - -def fn_check_sum(nr_voters, nr_candidates, votes_per_candidate, outparty): - """ - Check the sum of votes for each voter. - - Parameters: - - nr_voters (int): Number of voters. - - nr_candidates (int): Number of candidates. - - votes_per_candidate (list): List of lists representing votes per candidate. - - Returns: - - check_sum (list): List of Output objects representing the sum checks for each voter. - - if_sum_cheat_open (list): List of Output objects representing revealed votes of cheating voters. - """ - check_sum = [] - if_sum_cheat_open = [] - for v in range(nr_voters): - check = votes_per_candidate[0][v] - for c in range(1, nr_candidates): - vote_v_c = votes_per_candidate[c][v] - check += vote_v_c - check_sum.append(Output(check, "check_sum_v" + str(v), outparty)) - # Reveal if cheat - comp_v_sum = check <= UnsignedInteger(nr_candidates + 1) - for c in range(nr_candidates): - vote_v_c = votes_per_candidate[c][v] - if_sum_cheat_open_v_c = comp_v_sum.if_else(UnsignedInteger(0), vote_v_c) - if_sum_cheat_open.append(Output(if_sum_cheat_open_v_c, "if_sum_cheat_open_v" + str(v) + "_c" + str(c), outparty)) - - return check_sum, if_sum_cheat_open - -def fn_check_prod(nr_voters, nr_candidates, votes_per_candidate, outparty): - """ - Check the product of votes for each voter. - - Parameters: - - nr_voters (int): Number of voters. - - nr_candidates (int): Number of candidates. - - votes_per_candidate (list): List of lists representing votes per candidate. - - Returns: - - check_prod (list): List of Output objects representing the product checks for each voter. - - if_prod_cheat_open (list): List of Output objects representing revealed votes of cheating voters. - """ - check_prod = [] - if_prod_cheat_open = [] - all_comp_prod = [] - for v in range(nr_voters): - all_comp_v_prod = [] - for c in range(nr_candidates): - vote_v_c = votes_per_candidate[c][v] - check_v_c_product = (UnsignedInteger(1) - vote_v_c)*(UnsignedInteger(2) - vote_v_c) - check_prod.append(Output(check_v_c_product, "check_prod_v" + str(v) + "_c" + str(c), outparty)) - # collect all reveal conditions - comp_v_c_prod = check_v_c_product < UnsignedInteger(1) - all_comp_v_prod.append(comp_v_c_prod) - all_comp_prod.append(all_comp_v_prod) - # reveal all votes from voter v if - for v in range(nr_voters): - all_comp_v_prod = all_comp_prod[v] - for c in range(nr_candidates): - vote_v_c = votes_per_candidate[c][v] - if_prod_cheat_open_v_c = return_val_if_any_true(all_comp_v_prod, vote_v_c) - if_prod_cheat_open.append(Output(if_prod_cheat_open_v_c, "if_prod_cheat_open_v" + str(v) + "_c" + str(c), outparty)) - - return check_prod, if_prod_cheat_open - - -def nada_main(): - - # 0. Compiled-time constants - nr_voters = 3 - nr_candidates = 2 - - # 1. Parties initialization - voters = initialize_voters(nr_voters) - outparty = Party(name="OutParty") - - # 2. Inputs initialization - votes_per_candidate = inputs_initialization(nr_voters, nr_candidates, voters) - - # 3. Computation - # Count the votes - votes = count_votes(nr_voters, nr_candidates, votes_per_candidate, outparty) - # Check input soundness - check_sum, if_sum_cheat_open = fn_check_sum(nr_voters, nr_candidates, votes_per_candidate, outparty) - check_prod, if_prod_cheat_open = fn_check_prod(nr_voters, nr_candidates, votes_per_candidate, outparty) - - # 4. Output - # Concatenate lists - results = votes + check_sum + if_sum_cheat_open + check_prod + if_prod_cheat_open - return results -``` - -## Extra tip - -The above examples represent a scenario where the output is delivered to only one party. However, in some situations, we may want to output the same value to multiple parties. Usually, this can be done as follows: - -```python -return [Output(value, "value_output", outparty1), - Output(value, "value_output", outparty2)] -``` - -In case the number of output parties is big, we can make use of the Python interpreter to generate a list of outputs for us. Using the list of `voters` and `outparty` from program 6, in case we want to output the voting result to all voters we can do as follows: - -```python -[Output(result, "final_vote_count_c" + str(c), p) for p in voters + [outparty]] -``` - -# References - -[DTY21] [Fear not, vote truthfully: Secure Multiparty Computation of score based rules](https://www.sciencedirect.com/science/article/abs/pii/S0957417420310964), 2021 \ No newline at end of file diff --git a/testing/Dockerfile b/testing/Dockerfile deleted file mode 100644 index 468192ad..00000000 --- a/testing/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM mcr.microsoft.com/devcontainers/python - -USER vscode - -# Add Foundry and Nilup to Path -ENV PATH="$PATH:/home/vscode/.foundry/bin:/home/vscode/.nilup/bin:/home/vscode/.local/bin" - -# Install Nilup -RUN curl https://nilup.nilogy.xyz/install.sh | bash && \ - nilup install latest && \ - nilup use latest && \ - nilup init - -# Install foundry -RUN curl -L https://foundry.paradigm.xyz | bash && \ - bash -c "foundryup" - -WORKDIR /home/vscode -COPY --chown=vscode:vscode . . diff --git a/testing/docker_main.sh b/testing/docker_main.sh deleted file mode 100644 index 3fef4690..00000000 --- a/testing/docker_main.sh +++ /dev/null @@ -1,41 +0,0 @@ -run_pytest_in_dir() { - local dir_name="$1" - - # Check if the directory exists - if [ ! -d "$dir_name" ]; then - echo "Directory does not exist: $dir_name" - return 1 - fi - - cd "$dir_name" || return - - for file in *.py; do - # Check if the pattern matches at least one file - if [ -e "$file" ]; then - echo -e "$file\n\n" - pytest "$file" - echo "[DONE] $file" - else - echo "No Python files found in $dir_name." - break - fi - done - - cd .. # Return to the original directory -} - -cp .env.sample .env && \ -python3 -m pip install -r requirements.txt && \ -bash ./create_venv.sh && \ -./bootstrap-local-environment.sh && \ -echo "Waiting 60 seconds for preprocessing elements" && sleep 60 && \ -sh compile_programs.sh && \ - -run_pytest_in_dir client_single_party_compute && \ -cd examples_and_tutorials && \ -run_pytest_in_dir core_concept_multi_party_compute && \ -run_pytest_in_dir core_concept_permissions && \ -run_pytest_in_dir core_concept_single_party_compute && \ -run_pytest_in_dir core_concept_permissions && \ -run_pytest_in_dir core_concept_store_and_retrieve_secrets && \ -run_pytest_in_dir millionaires_problem_example diff --git a/testing/run_tests.sh b/testing/run_tests.sh deleted file mode 100755 index 9d7dffb5..00000000 --- a/testing/run_tests.sh +++ /dev/null @@ -1,5 +0,0 @@ -# This file presents how to automatically run the tests - -cd .. -docker build -t nillion-python-starter -f testing/Dockerfile . -docker run -it --rm nillion-python-starter bash /home/vscode/testing/docker_main.sh From ca1aeb84e9d8dafa9b2964e8aa5e973a8330859b Mon Sep 17 00:00:00 2001 From: davetbutler Date: Tue, 25 Jun 2024 11:09:17 +0100 Subject: [PATCH 21/43] remove pytest and final renaming and refactor of client code --- .gitignore | 3 ++- README.md | 2 +- .../client_code/secret_addition_complete.py | 20 +++++++++---------- .../nada-project.toml | 0 .../src/secret_addition_complete.py | 0 .../tests/secret_addition_complete.yaml | 0 requirements.txt | 1 - 7 files changed, 13 insertions(+), 13 deletions(-) rename quickstart_complete/{nada_programs => nada_quickstart_programs}/nada-project.toml (100%) rename quickstart_complete/{nada_programs => nada_quickstart_programs}/src/secret_addition_complete.py (100%) rename quickstart_complete/{nada_programs => nada_quickstart_programs}/tests/secret_addition_complete.yaml (100%) diff --git a/.gitignore b/.gitignore index e1f2b9f4..57248ff4 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,9 @@ nillion-venv/ permissions/.nillion-config.json examples_and_tutorials/nada_programs/target -quickstart_complete/nada_programs/target +quickstart_complete/nada_quickstart_programs/target .env +.idea .DS_Store *.key diff --git a/README.md b/README.md index 616ba56e..32a5b9cd 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,4 @@ This is an EXPERIMENTAL PAYMENTS ENABLED BRANCH of python starter repo for build Welcome to the start of your Nillion developer journey. -This repo corresponds to the Nillion Python quickstart. To get started with Nillion head over to the [Python QuickStart docs](https://docs.nillion.com/python-quickstart). +This repo corresponds to the Nillion Python quickstart. To get started with Nillion head over to the [Python QuickStart docs](https://docs.nillion.com/python-quickstart) and follow the quickstart guide. diff --git a/quickstart_complete/client_code/secret_addition_complete.py b/quickstart_complete/client_code/secret_addition_complete.py index 35b4b469..5e155df2 100644 --- a/quickstart_complete/client_code/secret_addition_complete.py +++ b/quickstart_complete/client_code/secret_addition_complete.py @@ -1,3 +1,10 @@ +"""In this example, we: +1. connect to the local nillion-devnet +2. store the secret addition program +3. store a secret to be used in the computation +4. compute the secret addition program with the stored secret and another computation time secret +""" + import asyncio import py_nillion_client as nillion import os @@ -43,7 +50,7 @@ async def main(): # 3. Pay for and store the program # Set the program name and path to the compiled program program_name = "secret_addition_complete" - program_mir_path = f"../nada_programs/target/{program_name}.nada.bin" + program_mir_path = f"../nada_quickstart_programs/target/{program_name}.nada.bin" # Create payments config, client and wallet payments_config = create_payments_config(chain_id, grpc_endpoint) @@ -74,7 +81,7 @@ async def main(): # 4. Create the 1st secret, add permissions, pay for and store it in the network # Create a secret named "my_int1" with any value, ex: 500 - new_secret = nillion.Secrets( + new_secret = nillion.NadaValues( { "my_int1": nillion.SecretInteger(500), } @@ -109,7 +116,7 @@ async def main(): compute_bindings.add_output_party(party_name, party_id) # Add my_int2, the 2nd secret at computation time - computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)}) + computation_time_secrets = nillion.NadaValues({"my_int2": nillion.SecretInteger(10)}) # Pay for the compute receipt_compute = await pay( @@ -126,7 +133,6 @@ async def main(): compute_bindings, [store_id], computation_time_secrets, - nillion.PublicVariables({}), receipt_compute, ) @@ -142,9 +148,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - -@pytest.mark.asyncio -async def test_main(): - result = await main() - assert result == {"my_output": 510} diff --git a/quickstart_complete/nada_programs/nada-project.toml b/quickstart_complete/nada_quickstart_programs/nada-project.toml similarity index 100% rename from quickstart_complete/nada_programs/nada-project.toml rename to quickstart_complete/nada_quickstart_programs/nada-project.toml diff --git a/quickstart_complete/nada_programs/src/secret_addition_complete.py b/quickstart_complete/nada_quickstart_programs/src/secret_addition_complete.py similarity index 100% rename from quickstart_complete/nada_programs/src/secret_addition_complete.py rename to quickstart_complete/nada_quickstart_programs/src/secret_addition_complete.py diff --git a/quickstart_complete/nada_programs/tests/secret_addition_complete.yaml b/quickstart_complete/nada_quickstart_programs/tests/secret_addition_complete.yaml similarity index 100% rename from quickstart_complete/nada_programs/tests/secret_addition_complete.yaml rename to quickstart_complete/nada_quickstart_programs/tests/secret_addition_complete.yaml diff --git a/requirements.txt b/requirements.txt index a6a9a7ab..1826f555 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ py-nillion-client nada-dsl python-dotenv==1.0.0 -pytest-asyncio>=0.23.6 cosmpy>=0.9.2 \ No newline at end of file From 10c4111c25b9531e55a6cb7173489954831ab0ff Mon Sep 17 00:00:00 2001 From: davetbutler Date: Tue, 25 Jun 2024 13:30:47 +0100 Subject: [PATCH 22/43] remove legacy --- .idea/.gitignore | 3 --- .idea/inspectionProfiles/Project_Default.xml | 15 --------------- .idea/inspectionProfiles/profiles_settings.xml | 6 ------ .idea/misc.xml | 7 ------- .idea/modules.xml | 8 -------- .idea/nillion-python-starter.iml | 14 -------------- .idea/vcs.xml | 6 ------ 7 files changed, 59 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/nillion-python-starter.iml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 26d33521..00000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index b5bd3f40..00000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2da..00000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 7e452e53..00000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 068226f1..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/nillion-python-starter.iml b/.idea/nillion-python-starter.iml deleted file mode 100644 index ee28fd34..00000000 --- a/.idea/nillion-python-starter.iml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddf..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From e82dc9e44c1436e7cc5316b9d4a488ef966fe543 Mon Sep 17 00:00:00 2001 From: davetbutler Date: Tue, 25 Jun 2024 20:50:30 +0100 Subject: [PATCH 23/43] helpers library, remove helpers raw files --- helpers/__init__.py | 0 helpers/nillion_client_helper.py | 51 ------------------- helpers/nillion_keypath_helper.py | 10 ---- .../client_code/secret_addition_complete.py | 13 ++--- 4 files changed, 3 insertions(+), 71 deletions(-) delete mode 100644 helpers/__init__.py delete mode 100644 helpers/nillion_client_helper.py delete mode 100644 helpers/nillion_keypath_helper.py diff --git a/helpers/__init__.py b/helpers/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/helpers/nillion_client_helper.py b/helpers/nillion_client_helper.py deleted file mode 100644 index 8b841122..00000000 --- a/helpers/nillion_client_helper.py +++ /dev/null @@ -1,51 +0,0 @@ -import os -import py_nillion_client as nillion - -from cosmpy.aerial.client import NetworkConfig -from cosmpy.aerial.tx import Transaction -from cosmpy.aerial.client.utils import prepare_and_broadcast_basic_transaction -from cosmpy.crypto.address import Address - - -def create_nillion_client(userkey, nodekey): - bootnodes = [os.getenv("NILLION_BOOTNODE_MULTIADDRESS")] - - return nillion.NillionClient( - nodekey, bootnodes, nillion.ConnectionMode.relay(), userkey - ) - - -async def pay( - client: nillion.NillionClient, - operation: nillion.Operation, - payments_wallet, - payments_client, - cluster_id, -) -> nillion.PaymentReceipt: - print("Getting quote for operation...") - quote = await client.request_price_quote(cluster_id, operation) - print(f"Quote cost is {quote.cost.total} unil") - address = str(Address(payments_wallet.public_key(), "nillion")) - message = nillion.create_payments_message(quote, address) - tx = Transaction() - tx.add_message(message) - submitted_tx = prepare_and_broadcast_basic_transaction( - payments_client, tx, payments_wallet, gas_limit=1000000 - ) - submitted_tx.wait_to_complete() - print( - f"Submitting payment receipt {quote.cost.total} unil, tx hash {submitted_tx.tx_hash}" - ) - return nillion.PaymentReceipt(quote, submitted_tx.tx_hash) - - -def create_payments_config(chain_id, payments_endpoint): - - return NetworkConfig( - chain_id=chain_id, - url=f"grpc+http://{payments_endpoint}/", - fee_minimum_gas_price=0, - fee_denomination="unil", - staking_denomination="unil", - faucet_url=None, - ) diff --git a/helpers/nillion_keypath_helper.py b/helpers/nillion_keypath_helper.py deleted file mode 100644 index 4a454137..00000000 --- a/helpers/nillion_keypath_helper.py +++ /dev/null @@ -1,10 +0,0 @@ -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/quickstart_complete/client_code/secret_addition_complete.py b/quickstart_complete/client_code/secret_addition_complete.py index 5e155df2..bbb0088b 100644 --- a/quickstart_complete/client_code/secret_addition_complete.py +++ b/quickstart_complete/client_code/secret_addition_complete.py @@ -1,4 +1,5 @@ -"""In this example, we: +""" +In this example, we: 1. connect to the local nillion-devnet 2. store the secret addition program 3. store a secret to be used in the computation @@ -8,23 +9,15 @@ import asyncio import py_nillion_client as nillion import os -import sys -import pytest from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv +from nillion_python_helpers import pay, create_nillion_client, create_payments_config from cosmpy.aerial.client import LedgerClient from cosmpy.aerial.wallet import LocalWallet from cosmpy.crypto.keypairs import PrivateKey -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) -from helpers.nillion_client_helper import ( - create_nillion_client, - pay, - create_payments_config, -) - home = os.getenv("HOME") load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") From f899bb7299ea6e655ab7063d091f9040f8820163 Mon Sep 17 00:00:00 2001 From: davetbutler Date: Tue, 25 Jun 2024 21:16:38 +0100 Subject: [PATCH 24/43] add target in nada project --- .../target/secret_addition_complete.nada.bin | Bin 0 -> 975 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 quickstart_complete/nada_quickstart_programs/target/secret_addition_complete.nada.bin diff --git a/quickstart_complete/nada_quickstart_programs/target/secret_addition_complete.nada.bin b/quickstart_complete/nada_quickstart_programs/target/secret_addition_complete.nada.bin new file mode 100644 index 0000000000000000000000000000000000000000..163fd4daf897c1cce3d8d0ea1a57853766c85b4b GIT binary patch literal 975 zcma)*!Ait15Qe)dx)u@d9*jA)x(Mx2DC~7XSizGkQo=SXg-u$rDbkk^U(4G*iO=99 znMivO6PQlY{P{AOOrprO3HoF7k4))4yTs9suNBuNtEyVpjV#xVXt!MRyS6_9NeXri zc7XCj@MF*k&WOIOrOt5_1@Nb!{Q#ST{m0EfV_N{%#PwxkwqqcpjRE531m1*>I}-p4V4*4hdt1+k@^ zNG5neU0KuANnAB%pn0+dzL1)~ag{72+&Qj54V^h);jrn5kOvU*KM0b)n Date: Fri, 28 Jun 2024 07:02:13 +0100 Subject: [PATCH 25/43] update for internal hackathon --- .env.sample | 29 ------------------ .../client_code/secret_addition_complete.py | 12 +++++--- .../nada-project.toml | 2 +- .../target/secret_addition_complete.nada.bin | Bin 975 -> 975 bytes requirements.txt | 3 +- 5 files changed, 9 insertions(+), 37 deletions(-) delete mode 100644 .env.sample diff --git a/.env.sample b/.env.sample deleted file mode 100644 index 38492505..00000000 --- a/.env.sample +++ /dev/null @@ -1,29 +0,0 @@ -# Leave these blank - the env variables will be populated when you run ./bootstrap-local-environment.sh -NILLION_NODEKEY_PATH_PARTY_1= -NILLION_NODEKEY_TEXT_PARTY_1= -NILLION_NODEKEY_PATH_PARTY_2= -NILLION_NODEKEY_TEXT_PARTY_2= -NILLION_NODEKEY_PATH_PARTY_3= -NILLION_NODEKEY_TEXT_PARTY_3= -NILLION_NODEKEY_PATH_PARTY_4= -NILLION_NODEKEY_TEXT_PARTY_4= -NILLION_NODEKEY_PATH_PARTY_5= -NILLION_NODEKEY_TEXT_PARTY_5= -NILLION_USERKEY_PATH_PARTY_1= -NILLION_USERKEY_TEXT_PARTY_1= -NILLION_USERKEY_PATH_PARTY_2= -NILLION_USERKEY_TEXT_PARTY_2= -NILLION_USERKEY_PATH_PARTY_3= -NILLION_USERKEY_TEXT_PARTY_3= -NILLION_USERKEY_PATH_PARTY_4= -NILLION_USERKEY_TEXT_PARTY_4= -NILLION_USERKEY_PATH_PARTY_5= -NILLION_USERKEY_TEXT_PARTY_5= -NILLION_WEBSOCKETS= -NILLION_CLUSTER_ID= -NILLION_BOOTNODE_MULTIADDRESS= -NILLION_JSON_RPC= -NILLION_GRPC= -NILLION_CHAIN_ID= -NILLION_WALLET_PRIVATE_KEY= - diff --git a/quickstart_complete/client_code/secret_addition_complete.py b/quickstart_complete/client_code/secret_addition_complete.py index bbb0088b..e73ee5e1 100644 --- a/quickstart_complete/client_code/secret_addition_complete.py +++ b/quickstart_complete/client_code/secret_addition_complete.py @@ -18,8 +18,10 @@ from cosmpy.aerial.wallet import LocalWallet from cosmpy.crypto.keypairs import PrivateKey -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") +#home = os.getenv("HOME") +#load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") + +load_dotenv() async def main(): # 1. Initial setup @@ -27,7 +29,7 @@ async def main(): cluster_id = os.getenv("NILLION_CLUSTER_ID") grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - + print(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0")) # 1.2 pick a seed and generate user and node keys seed = "my_seed" userkey = UserKey.from_seed(seed) @@ -56,7 +58,7 @@ async def main(): # Pay to store the program and obtain a receipt of the payment receipt_store_program = await pay( client, - nillion.Operation.store_program(), + nillion.Operation.store_program(program_mir_path), payments_wallet, payments_client, cluster_id, @@ -91,7 +93,7 @@ async def main(): # Pay for and store the secret in the network and print the returned store_id receipt_store = await pay( client, - nillion.Operation.store_values(new_secret), + nillion.Operation.store_values(new_secret, ttl_days=5), payments_wallet, payments_client, cluster_id, diff --git a/quickstart_complete/nada_quickstart_programs/nada-project.toml b/quickstart_complete/nada_quickstart_programs/nada-project.toml index 9d002b88..f66f59d9 100644 --- a/quickstart_complete/nada_quickstart_programs/nada-project.toml +++ b/quickstart_complete/nada_quickstart_programs/nada-project.toml @@ -4,4 +4,4 @@ authors = [""] [[programs]] path = "src/secret_addition_complete.py" -prime_size = 128 \ No newline at end of file +prime_size = 128 diff --git a/quickstart_complete/nada_quickstart_programs/target/secret_addition_complete.nada.bin b/quickstart_complete/nada_quickstart_programs/target/secret_addition_complete.nada.bin index 163fd4daf897c1cce3d8d0ea1a57853766c85b4b..a41163be3ba2d176c0eb2bfbefdd4a18e1ad3691 100644 GIT binary patch delta 113 zcmX@lex7}U8Y9bve-g}-wHSQ`1TIT3GcqtR@BuN1J^3!9n+ix4B*+59Ft)%$h!{-6 i=@QVQiRgu%fq&=8TLJ&ueX7%A~~z0DaXOMgRZ+ diff --git a/requirements.txt b/requirements.txt index 1826f555..59bf2e5b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ -py-nillion-client -nada-dsl +nillion-python-helpers==0.2.0 python-dotenv==1.0.0 cosmpy>=0.9.2 \ No newline at end of file From bd7d30612ae5e175f83081a42dee5dcba1ed74db Mon Sep 17 00:00:00 2001 From: davetbutler Date: Fri, 28 Jun 2024 14:30:01 +0100 Subject: [PATCH 26/43] nightly-v2024-06-27-7db213f55 ready for FEs --- .../client_code/secret_addition_complete.py | 6 ++---- .../target/secret_addition_complete.nada.bin | Bin 975 -> 975 bytes 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/quickstart_complete/client_code/secret_addition_complete.py b/quickstart_complete/client_code/secret_addition_complete.py index e73ee5e1..08811d10 100644 --- a/quickstart_complete/client_code/secret_addition_complete.py +++ b/quickstart_complete/client_code/secret_addition_complete.py @@ -18,10 +18,8 @@ from cosmpy.aerial.wallet import LocalWallet from cosmpy.crypto.keypairs import PrivateKey -#home = os.getenv("HOME") -#load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -load_dotenv() +home = os.getenv("HOME") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") async def main(): # 1. Initial setup diff --git a/quickstart_complete/nada_quickstart_programs/target/secret_addition_complete.nada.bin b/quickstart_complete/nada_quickstart_programs/target/secret_addition_complete.nada.bin index a41163be3ba2d176c0eb2bfbefdd4a18e1ad3691..ec77ecac1375005fb477b8371acb207fa53875bd 100644 GIT binary patch delta 95 zcmX@lex7}U8Y4@zu3Dayw0BNoj#{d8T delta 95 zcmX@lex7}U8Y9bve-g}-wHSQ`1TIT3GcqtR@BuN1J-MCHO#vhe5@Z2l7+c^WL=3Fx TEu#fLln>SjWN$WMn#~9RCj1m% From 9d855769ad1848eb7b88eeacd0701505e543f8bb Mon Sep 17 00:00:00 2001 From: davetbutler Date: Fri, 28 Jun 2024 14:56:54 +0100 Subject: [PATCH 27/43] remove print statement --- .../client_code/secret_addition_complete.py | 3 ++- .../target/secret_addition_complete.nada.bin | Bin 975 -> 975 bytes 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/quickstart_complete/client_code/secret_addition_complete.py b/quickstart_complete/client_code/secret_addition_complete.py index 08811d10..7d055ea8 100644 --- a/quickstart_complete/client_code/secret_addition_complete.py +++ b/quickstart_complete/client_code/secret_addition_complete.py @@ -21,13 +21,14 @@ home = os.getenv("HOME") load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") +#load_dotenv + async def main(): # 1. Initial setup # 1.1. Get cluster_id, grpc_endpoint, & chain_id from the .env file cluster_id = os.getenv("NILLION_CLUSTER_ID") grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - print(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0")) # 1.2 pick a seed and generate user and node keys seed = "my_seed" userkey = UserKey.from_seed(seed) diff --git a/quickstart_complete/nada_quickstart_programs/target/secret_addition_complete.nada.bin b/quickstart_complete/nada_quickstart_programs/target/secret_addition_complete.nada.bin index ec77ecac1375005fb477b8371acb207fa53875bd..14f648a93ee4a79e92fdeccc72349e905f170aa3 100644 GIT binary patch delta 95 zcmX@lex7}U8Y9aDCPwDTT8zE|0gQ~yj0_A6d_W9hPi|*)Qvk_=1X+L>#uljl&jbzu3Dayw0BNoj#{d8T From 9c5f4a067487f170d3c48130d922acd2ad71ddb7 Mon Sep 17 00:00:00 2001 From: davetbutler Date: Tue, 2 Jul 2024 10:26:51 +0100 Subject: [PATCH 28/43] rename file --- .../client_code/{secret_addition.py => run_my_first_program.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename quickstart/client_code/{secret_addition.py => run_my_first_program.py} (100%) diff --git a/quickstart/client_code/secret_addition.py b/quickstart/client_code/run_my_first_program.py similarity index 100% rename from quickstart/client_code/secret_addition.py rename to quickstart/client_code/run_my_first_program.py From 01f343f355ce538b9e669754d39431e910ecbc32 Mon Sep 17 00:00:00 2001 From: Dave Butler Date: Wed, 3 Jul 2024 12:34:48 +0100 Subject: [PATCH 29/43] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 32a5b9cd..725de6fd 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # Nillion Python Starter
-This is an EXPERIMENTAL PAYMENTS ENABLED BRANCH of python starter repo for building on the Nillion Network. In order to use this branch, you'll need the latest experimental version of the Nillion SDK, nada library, and python client library. - Welcome to the start of your Nillion developer journey. This repo corresponds to the Nillion Python quickstart. To get started with Nillion head over to the [Python QuickStart docs](https://docs.nillion.com/python-quickstart) and follow the quickstart guide. From c357e819b1d3b8772ed5123ee2c6eda3d217ef41 Mon Sep 17 00:00:00 2001 From: davetbutler Date: Wed, 3 Jul 2024 12:39:23 +0100 Subject: [PATCH 30/43] small changes --- .../client_code/secret_addition_complete.py | 7 +++---- .../target/secret_addition_complete.nada.bin | Bin 975 -> 975 bytes 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/quickstart_complete/client_code/secret_addition_complete.py b/quickstart_complete/client_code/secret_addition_complete.py index 7d055ea8..1a84bd91 100644 --- a/quickstart_complete/client_code/secret_addition_complete.py +++ b/quickstart_complete/client_code/secret_addition_complete.py @@ -18,11 +18,10 @@ from cosmpy.aerial.wallet import LocalWallet from cosmpy.crypto.keypairs import PrivateKey -home = os.getenv("HOME") -load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") - -#load_dotenv +#home = os.getenv("HOME") +#load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") +load_dotenv() async def main(): # 1. Initial setup # 1.1. Get cluster_id, grpc_endpoint, & chain_id from the .env file diff --git a/quickstart_complete/nada_quickstart_programs/target/secret_addition_complete.nada.bin b/quickstart_complete/nada_quickstart_programs/target/secret_addition_complete.nada.bin index 14f648a93ee4a79e92fdeccc72349e905f170aa3..4351b211b5ee0e0c053040d7af6f7bfb2346d48a 100644 GIT binary patch delta 104 zcmX@lex7}U8Y7Fqm#<8dwHST*1#YD=F)}bP@BuN9J=vJaT>&Hs5@P|0LD?{M0oQ;U c-!hsDfEX~1V8M1qH%7*Z=e0I3Wzu2<0EU1YYybcN delta 104 zcmX@lex7}U8Y9aDCPwDTT8zE|0gQ~yj0_A6d_W9hPc~t4Qvk_=1X+L>#uljl&jb)PTw=udiGEO|NwRtI%79#*<`Vx}> From 44feb5165bbdc182eeff7b2df00bd66c669e2f08 Mon Sep 17 00:00:00 2001 From: davetbutler Date: Wed, 3 Jul 2024 13:00:40 +0100 Subject: [PATCH 31/43] change env path for new devnet config --- quickstart_complete/client_code/secret_addition_complete.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/quickstart_complete/client_code/secret_addition_complete.py b/quickstart_complete/client_code/secret_addition_complete.py index 1a84bd91..86ff2c9b 100644 --- a/quickstart_complete/client_code/secret_addition_complete.py +++ b/quickstart_complete/client_code/secret_addition_complete.py @@ -18,10 +18,9 @@ from cosmpy.aerial.wallet import LocalWallet from cosmpy.crypto.keypairs import PrivateKey -#home = os.getenv("HOME") -#load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") +home = os.getenv("HOME") +load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") -load_dotenv() async def main(): # 1. Initial setup # 1.1. Get cluster_id, grpc_endpoint, & chain_id from the .env file From e5ab7fa132d7972af720bcbda379560500498766 Mon Sep 17 00:00:00 2001 From: Dave Butler Date: Wed, 3 Jul 2024 13:12:44 +0100 Subject: [PATCH 32/43] Bump versions --- requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 59bf2e5b..613e2432 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ +py-nillion-client=0.3.0 +nada-dsl=0.3.0 nillion-python-helpers==0.2.0 python-dotenv==1.0.0 -cosmpy>=0.9.2 \ No newline at end of file +cosmpy>=0.9.2 From 12399145dc23a6a2ba616550f655eee4ff8f5233 Mon Sep 17 00:00:00 2001 From: Dave Butler Date: Wed, 3 Jul 2024 13:22:36 +0100 Subject: [PATCH 33/43] typo --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 613e2432..ae1f4379 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -py-nillion-client=0.3.0 -nada-dsl=0.3.0 -nillion-python-helpers==0.2.0 +py-nillion-client +nada-dsl +nillion-python-helpers python-dotenv==1.0.0 cosmpy>=0.9.2 From 6a25682dbde2be712d6dea634b10c737631a53ae Mon Sep 17 00:00:00 2001 From: Dave Butler Date: Wed, 3 Jul 2024 13:27:30 +0100 Subject: [PATCH 34/43] get_quote_and_pay_update --- .../client_code/secret_addition_complete.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/quickstart_complete/client_code/secret_addition_complete.py b/quickstart_complete/client_code/secret_addition_complete.py index 86ff2c9b..2ca50740 100644 --- a/quickstart_complete/client_code/secret_addition_complete.py +++ b/quickstart_complete/client_code/secret_addition_complete.py @@ -12,7 +12,7 @@ from py_nillion_client import NodeKey, UserKey from dotenv import load_dotenv -from nillion_python_helpers import pay, create_nillion_client, create_payments_config +from nillion_python_helpers import get_quote_and_pay, create_nillion_client, create_payments_config from cosmpy.aerial.client import LedgerClient from cosmpy.aerial.wallet import LocalWallet @@ -53,7 +53,7 @@ async def main(): ) # Pay to store the program and obtain a receipt of the payment - receipt_store_program = await pay( + receipt_store_program = await get_quote_and_pay( client, nillion.Operation.store_program(program_mir_path), payments_wallet, @@ -88,7 +88,7 @@ async def main(): permissions.add_compute_permissions({client.user_id: {program_id}}) # Pay for and store the secret in the network and print the returned store_id - receipt_store = await pay( + receipt_store = await get_quote_and_pay( client, nillion.Operation.store_values(new_secret, ttl_days=5), payments_wallet, @@ -111,7 +111,7 @@ async def main(): computation_time_secrets = nillion.NadaValues({"my_int2": nillion.SecretInteger(10)}) # Pay for the compute - receipt_compute = await pay( + receipt_compute = await get_quote_and_pay( client, nillion.Operation.compute(program_id, computation_time_secrets), payments_wallet, From b69c00d637c33b871afa066d7b56ae72fb932fd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Cabrero-Holgueras?= Date: Wed, 3 Jul 2024 13:30:28 +0000 Subject: [PATCH 35/43] fix: updated secret addition format --- .../tests/secret_addition_complete.yaml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/quickstart_complete/nada_quickstart_programs/tests/secret_addition_complete.yaml b/quickstart_complete/nada_quickstart_programs/tests/secret_addition_complete.yaml index 46541fe8..3ce195d3 100644 --- a/quickstart_complete/nada_quickstart_programs/tests/secret_addition_complete.yaml +++ b/quickstart_complete/nada_quickstart_programs/tests/secret_addition_complete.yaml @@ -1,12 +1,10 @@ --- program: secret_addition_complete inputs: - secrets: - my_int1: - SecretInteger: "3" - my_int2: - SecretInteger: "3" - public_variables: {} + my_int1: + SecretInteger: "3" + my_int2: + SecretInteger: "3" expected_outputs: my_output: SecretInteger: "6" From 15e9fe0b43b980670bf1a97f703a8d2f25ca1afc Mon Sep 17 00:00:00 2001 From: Steph <91382964+oceans404@users.noreply.github.com> Date: Wed, 3 Jul 2024 16:09:23 -0700 Subject: [PATCH 36/43] Update README.md --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 725de6fd..996460f0 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,12 @@ Welcome to the start of your Nillion developer journey. This repo corresponds to the Nillion Python quickstart. To get started with Nillion head over to the [Python QuickStart docs](https://docs.nillion.com/python-quickstart) and follow the quickstart guide. + +For more python examples, check out https://github.com/NillionNetwork/python-examples which contains the following: +- core_concept_multi_party_compute +- core_concept_permissions +- core_concept_single_party_compute +- core_concept_store_and_retrieve_secrets +- millionaires_problem_example +- nada_programs +- voting_tutorial From 600f6e85187dbfa8c2ff1dc2ec3b0a84847eb34f Mon Sep 17 00:00:00 2001 From: davetbutler Date: Wed, 28 Aug 2024 13:52:07 +0100 Subject: [PATCH 37/43] update test files for new nada version --- .../target/secret_addition_complete.nada.bin | Bin 975 -> 945 bytes .../tests/secret_addition_complete.yaml | 9 +++------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/quickstart_complete/nada_quickstart_programs/target/secret_addition_complete.nada.bin b/quickstart_complete/nada_quickstart_programs/target/secret_addition_complete.nada.bin index 4351b211b5ee0e0c053040d7af6f7bfb2346d48a..f55dbf56c42ac6f19160f22370a3cdd2e7f1c368 100644 GIT binary patch delta 277 zcmX@lzL9-`94iY05KQzkVPu@>r#|s4Co2<3U@{YH}X7(ljz0L(0q zdKep3J=j@brvsHRPCT!*c`1_?BO@!2HCcj*XL1vh^5i@SJ@F@)%fbUxIq{q}56ID) IK&%MF05U`?Ei4p1({WvEdV^W?h!OSx;UK7ORWCKe10I|cw zUKMeHFJGA$fgF$^2m~Y+l~fusPgZ1Bv*3XUfk|d4Ti{k2SUn$*4`Z`K<#H?IGxJKo znvGOp27?>~W5YBAT!UB&wr286CLR@-2*_9%8`W4Nh+?2W7#Sz;XVTg{g;9$U0OJ2H A4*&oF diff --git a/quickstart_complete/nada_quickstart_programs/tests/secret_addition_complete.yaml b/quickstart_complete/nada_quickstart_programs/tests/secret_addition_complete.yaml index 3ce195d3..2e66372a 100644 --- a/quickstart_complete/nada_quickstart_programs/tests/secret_addition_complete.yaml +++ b/quickstart_complete/nada_quickstart_programs/tests/secret_addition_complete.yaml @@ -1,10 +1,7 @@ --- program: secret_addition_complete inputs: - my_int1: - SecretInteger: "3" - my_int2: - SecretInteger: "3" + my_int1: 3 + my_int2: 3 expected_outputs: - my_output: - SecretInteger: "6" + my_output: 6 From b0e77b24cf52013ebf0a2dd8020df91dfc6f6e9a Mon Sep 17 00:00:00 2001 From: psofiterol Date: Wed, 20 Nov 2024 13:05:39 +0200 Subject: [PATCH 38/43] chore: update example and reqs for 0.7.0 --- .../client_code/secret_addition_complete.py | 182 +++++++----------- requirements.txt | 4 +- 2 files changed, 75 insertions(+), 111 deletions(-) diff --git a/quickstart_complete/client_code/secret_addition_complete.py b/quickstart_complete/client_code/secret_addition_complete.py index 2ca50740..972b110b 100644 --- a/quickstart_complete/client_code/secret_addition_complete.py +++ b/quickstart_complete/client_code/secret_addition_complete.py @@ -1,141 +1,107 @@ """ In this example, we: -1. connect to the local nillion-devnet -2. store the secret addition program -3. store a secret to be used in the computation -4. compute the secret addition program with the stored secret and another computation time secret +1. import packages we'll be using +2. connect to the local nillion-devnet +3. store the secret addition program +4. store a secret to be used in the computation +5. compute the secret addition program with the stored secret and another computation time secret +6. return the computation result """ import asyncio -import py_nillion_client as nillion import os -from py_nillion_client import NodeKey, UserKey +from nillion_client import ( + InputPartyBinding, + Network, + NilChainPayer, + NilChainPrivateKey, + OutputPartyBinding, + Permissions, + SecretInteger, + VmClient, + PrivateKey, +) from dotenv import load_dotenv -from nillion_python_helpers import get_quote_and_pay, create_nillion_client, create_payments_config - -from cosmpy.aerial.client import LedgerClient -from cosmpy.aerial.wallet import LocalWallet -from cosmpy.crypto.keypairs import PrivateKey home = os.getenv("HOME") load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") async def main(): - # 1. Initial setup - # 1.1. Get cluster_id, grpc_endpoint, & chain_id from the .env file - cluster_id = os.getenv("NILLION_CLUSTER_ID") - grpc_endpoint = os.getenv("NILLION_NILCHAIN_GRPC") - chain_id = os.getenv("NILLION_NILCHAIN_CHAIN_ID") - # 1.2 pick a seed and generate user and node keys - seed = "my_seed" - userkey = UserKey.from_seed(seed) - nodekey = NodeKey.from_seed(seed) - - # 2. Initialize NillionClient against nillion-devnet - # Create Nillion Client for user - client = create_nillion_client(userkey, nodekey) - - party_id = client.party_id - user_id = client.user_id + # 2. Initial setup, Initialize NillionClient against nillion-devnet + # Use the devnet configuration generated by `nillion-devnet` + network = Network.from_config("devnet") + + # Create payments config and set up Nillion wallet with a private key to pay for operations + nilchain_key: str = os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0") # type: ignore + payer = NilChainPayer( + network, + wallet_private_key=NilChainPrivateKey(bytes.fromhex(nilchain_key)), + gas_limit=10000000, + ) - # 3. Pay for and store the program - # Set the program name and path to the compiled program + # Use a random key to identify ourselves + signing_key = PrivateKey() + client = await VmClient.create(signing_key, network, payer) + party_name = "Party1" program_name = "secret_addition_complete" program_mir_path = f"../nada_quickstart_programs/target/{program_name}.nada.bin" - # Create payments config, client and wallet - payments_config = create_payments_config(chain_id, grpc_endpoint) - payments_client = LedgerClient(payments_config) - payments_wallet = LocalWallet( - PrivateKey(bytes.fromhex(os.getenv("NILLION_NILCHAIN_PRIVATE_KEY_0"))), - prefix="nillion", - ) - # Pay to store the program and obtain a receipt of the payment - receipt_store_program = await get_quote_and_pay( - client, - nillion.Operation.store_program(program_mir_path), - payments_wallet, - payments_client, - cluster_id, - ) + # 3. Pay for and store the program + print("-----STORE PROGRAM") - # Store the program - action_id = await client.store_program( - cluster_id, program_name, program_mir_path, receipt_store_program - ) + # Store program + program_mir = open(program_mir_path, "rb").read() + program_id = await client.store_program(program_name, program_mir).invoke() - # Create a variable for the program_id, which is the {user_id}/{program_name}. We will need this later - program_id = f"{user_id}/{program_name}" - print("Stored program. action_id:", action_id) - print("Stored program_id:", program_id) + # Print details about stored program + print(f"Stored program_id: {program_id}") # 4. Create the 1st secret, add permissions, pay for and store it in the network - # Create a secret named "my_int1" with any value, ex: 500 - new_secret = nillion.NadaValues( - { - "my_int1": nillion.SecretInteger(500), - } - ) + print("-----STORE SECRETS") - # Set the input party for the secret - # The party name needs to match the party name that is storing "my_int1" in the program - party_name = "Party1" + # Create a secret + values = { + "my_int1": SecretInteger(500), + } - # Set permissions for the client to compute on the program - permissions = nillion.Permissions.default_for_user(client.user_id) - permissions.add_compute_permissions({client.user_id: {program_id}}) - - # Pay for and store the secret in the network and print the returned store_id - receipt_store = await get_quote_and_pay( - client, - nillion.Operation.store_values(new_secret, ttl_days=5), - payments_wallet, - payments_client, - cluster_id, - ) - # Store a secret - store_id = await client.store_values( - cluster_id, new_secret, permissions, receipt_store + # Create a permissions object to attach to the stored secret + permissions = Permissions.defaults_for_user(client.user_id).allow_compute( + client.user_id, program_id ) - print(f"Computing using program {program_id}") - print(f"Use secret store_id: {store_id}") + + # Store a secret, passing in the receipt that shows proof of payment + values_id = await client.store_values( + values, ttl_days=5, permissions=permissions + ).invoke() # 5. Create compute bindings to set input and output parties, add a computation time secret and pay for & run the computation - compute_bindings = nillion.ProgramBindings(program_id) - compute_bindings.add_input_party(party_name, party_id) - compute_bindings.add_output_party(party_name, party_id) - - # Add my_int2, the 2nd secret at computation time - computation_time_secrets = nillion.NadaValues({"my_int2": nillion.SecretInteger(10)}) - - # Pay for the compute - receipt_compute = await get_quote_and_pay( - client, - nillion.Operation.compute(program_id, computation_time_secrets), - payments_wallet, - payments_client, - cluster_id, - ) + print("-----COMPUTE") - # Compute on the secret - compute_id = await client.compute( - cluster_id, - compute_bindings, - [store_id], - computation_time_secrets, - receipt_compute, - ) + # Bind the parties in the computation to the client to set input and output parties + input_bindings = [InputPartyBinding(party_name, client.user_id)] + output_bindings = [OutputPartyBinding(party_name, [client.user_id])] - # 8. Return the computation result + # Create a computation time secret to use + compute_time_values = {"my_int2": SecretInteger(10)} + + # Compute, passing in the compute time values as well as the previously uploaded value. + print(f"Invoking computation using program {program_id} and values id {values_id}") + compute_id = await client.compute( + program_id, + input_bindings, + output_bindings, + values=compute_time_values, + value_ids=[values_id], + ).invoke() + + # 6. Return 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 + result = await client.retrieve_compute_results(compute_id).invoke() + print(f"✅ Compute complete for compute_id {compute_id}") + print(f"🖥️ The result is {result}") + return result if __name__ == "__main__": diff --git a/requirements.txt b/requirements.txt index ae1f4379..fa7d01d3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,3 @@ -py-nillion-client +nillion-client nada-dsl -nillion-python-helpers python-dotenv==1.0.0 -cosmpy>=0.9.2 From d04d465b980608228774daa1ac87c17111a3c441 Mon Sep 17 00:00:00 2001 From: psofiterol Date: Thu, 21 Nov 2024 14:33:00 +0200 Subject: [PATCH 39/43] chore: updated comments on completed examples --- .../client_code/secret_addition_complete.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/quickstart_complete/client_code/secret_addition_complete.py b/quickstart_complete/client_code/secret_addition_complete.py index 972b110b..42c415ee 100644 --- a/quickstart_complete/client_code/secret_addition_complete.py +++ b/quickstart_complete/client_code/secret_addition_complete.py @@ -27,8 +27,9 @@ home = os.getenv("HOME") load_dotenv(f"{home}/.config/nillion/nillion-devnet.env") + async def main(): - # 2. Initial setup, Initialize NillionClient against nillion-devnet + # 2. Initial setup/config, then initialize the NillionClient against nillion-devnet # Use the devnet configuration generated by `nillion-devnet` network = Network.from_config("devnet") @@ -47,8 +48,7 @@ async def main(): program_name = "secret_addition_complete" program_mir_path = f"../nada_quickstart_programs/target/{program_name}.nada.bin" - - # 3. Pay for and store the program + # 3. Store the program print("-----STORE PROGRAM") # Store program @@ -58,7 +58,7 @@ async def main(): # Print details about stored program print(f"Stored program_id: {program_id}") - # 4. Create the 1st secret, add permissions, pay for and store it in the network + # 4. Create the 1st secret, add permissions and store it in the network print("-----STORE SECRETS") # Create a secret @@ -71,7 +71,7 @@ async def main(): client.user_id, program_id ) - # Store a secret, passing in the receipt that shows proof of payment + # Store a secret values_id = await client.store_values( values, ttl_days=5, permissions=permissions ).invoke() From 0a8222b16fb587df7fa820469d8ab366607b17f6 Mon Sep 17 00:00:00 2001 From: psofiterol Date: Wed, 4 Dec 2024 12:48:50 +0200 Subject: [PATCH 40/43] chore: topup client balance | 0.8.0 release --- .../client_code/secret_addition_complete.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/quickstart_complete/client_code/secret_addition_complete.py b/quickstart_complete/client_code/secret_addition_complete.py index 42c415ee..924da2ad 100644 --- a/quickstart_complete/client_code/secret_addition_complete.py +++ b/quickstart_complete/client_code/secret_addition_complete.py @@ -48,6 +48,11 @@ async def main(): program_name = "secret_addition_complete" program_mir_path = f"../nada_quickstart_programs/target/{program_name}.nada.bin" + # Adding funds to the client balance so the upcoming operations can be paid for + funds_amount = 1000 + print(f"💰 Adding some funds to the client balance: {funds_amount}") + await client.add_funds(funds_amount) + # 3. Store the program print("-----STORE PROGRAM") @@ -101,6 +106,9 @@ async def main(): result = await client.retrieve_compute_results(compute_id).invoke() print(f"✅ Compute complete for compute_id {compute_id}") print(f"🖥️ The result is {result}") + balance = await client.balance() + print(f"💰 Final client balance: {balance.balance}") + client.close() return result From ca28cd9b05a8f41e7e8142fd361c12c7aca9e85f Mon Sep 17 00:00:00 2001 From: psofiterol Date: Thu, 5 Dec 2024 12:17:41 +0200 Subject: [PATCH 41/43] chore: clarify funds/balance are in uNIL --- quickstart_complete/client_code/secret_addition_complete.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quickstart_complete/client_code/secret_addition_complete.py b/quickstart_complete/client_code/secret_addition_complete.py index 924da2ad..f32dff66 100644 --- a/quickstart_complete/client_code/secret_addition_complete.py +++ b/quickstart_complete/client_code/secret_addition_complete.py @@ -50,7 +50,7 @@ async def main(): # Adding funds to the client balance so the upcoming operations can be paid for funds_amount = 1000 - print(f"💰 Adding some funds to the client balance: {funds_amount}") + print(f"💰 Adding some funds to the client balance: {funds_amount} uNIL") await client.add_funds(funds_amount) # 3. Store the program @@ -107,7 +107,7 @@ async def main(): print(f"✅ Compute complete for compute_id {compute_id}") print(f"🖥️ The result is {result}") balance = await client.balance() - print(f"💰 Final client balance: {balance.balance}") + print(f"💰 Final client balance: {balance.balance} uNIL") client.close() return result From 5eb18ce6d1dd62217ecb12a71e7b26c8ebade500 Mon Sep 17 00:00:00 2001 From: psofiterol Date: Wed, 5 Feb 2025 15:01:18 +0200 Subject: [PATCH 42/43] chore: update to 0.9.0 --- .nil-sdk.toml | 1 + .../client_code/secret_addition_complete.py | 4 ++-- .../target/secret_addition_complete.nada.bin | Bin 945 -> 666 bytes requirements.txt | 4 ++-- 4 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 .nil-sdk.toml diff --git a/.nil-sdk.toml b/.nil-sdk.toml new file mode 100644 index 00000000..a93ef52d --- /dev/null +++ b/.nil-sdk.toml @@ -0,0 +1 @@ +version = "v0.9.0-rc.60" \ No newline at end of file diff --git a/quickstart_complete/client_code/secret_addition_complete.py b/quickstart_complete/client_code/secret_addition_complete.py index f32dff66..a8105c1f 100644 --- a/quickstart_complete/client_code/secret_addition_complete.py +++ b/quickstart_complete/client_code/secret_addition_complete.py @@ -49,7 +49,7 @@ async def main(): program_mir_path = f"../nada_quickstart_programs/target/{program_name}.nada.bin" # Adding funds to the client balance so the upcoming operations can be paid for - funds_amount = 1000 + funds_amount = 3000000 print(f"💰 Adding some funds to the client balance: {funds_amount} uNIL") await client.add_funds(funds_amount) @@ -107,7 +107,7 @@ async def main(): print(f"✅ Compute complete for compute_id {compute_id}") print(f"🖥️ The result is {result}") balance = await client.balance() - print(f"💰 Final client balance: {balance.balance} uNIL") + print(f"💰 Final client balance: {balance.balance} Credits") client.close() return result diff --git a/quickstart_complete/nada_quickstart_programs/target/secret_addition_complete.nada.bin b/quickstart_complete/nada_quickstart_programs/target/secret_addition_complete.nada.bin index f55dbf56c42ac6f19160f22370a3cdd2e7f1c368..60e60e39350b81441500a33bd928bfe9374b4e60 100644 GIT binary patch delta 256 zcmdnUK8v+ph>MFYAhD>V(old!N}P*Hi9ra;mtxPYjL*y~G1OqfE@7m>s3pzC2~?F| zT2fG2BETdC)v5$EMuXW%SV&L^$dX`k;^cxEW+cwRC?pKTAPGh%dAN*`5g!MW5El>w zWt5mC7_AO5aY;`;$0W@xw1{c*97ZKZ7AF;m2)6*2#05qLc?6$BqK{F*!ATh+&n3Vn Z;REEtmGek^WK=MONwW#CNN6f30sv(+G)n*g literal 945 zcma))%}T^D5XbAs`cXu@docFY>LRp9q2P64!3B>ZC2X@&*rX+!BKr{f2tLz3k~5tQ zT0zXfF!}h+Ka)(Nh-}ZtBOe#ix@~^wXZt=LKpYdten)Ss#<;@IB9{9vA?E}>f#)o6 z`mNvguIt_6Q%`VnTn3(lj>eHmyabOg3d-KSQ)CwNc#x<>}maXpA%Bj0wW3um8wJHRT zFzO9I3Mg Date: Thu, 6 Feb 2025 13:54:20 +0200 Subject: [PATCH 43/43] chore: bump reqs --- .nil-sdk.toml | 2 +- requirements.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.nil-sdk.toml b/.nil-sdk.toml index a93ef52d..f3183813 100644 --- a/.nil-sdk.toml +++ b/.nil-sdk.toml @@ -1 +1 @@ -version = "v0.9.0-rc.60" \ No newline at end of file +version = "v0.9.0" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index bd497218..de3d849f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -nillion-client==0.2.0rc3 -nada-dsl==0.8.0rc3 +nillion-client==0.2.0 +nada-dsl==0.8.0 python-dotenv==1.0.0