Skip to content

Commit

Permalink
fix: fixed testing and added more examples (#10)
Browse files Browse the repository at this point in the history
* feat: added new examples and organized existing ones

* fix: fixed testing not correct results
  • Loading branch information
jcabrero authored May 24, 2024
1 parent 660043f commit cf823f1
Show file tree
Hide file tree
Showing 25 changed files with 586 additions and 70 deletions.
17 changes: 17 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,24 @@ on:
branches: [ "*" ]

jobs:
check-linting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v3
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install black
- name: Check linting
run: black --check .
# If PR fails in this stage, it's because of linting issues: run `black .` to fix them

build:
needs: check-linting
runs-on: ubuntu-latest
strategy:
fail-fast: false
Expand Down
46 changes: 46 additions & 0 deletions examples/broadcasting/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Broadcasting Tutorial

This tutorial shows how to efficiently use broadcasting in Nada using Nada Algebra.

```python
from nada_dsl import *

# Step 0: Nada Algebra is imported with this line
import nada_algebra as na


def nada_main():
# Step 1: We use Nada Algebra wrapper to create "Party0", "Party1" and "Party2"
parties = na.parties(3)

# Step 2: Party0 creates an array of dimension (3, ) with name "A"
a = na.array([3], parties[0], "A")

# Step 3: Party1 creates an array of dimension (3, ) with name "B"
b = na.array([3], parties[1], "B")

# Step 4: Party0 creates an array of dimension (3, ) with name "C"
c = na.array([3], parties[0], "C")

# Step 5: Party1 creates an array of dimension (3, ) with name "D"
d = na.array([3], parties[1], "D")

# Step 4: The result is of computing SIMD operations on top of the elements of the array
# SIMD operations are performed on all the elements of the array.
# The equivalent would be: for i in range(3): result += a[i] + b[i] - c[i] * d[i]
result = a + b - c * d
# Step 5: We can use result.output() to produce the output for Party2 and variable name "my_output"
return result.output(parties[2], "my_output")
```

0. We import Nada algebra using `import nada_algebra as na`.
1. We create an array of parties, with our wrapper using `parties = na.parties(3)` which creates an array of parties named: `Party0`, `Party1` and `Party2`.
2. We create our input array `a` with `na.array([3], parties[0], "A")`, meaning our array will have dimension 3, `Party0` will be in charge of giving its inputs and the name of the variable is `"A"`.
3. We create our input array `b` with `na.array([3], parties[1], "B")`, meaning our array will have dimension 3, `Party1` will be in charge of giving its inputs and the name of the variable is `"B"`.
4. We create our input array `c` with `na.array([3], parties[1], "C")`, meaning our array will have dimension 3, `Party0` will be in charge of giving its inputs and the name of the variable is `"C"`.
5. We create our input array `d` with `na.array([3], parties[1], "D")`, meaning our array will have dimension 3, `Party1` will be in charge of giving its inputs and the name of the variable is `"D"`.
5. Finally, we use Nada Algebra to produce the outputs of the array like: `result.output(parties[2], "my_output")` establishing that the output party will be `Party2`and the name of the output variable will be `my_output`.
# How to run the tutorial.

1. First, we need to compile the nada program running: `nada build`.
2. Then, we can test our program is running with: `nada test`.
7 changes: 7 additions & 0 deletions examples/broadcasting/nada-project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name = "broadcasting"
version = "0.1.0"
authors = [""]

[[programs]]
path = "src/broadcasting.py"
prime_size = 128
118 changes: 118 additions & 0 deletions examples/broadcasting/network/compute.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Import necessary libraries and modules
import asyncio
import py_nillion_client as nillion
import os
import sys
import pytest
import numpy as np
import time
from dotenv import load_dotenv

# Add the parent directory to the system path to import modules from it
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))

# Import helper functions for creating nillion client and getting keys
from dot_product.network.helpers.nillion_client_helper import create_nillion_client
from dot_product.network.helpers.nillion_keypath_helper import (
getUserKeyFromFile,
getNodeKeyFromFile,
)
import nada_algebra.client as na_client

# Load environment variables from a .env file
load_dotenv()
from dot_product.config.parameters import DIM


# Main asynchronous function to coordinate the process
async def main():
print(f"USING: {DIM}")
cluster_id = os.getenv("NILLION_CLUSTER_ID")
userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1"))
nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1"))
client = create_nillion_client(userkey, nodekey)
party_id = client.party_id
user_id = client.user_id
party_names = na_client.parties(3)
program_name = "main"
program_mir_path = f"./target/{program_name}.nada.bin"

# Store the program
action_id = await client.store_program(cluster_id, program_name, program_mir_path)
program_id = f"{user_id}/{program_name}"
print("Stored program. action_id:", action_id)
print("Stored program_id:", program_id)

# Create and store secrets for two parties
A = np.ones([DIM])
C = np.ones([DIM])
stored_secret = nillion.Secrets(
na_client.concat(
[
na_client.array(A, "A"),
na_client.array(C, "C"),
]
)
)
secret_bindings = nillion.ProgramBindings(program_id)
secret_bindings.add_input_party(party_names[0], party_id)

# Store the secret for the specified party
A_store_id = await client.store_secrets(
cluster_id, secret_bindings, stored_secret, None
)

