From 940c30b0cc22c3e22f990a6015a052d28acace69 Mon Sep 17 00:00:00 2001 From: Mefisto04 Date: Thu, 4 Jul 2024 18:08:36 +0000 Subject: [PATCH 1/2] my new nada program --- nohup.out | 14 ++++++++ quickstart/client_code/=0.9.2 | 19 +++++++++++ quickstart/client_code/nillion-python-starter | 1 + .../client_code/run_my_first_program.py | 30 ++++++++++++++++++ .../nada-project.toml | 7 ++++ .../nada_quickstart_programs/src/main.py | 30 ++++++++++++++++++ .../target/main.nada.bin | Bin 0 -> 1755 bytes .../nada_quickstart_programs/src/main.py | 30 ++++++++++++++++++ .../src/secret_addition_complete.py | 9 +++--- .../target/secret_addition_complete.nada.bin | Bin 975 -> 974 bytes 10 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 nohup.out create mode 100644 quickstart/client_code/=0.9.2 create mode 160000 quickstart/client_code/nillion-python-starter create mode 100644 quickstart/nada_quickstart_programs/nada-project.toml create mode 100644 quickstart/nada_quickstart_programs/src/main.py create mode 100644 quickstart/nada_quickstart_programs/target/main.nada.bin create mode 100644 quickstart_complete/nada_quickstart_programs/src/main.py diff --git a/nohup.out b/nohup.out new file mode 100644 index 00000000..0b8a018d --- /dev/null +++ b/nohup.out @@ -0,0 +1,14 @@ +ℹ️ cluster id is 9e68173f-9c23-4acc-ba81-4f079b639964 +ℹ️ using 256 bit prime +ℹ️ storing state in /tmp/.tmp1xYehT (79.99Gbs available) +🏃 starting nilchain node in: /tmp/.tmp1xYehT/nillion-chain +⛓ nilchain JSON RPC available at http://127.0.0.1:48102 +⛓ nilchain REST API available at http://localhost:26650 +⛓ nilchain gRPC available at localhost:26649 +🏃 starting node 12D3KooWMvw1hEqm7EWSDEyqTb6pNetUVkepahKY6hixuAuMZfJS +⏳ waiting until bootnode is up... +🏃 starting node 12D3KooWAiwGZUwSUaT2bYVxGS8jmfMrfsanZYkHwH3uL7WJPsFq +🏃 starting node 12D3KooWM3hsAswc7ZT6VpwQ1TCZU4GCYY55nLhcsxCcfjuixW57 +👛 funding nilchain keys +📝 nillion CLI configuration written to /root/.config/nillion/nillion-cli.yaml +🌄 environment file written to /root/.config/nillion/nillion-devnet.env diff --git a/quickstart/client_code/=0.9.2 b/quickstart/client_code/=0.9.2 new file mode 100644 index 00000000..972a8252 --- /dev/null +++ b/quickstart/client_code/=0.9.2 @@ -0,0 +1,19 @@ +Requirement already satisfied: cosmpy in /usr/local/lib/python3.10/dist-packages (0.9.2) +Requirement already satisfied: bech32 in /usr/local/lib/python3.10/dist-packages (from cosmpy) (1.2.0) +Requirement already satisfied: ecdsa in /usr/local/lib/python3.10/dist-packages (from cosmpy) (0.19.0) +Requirement already satisfied: googleapis-common-protos in /usr/local/lib/python3.10/dist-packages (from cosmpy) (1.63.2) +Requirement already satisfied: grpcio in /usr/local/lib/python3.10/dist-packages (from cosmpy) (1.64.1) +Requirement already satisfied: jsonschema<5,>=3.2.0 in /usr/local/lib/python3.10/dist-packages (from cosmpy) (4.19.2) +Requirement already satisfied: protobuf<5.0dev,>=4.21.6 in /usr/local/lib/python3.10/dist-packages (from cosmpy) (4.25.3) +Requirement already satisfied: pycryptodome<4.0.0,>=3.18.0 in /usr/local/lib/python3.10/dist-packages (from cosmpy) (3.20.0) +Requirement already satisfied: python-dateutil in /usr/local/lib/python3.10/dist-packages (from cosmpy) (2.8.2) +Requirement already satisfied: requests in /usr/local/lib/python3.10/dist-packages (from cosmpy) (2.31.0) +Requirement already satisfied: attrs>=22.2.0 in /usr/local/lib/python3.10/dist-packages (from jsonschema<5,>=3.2.0->cosmpy) (23.2.0) +Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /usr/local/lib/python3.10/dist-packages (from jsonschema<5,>=3.2.0->cosmpy) (2023.12.1) +Requirement already satisfied: referencing>=0.28.4 in /usr/local/lib/python3.10/dist-packages (from jsonschema<5,>=3.2.0->cosmpy) (0.35.1) +Requirement already satisfied: rpds-py>=0.7.1 in /usr/local/lib/python3.10/dist-packages (from jsonschema<5,>=3.2.0->cosmpy) (0.18.1) +Requirement already satisfied: six>=1.9.0 in /usr/local/lib/python3.10/dist-packages (from ecdsa->cosmpy) (1.16.0) +Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests->cosmpy) (3.3.2) +Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests->cosmpy) (3.7) +Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests->cosmpy) (2.0.7) +Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests->cosmpy) (2024.6.2) diff --git a/quickstart/client_code/nillion-python-starter b/quickstart/client_code/nillion-python-starter new file mode 160000 index 00000000..15e9fe0b --- /dev/null +++ b/quickstart/client_code/nillion-python-starter @@ -0,0 +1 @@ +Subproject commit 15e9fe0b43b980670bf1a97f703a8d2f25ca1afc diff --git a/quickstart/client_code/run_my_first_program.py b/quickstart/client_code/run_my_first_program.py index e69de29b..67161479 100644 --- a/quickstart/client_code/run_my_first_program.py +++ b/quickstart/client_code/run_my_first_program.py @@ -0,0 +1,30 @@ +from nada_dsl import * +from math import gcd + +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)) + + sum_int = my_int1 + my_int2 + + difference_int = my_int1 - my_int2 + + product_int = my_int1 * my_int2 + + quotient_int = my_int1 // my_int2 + + gcd_int = gcd(my_int1, my_int2) + + is_multiple = (my_int1 % my_int2) == 0 + multiple_message = is_multiple.if_else("Yes", "No") + + return [ + Output(sum_int, "sum_output", party=party1), + Output(difference_int, "difference_output", party=party1), + Output(product_int, "product_output", party=party1), + Output(quotient_int, "quotient_output", party=party1), + Output(gcd_int, "gcd_output", party=party1), + Output(multiple_message, "multiple_output", party=party1) + ] diff --git a/quickstart/nada_quickstart_programs/nada-project.toml b/quickstart/nada_quickstart_programs/nada-project.toml new file mode 100644 index 00000000..da166dde --- /dev/null +++ b/quickstart/nada_quickstart_programs/nada-project.toml @@ -0,0 +1,7 @@ +name = "nada_quickstart_programs" +version = "0.1.0" +authors = [""] + +[[programs]] +path = "src/main.py" +prime_size = 128 diff --git a/quickstart/nada_quickstart_programs/src/main.py b/quickstart/nada_quickstart_programs/src/main.py new file mode 100644 index 00000000..760e2c1b --- /dev/null +++ b/quickstart/nada_quickstart_programs/src/main.py @@ -0,0 +1,30 @@ +from nada_dsl import * +from math import gcd + +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)) + + sum_int = my_int1 + my_int2 + + difference_int = my_int1 - my_int2 + + product_int = my_int1 * my_int2 + + quotient_int = my_int1 // my_int2 + + gcd_int = gcd(my_int1, my_int2) + + is_multiple = (my_int1 % my_int2) == 0 + multiple_message = is_multiple.if_else("Yes", "No") + + return [ + Output(sum_int, "sum_output", party=party1), + Output(difference_int, "difference_output", party=party1), + Output(product_int, "product_output", party=party1), + Output(quotient_int, "quotient_output", party=party1), + Output(gcd_int, "gcd_output", party=party1), + Output(multiple_message, "multiple_output", party=party1) + ] diff --git a/quickstart/nada_quickstart_programs/target/main.nada.bin b/quickstart/nada_quickstart_programs/target/main.nada.bin new file mode 100644 index 0000000000000000000000000000000000000000..5be84d0de172abaa133a85914cea4a9ce3263b25 GIT binary patch literal 1755 zcmbVMO>fjN5Dgy!kpma5NSL6uNxQUU_kt=S_1X(vi4%e<3W>8sik*-+LW}ru95^Fx z{1$GEgSdYi=y^QB$9mlLYxIe&snKWu0KE#tq3VB+u58(Lcp5r`+{VrbV zZ4YT37g8+v^6=-! z*Wc_9=}%j#CnGg|t=I22hWvPB546oY2bam$5uoXfKb~$B$R6OmLbxNA@eAnzPe7Gy zQlj{FNCW`?4vBsSJ0)lMu!-{*T@fc4haGEj;9S*ms0T+Z~RLM^+FC} zyqS@VjBpU#keE-9m~Ts2sNa#Xk*cps;Em2?;spI7=SE_*egmJB4feZHd$%Lh<{q- zF)v&0d+&J*N%P)To0375x<;}OsHCfS)sL5tSp>Yc%*@#-pKa4_W)|2Z$7GRF(m{bv zlj-AP0Dm}67Qi9_S6h>5R57Xp@CVm!5zODU>$-bJ)pfTfQWxp=dDIB%{r?)qjlcq$ sY1w@$i*)}qFYLa6Hr5J~f3@a@#$jOq)O}Z`N&`wWFz^8}ggv>F(M1WOi-7@T0gMe(8gLD4;(JC-eJ-H%1t69OVu&!r zBp90=C=LR-mGPN*B}S7!GOF`}T?Q0$0Aj|8=d~D>CNE=>6HG}>Q^-q9NsP};%*@l! xwBnk4jY)a(4<;v3E(IWfnxJ5-5S*G^lv*;`k6DV*XmTdACZ{$`a`J3uV*p>_JevRj From 482861e235c18da468b6f8d870dc4dd1b9c87f58 Mon Sep 17 00:00:00 2001 From: Mefisto04 Date: Fri, 5 Jul 2024 17:56:17 +0000 Subject: [PATCH 2/2] my new nada program --- nohup.out | 14 ++ .../client_code/run_my_first_program.py | 123 +++++++++++++++--- .../nada_quickstart_programs/src/main.py | 123 +++++++++++++++--- .../target/main.nada.bin | Bin 1755 -> 9023 bytes .../target/secret_addition_complete.nada.bin | Bin 974 -> 974 bytes 5 files changed, 220 insertions(+), 40 deletions(-) diff --git a/nohup.out b/nohup.out index 0b8a018d..7dffe386 100644 --- a/nohup.out +++ b/nohup.out @@ -12,3 +12,17 @@ 👛 funding nilchain keys 📝 nillion CLI configuration written to /root/.config/nillion/nillion-cli.yaml 🌄 environment file written to /root/.config/nillion/nillion-devnet.env +ℹ️ cluster id is 9e68173f-9c23-4acc-ba81-4f079b639964 +ℹ️ using 256 bit prime +ℹ️ storing state in /tmp/.tmpgsHTsP (79.99Gbs available) +🏃 starting nilchain node in: /tmp/.tmpgsHTsP/nillion-chain +⛓ nilchain JSON RPC available at http://127.0.0.1:48102 +⛓ nilchain REST API available at http://localhost:26650 +⛓ nilchain gRPC available at localhost:26649 +🏃 starting node 12D3KooWMvw1hEqm7EWSDEyqTb6pNetUVkepahKY6hixuAuMZfJS +⏳ waiting until bootnode is up... +🏃 starting node 12D3KooWAiwGZUwSUaT2bYVxGS8jmfMrfsanZYkHwH3uL7WJPsFq +🏃 starting node 12D3KooWM3hsAswc7ZT6VpwQ1TCZU4GCYY55nLhcsxCcfjuixW57 +👛 funding nilchain keys +📝 nillion CLI configuration written to /root/.config/nillion/nillion-cli.yaml +🌄 environment file written to /root/.config/nillion/nillion-devnet.env diff --git a/quickstart/client_code/run_my_first_program.py b/quickstart/client_code/run_my_first_program.py index 67161479..71f2bd9c 100644 --- a/quickstart/client_code/run_my_first_program.py +++ b/quickstart/client_code/run_my_first_program.py @@ -1,30 +1,113 @@ from nada_dsl import * -from math import gcd -def nada_main(): - party1 = Party(name="Party1") +def initialize_managers(nr_managers): + """ + Initialize managers with unique identifiers. + + Parameters: + - nr_managers (int): Number of managers. + + Returns: + - managers (list): List of Party objects representing managers. + """ + managers = [] + for i in range(nr_managers): + managers.append(Party(name="Manager" + str(i))) + + return managers + +def inputs_initialization(nr_managers, nr_employees, managers): + """ + Initialize inputs for performance scores per employee. + + Parameters: + - nr_managers (int): Number of managers. + - nr_employees (int): Number of employees. + + Returns: + - scores_per_employee (list): List of lists representing scores per employee. + """ + scores_per_employee = [] + for e in range(nr_employees): + scores_per_employee.append([]) + for m in range(nr_managers): + scores_per_employee[e].append( + SecretUnsignedInteger( + Input(name="m" + str(m) + "_e" + str(e), party=managers[m]) + ) + ) + + return scores_per_employee - my_int1 = SecretInteger(Input(name="my_int1", party=party1)) - my_int2 = SecretInteger(Input(name="my_int2", party=party1)) +def compute_average_scores(nr_managers, nr_employees, scores_per_employee, outparty): + """ + Compute the average scores for each employee. - sum_int = my_int1 + my_int2 + Parameters: + - nr_managers (int): Number of managers. + - nr_employees (int): Number of employees. + - scores_per_employee (list): List of lists representing scores per employee. - difference_int = my_int1 - my_int2 + Returns: + - avg_scores (list): List of Output objects representing average scores for each employee. + """ + avg_scores = [] + for e in range(nr_employees): + total_score = scores_per_employee[e][0] + for m in range(1, nr_managers): + total_score += scores_per_employee[e][m] + average_score = total_score / UnsignedInteger(nr_managers) + avg_scores.append(Output(average_score, "avg_score_e" + str(e), outparty)) + + return avg_scores + +def verify_scores(nr_managers, nr_employees, scores_per_employee, max_score, outparty): + """ + Check if any score exceeds the allowed maximum score. + + Parameters: + - nr_managers (int): Number of managers. + - nr_employees (int): Number of employees. + - scores_per_employee (list): List of lists representing scores per employee. + + Returns: + - score_checks (list): List of Output objects representing score checks for each employee. + - if_cheat_open (list): List of Output objects representing revealed scores of cheating managers. + """ + score_checks = [] + if_cheat_open = [] + for e in range(nr_employees): + for m in range(nr_managers): + score = scores_per_employee[e][m] + check = score <= UnsignedInteger(max_score) + score_checks.append(Output(check, "check_score_m" + str(m) + "_e" + str(e), outparty)) + reveal_cheat = check.if_else(UnsignedInteger(0), score) + if_cheat_open.append(Output(reveal_cheat, "if_cheat_open_m" + str(m) + "_e" + str(e), outparty)) + + return score_checks, if_cheat_open + +def nada_main(): + # Compiled-time constants + nr_managers = 3 + nr_employees = 2 + max_score = 10 # Example maximum score allowed - product_int = my_int1 * my_int2 + # Parties initialization + managers = initialize_managers(nr_managers) + outparty = Party(name="OutParty") - quotient_int = my_int1 // my_int2 + # Inputs initialization + scores_per_employee = inputs_initialization(nr_managers, nr_employees, managers) - gcd_int = gcd(my_int1, my_int2) + # Computation + # Compute average scores + avg_scores = compute_average_scores(nr_managers, nr_employees, scores_per_employee, outparty) + # Check score validity + score_checks, if_cheat_open = verify_scores(nr_managers, nr_employees, scores_per_employee, max_score, outparty) - is_multiple = (my_int1 % my_int2) == 0 - multiple_message = is_multiple.if_else("Yes", "No") + # Output + results = avg_scores + score_checks + if_cheat_open + return results - return [ - Output(sum_int, "sum_output", party=party1), - Output(difference_int, "difference_output", party=party1), - Output(product_int, "product_output", party=party1), - Output(quotient_int, "quotient_output", party=party1), - Output(gcd_int, "gcd_output", party=party1), - Output(multiple_message, "multiple_output", party=party1) - ] +# Run the main function +nada_main() diff --git a/quickstart/nada_quickstart_programs/src/main.py b/quickstart/nada_quickstart_programs/src/main.py index 760e2c1b..d9773a92 100644 --- a/quickstart/nada_quickstart_programs/src/main.py +++ b/quickstart/nada_quickstart_programs/src/main.py @@ -1,30 +1,113 @@ from nada_dsl import * -from math import gcd -def nada_main(): - party1 = Party(name="Party1") +def initialize_managers(nr_managers): + """ + Initialize managers with unique identifiers. + + Parameters: + - nr_managers (int): Number of managers. + + Returns: + - managers (list): List of Party objects representing managers. + """ + managers = [] + for i in range(nr_managers): + managers.append(Party(name="Manager" + str(i))) + + return managers + +def inputs_initialization(nr_managers, nr_employees, managers): + """ + Initialize inputs for performance scores per employee. + + Parameters: + - nr_managers (int): Number of managers. + - nr_employees (int): Number of employees. + + Returns: + - scores_per_employee (list): List of lists representing scores per employee. + """ + scores_per_employee = [] + for e in range(nr_employees): + scores_per_employee.append([]) + for m in range(nr_managers): + scores_per_employee[e].append( + SecretUnsignedInteger( + Input(name="m" + str(m) + "_e" + str(e), party=managers[m]) + ) + ) + + return scores_per_employee - my_int1 = SecretInteger(Input(name="my_int1", party=party1)) - my_int2 = SecretInteger(Input(name="my_int2", party=party1)) +def compute_average_scores(nr_managers, nr_employees, scores_per_employee, outparty): + """ + Compute the average scores for each employee. - sum_int = my_int1 + my_int2 + Parameters: + - nr_managers (int): Number of managers. + - nr_employees (int): Number of employees. + - scores_per_employee (list): List of lists representing scores per employee. - difference_int = my_int1 - my_int2 + Returns: + - avg_scores (list): List of Output objects representing average scores for each employee. + """ + avg_scores = [] + for e in range(nr_employees): + total_score = scores_per_employee[e][0] + for m in range(1, nr_managers): + total_score += scores_per_employee[e][m] + average_score = total_score / UnsignedInteger(nr_managers) + avg_scores.append(Output(average_score, "avg_score_e" + str(e), outparty)) + + return avg_scores + +def verify_scores(nr_managers, nr_employees, scores_per_employee, max_score, outparty): + """ + Check if any score exceeds the allowed maximum score. + + Parameters: + - nr_managers (int): Number of managers. + - nr_employees (int): Number of employees. + - scores_per_employee (list): List of lists representing scores per employee. + + Returns: + - score_checks (list): List of Output objects representing score checks for each employee. + - if_cheat_open (list): List of Output objects representing revealed scores of cheating managers. + """ + score_checks = [] + if_cheat_open = [] + for e in range(nr_employees): + for m in range(nr_managers): + score = scores_per_employee[e][m] + check = score <= UnsignedInteger(max_score) + score_checks.append(Output(check, "check_score_m" + str(m) + "_e" + str(e), outparty)) + reveal_cheat = check.if_else(UnsignedInteger(0), score) + if_cheat_open.append(Output(reveal_cheat, "if_cheat_open_m" + str(m) + "_e" + str(e), outparty)) + + return score_checks, if_cheat_open + +def nada_main(): + # Compiled-time constants + nr_managers = 3 + nr_employees = 2 + max_score = 10 # Example maximum score allowed - product_int = my_int1 * my_int2 + # Parties initialization + managers = initialize_managers(nr_managers) + outparty = Party(name="OutParty") - quotient_int = my_int1 // my_int2 + # Inputs initialization + scores_per_employee = inputs_initialization(nr_managers, nr_employees, managers) - gcd_int = gcd(my_int1, my_int2) + # Computation + # Compute average scores + avg_scores = compute_average_scores(nr_managers, nr_employees, scores_per_employee, outparty) + # Check score validity + score_checks, if_cheat_open = verify_scores(nr_managers, nr_employees, scores_per_employee, max_score, outparty) - is_multiple = (my_int1 % my_int2) == 0 - multiple_message = is_multiple.if_else("Yes", "No") + # Output + results = avg_scores + score_checks + if_cheat_open + return results - return [ - Output(sum_int, "sum_output", party=party1), - Output(difference_int, "difference_output", party=party1), - Output(product_int, "product_output", party=party1), - Output(quotient_int, "quotient_output", party=party1), - Output(gcd_int, "gcd_output", party=party1), - Output(multiple_message, "multiple_output", party=party1) - ] +# Run the main function +nada_main() diff --git a/quickstart/nada_quickstart_programs/target/main.nada.bin b/quickstart/nada_quickstart_programs/target/main.nada.bin index 5be84d0de172abaa133a85914cea4a9ce3263b25..f48474c73ff38a4f7e15d26cd1dd81d8e57ade90 100644 GIT binary patch literal 9023 zcmc&)&2Jk;6kooPK?TGKaTz5=vS}K;&WG#LNTpSUidq7Jf>4Ou^?IBv^{(CBb(-*{ z{0Y5s%aLRMgj_fxmCqX@hzmD@#0>T_;}#~%c{eiS!$2Iv^K)h)j|A*6UMulr4Af?=Sj^H9}d(#qfGS?@fXYTt4&^2vX#L)3D3WH$g);pF2bXF`VtqX5{ul4yLN!Ts?8_ql8~XOXN$2PUqkegJ)|n=Rn!l-yaL= z8KIM9xm(=5q4W~0K6)n~57o-3_7*()tYld&m~-)vodZaOQEc!1d?nWv zW8T-k{rmLr;J)_Jk7MCItNiPAn&4t5Zua^-XbKOrOZgVeA>TDXIsb%ZbzvTYh(5ml z-ZT->%LlJa6A}G<^jp3i$G)|_e>6=*^!44J#=7;K^7X-Mayyn&l9~G3D5)zG-fzR! zKOwv?!b}MZNkH=5kL7Xuk(_fZ`7$j30Sf8`m}xmKBl+kPxs`o76yhu_{~RC|VSZYj zh^duiU4pPA=csTJ)>1jHs^f4!$>x5$U_a%u=V3p|-z%TxR}8Anku!?Kj|t*M%IOG1l+rDRjJBC8z!A>%-(reubC!I)4rN>$GR z6v-bh8L=ei0%nL(dB)R8{&uv}_t)e}$Rm;&$T%kC(cQlj zijvF(@WjsF+eow~nV?5$O*8H=GbS+|ZAmhbqJRWcn~;(u6B+h?mBu)T6K`jUjNMNClW1rTPLCt25Vl~D>H=miQc!=c^}{vuja%j$$Z zp9N0aX||&-^Zb4}7kIJfbiEIG)0b}jS^VL)PnPRJD7J z$Ad7?TlJact{36PD=^_U@p6*&S|4yXj#$Y1A&*d#VADV}FQSoF*!qT0w9^Zj2j*a* z6KwJv8{l@@Ht+P|y+FGx&MgOU!{TiiX@XE(LhmKJ_Xly*)aG|$ zuNRo7&Y_{W-|zNzIgjS__Te7!0)%9Ekvl+n87F>pQ_ETWQ z4g+fl-a6kPNR5X}alFM{V9d23@-_qBz8u6HUj3JkEyJak(c_AKJ`vBa00U>5oIdl) z9P8uD`xVk}-8Zg*I5?7v$xq@yMco{WNEW+2A2i@iXPbxc3x#|kno1y98s|XGvECpS z8fLhCQKGYWi!*fsu^Ele9e3;hqBPp*bRJX2Cb6)0Cg16I-w?o zK1Gai$bAM%+>4#A^f_C}GX~Iny(-fyo|uUkTX2I&nF$&b`_xcyKLJls5Y#aNUSipB zGKx?_{KOIzoYKnW6!sjO(F@a2rfHl`{CagHg9c*7>+FsjE55TMw+;^*{B44H9p(hP z(kqPbxSY2m8A;u4?-p-ELD}*AfiGA5#}xtD@b8C@$Tj#L-@S2v0VFmuwJ|5vNcB^{ z5!_dRhJ2elUGRa-hSZXRsNyAdyE zGei~2rnq@p&6LSAdYbs4Zffbs3LmSx$|iFV(NcBso0Lo?qN&GjzpT>dEW@Jd0k=-a zp3j-v3!>Nw;z+<6K8E`*&@;^rcvuq;wU6M5U1e;RUD|P=7v<@!!5o=OWS=`L+Hj9Y zKt1)PLUgqXQT!K%2mtk8hkWG_>&b<%-fXfVA&ZYQD#gyq9w_%n8C>ZW6Hmu3d7Q?Z zZYkT6x5cLb`&N_p@#cxa{ IBLYhQ0Df|nMgRZ+ literal 1755 zcmbVMO>fjN5Dgy!kpma5NSL6uNxQUU_kt=S_1X(vi4%e<3W>8sik*-+LW}ru95^Fx z{1$GEgSdYi=y^QB$9mlLYxIe&snKWu0KE#tq3VB+u58(Lcp5r`+{VrbV zZ4YT37g8+v^6=-! z*Wc_9=}%j#CnGg|t=I22hWvPB546oY2bam$5uoXfKb~$B$R6OmLbxNA@eAnzPe7Gy zQlj{FNCW`?4vBsSJ0)lMu!-{*T@fc4haGEj;9S*ms0T+Z~RLM^+FC} zyqS@VjBpU#keE-9m~Ts2sNa#Xk*cps;Em2?;spI7=SE_*egmJB4feZHd$%Lh<{q- zF)v&0d+&J*N%P)To0375x<;}OsHCfS)sL5tSp>Yc%*@#-pKa4_W)|2Z$7GRF(m{bv zlj-AP0Dm}67Qi9_S6h>5R57Xp@CVm!5zODU>$-bJ)pfTfQWxp=dDIB%{r?)qjlcq$ sY1w@$i*)}qFYLa6Hr5J~f3@a@#$jOq)O}Z`V> pAX=Cw*E6ZfaRODG2V!|3hRJ{ogRmz*V02+*oOoVq^HL^FMgVF_GeiIY