Skip to content

Commit cf823f1

Browse files
authored
fix: fixed testing and added more examples (#10)
* feat: added new examples and organized existing ones * fix: fixed testing not correct results
1 parent 660043f commit cf823f1

25 files changed

+586
-70
lines changed

.github/workflows/test.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,24 @@ on:
1010
branches: [ "*" ]
1111

1212
jobs:
13+
check-linting:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
- name: Set up Python 3.12
18+
uses: actions/setup-python@v3
19+
with:
20+
python-version: "3.12"
21+
- name: Install dependencies
22+
run: |
23+
python -m pip install --upgrade pip
24+
pip install black
25+
- name: Check linting
26+
run: black --check .
27+
# If PR fails in this stage, it's because of linting issues: run `black .` to fix them
28+
1329
build:
30+
needs: check-linting
1431
runs-on: ubuntu-latest
1532
strategy:
1633
fail-fast: false

examples/broadcasting/README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Broadcasting Tutorial
2+
3+
This tutorial shows how to efficiently use broadcasting in Nada using Nada Algebra.
4+
5+
```python
6+
from nada_dsl import *
7+
8+
# Step 0: Nada Algebra is imported with this line
9+
import nada_algebra as na
10+
11+
12+
def nada_main():
13+
# Step 1: We use Nada Algebra wrapper to create "Party0", "Party1" and "Party2"
14+
parties = na.parties(3)
15+
16+
# Step 2: Party0 creates an array of dimension (3, ) with name "A"
17+
a = na.array([3], parties[0], "A")
18+
19+
# Step 3: Party1 creates an array of dimension (3, ) with name "B"
20+
b = na.array([3], parties[1], "B")
21+
22+
# Step 4: Party0 creates an array of dimension (3, ) with name "C"
23+
c = na.array([3], parties[0], "C")
24+
25+
# Step 5: Party1 creates an array of dimension (3, ) with name "D"
26+
d = na.array([3], parties[1], "D")
27+
28+
# Step 4: The result is of computing SIMD operations on top of the elements of the array
29+
# SIMD operations are performed on all the elements of the array.
30+
# The equivalent would be: for i in range(3): result += a[i] + b[i] - c[i] * d[i]
31+
result = a + b - c * d
32+
# Step 5: We can use result.output() to produce the output for Party2 and variable name "my_output"
33+
return result.output(parties[2], "my_output")
34+
```
35+
36+
0. We import Nada algebra using `import nada_algebra as na`.
37+
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`.
38+
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"`.
39+
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"`.
40+
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"`.
41+
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"`.
42+
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`.
43+
# How to run the tutorial.
44+
45+
1. First, we need to compile the nada program running: `nada build`.
46+
2. Then, we can test our program is running with: `nada test`.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
name = "broadcasting"
2+
version = "0.1.0"
3+
authors = [""]
4+
5+
[[programs]]
6+
path = "src/broadcasting.py"
7+
prime_size = 128
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Import necessary libraries and modules
2+
import asyncio
3+
import py_nillion_client as nillion
4+
import os
5+
import sys
6+
import pytest
7+
import numpy as np
8+
import time
9+
from dotenv import load_dotenv
10+
11+
# Add the parent directory to the system path to import modules from it
12+
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
13+
14+
# Import helper functions for creating nillion client and getting keys
15+
from dot_product.network.helpers.nillion_client_helper import create_nillion_client
16+
from dot_product.network.helpers.nillion_keypath_helper import (
17+
getUserKeyFromFile,
18+
getNodeKeyFromFile,
19+
)
20+
import nada_algebra.client as na_client
21+
22+
# Load environment variables from a .env file
23+
load_dotenv()
24+
from dot_product.config.parameters import DIM
25+
26+
27+
# Main asynchronous function to coordinate the process
28+
async def main():
29+
print(f"USING: {DIM}")
30+
cluster_id = os.getenv("NILLION_CLUSTER_ID")
31+
userkey = getUserKeyFromFile(os.getenv("NILLION_USERKEY_PATH_PARTY_1"))
32+
nodekey = getNodeKeyFromFile(os.getenv("NILLION_NODEKEY_PATH_PARTY_1"))
33+
client = create_nillion_client(userkey, nodekey)
34+
party_id = client.party_id
35+
user_id = client.user_id
36+
party_names = na_client.parties(3)
37+
program_name = "main"
38+
program_mir_path = f"./target/{program_name}.nada.bin"
39+
40+
# Store the program
41+
action_id = await client.store_program(cluster_id, program_name, program_mir_path)
42+
program_id = f"{user_id}/{program_name}"
43+
print("Stored program. action_id:", action_id)
44+
print("Stored program_id:", program_id)
45+
46+
# Create and store secrets for two parties
47+
A = np.ones([DIM])
48+
C = np.ones([DIM])
49+
stored_secret = nillion.Secrets(
50+
na_client.concat(
51+
[
52+
na_client.array(A, "A"),
53+
na_client.array(C, "C"),
54+
]
55+
)
56+
)
57+
secret_bindings = nillion.ProgramBindings(program_id)
58+
secret_bindings.add_input_party(party_names[0], party_id)
59+
60+
# Store the secret for the specified party
61+
A_store_id = await client.store_secrets(
62+
cluster_id, secret_bindings, stored_secret, None
63+
)
64+
65+
B = np.ones([DIM])
66+
D = np.ones([DIM])
67+
stored_secret = nillion.Secrets(
68+
na_client.concat(
69+
[
70+
na_client.array(B, "B"),
71+
na_client.array(D, "D"),
72+
]
73+
)
74+
)
75+
secret_bindings = nillion.ProgramBindings(program_id)
76+
secret_bindings.add_input_party(party_names[1], party_id)
77+
78+
# Store the secret for the specified party
79+
B_store_id = await client.store_secrets(
80+
cluster_id, secret_bindings, stored_secret, None
81+
)
82+
83+
# Set up the compute bindings for the parties
84+
compute_bindings = nillion.ProgramBindings(program_id)
85+
[
86+
compute_bindings.add_input_party(party_name, party_id)
87+
for party_name in party_names[:-1]
88+
]
89+
compute_bindings.add_output_party(party_names[-1], party_id)
90+
91+
print(f"Computing using program {program_id}")
92+
print(f"Use secret store_id: {A_store_id}, {B_store_id}")
93+
94+
computation_time_secrets = nillion.Secrets({"my_int2": nillion.SecretInteger(10)})
95+
96+
# Perform the computation and return the result
97+
compute_id = await client.compute(
98+
cluster_id,
99+
compute_bindings,
100+
[A_store_id, B_store_id],
101+
computation_time_secrets,
102+
nillion.PublicVariables({}),
103+
)
104+
105+
# Monitor and print the computation result
106+
print(f"The computation was sent to the network. compute_id: {compute_id}")
107+
while True:
108+
compute_event = await client.next_compute_event()
109+
if isinstance(compute_event, nillion.ComputeFinishedEvent):
110+
print(f"✅ Compute complete for compute_id {compute_event.uuid}")
111+
print(f"🖥️ The result is {compute_event.result.value}")
112+
return compute_event.result.value
113+
return result
114+
115+
116+
# Run the main function if the script is executed directly
117+
if __name__ == "__main__":
118+
asyncio.run(main())
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import os
2+
import py_nillion_client as nillion
3+
from helpers.nillion_payments_helper import create_payments_config
4+
5+
6+
def create_nillion_client(userkey, nodekey):
7+
bootnodes = [os.getenv("NILLION_BOOTNODE_MULTIADDRESS")]
8+
payments_config = create_payments_config()
9+
10+
return nillion.NillionClient(
11+
nodekey, bootnodes, nillion.ConnectionMode.relay(), userkey, payments_config
12+
)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import os
2+
import py_nillion_client as nillion
3+
4+
5+
def getUserKeyFromFile(userkey_filepath):
6+
return nillion.UserKey.from_file(userkey_filepath)
7+
8+
9+
def getNodeKeyFromFile(nodekey_filepath):
10+
return nillion.NodeKey.from_file(nodekey_filepath)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import os
2+
import py_nillion_client as nillion
3+
4+
5+
def create_payments_config():
6+
return nillion.PaymentsConfig(
7+
os.getenv("NILLION_BLOCKCHAIN_RPC_ENDPOINT"),
8+
os.getenv("NILLION_WALLET_PRIVATE_KEY"),
9+
int(os.getenv("NILLION_CHAIN_ID")),
10+
os.getenv("NILLION_PAYMENTS_SC_ADDRESS"),
11+
os.getenv("NILLION_BLINDING_FACTORS_MANAGER_SC_ADDRESS"),
12+
)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from nada_dsl import *
2+
3+
# Step 0: Nada Algebra is imported with this line
4+
import nada_algebra as na
5+
6+
7+
def nada_main():
8+
# Step 1: We use Nada Algebra wrapper to create "Party0", "Party1" and "Party2"
9+
parties = na.parties(3)
10+
11+
# Step 2: Party0 creates an array of dimension (3, ) with name "A"
12+
a = na.array([3], parties[0], "A")
13+
14+
# Step 3: Party1 creates an array of dimension (3, ) with name "B"
15+
b = na.array([3], parties[1], "B")
16+
17+
# Step 4: Party0 creates an array of dimension (3, ) with name "C"
18+
c = na.array([3], parties[0], "C")
19+
20+
# Step 5: Party1 creates an array of dimension (3, ) with name "D"
21+
d = na.array([3], parties[1], "D")
22+
23+
# Step 4: The result is of computing SIMD operations on top of the elements of the array
24+
# SIMD operations are performed on all the elements of the array.
25+
# The equivalent would be: for i in range(3): result += a[i] + b[i] - c[i] * d[i]
26+
result = a + b - c * d
27+
# Step 5: We can use result.output() to produce the output for Party2 and variable name "my_output"
28+
return result.output(parties[2], "my_output")
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# This directory is kept purposely, so that no compilation errors arise.
2+
# Ignore everything in this directory
3+
*
4+
# Except this file
5+
!.gitignore
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
program: broadcasting
3+
inputs:
4+
secrets:
5+
B_1:
6+
SecretInteger: "3"
7+
A_0:
8+
SecretInteger: "3"
9+
C_1:
10+
SecretInteger: "3"
11+
B_0:
12+
SecretInteger: "3"
13+
D_0:
14+
SecretInteger: "3"
15+
C_2:
16+
SecretInteger: "3"
17+
C_0:
18+
SecretInteger: "3"
19+
A_2:
20+
SecretInteger: "3"
21+
A_1:
22+
SecretInteger: "3"
23+
D_1:
24+
SecretInteger: "3"
25+
B_2:
26+
SecretInteger: "3"
27+
D_2:
28+
SecretInteger: "3"
29+
public_variables: {}
30+
expected_outputs:
31+
my_output_1:
32+
SecretInteger: "-3"
33+
my_output_2:
34+
SecretInteger: "-3"
35+
my_output_0:
36+
SecretInteger: "-3"

0 commit comments

Comments
 (0)