B = np.ones([DIM])
D = np.ones([DIM])
stored_secret = nillion.Secrets(
na_client.concat(
[
na_client.array(B, "B"),
na_client.array(D, "D"),
]
)
)
secret_bindings = nillion.ProgramBindings(program_id)
secret_bindings.add_input_party(party_names[1], party_id)

# Store the secret for the specified party
B_store_id = await client.store_secrets(
cluster_id, secret_bindings, stored_secret, None
)

# Set up the compute bindings for the parties
compute_bindings = nillion.ProgramBindings(program_id)
[
compute_bindings.add_input_party(party_name, party_id)
for party_name in party_names[:-1]
]
compute_bindings.add_output_party(party_names[-1], party_id)

print(f"Computing using program {program_id}")
print(f"Use secret store_id: {A_store_id}, {B_store_id}")

computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)})

# Perform the computation and return the result
compute_id = await client.compute(
cluster_id,
compute_bindings,
[A_store_id, B_store_id],
computation_time_secrets,
nillion.PublicVariables({}),
)

# Monitor and print the computation result
print(f"The computation was sent to the network. compute_id: {compute_id}")
while True:
compute_event = await client.next_compute_event()
if isinstance(compute_event, nillion.ComputeFinishedEvent):
print(f"✅ Compute complete for compute_id {compute_event.uuid}")
print(f"🖥️ The result is {compute_event.result.value}")
return compute_event.result.value
return result


# Run the main function if the script is executed directly
if __name__ == "__main__":
asyncio.run(main())
12 changes: 12 additions & 0 deletions examples/broadcasting/network/helpers/nillion_client_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import os
import py_nillion_client as nillion
from helpers.nillion_payments_helper import create_payments_config


def create_nillion_client(userkey, nodekey):
bootnodes = [os.getenv("NILLION_BOOTNODE_MULTIADDRESS")]
payments_config = create_payments_config()

return nillion.NillionClient(
nodekey, bootnodes, nillion.ConnectionMode.relay(), userkey, payments_config
)
10 changes: 10 additions & 0 deletions examples/broadcasting/network/helpers/nillion_keypath_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import os
import py_nillion_client as nillion


def getUserKeyFromFile(userkey_filepath):
return nillion.UserKey.from_file(userkey_filepath)


def getNodeKeyFromFile(nodekey_filepath):
return nillion.NodeKey.from_file(nodekey_filepath)
12 changes: 12 additions & 0 deletions examples/broadcasting/network/helpers/nillion_payments_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import os
import py_nillion_client as nillion


def create_payments_config():
return nillion.PaymentsConfig(
os.getenv("NILLION_BLOCKCHAIN_RPC_ENDPOINT"),
os.getenv("NILLION_WALLET_PRIVATE_KEY"),
int(os.getenv("NILLION_CHAIN_ID")),
os.getenv("NILLION_PAYMENTS_SC_ADDRESS"),
os.getenv("NILLION_BLINDING_FACTORS_MANAGER_SC_ADDRESS"),
)
28 changes: 28 additions & 0 deletions examples/broadcasting/src/broadcasting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from nada_dsl import *

# Step 0: Nada Algebra is imported with this line
import nada_algebra as na


def nada_main():
# Step 1: We use Nada Algebra wrapper to create "Party0", "Party1" and "Party2"
parties = na.parties(3)

# Step 2: Party0 creates an array of dimension (3, ) with name "A"
a = na.array([3], parties[0], "A")

# Step 3: Party1 creates an array of dimension (3, ) with name "B"
b = na.array([3], parties[1], "B")

# Step 4: Party0 creates an array of dimension (3, ) with name "C"
c = na.array([3], parties[0], "C")

# Step 5: Party1 creates an array of dimension (3, ) with name "D"
d = na.array([3], parties[1], "D")

# Step 4: The result is of computing SIMD operations on top of the elements of the array
# SIMD operations are performed on all the elements of the array.
# The equivalent would be: for i in range(3): result += a[i] + b[i] - c[i] * d[i]
result = a + b - c * d
# Step 5: We can use result.output() to produce the output for Party2 and variable name "my_output"
return result.output(parties[2], "my_output")
5 changes: 5 additions & 0 deletions examples/broadcasting/target/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# This directory is kept purposely, so that no compilation errors arise.
# Ignore everything in this directory
*
# Except this file
!.gitignore
36 changes: 36 additions & 0 deletions examples/broadcasting/tests/broadcasting.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
program: broadcasting
inputs:
secrets:
B_1:
SecretInteger: "3"
A_0:
SecretInteger: "3"
C_1:
SecretInteger: "3"
B_0:
SecretInteger: "3"
D_0:
SecretInteger: "3"
C_2:
SecretInteger: "3"
C_0:
SecretInteger: "3"
A_2:
SecretInteger: "3"
A_1:
SecretInteger: "3"
D_1:
SecretInteger: "3"
B_2:
SecretInteger: "3"
D_2:
SecretInteger: "3"
public_variables: {}
expected_outputs:
my_output_1:
SecretInteger: "-3"
my_output_2:
SecretInteger: "-3"
my_output_0:
SecretInteger: "-3"
Loading

0 comments on commit cf823f1

Please sign in to comment.