diff --git a/bootstrap-local-environment-colab.sh b/bootstrap-local-environment-colab.sh new file mode 100644 index 00000000..35497ebe --- /dev/null +++ b/bootstrap-local-environment-colab.sh @@ -0,0 +1,152 @@ +#!/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="/root/.nilup/sdks/latest/nillion-devnet" +NILLION_CLI="/root/.nilup/sdks/latest/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 $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") + +# 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_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 + +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 \ No newline at end of file diff --git a/compile_programs_colab.sh b/compile_programs_colab.sh new file mode 100644 index 00000000..2c4bfe22 --- /dev/null +++ b/compile_programs_colab.sh @@ -0,0 +1,35 @@ +#!/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="/root/.nilup/sdks/latest/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/examples_and_tutorials/core_concept_single_party_compute/my_first_program.py b/examples_and_tutorials/core_concept_single_party_compute/my_first_program.py new file mode 100644 index 00000000..e14971a5 --- /dev/null +++ b/examples_and_tutorials/core_concept_single_party_compute/my_first_program.py @@ -0,0 +1,85 @@ +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 your first program 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 = "my_first_program" + 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/programs/my_first_program.py b/programs/my_first_program.py new file mode 100644 index 00000000..e930708d --- /dev/null +++ b/programs/my_first_program.py @@ -0,0 +1,12 @@ +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)) + + # write the computation for your program here - use my_int1 and my_int2 as inputs + # make sure you change the output below to be your new output + + return [Output(my_int1, "my_output", party1)] \ No newline at end of file