From 38e1476bd93a9010dd8ab32c0504cc8769fe83cf Mon Sep 17 00:00:00 2001 From: manel1874 Date: Fri, 28 Feb 2025 16:13:21 +0000 Subject: [PATCH 1/4] feat: add support for eddsa dkg and sign operations --- .nil-sdk.toml | 2 +- client-core/Cargo.lock | 430 +++- client-core/Cargo.toml | 2 +- client-core/nillion_client_core.pyi | 43 + client-core/pyproject.toml | 2 +- client-core/src/encrypted_value.rs | 83 +- client-core/src/lib.rs | 10 +- client-core/src/secrets_tests.rs | 111 + client-core/src/values/ecdsa_private_key.rs | 4 +- client-core/src/values/ecdsa_signature.rs | 4 +- client-core/src/values/eddsa_message.rs | 104 + client-core/src/values/eddsa_private_key.rs | 117 + client-core/src/values/eddsa_public_key.rs | 111 + client-core/src/values/eddsa_signature.rs | 116 + client-core/src/values/mod.rs | 24 + client-proto/pyproject.toml | 2 +- .../nillion/compute/v1/stream/__init__.py | 21 + .../nillion/payments/v1/config/__init__.py | 3 + .../nillion/values/v1/value/__init__.py | 77 +- nilvm | 2 +- patch.txt | 1892 +++++++++++++++++ pyproject.toml | 6 +- src/nillion_client/__init__.py | 8 + src/nillion_client/values.py | 56 + src/nillion_client/vm_operation.py | 8 + tests/test_nillion_client.py | 312 ++- 26 files changed, 3496 insertions(+), 54 deletions(-) create mode 100644 client-core/src/values/eddsa_message.rs create mode 100644 client-core/src/values/eddsa_private_key.rs create mode 100644 client-core/src/values/eddsa_public_key.rs create mode 100644 client-core/src/values/eddsa_signature.rs create mode 100644 patch.txt diff --git a/.nil-sdk.toml b/.nil-sdk.toml index 7dafb23..1d6ec7b 100644 --- a/.nil-sdk.toml +++ b/.nil-sdk.toml @@ -1 +1 @@ -version = "v0.9.0-rc.62" +version = "v0.10.0-rc.23" \ No newline at end of file diff --git a/client-core/Cargo.lock b/client-core/Cargo.lock index 09d5d29..c93754e 100644 --- a/client-core/Cargo.lock +++ b/client-core/Cargo.lock @@ -39,12 +39,18 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "basic-types" version = "0.1.0" dependencies = [ "hex", - "thiserror", + "thiserror 1.0.68", "uuid", ] @@ -96,6 +102,37 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cggmp21-keygen" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaa8c850290c494f951abe0350e56c31e4f5664863490197490ff48cb825447d" +dependencies = [ + "digest", + "displaydoc", + "generic-ec", + "generic-ec-zkp", + "hex", + "key-share", + "rand_core", + "round-based", + "serde", + "serde_with", + "sha2", + "thiserror 1.0.68", + "udigest", +] + +[[package]] +name = "chrono" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +dependencies = [ + "num-traits", + "serde", +] + [[package]] name = "cmake" version = "0.1.52" @@ -162,6 +199,8 @@ dependencies = [ "cpufeatures", "curve25519-dalek-derive", "fiat-crypto", + "group", + "rand_core", "rustc_version", "subtle", "zeroize", @@ -178,6 +217,41 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.87", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.87", +] + [[package]] name = "der" version = "0.7.9" @@ -188,6 +262,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + [[package]] name = "digest" version = "0.10.7" @@ -196,6 +279,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -219,17 +303,6 @@ dependencies = [ "proc-macro-error", ] -[[package]] -name = "ecdsa-keypair" -version = "0.1.0" -dependencies = [ - "generic-ec", - "key-share", - "rand", - "subtle", - "thiserror", -] - [[package]] name = "educe" version = "0.4.23" @@ -347,12 +420,50 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ + "serde", "typenum", "version_check", "zeroize", @@ -365,12 +476,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50e3268b3f97e2046ebf69e24a1e7e12dad4dec247d87f59268fd57ab514b5f8" dependencies = [ "curve25519-dalek", + "digest", "generic-ec-core", "generic-ec-curves", "hex", - "phantom-type", + "phantom-type 0.4.2", "rand_core", + "rand_hash", + "serde", + "serde_with", "subtle", + "udigest", "zeroize", ] @@ -382,6 +498,7 @@ checksum = "049128cc67cac6176ada5218e294ce46421470d92a7340c93d5cfd3ecfbc29a4" dependencies = [ "generic-array", "rand_core", + "serde", "subtle", "zeroize", ] @@ -392,8 +509,10 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e663405a17b229dede990904edcfd2056cf6641f1240869a1f44fa13bd4ee4d" dependencies = [ + "curve25519-dalek", "elliptic-curve", "generic-ec-core", + "group", "k256", "rand_core", "sha2", @@ -426,6 +545,24 @@ dependencies = [ "wasi", ] +[[package]] +name = "givre" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af482004b1f70b5b3b584c3fc3733dd24c71f8c0081a6087f49e09746746788a" +dependencies = [ + "cggmp21-keygen", + "digest", + "generic-ec", + "hd-wallet", + "k256", + "key-share", + "rand_core", + "serde", + "sha2", + "static_assertions", +] + [[package]] name = "group" version = "0.13.0" @@ -443,6 +580,19 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +[[package]] +name = "hd-wallet" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6522551bb35937363845f39a6d4c49e60bdb35a8f7154ebdd078cab50be97992" +dependencies = [ + "generic-array", + "generic-ec", + "hmac", + "sha2", + "subtle", +] + [[package]] name = "heck" version = "0.4.1" @@ -460,6 +610,24 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" @@ -487,6 +655,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + [[package]] name = "jit-compiler" version = "0.1.0" @@ -500,7 +674,7 @@ dependencies = [ "nada-compiler-backend", "nada-type", "substring", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -515,14 +689,18 @@ dependencies = [ [[package]] name = "key-share" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea364cb2397405d8c79afd3de173ca7e2e1d83a4ddd94d359263480ad96f06f" +checksum = "3ee8e510bb9f738ac400b7dedd98aeb23677c6b48cd3b1b682651ea5091f4282" dependencies = [ "displaydoc", "generic-ec", "generic-ec-zkp", + "hex", "rand_core", + "serde", + "serde_with", + "thiserror 1.0.68", ] [[package]] @@ -547,14 +725,13 @@ checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" name = "math_lib" version = "0.1.0" dependencies = [ - "basic-types", "crypto-bigint", "num-bigint", "num-traits", "paste", "rand", "subtle", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -583,7 +760,7 @@ dependencies = [ "serde", "serde_repr", "substring", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -601,12 +778,12 @@ name = "mpc-vm" version = "0.1.0" dependencies = [ "anyhow", - "ecdsa-keypair", "generic-ec", "jit-compiler", "nada-compiler-backend", "nada-value", "strum", + "threshold-keypair", ] [[package]] @@ -621,12 +798,11 @@ version = "0.1.0" dependencies = [ "anyhow", "ariadne", - "basic-types", "duplicate", "mir-model", "nada-value", "num-bigint", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -638,7 +814,7 @@ dependencies = [ "serde", "strum", "strum_macros", - "thiserror", + "thiserror 1.0.68", "types-proc-macros", ] @@ -648,9 +824,9 @@ version = "0.1.0" dependencies = [ "anyhow", "basic-types", - "ecdsa-keypair", "enum-as-inner", "generic-ec", + "givre", "indexmap", "key-share", "math_lib", @@ -659,7 +835,8 @@ dependencies = [ "num-traits", "shamir-sharing", "strum_macros", - "thiserror", + "thiserror 1.0.68", + "threshold-keypair", "types-proc-macros", ] @@ -668,18 +845,18 @@ name = "nillion-client-core" version = "0.1.0" dependencies = [ "basic-types", - "ecdsa-keypair", "key-share", "math_lib", "mpc-vm", "nada-value", "program-auditor", "shamir-sharing", + "threshold-keypair", ] [[package]] name = "nillion_client_core" -version = "0.2.1-rc1" +version = "0.2.1-rc2" dependencies = [ "ctor", "nillion-client-core", @@ -696,6 +873,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" version = "0.1.46" @@ -736,6 +919,15 @@ dependencies = [ "indexmap", ] +[[package]] +name = "phantom-type" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f710afd11c9711b04f97ab61bb9747d5a04562fdf0f9f44abc3de92490084982" +dependencies = [ + "educe", +] + [[package]] name = "phantom-type" version = "0.4.2" @@ -745,12 +937,30 @@ dependencies = [ "educe", ] +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "portable-atomic" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -810,7 +1020,7 @@ dependencies = [ "anyhow", "mpc-vm", "nada-compiler-backend", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -977,6 +1187,17 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16bc1dd921383c6564eb0b8252f5b3f6622b84d40c6e35f5e6790e1fd7abb7a9" +dependencies = [ + "digest", + "rand_core", + "udigest", +] + [[package]] name = "regex" version = "1.11.1" @@ -1006,6 +1227,30 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "round-based" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da76edf50de0a9d6911fc79261bb04cc9f3f3a375e0201799f5edf58499af341" +dependencies = [ + "futures-util", + "phantom-type 0.3.1", + "round-based-derive", + "thiserror 2.0.11", + "tracing", +] + +[[package]] +name = "round-based-derive" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afa4d5b318bcafae8a7ebc57c1cb7d4b2db7358293e34d71bfd605fd327cc13" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "rustc-hash" version = "2.1.0" @@ -1040,6 +1285,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +[[package]] +name = "ryu" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" + [[package]] name = "sec1" version = "0.7.3" @@ -1079,6 +1330,18 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "serde_json" +version = "1.0.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "serde_repr" version = "0.1.19" @@ -1090,6 +1353,33 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "serde_with" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" +dependencies = [ + "base64", + "chrono", + "hex", + "serde", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "sha2" version = "0.10.8" @@ -1110,7 +1400,7 @@ dependencies = [ "math_lib", "rand", "rustc-hash", - "thiserror", + "thiserror 1.0.68", ] [[package]] @@ -1119,6 +1409,18 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.26.3" @@ -1204,7 +1506,16 @@ version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.68", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", ] [[package]] @@ -1218,6 +1529,64 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "threshold-keypair" +version = "0.1.0" +dependencies = [ + "generic-ec", + "givre", + "key-share", + "rand", + "subtle", + "thiserror 1.0.68", +] + +[[package]] +name = "time" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" + [[package]] name = "typenum" version = "1.17.0" @@ -1240,6 +1609,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cd61fa9fb78569e9fe34acf0048fd8cb9ebdbacc47af740745487287043ff0" dependencies = [ + "digest", "udigest-derive", ] diff --git a/client-core/Cargo.toml b/client-core/Cargo.toml index 458bc4c..0200e95 100644 --- a/client-core/Cargo.toml +++ b/client-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nillion_client_core" -version = "0.2.1-rc1" +version = "0.2.1-rc2" edition = "2021" [lib] diff --git a/client-core/nillion_client_core.pyi b/client-core/nillion_client_core.pyi index 8a05264..302472f 100644 --- a/client-core/nillion_client_core.pyi +++ b/client-core/nillion_client_core.pyi @@ -14,6 +14,10 @@ NadaValue = Union[ EcdsaSignature, EcdsaPublicKey, StoreId, + EddsaPrivateKey, + EddsaPublicKey, + EddsaSignature, + EddsaMessage, ] class SecretUnsignedInteger: @@ -139,6 +143,42 @@ class StoreId: def __eq__(self, other: object) -> bool: ... def __repr__(self) -> str: ... +class EddsaPrivateKey: + """Encodes a secret as an eddsa private key.""" + + value: bytearray + + def __init__(self, value: bytearray) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __repr__(self) -> str: ... + +class EddsaPublicKey: + """Encodes an eddsa public key.""" + + value: bytearray + + def __init__(self, value: bytearray) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __repr__(self) -> str: ... + +class EddsaSignature: + """Encodes an eddsa signature.""" + + value: Tuple[bytearray, bytearray] + + def __init__(self, value: Tuple[bytearray, bytearray]) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __repr__(self) -> str: ... + +class EddsaMessage: + """Encodes an eddsa message.""" + + value: bytearray + + def __init__(self, value: bytearray) -> None: ... + def __eq__(self, other: object) -> bool: ... + def __repr__(self) -> str: ... + class ProgramRequirements: """A program preprocessing requirements""" @@ -205,6 +245,9 @@ class NadaValuesClassification: ecdsa_private_key_shares: int """The number of ecdsa private key shares.""" + ecdsa_signature_shares: int + """The number of ecdsa signature shares.""" + class SecretMasker: """A secret masker. This allows masking and unmasking secrets.""" diff --git a/client-core/pyproject.toml b/client-core/pyproject.toml index 14ff10e..79b93f8 100644 --- a/client-core/pyproject.toml +++ b/client-core/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "nillion-client-core" -version = "0.2.1rc1" +version = "0.2.1rc2" requires-python = ">=3.10" classifiers = [ "Programming Language :: Rust", diff --git a/client-core/src/encrypted_value.rs b/client-core/src/encrypted_value.rs index de1181c..6c9ac62 100644 --- a/client-core/src/encrypted_value.rs +++ b/client-core/src/encrypted_value.rs @@ -1,10 +1,11 @@ use nillion_client_core::{ - generic_ec::{curves::Secp256k1, serde::CurveName, NonZero, Point, Scalar, SecretScalar}, + generic_ec::{serde::CurveName, Curve, NonZero, Point, Scalar, SecretScalar}, key_share::{DirtyCoreKeyShare, DirtyKeyInfo, Validate}, - privatekey::EcdsaPrivateKeyShare, - signature::EcdsaSignatureShare, + privatekey::ThresholdPrivateKeyShare, + signature::{EcdsaSignatureShare, EddsaSignature}, values::{BlobPrimitiveType, Encoded, EncodedModularNumber, EncodedModulo, Encrypted, NadaValue}, }; + use pyo3::{ exceptions::PyValueError, pyclass, pymethods, @@ -36,6 +37,10 @@ pub enum EncryptedNadaValue { EcdsaPrivateKey { i: u16, x: Vec, shared_public_key: Vec, public_shares: Vec> }, EcdsaPublicKey { value: Vec }, StoreId { value: Vec }, + EddsaPrivateKey { i: u16, x: Vec, shared_public_key: Vec, public_shares: Vec> }, + EddsaPublicKey { value: Vec }, + EddsaSignature { value: Vec }, + EddsaMessage { value: Vec }, } impl EncryptedNadaValue { @@ -76,6 +81,22 @@ impl EncryptedNadaValue { } } NadaValue::StoreId(value) => Self::StoreId { value: value.to_vec() }, + NadaValue::EddsaPrivateKey(key) => { + let key = key.into_inner(); + Self::EddsaPrivateKey { + i: key.i, + x: key.x.clone().into_inner().as_ref().to_le_bytes().to_vec(), + shared_public_key: key.key_info.shared_public_key.to_bytes(true).to_vec(), + public_shares: key.key_info.public_shares.iter().map(|s| s.to_bytes(true).to_vec()).collect(), + } + } + NadaValue::EddsaPublicKey(value) => Self::EddsaPublicKey { value: value.to_vec() }, + NadaValue::EddsaSignature(signature) => { + let mut out = vec![0u8; signature.serialized_len()]; + signature.signature.write_to_slice(&mut out); + Self::EddsaSignature { value: out } + } + NadaValue::EddsaMessage(message) => Self::EddsaMessage { value: message }, _ => Err(PyValueError::new_err("Unsupported NadaValue variant for conversion to PyObject"))?, }; Ok(value) @@ -153,8 +174,38 @@ impl EncryptedNadaValue { } .validate() .map_err(|e| PyValueError::new_err(e.to_string()))?; - NadaValue::new_ecdsa_private_key(EcdsaPrivateKeyShare::new(share)) + NadaValue::new_ecdsa_private_key(ThresholdPrivateKeyShare::new(share)) + } + E::EddsaPrivateKey { i, x, shared_public_key, public_shares } => { + let share = DirtyCoreKeyShare { + i, + key_info: DirtyKeyInfo { + curve: CurveName::new(), + shared_public_key: non_zero_point_from_bytes(&shared_public_key)?, + public_shares: public_shares + .iter() + .map(|s| non_zero_point_from_bytes(s)) + .collect::>()?, + vss_setup: None, + }, + x: non_zero_secret_scalar_from_bytes(&x)?, + } + .validate() + .map_err(|e| PyValueError::new_err(e.to_string()))?; + NadaValue::new_eddsa_private_key(ThresholdPrivateKeyShare::new(share)) + } + E::EddsaPublicKey { value } => { + let value: [u8; 32] = + value.try_into().map_err(|_| PyValueError::new_err("invalid public key length"))?; + NadaValue::new_eddsa_public_key(value) + } + E::EddsaSignature { value } => { + let signature = EddsaSignature::from_bytes(&value) + .map_err(|_| PyValueError::new_err("Failed to deserialize EdDSA signature"))?; + + NadaValue::new_eddsa_signature(signature) } + E::EddsaMessage { value } => NadaValue::new_eddsa_message(value), }; Ok(value) } @@ -180,16 +231,20 @@ impl EncryptedNadaValue { Self::EcdsaPrivateKey { .. } => "EcdsaPrivateKey {..}".into(), Self::EcdsaPublicKey { .. } => "EcdsaPublicKey {..}".into(), Self::StoreId { .. } => "StoreId {..}".into(), + Self::EddsaPrivateKey { .. } => "EddsaPrivateKey {..}".into(), + Self::EddsaPublicKey { .. } => "EddsaPublicKey {..}".into(), + Self::EddsaSignature { .. } => "EddsaSignature {..}".into(), + Self::EddsaMessage { .. } => "EddsaMessage {..}".into(), } } } -fn non_zero_point_from_bytes(bytes: &[u8]) -> PyResult>> { +fn non_zero_point_from_bytes(bytes: &[u8]) -> PyResult>> { let point = Point::from_bytes(bytes).map_err(|_| PyValueError::new_err("invalid bytes"))?; NonZero::from_point(point).ok_or_else(|| PyValueError::new_err("point is zero")) } -fn non_zero_secret_scalar_from_bytes(bytes: &[u8]) -> PyResult>> { +fn non_zero_secret_scalar_from_bytes(bytes: &[u8]) -> PyResult>> { let scalar = SecretScalar::from_le_bytes(bytes).map_err(|_| PyValueError::new_err("invalid bytes"))?; NonZero::from_secret_scalar(scalar).ok_or_else(|| PyValueError::new_err("scalar is zero")) } @@ -211,6 +266,10 @@ pub enum EncryptedNadaType { EcdsaPrivateKey(), EcdsaPublicKey(), StoreId(), + EddsaPrivateKey(), + EddsaPublicKey(), + EddsaSignature(), + EddsaMessage(), } impl EncryptedNadaType { @@ -236,6 +295,10 @@ impl EncryptedNadaType { T::EcdsaSignature => Self::EcdsaSignature(), T::EcdsaPublicKey => Self::EcdsaPublicKey(), T::StoreId => Self::StoreId(), + T::EddsaPrivateKey => Self::EddsaPrivateKey(), + T::EddsaPublicKey => Self::EddsaPublicKey(), + T::EddsaSignature => Self::EddsaSignature(), + T::EddsaMessage => Self::EddsaMessage(), T::SecretInteger | T::SecretUnsignedInteger | T::SecretBoolean | T::NTuple { .. } | T::Object { .. } => { return Err(PyValueError::new_err(format!("unsupported type: {t}",))); } @@ -270,6 +333,10 @@ impl EncryptedNadaType { EncryptedNadaType::EcdsaPrivateKey() => T::EcdsaPrivateKey, EncryptedNadaType::EcdsaPublicKey() => T::EcdsaPublicKey, EncryptedNadaType::StoreId() => T::StoreId, + EncryptedNadaType::EddsaPrivateKey() => T::EddsaPrivateKey, + EncryptedNadaType::EddsaPublicKey() => T::EddsaPublicKey, + EncryptedNadaType::EddsaSignature() => T::EddsaSignature, + EncryptedNadaType::EddsaMessage() => T::EddsaMessage, }; Ok(output) } @@ -293,6 +360,10 @@ impl EncryptedNadaType { Self::EcdsaPrivateKey { .. } => "EcdsaPrivateKey {..}".into(), Self::EcdsaPublicKey { .. } => "EcdsaPublicKey {..}".into(), Self::StoreId { .. } => "StoreId {..}".into(), + Self::EddsaPrivateKey { .. } => "EddsaPrivateKey {..}".into(), + Self::EddsaPublicKey { .. } => "EddsaPublicKey {..}".into(), + Self::EddsaSignature { .. } => "EddsaSignature {..}".into(), + Self::EddsaMessage { .. } => "EddsaMessage {..}".into(), } } } diff --git a/client-core/src/lib.rs b/client-core/src/lib.rs index ff91bfe..9b1341a 100644 --- a/client-core/src/lib.rs +++ b/client-core/src/lib.rs @@ -118,6 +118,9 @@ struct NadaValuesClassification { /// The number of ecdsa key shares ecdsa_private_key_shares: u64, + + /// The number of ecdsa signatures shares + ecdsa_signature_shares: u64, } #[pymethods] @@ -132,7 +135,12 @@ impl NadaValuesClassification { impl From<::nillion_client_core::values::NadaValuesClassification> for NadaValuesClassification { fn from(value: ::nillion_client_core::values::NadaValuesClassification) -> Self { - Self { shares: value.shares, public: value.public, ecdsa_private_key_shares: value.ecdsa_private_key_shares } + Self { + shares: value.shares, + public: value.public, + ecdsa_private_key_shares: value.ecdsa_private_key_shares, + ecdsa_signature_shares: value.ecdsa_signature_shares, + } } } diff --git a/client-core/src/secrets_tests.rs b/client-core/src/secrets_tests.rs index 5b8364d..14fa7da 100644 --- a/client-core/src/secrets_tests.rs +++ b/client-core/src/secrets_tests.rs @@ -253,3 +253,114 @@ assert store_id.value == store_id_bytes # Compare against the same bytes .unwrap(); }) } + +#[test] +fn test_eddsa_private_key() { + Python::with_gil(|py| { + Python::run_bound( + py, + r#" +from nillion_client_core import * + +eddsa_pk_ba = bytearray([84, 104, 105, 115, 32, 109, 101, 115, 115, 97, 103, 101, 32, 105, 115, 32, 101, 120, 97, 99, 116, 108, 121, 32, 51, 50, 32, 98, 121, 116, 101, 0]) +eddsa_pk = EddsaPrivateKey(eddsa_pk_ba) +assert eddsa_pk.value == eddsa_pk_ba +"#, + None, + None, + ) + .unwrap(); + }) +} + +#[test] +fn test_bad_eddsa_private_key() { + Python::with_gil(|py| { + Python::run_bound( + py, + r#" +from nillion_client_core import * +import os + +# Check eddsa private key creation fails with bytearray size different from 32 +try: + eddsa_pk_ba = bytearray(os.urandom(33)) + eddsa_pk = EddsaPrivateKey(eddsa_pk_ba) + raise AssertionError("Expected ValueError not raised for invalid key size") +except ValueError as e: + assert "Private key format error" in str(e), "Unexpected error message" + +# Check eddsa private key creation fails with 0 key +try: + zero_key_ba = bytearray([0] * 32) + eddsa_pk = EddsaPrivateKey(zero_key_ba) + raise AssertionError("Expected ValueError not raised for zero key") +except ValueError as e: + assert "Private key format error" in str(e), "Unexpected error message" +"#, + None, + None, + ) + .unwrap(); + }) +} + +#[test] +fn test_eddsa_public_key() { + Python::with_gil(|py| { + Python::run_bound( + py, + r#" +from nillion_client_core import * +import os + +key_bytes = bytearray(os.urandom(32)) +eddsa_pk = EddsaPublicKey(key_bytes) +assert eddsa_pk.value == key_bytes # Compare against the same bytes +"#, + None, + None, + ) + .unwrap(); + }) +} + +#[test] +fn test_eddsa_message() { + Python::with_gil(|py| { + Python::run_bound( + py, + r#" +from nillion_client_core import * +import os + +eddsa_msg_ba = bytearray(os.urandom(45)) +eddsa_msg = EddsaMessage(eddsa_msg_ba) +"#, + None, + None, + ) + .unwrap(); + }) +} + +#[test] +fn test_eddsa_signature() { + Python::with_gil(|py| { + Python::run_bound( + py, + r#" +from nillion_client_core import * +import os + +r = bytearray([6, 125, 237, 201, 123, 78, 227, 152, 251, 46, 236, 39, 224, 73, 18, 4, 103, 85, 109, 69, 181, 210, 56, 234, 17, 157, 209, 38, 242, 124, 237, 250]) +z = bytearray(os.urandom(10)) +eddsa_msg = EddsaSignature((r, z)) +print("Eddsa signature is: ", eddsa_msg.value) +"#, + None, + None, + ) + .unwrap(); + }) +} diff --git a/client-core/src/values/ecdsa_private_key.rs b/client-core/src/values/ecdsa_private_key.rs index 84fd33a..3d459b0 100644 --- a/client-core/src/values/ecdsa_private_key.rs +++ b/client-core/src/values/ecdsa_private_key.rs @@ -62,7 +62,7 @@ impl EcdsaPrivateKey { /// Returns a new EcdsaPrivateKey. The byte array should be in big-endian format. #[new] fn new(value: &Bound<'_, PyByteArray>) -> PyResult { - let ecdsa_private_key = privatekey::EcdsaPrivateKey::from_bytes(&value.to_vec()).map_err(|_| { + let ecdsa_private_key = privatekey::ThresholdPrivateKey::from_be_bytes(&value.to_vec()).map_err(|_| { PyValueError::new_err( "Private key format error. Check your ecdsa secret key is exactly 32 bytes and different from 0.", ) @@ -101,7 +101,7 @@ impl EcdsaPrivateKey { .as_ecdsa_private_key() .ok_or_else(|| PyValueError::new_err("expected ecdsa private key"))? .clone() - .to_bytes(); + .to_be_bytes(); Ok(PyByteArray::new_bound(py, &bytes).into()) } diff --git a/client-core/src/values/ecdsa_signature.rs b/client-core/src/values/ecdsa_signature.rs index e48e0dc..e51e2cf 100644 --- a/client-core/src/values/ecdsa_signature.rs +++ b/client-core/src/values/ecdsa_signature.rs @@ -82,12 +82,12 @@ impl EcdsaSignature { self.inner.to_string() } - /// Getter for the `r` inside a + /// Getter for the tuple `(r, s)` inside a /// :py:class:`EcdsaSignature` instance. /// /// Returns /// ------- - /// int + /// tuple /// The value of the private ecdsa key. /// /// Example diff --git a/client-core/src/values/eddsa_message.rs b/client-core/src/values/eddsa_message.rs new file mode 100644 index 0000000..3e9beb0 --- /dev/null +++ b/client-core/src/values/eddsa_message.rs @@ -0,0 +1,104 @@ +use nillion_client_core::values::{Clear, NadaValue}; +use pyo3::{exceptions::PyValueError, prelude::*, types::PyByteArray}; + +/// This is a :py:class:`EddsaMessage` class used to +/// encode a secret as a message digest. +/// +/// Arguments +/// --------- +/// value : bytearray +/// Value of the secret message digest as a `bytearray`. +/// +/// Returns +/// ------- +/// EddsaMessage +/// Instance of the :py:class:`EddsaMessage` class. +/// +/// Raises +/// ------- +/// VTypeError: argument 'value' +/// Raises an error when a non-bytearray object is provided. +/// +/// Example +/// ------- +/// +/// .. code-block:: py3 +/// +/// import py_nillion_client as nillion +/// +/// gm_blob_ba = bytearray("gm, builder!", "utf-8") +/// gm_blob = nillion.EcdsaDigestMessage(gm_blob_ba) +/// ready_blob_ba = bytearray("ready to build!", "utf-8") +/// ready_blob = nillion.EcdsaDigestMessage(ready_blob_ba) +/// +/// print("Are these blobs the same?", gm_blob == ready_blob) +/// +/// .. code-block:: text +/// +/// >>> Are these blobs the same? False +#[pyclass(eq)] +#[derive(PartialEq, Clone)] +pub struct EddsaMessage { + pub(crate) inner: NadaValue, +} + +impl TryFrom> for EddsaMessage { + type Error = PyErr; + + fn try_from(value: NadaValue) -> Result { + value + .is_eddsa_message() + .then(|| EddsaMessage { inner: value }) + .ok_or_else(|| PyValueError::new_err("expected eddsa message")) + } +} + +#[pymethods] +impl EddsaMessage { + /// Returns a new EddsaMessage. + #[new] + fn new(value: &Bound<'_, PyByteArray>) -> PyResult { + let eddsa_message = value.to_vec(); + + Ok(EddsaMessage { inner: NadaValue::new_eddsa_message(eddsa_message) }) + } + + /// Getter and setter for the `value` inside a + /// :py:class:`EddsaMessage` instance. + /// + /// Returns + /// ------- + /// int + /// The value of the secret message digest. + /// + /// Example + /// ------- + /// + /// .. code-block:: py3 + /// + /// gm_blob_ba = bytearray("gm, builder!", "utf-8") + /// blob = nillion.EddsaMessage(gm_blob_ba) + /// print("Blob is: ", blob.value) + /// ready_blob_ba = bytearray("ready to build!", "utf-8") + /// blob.value = ready_blob_ba + /// print("Blob is now: ", blob.value) + /// + /// .. code-block:: text + /// + /// >>> Blob is: bytearray(b'gm, builder!') + /// >>> Blob is now: bytearray(b'ready to build!') + #[getter] + fn get_value(&self, py: Python<'_>) -> PyResult> { + Ok(PyByteArray::new_bound( + py, + self.inner.as_eddsa_message().ok_or_else(|| PyValueError::new_err("expected eddsa message"))?, + ) + .into()) + } + + #[setter] + fn set_value(&mut self, value: &Bound<'_, PyByteArray>) -> PyResult<()> { + *self = Self::new(value)?; + Ok(()) + } +} diff --git a/client-core/src/values/eddsa_private_key.rs b/client-core/src/values/eddsa_private_key.rs new file mode 100644 index 0000000..3e785e8 --- /dev/null +++ b/client-core/src/values/eddsa_private_key.rs @@ -0,0 +1,117 @@ +use nillion_client_core::{ + privatekey, + values::{Clear, NadaValue}, +}; +use pyo3::{exceptions::PyValueError, prelude::*, types::PyByteArray}; + +/// This is a :py:class:`EddsaPrivateKey` class used to +/// encode a secret bytearray as an eddsa private key. +/// +/// Arguments +/// --------- +/// value : bytearray +/// Value of the private eddsa key as a `bytearray`. +/// +/// Returns +/// ------- +/// EddsaPrivateKey +/// Instance of the :py:class:`EddsaPrivateKey` class. +/// +/// Raises +/// ------- +/// VTypeError: argument 'value' +/// Raises an error when a non-bytearray object is provided. +/// +/// Example +/// ------- +/// +/// .. code-block:: py3 +/// +/// from nillion_client import EddsaPrivateKey +/// import os +/// +/// pk1_bytes = bytearray(os.urandom(32)) +/// pk1 = EddsaPrivateKey(pk1_bytes) +/// pk2_bytes = bytearray(os.urandom(32)) +/// pk2 = EddsaPrivateKey(pk2_bytes) +/// +/// print("Are these eddsa private keys the same?", pk1 == pk2) +/// +/// .. code-block:: text +/// +/// >>> Are these eddsa private keys the same? False +#[pyclass(eq)] +#[derive(PartialEq, Clone)] +pub struct EddsaPrivateKey { + pub(crate) inner: NadaValue, +} + +impl TryFrom> for EddsaPrivateKey { + type Error = PyErr; + + fn try_from(value: NadaValue) -> Result { + value + .is_eddsa_private_key() + .then(|| EddsaPrivateKey { inner: value }) + .ok_or_else(|| PyValueError::new_err("expected eddsa private key")) + } +} + +#[pymethods] +impl EddsaPrivateKey { + /// Returns a new EddsaPrivateKey. The byte array should be in big-endian format. + #[new] + fn new(value: &Bound<'_, PyByteArray>) -> PyResult { + let eddsa_private_key = privatekey::ThresholdPrivateKey::from_le_bytes(&value.to_vec()).map_err(|_| { + PyValueError::new_err( + "Private key format error. Check your eddsa secret key is exactly 32 bytes and different from 0.", + ) + })?; + Ok(EddsaPrivateKey { inner: NadaValue::new_eddsa_private_key(eddsa_private_key) }) + } + + /// Getter and setter for the `value` inside a + /// :py:class:`EddsaPrivateKey` instance. + /// + /// Returns + /// ------- + /// int + /// The value of the private eddsa key. + /// + /// Example + /// ------- + /// + /// .. code-block:: py3 + /// + /// ecdas_pk_ba = bytearray(b'these are not random 32 bytes!!!') + /// eddsa_pk = EddsaPrivateKey(eddsa_pk_ba) + /// print("Eddsa private key is: ", eddsa_pk.value) + /// eddsa_pk_ba_prime = bytearray(b'these are good random 32 bytes!!') + /// eddsa_pk.value = eddsa_pk_ba_prime + /// print("Eddsa private key is now: ", eddsa_pk.value) + /// + /// .. code-block:: text + /// + /// >>> Eddsa private key is: bytearray(b'these are not random 32 bytes!!!') + /// >>> Eddsa private key is now: bytearray(b'these are good random 32 bytes!!') + #[getter] + fn get_value(&self, py: Python<'_>) -> PyResult> { + let bytes = self + .inner + .as_eddsa_private_key() + .ok_or_else(|| PyValueError::new_err("expected eddsa private key"))? + .clone() + .to_le_bytes(); + Ok(PyByteArray::new_bound(py, &bytes).into()) + } + + #[setter] + fn set_value(&mut self, value: &Bound<'_, PyByteArray>) -> PyResult<()> { + *self = Self::new(value)?; + Ok(()) + } + + fn __repr__(&self) -> String { + self.inner.to_string() + } +} diff --git a/client-core/src/values/eddsa_public_key.rs b/client-core/src/values/eddsa_public_key.rs new file mode 100644 index 0000000..932da63 --- /dev/null +++ b/client-core/src/values/eddsa_public_key.rs @@ -0,0 +1,111 @@ +use nillion_client_core::values::{Clear, NadaValue}; +use pyo3::{exceptions::PyValueError, prelude::*, types::PyByteArray}; + +/// This is a :py:class:`EddsaPublicKey` class used to +/// encode an eddsa public key. +/// +/// Arguments +/// --------- +/// value : bytearray +/// Value of the eddsa public key as a `bytearray`. +/// +/// Returns +/// ------- +/// EddsaPublicKey +/// Instance of the :py:class:`EddsaPublicKey` class. +/// +/// Raises +/// ------- +/// VTypeError: argument 'value' +/// Raises an error when a non-bytearray object is provided. +/// +/// Example +/// ------- +/// +/// .. code-block:: py3 +/// +/// import py_nillion_client as nillion +/// +/// gm_eddsa_pk_ba = bytearray("gm, builder!", "utf-8") +/// gm_eddsa_pk = nillion.EddsaPublicKey(gm_eddsa_pk_ba) +/// ready_eddsa_pk_ba = bytearray("ready to build!", "utf-8") +/// ready_eddsa_pk = nillion.EddsaPublicKey(ready_eddsa_pk_ba) +/// +/// print("Are these eddsa public keys the same?", gm_eddsa_pk == ready_eddsa_pk) +/// +/// .. code-block:: text +/// +/// >>> Are these eddsa public keys the same? False +#[pyclass(eq)] +#[derive(PartialEq, Clone)] +pub struct EddsaPublicKey { + pub(crate) inner: NadaValue, +} + +impl TryFrom> for EddsaPublicKey { + type Error = PyErr; + + fn try_from(value: NadaValue) -> Result { + value + .is_eddsa_public_key() + .then(|| EddsaPublicKey { inner: value }) + .ok_or_else(|| PyValueError::new_err("expected eddsa public key")) + } +} + +#[pymethods] +impl EddsaPublicKey { + /// Returns a new EddsaPublicKey. + #[new] + fn new(value: &Bound<'_, PyByteArray>) -> PyResult { + let eddsa_public_key = to_32_byte_array(value)?; + + Ok(EddsaPublicKey { inner: NadaValue::new_eddsa_public_key(eddsa_public_key) }) + } + + /// Getter and setter for the `value` inside a + /// :py:class:`EddsaPublicKey` instance. + /// + /// Returns + /// ------- + /// int + /// The value of the eddsa public key. + /// + /// Example + /// ------- + /// + /// .. code-block:: py3 + /// + /// gm_eddsa_pk_ba = bytearray("gm, builder!", "utf-8") + /// eddsa_pk = nillion.EddsaPublicKey(gm_eddsa_pk_ba) + /// print("EddsaPublicKey is: ", eddsa_pk.value) + /// ready_eddsa_pk_ba = bytearray("ready to build!", "utf-8") + /// eddsa_pk.value = ready_eddsa_pk_ba + /// print("EddsaPublicKey is now: ", eddsa_pk.value) + /// + /// .. code-block:: text + /// + /// >>> EddsaPublicKey is: bytearray(b'gm, builder!') + /// >>> EddsaPublicKey is now: bytearray(b'ready to build!') + #[getter] + fn get_value(&self, py: Python<'_>) -> PyResult> { + Ok(PyByteArray::new_bound( + py, + self.inner.as_eddsa_public_key().ok_or_else(|| PyValueError::new_err("expected eddsa public key"))?, + ) + .into()) + } + + #[setter] + fn set_value(&mut self, value: &Bound<'_, PyByteArray>) -> PyResult<()> { + *self = Self::new(value)?; + Ok(()) + } +} + +fn to_32_byte_array(value: &Bound<'_, PyByteArray>) -> PyResult<[u8; 32]> { + let array: [u8; 32] = value.to_vec().try_into().map_err(|_| { + PyErr::new::("Eddsa public key must be exactly 32 bytes long") + })?; + Ok(array) +} diff --git a/client-core/src/values/eddsa_signature.rs b/client-core/src/values/eddsa_signature.rs new file mode 100644 index 0000000..996dde7 --- /dev/null +++ b/client-core/src/values/eddsa_signature.rs @@ -0,0 +1,116 @@ +use nillion_client_core::{ + generic_ec::Scalar, + signature, + values::{Clear, NadaValue}, +}; +use pyo3::{ + exceptions::PyValueError, + prelude::*, + types::{PyByteArray, PyTuple}, +}; + +/// This is a :py:class:`EddsaSignature` class used to +/// encode a secret bytearray as an eddsa private key. +/// +/// Arguments +/// --------- +/// value : bytearray +/// Value of the private eddsa key as a `bytearray`. +/// +/// Returns +/// ------- +/// EddsaSignature +/// Instance of the :py:class:`EddsaSignature` class. +/// +/// Raises +/// ------- +/// VTypeError: argument 'value' +/// Raises an error when a non-bytearray object is provided. +/// +/// Example +/// ------- +/// +/// .. code-block:: py3 +/// +/// from nillion_client import EddsaSignature +/// import os +/// +/// r = bytearray(os.urandom(32)) +/// z = bytearray(os.urandom(32)) +/// sig = EddsaSignature((r, z)) +#[pyclass(eq)] +#[derive(PartialEq, Clone)] +pub struct EddsaSignature { + pub(crate) inner: NadaValue, +} + +impl TryFrom> for EddsaSignature { + type Error = PyErr; + + fn try_from(value: NadaValue) -> Result { + value + .is_eddsa_signature() + .then(|| EddsaSignature { inner: value }) + .ok_or_else(|| PyValueError::new_err("expected eddsa signature")) + } +} + +#[pymethods] +impl EddsaSignature { + /// Returns a new EddsaSignature. The byte arrays corresponding to r and z should be in big-endian format. + #[new] + fn new(value: &Bound<'_, PyTuple>) -> PyResult { + // let (r, z) = value; + if value.len() != 2 { + return Err(PyValueError::new_err("Expected a tuple with exactly two elements.")); + } + + let r_mid = value.get_item(0)?; + let r: &Bound<'_, PyByteArray> = r_mid.downcast::()?; + let s_mid = value.get_item(1)?; + let s: &Bound<'_, PyByteArray> = s_mid.downcast::()?; + + let signature = signature::EddsaSignature::from_components_bytes(&r.to_vec(), &s.to_vec()) + .map_err(|e| PyValueError::new_err(e.to_string()))?; + + Ok(EddsaSignature { inner: NadaValue::new_eddsa_signature(signature) }) + } + + fn __repr__(&self) -> String { + self.inner.to_string() + } + + /// Getter for the tuple `(r, z)` inside a + /// :py:class:`EddsaSignature` instance. + /// + /// Returns + /// ------- + /// tuple + /// The value of the private eddsa key. + /// + /// Example + /// ------- + /// + /// .. code-block:: py3 + /// + /// from nillion_client import EddsaSignature + /// import os + /// + /// r = bytearray(os.urandom(32)) + /// z = bytearray(os.urandom(32)) + /// signature = EddsaSignature((r, z)) + /// print("Eddsa signature is: ", signature.value) + #[getter] + fn get_value(&self, py: Python<'_>) -> PyResult<(Py, Py)> { + let signature::EddsaSignature { signature } = + self.inner.as_eddsa_signature().ok_or_else(|| PyValueError::new_err("expected eddsa signature"))?; + + let r_bytes = signature.r.to_bytes().to_vec(); + let z_bytes = Scalar::to_le_bytes(&signature.z); + + let r_pybytes = PyByteArray::new_bound(py, &r_bytes).into(); + let z_pybytes = PyByteArray::new_bound(py, &z_bytes).into(); + + Ok((r_pybytes, z_pybytes)) + } +} diff --git a/client-core/src/values/mod.rs b/client-core/src/values/mod.rs index 5aa9e2f..7cf9766 100644 --- a/client-core/src/values/mod.rs +++ b/client-core/src/values/mod.rs @@ -6,6 +6,10 @@ use self::{ ecdsa_private_key::EcdsaPrivateKey, ecdsa_public_key::EcdsaPublicKey, ecdsa_signature::EcdsaSignature, + eddsa_message::EddsaMessage, + eddsa_private_key::EddsaPrivateKey, + eddsa_public_key::EddsaPublicKey, + eddsa_signature::EddsaSignature, integer::{Integer, SecretInteger}, store_id::StoreId, unsigned_integer::{SecretUnsignedInteger, UnsignedInteger}, @@ -21,6 +25,10 @@ pub mod ecdsa_digest_message; pub mod ecdsa_private_key; pub mod ecdsa_public_key; pub mod ecdsa_signature; +pub mod eddsa_message; +pub mod eddsa_private_key; +pub mod eddsa_public_key; +pub mod eddsa_signature; pub mod integer; pub mod store_id; pub mod unsigned_integer; @@ -46,6 +54,10 @@ pub(crate) fn nada_value_clear_to_pyobject(py: Python<'_>, value: NadaValue { Array::try_from(NadaValue::Array { values, inner_type })?.into_py(py) } + NadaValue::EddsaPrivateKey(value) => EddsaPrivateKey::try_from(NadaValue::EddsaPrivateKey(value))?.into_py(py), + NadaValue::EddsaPublicKey(value) => EddsaPublicKey::try_from(NadaValue::EddsaPublicKey(value))?.into_py(py), + NadaValue::EddsaSignature(value) => EddsaSignature::try_from(NadaValue::EddsaSignature(value))?.into_py(py), + NadaValue::EddsaMessage(value) => EddsaMessage::try_from(NadaValue::EddsaMessage(value))?.into_py(py), NadaValue::Tuple { .. } | NadaValue::NTuple { .. } | NadaValue::Object { .. } @@ -85,6 +97,14 @@ fn pyany_to_nada_value_clear(value: Bound) -> Result, Py value.inner } else if let Ok(value) = value.extract::() { value.inner + } else if let Ok(value) = value.extract::() { + value.inner + } else if let Ok(value) = value.extract::() { + value.inner + } else if let Ok(value) = value.extract::() { + value.inner + } else if let Ok(value) = value.extract::() { + value.inner } else { Err(PyValueError::new_err("Unsupported NadaValue variant for conversion to PyObject"))? }; @@ -127,5 +147,9 @@ pub fn add_module(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; Ok(()) } diff --git a/client-proto/pyproject.toml b/client-proto/pyproject.toml index 53e8856..a9599e3 100644 --- a/client-proto/pyproject.toml +++ b/client-proto/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "nillion-client-proto" -version = "0.2.1rc1" +version = "0.2.1rc2" description = "Nillion client Protobuf files" license = { text = "MIT" } readme = "README.md" diff --git a/client-proto/src/nillion_client_proto/nillion/compute/v1/stream/__init__.py b/client-proto/src/nillion_client_proto/nillion/compute/v1/stream/__init__.py index b6d1945..7ad97dc 100644 --- a/client-proto/src/nillion_client_proto/nillion/compute/v1/stream/__init__.py +++ b/client-proto/src/nillion_client_proto/nillion/compute/v1/stream/__init__.py @@ -8,6 +8,24 @@ import betterproto +class ComputeType(betterproto.Enum): + """ + The type of compute performed. We currently support three types: + - GENERAL: A general compute that computes some Nada program. + - ECDSA_DKG: A specific compute operation for ECDSA distributed key generation. + - EDDSA_DKG: A specific compute operation for Eddsa distributed key generation. + """ + + GENERAL = 0 + """A general compute.""" + + ECDSA_DKG = 1 + """An ECDSA distributed key generation protocol.""" + + EDDSA_DKG = 2 + """An Eddsa distributed key generation protocol.""" + + @dataclass(eq=False, repr=False) class ComputeStreamMessage(betterproto.Message): """A message for a compute stream.""" @@ -22,3 +40,6 @@ class ComputeStreamMessage(betterproto.Message): bincode_message: bytes = betterproto.bytes_field(2) """The VM message in bincode format.""" + + compute_type: "ComputeType" = betterproto.enum_field(3) + """The type of compute.""" diff --git a/client-proto/src/nillion_client_proto/nillion/payments/v1/config/__init__.py b/client-proto/src/nillion_client_proto/nillion/payments/v1/config/__init__.py index 483eb40..fb3f91b 100644 --- a/client-proto/src/nillion_client_proto/nillion/payments/v1/config/__init__.py +++ b/client-proto/src/nillion_client_proto/nillion/payments/v1/config/__init__.py @@ -16,3 +16,6 @@ class PaymentsConfigResponse(betterproto.Message): """ The minimum amount of unil that can be added in a `Payments.add_funds` request. """ + + credits_per_nil: int = betterproto.uint64_field(2) + """The number of credits one gets for every nil funded to an account.""" diff --git a/client-proto/src/nillion_client_proto/nillion/values/v1/value/__init__.py b/client-proto/src/nillion_client_proto/nillion/values/v1/value/__init__.py index 47b0a3d..e2b1dc3 100644 --- a/client-proto/src/nillion_client_proto/nillion/values/v1/value/__init__.py +++ b/client-proto/src/nillion_client_proto/nillion/values/v1/value/__init__.py @@ -77,6 +77,20 @@ class Value(betterproto.Message): store_id: "StoreId" = betterproto.message_field(14, group="value") """A store id.""" + eddsa_private_key_share: "EddsaPrivateKeyShare" = betterproto.message_field( + 15, group="value" + ) + """An Eddsa private key share.""" + + eddsa_signature: "EddsaSignature" = betterproto.message_field(16, group="value") + """An Eddsa signature.""" + + eddsa_message: "EddsaMessage" = betterproto.message_field(17, group="value") + """An Eddsa message.""" + + eddsa_public_key: "EddsaPublicKey" = betterproto.message_field(18, group="value") + """An Eddsa public key.""" + @dataclass(eq=False, repr=False) class PublicInteger(betterproto.Message): @@ -161,7 +175,7 @@ class EcdsaPublicKey(betterproto.Message): """An ECDSA public key.""" public_key: bytes = betterproto.bytes_field(1) - """The public key, in compressed form.""" + """The public key.""" @dataclass(eq=False, repr=False) @@ -172,6 +186,47 @@ class StoreId(betterproto.Message): """The store id.""" +@dataclass(eq=False, repr=False) +class EddsaPrivateKeyShare(betterproto.Message): + """An Eddsa private key share.""" + + i: int = betterproto.uint32_field(1) + """Index of local party in key generation protocol.""" + + x: bytes = betterproto.bytes_field(2) + """The secret share x.""" + + shared_public_key: bytes = betterproto.bytes_field(3) + """Public key corresponding to shared secret key, in compressed form.""" + + public_shares: List[bytes] = betterproto.bytes_field(4) + """Public shares of all signers sharing the key, in compressed form.""" + + +@dataclass(eq=False, repr=False) +class EddsaSignature(betterproto.Message): + """An Eddsa signature.""" + + signature: bytes = betterproto.bytes_field(1) + """The signature.""" + + +@dataclass(eq=False, repr=False) +class EddsaMessage(betterproto.Message): + """An Eddsa message.""" + + message: bytes = betterproto.bytes_field(1) + """The message.""" + + +@dataclass(eq=False, repr=False) +class EddsaPublicKey(betterproto.Message): + """An Eddsa public key.""" + + public_key: bytes = betterproto.bytes_field(1) + """The public key.""" + + @dataclass(eq=False, repr=False) class ShamirSharesBlob(betterproto.Message): """Shamir shares of a blob.""" @@ -248,6 +303,26 @@ class ValueType(betterproto.Message): ) """A store id.""" + eddsa_private_key_share: "betterproto_lib_google_protobuf.Empty" = ( + betterproto.message_field(14, group="value_type") + ) + """An Eddsa private key share.""" + + eddsa_signature: "betterproto_lib_google_protobuf.Empty" = ( + betterproto.message_field(15, group="value_type") + ) + """An Eddsa signature.""" + + eddsa_message: "betterproto_lib_google_protobuf.Empty" = betterproto.message_field( + 16, group="value_type" + ) + """An Eddsa message.""" + + eddsa_public_key: "betterproto_lib_google_protobuf.Empty" = ( + betterproto.message_field(17, group="value_type") + ) + """An Eddsa public key.""" + @dataclass(eq=False, repr=False) class ArrayType(betterproto.Message): diff --git a/nilvm b/nilvm index c92a3b2..ead1c20 160000 --- a/nilvm +++ b/nilvm @@ -1 +1 @@ -Subproject commit c92a3b2c3821f25915eb280513f402b3e04f18ff +Subproject commit ead1c20c9448b1b60af7ca8c10b4cb70a67ea2a3 diff --git a/patch.txt b/patch.txt new file mode 100644 index 0000000..253eeff --- /dev/null +++ b/patch.txt @@ -0,0 +1,1892 @@ +diff --git a/.nil-sdk.toml b/.nil-sdk.toml +index 7dafb23..1d6ec7b 100644 +--- a/.nil-sdk.toml ++++ b/.nil-sdk.toml +@@ -1 +1 @@ +-version = "v0.9.0-rc.62" ++version = "v0.10.0-rc.23" +\ No newline at end of file +diff --git a/client-core/Cargo.lock b/client-core/Cargo.lock +index 09d5d29..efb99dd 100644 +--- a/client-core/Cargo.lock ++++ b/client-core/Cargo.lock +@@ -39,12 +39,18 @@ version = "0.2.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + ++[[package]] ++name = "base64" ++version = "0.13.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" ++ + [[package]] + name = "basic-types" + version = "0.1.0" + dependencies = [ + "hex", +- "thiserror", ++ "thiserror 1.0.68", + "uuid", + ] + +@@ -96,6 +102,37 @@ version = "1.0.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + ++[[package]] ++name = "cggmp21-keygen" ++version = "0.5.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "aaa8c850290c494f951abe0350e56c31e4f5664863490197490ff48cb825447d" ++dependencies = [ ++ "digest", ++ "displaydoc", ++ "generic-ec", ++ "generic-ec-zkp", ++ "hex", ++ "key-share", ++ "rand_core", ++ "round-based", ++ "serde", ++ "serde_with", ++ "sha2", ++ "thiserror 1.0.68", ++ "udigest", ++] ++ ++[[package]] ++name = "chrono" ++version = "0.4.40" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" ++dependencies = [ ++ "num-traits", ++ "serde", ++] ++ + [[package]] + name = "cmake" + version = "0.1.52" +@@ -162,6 +199,8 @@ dependencies = [ + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", ++ "group", ++ "rand_core", + "rustc_version", + "subtle", + "zeroize", +@@ -178,6 +217,41 @@ dependencies = [ + "syn 2.0.87", + ] + ++[[package]] ++name = "darling" ++version = "0.20.10" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" ++dependencies = [ ++ "darling_core", ++ "darling_macro", ++] ++ ++[[package]] ++name = "darling_core" ++version = "0.20.10" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" ++dependencies = [ ++ "fnv", ++ "ident_case", ++ "proc-macro2", ++ "quote", ++ "strsim", ++ "syn 2.0.87", ++] ++ ++[[package]] ++name = "darling_macro" ++version = "0.20.10" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" ++dependencies = [ ++ "darling_core", ++ "quote", ++ "syn 2.0.87", ++] ++ + [[package]] + name = "der" + version = "0.7.9" +@@ -188,6 +262,15 @@ dependencies = [ + "zeroize", + ] + ++[[package]] ++name = "deranged" ++version = "0.3.11" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" ++dependencies = [ ++ "powerfmt", ++] ++ + [[package]] + name = "digest" + version = "0.10.7" +@@ -196,6 +279,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" + dependencies = [ + "block-buffer", + "crypto-common", ++ "subtle", + ] + + [[package]] +@@ -219,17 +303,6 @@ dependencies = [ + "proc-macro-error", + ] + +-[[package]] +-name = "ecdsa-keypair" +-version = "0.1.0" +-dependencies = [ +- "generic-ec", +- "key-share", +- "rand", +- "subtle", +- "thiserror", +-] +- + [[package]] + name = "educe" + version = "0.4.23" +@@ -347,12 +420,50 @@ version = "0.4.2" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + ++[[package]] ++name = "fnv" ++version = "1.0.7" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" ++ ++[[package]] ++name = "futures-core" ++version = "0.3.31" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" ++ ++[[package]] ++name = "futures-sink" ++version = "0.3.31" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" ++ ++[[package]] ++name = "futures-task" ++version = "0.3.31" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" ++ ++[[package]] ++name = "futures-util" ++version = "0.3.31" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" ++dependencies = [ ++ "futures-core", ++ "futures-sink", ++ "futures-task", ++ "pin-project-lite", ++ "pin-utils", ++] ++ + [[package]] + name = "generic-array" + version = "0.14.7" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" + dependencies = [ ++ "serde", + "typenum", + "version_check", + "zeroize", +@@ -365,12 +476,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "50e3268b3f97e2046ebf69e24a1e7e12dad4dec247d87f59268fd57ab514b5f8" + dependencies = [ + "curve25519-dalek", ++ "digest", + "generic-ec-core", + "generic-ec-curves", + "hex", +- "phantom-type", ++ "phantom-type 0.4.2", + "rand_core", ++ "rand_hash", ++ "serde", ++ "serde_with", + "subtle", ++ "udigest", + "zeroize", + ] + +@@ -382,6 +498,7 @@ checksum = "049128cc67cac6176ada5218e294ce46421470d92a7340c93d5cfd3ecfbc29a4" + dependencies = [ + "generic-array", + "rand_core", ++ "serde", + "subtle", + "zeroize", + ] +@@ -392,8 +509,10 @@ version = "0.2.2" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "1e663405a17b229dede990904edcfd2056cf6641f1240869a1f44fa13bd4ee4d" + dependencies = [ ++ "curve25519-dalek", + "elliptic-curve", + "generic-ec-core", ++ "group", + "k256", + "rand_core", + "sha2", +@@ -426,6 +545,24 @@ dependencies = [ + "wasi", + ] + ++[[package]] ++name = "givre" ++version = "0.2.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "af482004b1f70b5b3b584c3fc3733dd24c71f8c0081a6087f49e09746746788a" ++dependencies = [ ++ "cggmp21-keygen", ++ "digest", ++ "generic-ec", ++ "hd-wallet", ++ "k256", ++ "key-share", ++ "rand_core", ++ "serde", ++ "sha2", ++ "static_assertions", ++] ++ + [[package]] + name = "group" + version = "0.13.0" +@@ -443,6 +580,19 @@ version = "0.15.1" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" + ++[[package]] ++name = "hd-wallet" ++version = "0.6.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "6522551bb35937363845f39a6d4c49e60bdb35a8f7154ebdd078cab50be97992" ++dependencies = [ ++ "generic-array", ++ "generic-ec", ++ "hmac", ++ "sha2", ++ "subtle", ++] ++ + [[package]] + name = "heck" + version = "0.4.1" +@@ -460,6 +610,24 @@ name = "hex" + version = "0.4.3" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" ++dependencies = [ ++ "serde", ++] ++ ++[[package]] ++name = "hmac" ++version = "0.12.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" ++dependencies = [ ++ "digest", ++] ++ ++[[package]] ++name = "ident_case" ++version = "1.0.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + + [[package]] + name = "indexmap" +@@ -487,6 +655,12 @@ dependencies = [ + "either", + ] + ++[[package]] ++name = "itoa" ++version = "1.0.14" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" ++ + [[package]] + name = "jit-compiler" + version = "0.1.0" +@@ -500,7 +674,7 @@ dependencies = [ + "nada-compiler-backend", + "nada-type", + "substring", +- "thiserror", ++ "thiserror 1.0.68", + ] + + [[package]] +@@ -515,14 +689,18 @@ dependencies = [ + + [[package]] + name = "key-share" +-version = "0.5.0" ++version = "0.6.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "8ea364cb2397405d8c79afd3de173ca7e2e1d83a4ddd94d359263480ad96f06f" ++checksum = "3ee8e510bb9f738ac400b7dedd98aeb23677c6b48cd3b1b682651ea5091f4282" + dependencies = [ + "displaydoc", + "generic-ec", + "generic-ec-zkp", ++ "hex", + "rand_core", ++ "serde", ++ "serde_with", ++ "thiserror 1.0.68", + ] + + [[package]] +@@ -547,14 +725,13 @@ checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" + name = "math_lib" + version = "0.1.0" + dependencies = [ +- "basic-types", + "crypto-bigint", + "num-bigint", + "num-traits", + "paste", + "rand", + "subtle", +- "thiserror", ++ "thiserror 1.0.68", + ] + + [[package]] +@@ -583,7 +760,7 @@ dependencies = [ + "serde", + "serde_repr", + "substring", +- "thiserror", ++ "thiserror 1.0.68", + ] + + [[package]] +@@ -601,12 +778,12 @@ name = "mpc-vm" + version = "0.1.0" + dependencies = [ + "anyhow", +- "ecdsa-keypair", + "generic-ec", + "jit-compiler", + "nada-compiler-backend", + "nada-value", + "strum", ++ "threshold-keypair", + ] + + [[package]] +@@ -621,12 +798,11 @@ version = "0.1.0" + dependencies = [ + "anyhow", + "ariadne", +- "basic-types", + "duplicate", + "mir-model", + "nada-value", + "num-bigint", +- "thiserror", ++ "thiserror 1.0.68", + ] + + [[package]] +@@ -638,7 +814,7 @@ dependencies = [ + "serde", + "strum", + "strum_macros", +- "thiserror", ++ "thiserror 1.0.68", + "types-proc-macros", + ] + +@@ -648,9 +824,9 @@ version = "0.1.0" + dependencies = [ + "anyhow", + "basic-types", +- "ecdsa-keypair", + "enum-as-inner", + "generic-ec", ++ "givre", + "indexmap", + "key-share", + "math_lib", +@@ -659,7 +835,8 @@ dependencies = [ + "num-traits", + "shamir-sharing", + "strum_macros", +- "thiserror", ++ "thiserror 1.0.68", ++ "threshold-keypair", + "types-proc-macros", + ] + +@@ -668,13 +845,13 @@ name = "nillion-client-core" + version = "0.1.0" + dependencies = [ + "basic-types", +- "ecdsa-keypair", + "key-share", + "math_lib", + "mpc-vm", + "nada-value", + "program-auditor", + "shamir-sharing", ++ "threshold-keypair", + ] + + [[package]] +@@ -696,6 +873,12 @@ dependencies = [ + "num-traits", + ] + ++[[package]] ++name = "num-conv" ++version = "0.1.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" ++ + [[package]] + name = "num-integer" + version = "0.1.46" +@@ -736,6 +919,15 @@ dependencies = [ + "indexmap", + ] + ++[[package]] ++name = "phantom-type" ++version = "0.3.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "f710afd11c9711b04f97ab61bb9747d5a04562fdf0f9f44abc3de92490084982" ++dependencies = [ ++ "educe", ++] ++ + [[package]] + name = "phantom-type" + version = "0.4.2" +@@ -745,12 +937,30 @@ dependencies = [ + "educe", + ] + ++[[package]] ++name = "pin-project-lite" ++version = "0.2.16" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" ++ ++[[package]] ++name = "pin-utils" ++version = "0.1.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" ++ + [[package]] + name = "portable-atomic" + version = "1.9.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + ++[[package]] ++name = "powerfmt" ++version = "0.2.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" ++ + [[package]] + name = "ppv-lite86" + version = "0.2.20" +@@ -810,7 +1020,7 @@ dependencies = [ + "anyhow", + "mpc-vm", + "nada-compiler-backend", +- "thiserror", ++ "thiserror 1.0.68", + ] + + [[package]] +@@ -977,6 +1187,17 @@ dependencies = [ + "getrandom", + ] + ++[[package]] ++name = "rand_hash" ++version = "0.1.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "16bc1dd921383c6564eb0b8252f5b3f6622b84d40c6e35f5e6790e1fd7abb7a9" ++dependencies = [ ++ "digest", ++ "rand_core", ++ "udigest", ++] ++ + [[package]] + name = "regex" + version = "1.11.1" +@@ -1006,6 +1227,30 @@ version = "0.8.5" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + ++[[package]] ++name = "round-based" ++version = "0.4.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "da76edf50de0a9d6911fc79261bb04cc9f3f3a375e0201799f5edf58499af341" ++dependencies = [ ++ "futures-util", ++ "phantom-type 0.3.1", ++ "round-based-derive", ++ "thiserror 2.0.11", ++ "tracing", ++] ++ ++[[package]] ++name = "round-based-derive" ++version = "0.2.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "4afa4d5b318bcafae8a7ebc57c1cb7d4b2db7358293e34d71bfd605fd327cc13" ++dependencies = [ ++ "proc-macro2", ++ "quote", ++ "syn 1.0.109", ++] ++ + [[package]] + name = "rustc-hash" + version = "2.1.0" +@@ -1040,6 +1285,12 @@ version = "1.0.18" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + ++[[package]] ++name = "ryu" ++version = "1.0.19" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" ++ + [[package]] + name = "sec1" + version = "0.7.3" +@@ -1079,6 +1330,18 @@ dependencies = [ + "syn 2.0.87", + ] + ++[[package]] ++name = "serde_json" ++version = "1.0.139" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" ++dependencies = [ ++ "itoa", ++ "memchr", ++ "ryu", ++ "serde", ++] ++ + [[package]] + name = "serde_repr" + version = "0.1.19" +@@ -1090,6 +1353,33 @@ dependencies = [ + "syn 2.0.87", + ] + ++[[package]] ++name = "serde_with" ++version = "2.3.3" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" ++dependencies = [ ++ "base64", ++ "chrono", ++ "hex", ++ "serde", ++ "serde_json", ++ "serde_with_macros", ++ "time", ++] ++ ++[[package]] ++name = "serde_with_macros" ++version = "2.3.3" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" ++dependencies = [ ++ "darling", ++ "proc-macro2", ++ "quote", ++ "syn 2.0.87", ++] ++ + [[package]] + name = "sha2" + version = "0.10.8" +@@ -1110,7 +1400,7 @@ dependencies = [ + "math_lib", + "rand", + "rustc-hash", +- "thiserror", ++ "thiserror 1.0.68", + ] + + [[package]] +@@ -1119,6 +1409,18 @@ version = "1.3.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + ++[[package]] ++name = "static_assertions" ++version = "1.1.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" ++ ++[[package]] ++name = "strsim" ++version = "0.11.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" ++ + [[package]] + name = "strum" + version = "0.26.3" +@@ -1204,7 +1506,16 @@ version = "1.0.68" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" + dependencies = [ +- "thiserror-impl", ++ "thiserror-impl 1.0.68", ++] ++ ++[[package]] ++name = "thiserror" ++version = "2.0.11" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" ++dependencies = [ ++ "thiserror-impl 2.0.11", + ] + + [[package]] +@@ -1218,6 +1529,64 @@ dependencies = [ + "syn 2.0.87", + ] + ++[[package]] ++name = "thiserror-impl" ++version = "2.0.11" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" ++dependencies = [ ++ "proc-macro2", ++ "quote", ++ "syn 2.0.87", ++] ++ ++[[package]] ++name = "threshold-keypair" ++version = "0.1.0" ++dependencies = [ ++ "generic-ec", ++ "givre", ++ "key-share", ++ "rand", ++ "subtle", ++ "thiserror 1.0.68", ++] ++ ++[[package]] ++name = "time" ++version = "0.3.37" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" ++dependencies = [ ++ "deranged", ++ "num-conv", ++ "powerfmt", ++ "serde", ++ "time-core", ++] ++ ++[[package]] ++name = "time-core" ++version = "0.1.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" ++ ++[[package]] ++name = "tracing" ++version = "0.1.41" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" ++dependencies = [ ++ "pin-project-lite", ++ "tracing-core", ++] ++ ++[[package]] ++name = "tracing-core" ++version = "0.1.33" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" ++ + [[package]] + name = "typenum" + version = "1.17.0" +@@ -1240,6 +1609,7 @@ version = "0.2.2" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "81cd61fa9fb78569e9fe34acf0048fd8cb9ebdbacc47af740745487287043ff0" + dependencies = [ ++ "digest", + "udigest-derive", + ] + +diff --git a/client-core/nillion_client_core.pyi b/client-core/nillion_client_core.pyi +index 8a05264..302472f 100644 +--- a/client-core/nillion_client_core.pyi ++++ b/client-core/nillion_client_core.pyi +@@ -14,6 +14,10 @@ NadaValue = Union[ + EcdsaSignature, + EcdsaPublicKey, + StoreId, ++ EddsaPrivateKey, ++ EddsaPublicKey, ++ EddsaSignature, ++ EddsaMessage, + ] + + class SecretUnsignedInteger: +@@ -139,6 +143,42 @@ class StoreId: + def __eq__(self, other: object) -> bool: ... + def __repr__(self) -> str: ... + ++class EddsaPrivateKey: ++ """Encodes a secret as an eddsa private key.""" ++ ++ value: bytearray ++ ++ def __init__(self, value: bytearray) -> None: ... ++ def __eq__(self, other: object) -> bool: ... ++ def __repr__(self) -> str: ... ++ ++class EddsaPublicKey: ++ """Encodes an eddsa public key.""" ++ ++ value: bytearray ++ ++ def __init__(self, value: bytearray) -> None: ... ++ def __eq__(self, other: object) -> bool: ... ++ def __repr__(self) -> str: ... ++ ++class EddsaSignature: ++ """Encodes an eddsa signature.""" ++ ++ value: Tuple[bytearray, bytearray] ++ ++ def __init__(self, value: Tuple[bytearray, bytearray]) -> None: ... ++ def __eq__(self, other: object) -> bool: ... ++ def __repr__(self) -> str: ... ++ ++class EddsaMessage: ++ """Encodes an eddsa message.""" ++ ++ value: bytearray ++ ++ def __init__(self, value: bytearray) -> None: ... ++ def __eq__(self, other: object) -> bool: ... ++ def __repr__(self) -> str: ... ++ + class ProgramRequirements: + """A program preprocessing requirements""" + +@@ -205,6 +245,9 @@ class NadaValuesClassification: + ecdsa_private_key_shares: int + """The number of ecdsa private key shares.""" + ++ ecdsa_signature_shares: int ++ """The number of ecdsa signature shares.""" ++ + class SecretMasker: + """A secret masker. This allows masking and unmasking secrets.""" + +diff --git a/client-core/src/encrypted_value.rs b/client-core/src/encrypted_value.rs +index de1181c..6c9ac62 100644 +--- a/client-core/src/encrypted_value.rs ++++ b/client-core/src/encrypted_value.rs +@@ -1,10 +1,11 @@ + use nillion_client_core::{ +- generic_ec::{curves::Secp256k1, serde::CurveName, NonZero, Point, Scalar, SecretScalar}, ++ generic_ec::{serde::CurveName, Curve, NonZero, Point, Scalar, SecretScalar}, + key_share::{DirtyCoreKeyShare, DirtyKeyInfo, Validate}, +- privatekey::EcdsaPrivateKeyShare, +- signature::EcdsaSignatureShare, ++ privatekey::ThresholdPrivateKeyShare, ++ signature::{EcdsaSignatureShare, EddsaSignature}, + values::{BlobPrimitiveType, Encoded, EncodedModularNumber, EncodedModulo, Encrypted, NadaValue}, + }; ++ + use pyo3::{ + exceptions::PyValueError, + pyclass, pymethods, +@@ -36,6 +37,10 @@ pub enum EncryptedNadaValue { + EcdsaPrivateKey { i: u16, x: Vec, shared_public_key: Vec, public_shares: Vec> }, + EcdsaPublicKey { value: Vec }, + StoreId { value: Vec }, ++ EddsaPrivateKey { i: u16, x: Vec, shared_public_key: Vec, public_shares: Vec> }, ++ EddsaPublicKey { value: Vec }, ++ EddsaSignature { value: Vec }, ++ EddsaMessage { value: Vec }, + } + + impl EncryptedNadaValue { +@@ -76,6 +81,22 @@ impl EncryptedNadaValue { + } + } + NadaValue::StoreId(value) => Self::StoreId { value: value.to_vec() }, ++ NadaValue::EddsaPrivateKey(key) => { ++ let key = key.into_inner(); ++ Self::EddsaPrivateKey { ++ i: key.i, ++ x: key.x.clone().into_inner().as_ref().to_le_bytes().to_vec(), ++ shared_public_key: key.key_info.shared_public_key.to_bytes(true).to_vec(), ++ public_shares: key.key_info.public_shares.iter().map(|s| s.to_bytes(true).to_vec()).collect(), ++ } ++ } ++ NadaValue::EddsaPublicKey(value) => Self::EddsaPublicKey { value: value.to_vec() }, ++ NadaValue::EddsaSignature(signature) => { ++ let mut out = vec![0u8; signature.serialized_len()]; ++ signature.signature.write_to_slice(&mut out); ++ Self::EddsaSignature { value: out } ++ } ++ NadaValue::EddsaMessage(message) => Self::EddsaMessage { value: message }, + _ => Err(PyValueError::new_err("Unsupported NadaValue variant for conversion to PyObject"))?, + }; + Ok(value) +@@ -153,8 +174,38 @@ impl EncryptedNadaValue { + } + .validate() + .map_err(|e| PyValueError::new_err(e.to_string()))?; +- NadaValue::new_ecdsa_private_key(EcdsaPrivateKeyShare::new(share)) ++ NadaValue::new_ecdsa_private_key(ThresholdPrivateKeyShare::new(share)) ++ } ++ E::EddsaPrivateKey { i, x, shared_public_key, public_shares } => { ++ let share = DirtyCoreKeyShare { ++ i, ++ key_info: DirtyKeyInfo { ++ curve: CurveName::new(), ++ shared_public_key: non_zero_point_from_bytes(&shared_public_key)?, ++ public_shares: public_shares ++ .iter() ++ .map(|s| non_zero_point_from_bytes(s)) ++ .collect::>()?, ++ vss_setup: None, ++ }, ++ x: non_zero_secret_scalar_from_bytes(&x)?, ++ } ++ .validate() ++ .map_err(|e| PyValueError::new_err(e.to_string()))?; ++ NadaValue::new_eddsa_private_key(ThresholdPrivateKeyShare::new(share)) ++ } ++ E::EddsaPublicKey { value } => { ++ let value: [u8; 32] = ++ value.try_into().map_err(|_| PyValueError::new_err("invalid public key length"))?; ++ NadaValue::new_eddsa_public_key(value) ++ } ++ E::EddsaSignature { value } => { ++ let signature = EddsaSignature::from_bytes(&value) ++ .map_err(|_| PyValueError::new_err("Failed to deserialize EdDSA signature"))?; ++ ++ NadaValue::new_eddsa_signature(signature) + } ++ E::EddsaMessage { value } => NadaValue::new_eddsa_message(value), + }; + Ok(value) + } +@@ -180,16 +231,20 @@ impl EncryptedNadaValue { + Self::EcdsaPrivateKey { .. } => "EcdsaPrivateKey {..}".into(), + Self::EcdsaPublicKey { .. } => "EcdsaPublicKey {..}".into(), + Self::StoreId { .. } => "StoreId {..}".into(), ++ Self::EddsaPrivateKey { .. } => "EddsaPrivateKey {..}".into(), ++ Self::EddsaPublicKey { .. } => "EddsaPublicKey {..}".into(), ++ Self::EddsaSignature { .. } => "EddsaSignature {..}".into(), ++ Self::EddsaMessage { .. } => "EddsaMessage {..}".into(), + } + } + } + +-fn non_zero_point_from_bytes(bytes: &[u8]) -> PyResult>> { ++fn non_zero_point_from_bytes(bytes: &[u8]) -> PyResult>> { + let point = Point::from_bytes(bytes).map_err(|_| PyValueError::new_err("invalid bytes"))?; + NonZero::from_point(point).ok_or_else(|| PyValueError::new_err("point is zero")) + } + +-fn non_zero_secret_scalar_from_bytes(bytes: &[u8]) -> PyResult>> { ++fn non_zero_secret_scalar_from_bytes(bytes: &[u8]) -> PyResult>> { + let scalar = SecretScalar::from_le_bytes(bytes).map_err(|_| PyValueError::new_err("invalid bytes"))?; + NonZero::from_secret_scalar(scalar).ok_or_else(|| PyValueError::new_err("scalar is zero")) + } +@@ -211,6 +266,10 @@ pub enum EncryptedNadaType { + EcdsaPrivateKey(), + EcdsaPublicKey(), + StoreId(), ++ EddsaPrivateKey(), ++ EddsaPublicKey(), ++ EddsaSignature(), ++ EddsaMessage(), + } + + impl EncryptedNadaType { +@@ -236,6 +295,10 @@ impl EncryptedNadaType { + T::EcdsaSignature => Self::EcdsaSignature(), + T::EcdsaPublicKey => Self::EcdsaPublicKey(), + T::StoreId => Self::StoreId(), ++ T::EddsaPrivateKey => Self::EddsaPrivateKey(), ++ T::EddsaPublicKey => Self::EddsaPublicKey(), ++ T::EddsaSignature => Self::EddsaSignature(), ++ T::EddsaMessage => Self::EddsaMessage(), + T::SecretInteger | T::SecretUnsignedInteger | T::SecretBoolean | T::NTuple { .. } | T::Object { .. } => { + return Err(PyValueError::new_err(format!("unsupported type: {t}",))); + } +@@ -270,6 +333,10 @@ impl EncryptedNadaType { + EncryptedNadaType::EcdsaPrivateKey() => T::EcdsaPrivateKey, + EncryptedNadaType::EcdsaPublicKey() => T::EcdsaPublicKey, + EncryptedNadaType::StoreId() => T::StoreId, ++ EncryptedNadaType::EddsaPrivateKey() => T::EddsaPrivateKey, ++ EncryptedNadaType::EddsaPublicKey() => T::EddsaPublicKey, ++ EncryptedNadaType::EddsaSignature() => T::EddsaSignature, ++ EncryptedNadaType::EddsaMessage() => T::EddsaMessage, + }; + Ok(output) + } +@@ -293,6 +360,10 @@ impl EncryptedNadaType { + Self::EcdsaPrivateKey { .. } => "EcdsaPrivateKey {..}".into(), + Self::EcdsaPublicKey { .. } => "EcdsaPublicKey {..}".into(), + Self::StoreId { .. } => "StoreId {..}".into(), ++ Self::EddsaPrivateKey { .. } => "EddsaPrivateKey {..}".into(), ++ Self::EddsaPublicKey { .. } => "EddsaPublicKey {..}".into(), ++ Self::EddsaSignature { .. } => "EddsaSignature {..}".into(), ++ Self::EddsaMessage { .. } => "EddsaMessage {..}".into(), + } + } + } +diff --git a/client-core/src/lib.rs b/client-core/src/lib.rs +index ff91bfe..9b1341a 100644 +--- a/client-core/src/lib.rs ++++ b/client-core/src/lib.rs +@@ -118,6 +118,9 @@ struct NadaValuesClassification { + + /// The number of ecdsa key shares + ecdsa_private_key_shares: u64, ++ ++ /// The number of ecdsa signatures shares ++ ecdsa_signature_shares: u64, + } + + #[pymethods] +@@ -132,7 +135,12 @@ impl NadaValuesClassification { + + impl From<::nillion_client_core::values::NadaValuesClassification> for NadaValuesClassification { + fn from(value: ::nillion_client_core::values::NadaValuesClassification) -> Self { +- Self { shares: value.shares, public: value.public, ecdsa_private_key_shares: value.ecdsa_private_key_shares } ++ Self { ++ shares: value.shares, ++ public: value.public, ++ ecdsa_private_key_shares: value.ecdsa_private_key_shares, ++ ecdsa_signature_shares: value.ecdsa_signature_shares, ++ } + } + } + +diff --git a/client-core/src/secrets_tests.rs b/client-core/src/secrets_tests.rs +index 5b8364d..14fa7da 100644 +--- a/client-core/src/secrets_tests.rs ++++ b/client-core/src/secrets_tests.rs +@@ -253,3 +253,114 @@ assert store_id.value == store_id_bytes # Compare against the same bytes + .unwrap(); + }) + } ++ ++#[test] ++fn test_eddsa_private_key() { ++ Python::with_gil(|py| { ++ Python::run_bound( ++ py, ++ r#" ++from nillion_client_core import * ++ ++eddsa_pk_ba = bytearray([84, 104, 105, 115, 32, 109, 101, 115, 115, 97, 103, 101, 32, 105, 115, 32, 101, 120, 97, 99, 116, 108, 121, 32, 51, 50, 32, 98, 121, 116, 101, 0]) ++eddsa_pk = EddsaPrivateKey(eddsa_pk_ba) ++assert eddsa_pk.value == eddsa_pk_ba ++"#, ++ None, ++ None, ++ ) ++ .unwrap(); ++ }) ++} ++ ++#[test] ++fn test_bad_eddsa_private_key() { ++ Python::with_gil(|py| { ++ Python::run_bound( ++ py, ++ r#" ++from nillion_client_core import * ++import os ++ ++# Check eddsa private key creation fails with bytearray size different from 32 ++try: ++ eddsa_pk_ba = bytearray(os.urandom(33)) ++ eddsa_pk = EddsaPrivateKey(eddsa_pk_ba) ++ raise AssertionError("Expected ValueError not raised for invalid key size") ++except ValueError as e: ++ assert "Private key format error" in str(e), "Unexpected error message" ++ ++# Check eddsa private key creation fails with 0 key ++try: ++ zero_key_ba = bytearray([0] * 32) ++ eddsa_pk = EddsaPrivateKey(zero_key_ba) ++ raise AssertionError("Expected ValueError not raised for zero key") ++except ValueError as e: ++ assert "Private key format error" in str(e), "Unexpected error message" ++"#, ++ None, ++ None, ++ ) ++ .unwrap(); ++ }) ++} ++ ++#[test] ++fn test_eddsa_public_key() { ++ Python::with_gil(|py| { ++ Python::run_bound( ++ py, ++ r#" ++from nillion_client_core import * ++import os ++ ++key_bytes = bytearray(os.urandom(32)) ++eddsa_pk = EddsaPublicKey(key_bytes) ++assert eddsa_pk.value == key_bytes # Compare against the same bytes ++"#, ++ None, ++ None, ++ ) ++ .unwrap(); ++ }) ++} ++ ++#[test] ++fn test_eddsa_message() { ++ Python::with_gil(|py| { ++ Python::run_bound( ++ py, ++ r#" ++from nillion_client_core import * ++import os ++ ++eddsa_msg_ba = bytearray(os.urandom(45)) ++eddsa_msg = EddsaMessage(eddsa_msg_ba) ++"#, ++ None, ++ None, ++ ) ++ .unwrap(); ++ }) ++} ++ ++#[test] ++fn test_eddsa_signature() { ++ Python::with_gil(|py| { ++ Python::run_bound( ++ py, ++ r#" ++from nillion_client_core import * ++import os ++ ++r = bytearray([6, 125, 237, 201, 123, 78, 227, 152, 251, 46, 236, 39, 224, 73, 18, 4, 103, 85, 109, 69, 181, 210, 56, 234, 17, 157, 209, 38, 242, 124, 237, 250]) ++z = bytearray(os.urandom(10)) ++eddsa_msg = EddsaSignature((r, z)) ++print("Eddsa signature is: ", eddsa_msg.value) ++"#, ++ None, ++ None, ++ ) ++ .unwrap(); ++ }) ++} +diff --git a/client-core/src/values/ecdsa_private_key.rs b/client-core/src/values/ecdsa_private_key.rs +index 84fd33a..3d459b0 100644 +--- a/client-core/src/values/ecdsa_private_key.rs ++++ b/client-core/src/values/ecdsa_private_key.rs +@@ -62,7 +62,7 @@ impl EcdsaPrivateKey { + /// Returns a new EcdsaPrivateKey. The byte array should be in big-endian format. + #[new] + fn new(value: &Bound<'_, PyByteArray>) -> PyResult { +- let ecdsa_private_key = privatekey::EcdsaPrivateKey::from_bytes(&value.to_vec()).map_err(|_| { ++ let ecdsa_private_key = privatekey::ThresholdPrivateKey::from_be_bytes(&value.to_vec()).map_err(|_| { + PyValueError::new_err( + "Private key format error. Check your ecdsa secret key is exactly 32 bytes and different from 0.", + ) +@@ -101,7 +101,7 @@ impl EcdsaPrivateKey { + .as_ecdsa_private_key() + .ok_or_else(|| PyValueError::new_err("expected ecdsa private key"))? + .clone() +- .to_bytes(); ++ .to_be_bytes(); + Ok(PyByteArray::new_bound(py, &bytes).into()) + } + +diff --git a/client-core/src/values/ecdsa_signature.rs b/client-core/src/values/ecdsa_signature.rs +index e48e0dc..e51e2cf 100644 +--- a/client-core/src/values/ecdsa_signature.rs ++++ b/client-core/src/values/ecdsa_signature.rs +@@ -82,12 +82,12 @@ impl EcdsaSignature { + self.inner.to_string() + } + +- /// Getter for the `r` inside a ++ /// Getter for the tuple `(r, s)` inside a + /// :py:class:`EcdsaSignature` instance. + /// + /// Returns + /// ------- +- /// int ++ /// tuple + /// The value of the private ecdsa key. + /// + /// Example +diff --git a/client-core/src/values/mod.rs b/client-core/src/values/mod.rs +index 5aa9e2f..7cf9766 100644 +--- a/client-core/src/values/mod.rs ++++ b/client-core/src/values/mod.rs +@@ -6,6 +6,10 @@ use self::{ + ecdsa_private_key::EcdsaPrivateKey, + ecdsa_public_key::EcdsaPublicKey, + ecdsa_signature::EcdsaSignature, ++ eddsa_message::EddsaMessage, ++ eddsa_private_key::EddsaPrivateKey, ++ eddsa_public_key::EddsaPublicKey, ++ eddsa_signature::EddsaSignature, + integer::{Integer, SecretInteger}, + store_id::StoreId, + unsigned_integer::{SecretUnsignedInteger, UnsignedInteger}, +@@ -21,6 +25,10 @@ pub mod ecdsa_digest_message; + pub mod ecdsa_private_key; + pub mod ecdsa_public_key; + pub mod ecdsa_signature; ++pub mod eddsa_message; ++pub mod eddsa_private_key; ++pub mod eddsa_public_key; ++pub mod eddsa_signature; + pub mod integer; + pub mod store_id; + pub mod unsigned_integer; +@@ -46,6 +54,10 @@ pub(crate) fn nada_value_clear_to_pyobject(py: Python<'_>, value: NadaValue { + Array::try_from(NadaValue::Array { values, inner_type })?.into_py(py) + } ++ NadaValue::EddsaPrivateKey(value) => EddsaPrivateKey::try_from(NadaValue::EddsaPrivateKey(value))?.into_py(py), ++ NadaValue::EddsaPublicKey(value) => EddsaPublicKey::try_from(NadaValue::EddsaPublicKey(value))?.into_py(py), ++ NadaValue::EddsaSignature(value) => EddsaSignature::try_from(NadaValue::EddsaSignature(value))?.into_py(py), ++ NadaValue::EddsaMessage(value) => EddsaMessage::try_from(NadaValue::EddsaMessage(value))?.into_py(py), + NadaValue::Tuple { .. } + | NadaValue::NTuple { .. } + | NadaValue::Object { .. } +@@ -85,6 +97,14 @@ fn pyany_to_nada_value_clear(value: Bound) -> Result, Py + value.inner + } else if let Ok(value) = value.extract::() { + value.inner ++ } else if let Ok(value) = value.extract::() { ++ value.inner ++ } else if let Ok(value) = value.extract::() { ++ value.inner ++ } else if let Ok(value) = value.extract::() { ++ value.inner ++ } else if let Ok(value) = value.extract::() { ++ value.inner + } else { + Err(PyValueError::new_err("Unsupported NadaValue variant for conversion to PyObject"))? + }; +@@ -127,5 +147,9 @@ pub fn add_module(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; ++ m.add_class::()?; ++ m.add_class::()?; ++ m.add_class::()?; ++ m.add_class::()?; + Ok(()) + } +diff --git a/client-proto/src/nillion_client_proto/nillion/compute/v1/stream/__init__.py b/client-proto/src/nillion_client_proto/nillion/compute/v1/stream/__init__.py +index b6d1945..7ad97dc 100644 +--- a/client-proto/src/nillion_client_proto/nillion/compute/v1/stream/__init__.py ++++ b/client-proto/src/nillion_client_proto/nillion/compute/v1/stream/__init__.py +@@ -8,6 +8,24 @@ from dataclasses import dataclass + import betterproto + + ++class ComputeType(betterproto.Enum): ++ """ ++ The type of compute performed. We currently support three types: ++ - GENERAL: A general compute that computes some Nada program. ++ - ECDSA_DKG: A specific compute operation for ECDSA distributed key generation. ++ - EDDSA_DKG: A specific compute operation for Eddsa distributed key generation. ++ """ ++ ++ GENERAL = 0 ++ """A general compute.""" ++ ++ ECDSA_DKG = 1 ++ """An ECDSA distributed key generation protocol.""" ++ ++ EDDSA_DKG = 2 ++ """An Eddsa distributed key generation protocol.""" ++ ++ + @dataclass(eq=False, repr=False) + class ComputeStreamMessage(betterproto.Message): + """A message for a compute stream.""" +@@ -22,3 +40,6 @@ class ComputeStreamMessage(betterproto.Message): + + bincode_message: bytes = betterproto.bytes_field(2) + """The VM message in bincode format.""" ++ ++ compute_type: "ComputeType" = betterproto.enum_field(3) ++ """The type of compute.""" +diff --git a/client-proto/src/nillion_client_proto/nillion/payments/v1/config/__init__.py b/client-proto/src/nillion_client_proto/nillion/payments/v1/config/__init__.py +index 483eb40..fb3f91b 100644 +--- a/client-proto/src/nillion_client_proto/nillion/payments/v1/config/__init__.py ++++ b/client-proto/src/nillion_client_proto/nillion/payments/v1/config/__init__.py +@@ -16,3 +16,6 @@ class PaymentsConfigResponse(betterproto.Message): + """ + The minimum amount of unil that can be added in a `Payments.add_funds` request. + """ ++ ++ credits_per_nil: int = betterproto.uint64_field(2) ++ """The number of credits one gets for every nil funded to an account.""" +diff --git a/client-proto/src/nillion_client_proto/nillion/values/v1/value/__init__.py b/client-proto/src/nillion_client_proto/nillion/values/v1/value/__init__.py +index 47b0a3d..e2b1dc3 100644 +--- a/client-proto/src/nillion_client_proto/nillion/values/v1/value/__init__.py ++++ b/client-proto/src/nillion_client_proto/nillion/values/v1/value/__init__.py +@@ -77,6 +77,20 @@ class Value(betterproto.Message): + store_id: "StoreId" = betterproto.message_field(14, group="value") + """A store id.""" + ++ eddsa_private_key_share: "EddsaPrivateKeyShare" = betterproto.message_field( ++ 15, group="value" ++ ) ++ """An Eddsa private key share.""" ++ ++ eddsa_signature: "EddsaSignature" = betterproto.message_field(16, group="value") ++ """An Eddsa signature.""" ++ ++ eddsa_message: "EddsaMessage" = betterproto.message_field(17, group="value") ++ """An Eddsa message.""" ++ ++ eddsa_public_key: "EddsaPublicKey" = betterproto.message_field(18, group="value") ++ """An Eddsa public key.""" ++ + + @dataclass(eq=False, repr=False) + class PublicInteger(betterproto.Message): +@@ -161,7 +175,7 @@ class EcdsaPublicKey(betterproto.Message): + """An ECDSA public key.""" + + public_key: bytes = betterproto.bytes_field(1) +- """The public key, in compressed form.""" ++ """The public key.""" + + + @dataclass(eq=False, repr=False) +@@ -172,6 +186,47 @@ class StoreId(betterproto.Message): + """The store id.""" + + ++@dataclass(eq=False, repr=False) ++class EddsaPrivateKeyShare(betterproto.Message): ++ """An Eddsa private key share.""" ++ ++ i: int = betterproto.uint32_field(1) ++ """Index of local party in key generation protocol.""" ++ ++ x: bytes = betterproto.bytes_field(2) ++ """The secret share x.""" ++ ++ shared_public_key: bytes = betterproto.bytes_field(3) ++ """Public key corresponding to shared secret key, in compressed form.""" ++ ++ public_shares: List[bytes] = betterproto.bytes_field(4) ++ """Public shares of all signers sharing the key, in compressed form.""" ++ ++ ++@dataclass(eq=False, repr=False) ++class EddsaSignature(betterproto.Message): ++ """An Eddsa signature.""" ++ ++ signature: bytes = betterproto.bytes_field(1) ++ """The signature.""" ++ ++ ++@dataclass(eq=False, repr=False) ++class EddsaMessage(betterproto.Message): ++ """An Eddsa message.""" ++ ++ message: bytes = betterproto.bytes_field(1) ++ """The message.""" ++ ++ ++@dataclass(eq=False, repr=False) ++class EddsaPublicKey(betterproto.Message): ++ """An Eddsa public key.""" ++ ++ public_key: bytes = betterproto.bytes_field(1) ++ """The public key.""" ++ ++ + @dataclass(eq=False, repr=False) + class ShamirSharesBlob(betterproto.Message): + """Shamir shares of a blob.""" +@@ -248,6 +303,26 @@ class ValueType(betterproto.Message): + ) + """A store id.""" + ++ eddsa_private_key_share: "betterproto_lib_google_protobuf.Empty" = ( ++ betterproto.message_field(14, group="value_type") ++ ) ++ """An Eddsa private key share.""" ++ ++ eddsa_signature: "betterproto_lib_google_protobuf.Empty" = ( ++ betterproto.message_field(15, group="value_type") ++ ) ++ """An Eddsa signature.""" ++ ++ eddsa_message: "betterproto_lib_google_protobuf.Empty" = betterproto.message_field( ++ 16, group="value_type" ++ ) ++ """An Eddsa message.""" ++ ++ eddsa_public_key: "betterproto_lib_google_protobuf.Empty" = ( ++ betterproto.message_field(17, group="value_type") ++ ) ++ """An Eddsa public key.""" ++ + + @dataclass(eq=False, repr=False) + class ArrayType(betterproto.Message): +diff --git a/nilvm b/nilvm +index c92a3b2..b5b4fe4 160000 +--- a/nilvm ++++ b/nilvm +@@ -1 +1 @@ +-Subproject commit c92a3b2c3821f25915eb280513f402b3e04f18ff ++Subproject commit b5b4fe4588211a3c5cf7139fd16d2fd0c3492aa0 +diff --git a/src/nillion_client/__init__.py b/src/nillion_client/__init__.py +index 181cc8a..0fb7fce 100644 +--- a/src/nillion_client/__init__.py ++++ b/src/nillion_client/__init__.py +@@ -23,6 +23,10 @@ from nillion_client_core import ( + EcdsaSignature, + EcdsaPublicKey, + StoreId, ++ EddsaPrivateKey, ++ EddsaPublicKey, ++ EddsaSignature, ++ EddsaMessage, + ) + from nillion_client_proto.nillion.preprocessing.v1.element import PreprocessingElement + from cosmpy.crypto.keypairs import PrivateKey as NilChainPrivateKey +@@ -57,6 +61,10 @@ __all__ = [ + "EcdsaSignature", + "EcdsaPublicKey", + "StoreId", ++ "EddsaPrivateKey", ++ "EddsaPublicKey", ++ "EddsaSignature", ++ "EddsaMessage", + "PreprocessingElement", + "PrivateKey", + "NilChainPrivateKey", +diff --git a/src/nillion_client/values.py b/src/nillion_client/values.py +index 476645d..0034e9b 100644 +--- a/src/nillion_client/values.py ++++ b/src/nillion_client/values.py +@@ -12,6 +12,10 @@ from nillion_client_proto.nillion.values.v1.value import ( + EcdsaPublicKey, + EcdsaPrivateKeyShare, + EcdsaSignatureShare, ++ EddsaMessage, ++ EddsaPrivateKeyShare, ++ EddsaPublicKey, ++ EddsaSignature, + NamedValue, + PublicInteger, + ShamirShare, +@@ -103,6 +107,25 @@ def encrypted_nada_value_to_protobuf(value: EncryptedNadaValue) -> Value: + store_id=bytes(value.value), + ) + ) ++ case EncryptedNadaValue.EddsaMessage(): # type: ignore ++ return Value(eddsa_message=EddsaMessage(message=bytes(value.value))) ++ case EncryptedNadaValue.EddsaSignature(): # type: ignore ++ return Value(eddsa_signature=EddsaSignature(signature=bytes(value.value))) ++ case EncryptedNadaValue.EddsaPrivateKey(): # type: ignore ++ return Value( ++ eddsa_private_key_share=EddsaPrivateKeyShare( ++ i=value.i, ++ x=bytes(value.x), ++ shared_public_key=bytes(value.shared_public_key), ++ public_shares=[bytes(s) for s in value.public_shares], ++ ) ++ ) ++ case EncryptedNadaValue.EddsaPublicKey(): # type: ignore ++ return Value( ++ eddsa_public_key=EddsaPublicKey( ++ public_key=bytes(value.value), ++ ) ++ ) + case _: + raise Exception(f"unsupported type: {value}") + +@@ -157,6 +180,23 @@ def encrypted_nada_value_from_protobuf(value: Value) -> EncryptedNadaValue: + return EncryptedNadaValue.EcdsaPublicKey( + value=bytes(value.public_key), + ) ++ case Value(eddsa_message=value): ++ return EncryptedNadaValue.EddsaMessage(value=bytes(value.message)) ++ case Value(eddsa_signature=value): ++ return EncryptedNadaValue.EddsaSignature( ++ value=bytes(value.signature), ++ ) ++ case Value(eddsa_private_key_share=value): ++ return EncryptedNadaValue.EddsaPrivateKey( ++ i=value.i, ++ x=list(value.x), ++ shared_public_key=list(value.shared_public_key), ++ public_shares=[list(s) for s in value.public_shares], ++ ) ++ case Value(eddsa_public_key=value): ++ return EncryptedNadaValue.EddsaPublicKey( ++ value=bytes(value.public_key), ++ ) + case Value(store_id=value): + return EncryptedNadaValue.StoreId( + value=bytes(value.store_id), +@@ -203,6 +243,14 @@ def encrypted_nada_type_to_protobuf(nada_type: EncryptedNadaType) -> ValueType: + return ValueType(ecdsa_public_key=Empty()) + case EncryptedNadaType.StoreId(): # type: ignore + return ValueType(store_id=Empty()) ++ case EncryptedNadaType.EddsaMessage(): # type: ignore ++ return ValueType(eddsa_message=Empty()) ++ case EncryptedNadaType.EddsaSignature(): # type: ignore ++ return ValueType(eddsa_signature=Empty()) ++ case EncryptedNadaType.EddsaPrivateKey(): # type: ignore ++ return ValueType(eddsa_private_key_share=Empty()) ++ case EncryptedNadaType.EddsaPublicKey(): # type: ignore ++ return ValueType(eddsa_public_key=Empty()) + case _: + raise Exception(f"unsupported encrypted type: {nada_type}") + +@@ -241,5 +289,13 @@ def encrypted_nada_type_from_protobuf(nada_type: ValueType) -> EncryptedNadaType + return EncryptedNadaType.EcdsaPublicKey() # type: ignore + case ValueType(store_id=_): + return EncryptedNadaType.StoreId() # type: ignore ++ case ValueType(eddsa_message=_): ++ return EncryptedNadaType.EddsaMessage() # type: ignore ++ case ValueType(eddsa_signature=_): ++ return EncryptedNadaType.EddsaSignature() # type: ignore ++ case ValueType(eddsa_private_key_share=_): ++ return EncryptedNadaType.EddsaPrivateKey() # type: ignore ++ case ValueType(eddsa_public_key=_): ++ return EncryptedNadaType.EddsaPublicKey() # type: ignore + case _: + raise Exception(f"unsupported type: {nada_type}") +diff --git a/src/nillion_client/vm_operation.py b/src/nillion_client/vm_operation.py +index 5c4b138..1545928 100644 +--- a/src/nillion_client/vm_operation.py ++++ b/src/nillion_client/vm_operation.py +@@ -20,6 +20,10 @@ from nillion_client_core import ( + EcdsaDigestMessage, + EcdsaSignature, + EcdsaPublicKey, ++ EddsaPrivateKey, ++ EddsaSignature, ++ EddsaPublicKey, ++ EddsaMessage, + StoreId, + ) + +@@ -55,6 +59,10 @@ NadaValue = Union[ + EcdsaDigestMessage, + EcdsaSignature, + EcdsaPublicKey, ++ EddsaPrivateKey, ++ EddsaSignature, ++ EddsaPublicKey, ++ EddsaMessage, + StoreId, + ] + +diff --git a/tests/test_nillion_client.py b/tests/test_nillion_client.py +index fae2710..b3c55e1 100644 +--- a/tests/test_nillion_client.py ++++ b/tests/test_nillion_client.py +@@ -11,6 +11,7 @@ from cryptography.hazmat.primitives.asymmetric.ec import ( + SECP256K1, + EllipticCurvePublicKey, + ) ++from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey + + from nillion_client.errors import PartyError + from nillion_client.ids import UserId +@@ -118,12 +119,128 @@ async def test_store_retrieve_all_value_types(devnet_setup): + "array": nillion_client.Array( + [nillion_client.Integer(1), nillion_client.Integer(2)] + ), +- "key": nillion_client.EcdsaPrivateKey(bytearray(os.urandom(32))), +- "message": nillion_client.EcdsaDigestMessage(bytearray(os.urandom(32))), +- "signature": nillion_client.EcdsaSignature( ++ "ecdsa_key": nillion_client.EcdsaPrivateKey(bytearray(os.urandom(32))), ++ "ecdsa_message": nillion_client.EcdsaDigestMessage(bytearray(os.urandom(32))), ++ "ecdsa_signature": nillion_client.EcdsaSignature( + (bytearray([1, 2, 3]), bytearray([1, 2, 3])) + ), +- "public_key": nillion_client.EcdsaPublicKey(bytearray(os.urandom(33))), ++ "ecdsa_public_key": nillion_client.EcdsaPublicKey(bytearray(os.urandom(33))), ++ "eddsa_key": nillion_client.EddsaPrivateKey(bytearray([1] * 32)), ++ "eddsa_message": nillion_client.EddsaMessage(bytearray(os.urandom(32))), ++ "eddsa_signature": nillion_client.EddsaSignature( ++ ( ++ bytearray( ++ [ ++ 228, ++ 118, ++ 63, ++ 53, ++ 138, ++ 161, ++ 20, ++ 164, ++ 93, ++ 86, ++ 233, ++ 11, ++ 211, ++ 204, ++ 186, ++ 63, ++ 255, ++ 174, ++ 220, ++ 173, ++ 222, ++ 58, ++ 64, ++ 79, ++ 108, ++ 173, ++ 130, ++ 1, ++ 134, ++ 44, ++ 244, ++ 104, ++ ] ++ ), ++ bytearray( ++ [ ++ 137, ++ 73, ++ 233, ++ 168, ++ 34, ++ 64, ++ 148, ++ 185, ++ 177, ++ 91, ++ 184, ++ 21, ++ 246, ++ 82, ++ 65, ++ 207, ++ 83, ++ 158, ++ 44, ++ 181, ++ 199, ++ 94, ++ 83, ++ 178, ++ 88, ++ 238, ++ 210, ++ 220, ++ 10, ++ 49, ++ 154, ++ 1, ++ ] ++ ), ++ ) ++ ), ++ "eddsa_public_key": nillion_client.EddsaPublicKey( ++ bytearray( ++ [ ++ 186, ++ 236, ++ 247, ++ 198, ++ 7, ++ 225, ++ 204, ++ 147, ++ 116, ++ 47, ++ 207, ++ 45, ++ 149, ++ 49, ++ 212, ++ 168, ++ 136, ++ 145, ++ 98, ++ 150, ++ 152, ++ 122, ++ 50, ++ 91, ++ 141, ++ 227, ++ 182, ++ 233, ++ 8, ++ 245, ++ 72, ++ 38, ++ ] ++ ) ++ ), + "store_id": nillion_client.StoreId(bytearray(os.urandom(16))), + } + # nest all the types above under an array +@@ -467,6 +584,193 @@ async def test_ecdsa_compute(devnet_setup): + client.close() + + ++@pytest.mark.asyncio ++async def test_eddsa_compute(devnet_setup): ++ """Test that we can generate an eddsa private key, store it, and use it to sign a message""" ++ ++ client = await new_client(devnet_setup) ++ ++ ########################################### ++ # # ++ # EDDSA CONFIG NAMES # ++ # # ++ ########################################### ++ ++ # program id ++ teddsa_sign_program_id = "builtin/teddsa_sign" ++ teddsa_dks_program_id = "builtin/teddsa_dkg" ++ # input store name ++ teddsa_message_name = "teddsa_message" ++ # output store name ++ teddsa_signature_name = "teddsa_signature" ++ teddsa_public_key_name = "teddsa_public_key" ++ teddsa_store_id_name = "teddsa_store_id" ++ # party names ++ teddsa_key_party = "teddsa_key_party" ++ teddsa_message_party = "teddsa_message_party" ++ teddsa_output_party = "teddsa_output_party" ++ teddsa_private_key_store_id_party = "teddsa_private_key_store_id_party" ++ teddsa_public_key_party = "teddsa_public_key_party" ++ ++ ########################################### ++ # # ++ # EDDSA MESSAGE # ++ # # ++ ########################################### ++ ++ ##### GENERATE MESSAGE ++ ++ # The message to sign ++ message = b"A deep message with a deep number: 42." ++ ++ teddsa_value = bytearray(message) ++ # eddsa message to be used for signing ++ my_eddsa_message = { ++ teddsa_message_name: nillion_client.EddsaMessage(teddsa_value), ++ } ++ ++ ########################################### ++ # # ++ # EDDSA DKG # ++ # # ++ ########################################### ++ ++ ##### EDDSA DKG ++ print("\n-----EDDSA DKG") ++ ++ # Bind the parties in the computation to the client to set input and output parties ++ input_bindings = [] ++ output_bindings = [ ++ nillion_client.OutputPartyBinding( ++ teddsa_private_key_store_id_party, [client.user_id] ++ ), ++ nillion_client.OutputPartyBinding(teddsa_public_key_party, [client.user_id]), ++ ] ++ ++ # Create a computation time secret to use ++ compute_time_values = {} ++ ++ # Compute, passing in the compute time values as well as the previously uploaded value. ++ print(f"Invoking DKG using program {teddsa_dks_program_id}") ++ compute_id = await client.compute( ++ teddsa_dks_program_id, ++ input_bindings, ++ output_bindings, ++ values=compute_time_values, ++ value_ids=[], ++ ).invoke() ++ ++ # 6. Return the computation result ++ result = await client.retrieve_compute_results(compute_id).invoke() ++ # Get the store ID and public key from results ++ private_key_store_id = result[teddsa_store_id_name].value ++ teddsa_public_key_value = result[teddsa_public_key_name].value ++ # Ensure private_key_store_id is a bytearray and convert to bytes ++ if isinstance(private_key_store_id, bytearray): # this is required to pass pyright ++ private_key_store_id_bytes = bytes(private_key_store_id) ++ else: ++ raise TypeError("private_key_store_id must be a bytearray") ++ ecdsa_private_key_store_id = uuid.UUID(bytes=private_key_store_id_bytes) ++ # Ensure tecdsa_public_key_value is a bytearray and convert to bytes ++ if isinstance( ++ teddsa_public_key_value, bytearray ++ ): # this is required to pass pyright ++ teddsa_public_key = bytes(teddsa_public_key_value) ++ else: ++ raise TypeError("teddsa_public_key must be a bytearray") ++ ++ ########################################### ++ # # ++ # ECDSA SIGNING # ++ # # ++ ########################################### ++ ++ ##### EDDSA SIGNING ++ print("-----EDDSA SIGNING") ++ ++ # Bind the parties in the computation to the client to set input and output parties ++ input_bindings = [ ++ nillion_client.InputPartyBinding(teddsa_key_party, client.user_id), ++ nillion_client.InputPartyBinding(teddsa_message_party, client.user_id), ++ ] ++ output_bindings = [ ++ nillion_client.OutputPartyBinding(teddsa_output_party, [client.user_id]) ++ ] ++ ++ # Create a computation time secret to use ++ compute_time_values = my_eddsa_message ++ ++ # Compute, passing in the compute time values as well as the previously uploaded value. ++ compute_id = await client.compute( ++ teddsa_sign_program_id, ++ input_bindings, ++ output_bindings, ++ values=my_eddsa_message, ++ value_ids=[ecdsa_private_key_store_id], ++ ).invoke() ++ # 6. Return the computation result ++ result = await client.retrieve_compute_results(compute_id).invoke() ++ signature_value = result[teddsa_signature_name] ++ public_key_value = result[teddsa_public_key_name] ++ message_value = result[teddsa_message_name] ++ ++ # Ensure the signature is of the correct type ++ if isinstance(signature_value, nillion_client.EddsaSignature): ++ signature_output = signature_value ++ else: ++ raise TypeError("Cannot convert to EddsaSignature.") ++ ++ # Ensure the public key is of the correct type ++ if isinstance(public_key_value, nillion_client.EddsaPublicKey): ++ public_key_output = public_key_value ++ else: ++ raise TypeError("Cannot convert to EddsaPublicKey.") ++ ++ # Ensure the message is of the correct type ++ if isinstance(message_value, nillion_client.EddsaMessage): ++ message_output = message_value ++ else: ++ raise TypeError("Cannot convert to EddsaMessage.") ++ ++ ########################################### ++ # # ++ # ECDSA VERIFICATION # ++ # # ++ ########################################### ++ ++ ##### OUTPUT VERIFICATION ++ # Verify the public key output from dkg is the same as the one given by signing ++ assert teddsa_public_key == public_key_output.value ++ ++ # Verify the message output from signing is the same as input for signing ++ assert message_output.value == message ++ ++ ##### EDDSA VERIFICATION ++ print("-----EDDSA VERIFICATION") ++ ++ # Transform the result signature to bytes for verification ++ (r, z) = signature_output.value ++ # Convert r and z to bytes - note that EdDSA uses concatenated format (R || Z) ++ r_bytes = bytes(r) ++ z_bytes = bytes(z) ++ # Ed25519 signatures are simply the concatenation of R and Z components ++ signature_bytes = r_bytes + z_bytes ++ ++ # Create the public key object from the raw bytes ++ try: ++ ed25519_public_key = Ed25519PublicKey.from_public_bytes(teddsa_public_key) ++ except Exception as e: ++ raise ValueError(f"Invalid Ed25519 public key format: {str(e)}") ++ ++ # Verify the signature ++ try: ++ ed25519_public_key.verify(signature_bytes, message) ++ except Exception as e: ++ raise ValueError(f"Signature is invalid: {str(e)}") ++ ++ client.close() ++ ++ + @pytest.mark.asyncio + async def test_complex_compute(devnet_setup): + client_party1 = await new_client(devnet_setup) diff --git a/pyproject.toml b/pyproject.toml index 74a513d..c8c11ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "nillion-client" -version = "0.2.1rc1" +version = "0.2.1rc2" requires-python = ">=3.10" description = "Nillion client" license = { text = "MIT" } @@ -19,8 +19,8 @@ dependencies = [ "secp256k1==0.14.0", "base58==2.1.1", "tenacity==9.0.0", - "nillion-client-core==0.2.1rc1", - "nillion-client-proto==0.2.1rc1", + "nillion-client-core==0.2.1rc2", + "nillion-client-proto==0.2.1rc2", "pyyaml==6.0.2" ] diff --git a/src/nillion_client/__init__.py b/src/nillion_client/__init__.py index 181cc8a..0fb7fce 100644 --- a/src/nillion_client/__init__.py +++ b/src/nillion_client/__init__.py @@ -23,6 +23,10 @@ EcdsaSignature, EcdsaPublicKey, StoreId, + EddsaPrivateKey, + EddsaPublicKey, + EddsaSignature, + EddsaMessage, ) from nillion_client_proto.nillion.preprocessing.v1.element import PreprocessingElement from cosmpy.crypto.keypairs import PrivateKey as NilChainPrivateKey @@ -57,6 +61,10 @@ "EcdsaSignature", "EcdsaPublicKey", "StoreId", + "EddsaPrivateKey", + "EddsaPublicKey", + "EddsaSignature", + "EddsaMessage", "PreprocessingElement", "PrivateKey", "NilChainPrivateKey", diff --git a/src/nillion_client/values.py b/src/nillion_client/values.py index 476645d..0034e9b 100644 --- a/src/nillion_client/values.py +++ b/src/nillion_client/values.py @@ -12,6 +12,10 @@ EcdsaPublicKey, EcdsaPrivateKeyShare, EcdsaSignatureShare, + EddsaMessage, + EddsaPrivateKeyShare, + EddsaPublicKey, + EddsaSignature, NamedValue, PublicInteger, ShamirShare, @@ -103,6 +107,25 @@ def encrypted_nada_value_to_protobuf(value: EncryptedNadaValue) -> Value: store_id=bytes(value.value), ) ) + case EncryptedNadaValue.EddsaMessage(): # type: ignore + return Value(eddsa_message=EddsaMessage(message=bytes(value.value))) + case EncryptedNadaValue.EddsaSignature(): # type: ignore + return Value(eddsa_signature=EddsaSignature(signature=bytes(value.value))) + case EncryptedNadaValue.EddsaPrivateKey(): # type: ignore + return Value( + eddsa_private_key_share=EddsaPrivateKeyShare( + i=value.i, + x=bytes(value.x), + shared_public_key=bytes(value.shared_public_key), + public_shares=[bytes(s) for s in value.public_shares], + ) + ) + case EncryptedNadaValue.EddsaPublicKey(): # type: ignore + return Value( + eddsa_public_key=EddsaPublicKey( + public_key=bytes(value.value), + ) + ) case _: raise Exception(f"unsupported type: {value}") @@ -157,6 +180,23 @@ def encrypted_nada_value_from_protobuf(value: Value) -> EncryptedNadaValue: return EncryptedNadaValue.EcdsaPublicKey( value=bytes(value.public_key), ) + case Value(eddsa_message=value): + return EncryptedNadaValue.EddsaMessage(value=bytes(value.message)) + case Value(eddsa_signature=value): + return EncryptedNadaValue.EddsaSignature( + value=bytes(value.signature), + ) + case Value(eddsa_private_key_share=value): + return EncryptedNadaValue.EddsaPrivateKey( + i=value.i, + x=list(value.x), + shared_public_key=list(value.shared_public_key), + public_shares=[list(s) for s in value.public_shares], + ) + case Value(eddsa_public_key=value): + return EncryptedNadaValue.EddsaPublicKey( + value=bytes(value.public_key), + ) case Value(store_id=value): return EncryptedNadaValue.StoreId( value=bytes(value.store_id), @@ -203,6 +243,14 @@ def encrypted_nada_type_to_protobuf(nada_type: EncryptedNadaType) -> ValueType: return ValueType(ecdsa_public_key=Empty()) case EncryptedNadaType.StoreId(): # type: ignore return ValueType(store_id=Empty()) + case EncryptedNadaType.EddsaMessage(): # type: ignore + return ValueType(eddsa_message=Empty()) + case EncryptedNadaType.EddsaSignature(): # type: ignore + return ValueType(eddsa_signature=Empty()) + case EncryptedNadaType.EddsaPrivateKey(): # type: ignore + return ValueType(eddsa_private_key_share=Empty()) + case EncryptedNadaType.EddsaPublicKey(): # type: ignore + return ValueType(eddsa_public_key=Empty()) case _: raise Exception(f"unsupported encrypted type: {nada_type}") @@ -241,5 +289,13 @@ def encrypted_nada_type_from_protobuf(nada_type: ValueType) -> EncryptedNadaType return EncryptedNadaType.EcdsaPublicKey() # type: ignore case ValueType(store_id=_): return EncryptedNadaType.StoreId() # type: ignore + case ValueType(eddsa_message=_): + return EncryptedNadaType.EddsaMessage() # type: ignore + case ValueType(eddsa_signature=_): + return EncryptedNadaType.EddsaSignature() # type: ignore + case ValueType(eddsa_private_key_share=_): + return EncryptedNadaType.EddsaPrivateKey() # type: ignore + case ValueType(eddsa_public_key=_): + return EncryptedNadaType.EddsaPublicKey() # type: ignore case _: raise Exception(f"unsupported type: {nada_type}") diff --git a/src/nillion_client/vm_operation.py b/src/nillion_client/vm_operation.py index 5c4b138..1545928 100644 --- a/src/nillion_client/vm_operation.py +++ b/src/nillion_client/vm_operation.py @@ -20,6 +20,10 @@ EcdsaDigestMessage, EcdsaSignature, EcdsaPublicKey, + EddsaPrivateKey, + EddsaSignature, + EddsaPublicKey, + EddsaMessage, StoreId, ) @@ -55,6 +59,10 @@ EcdsaDigestMessage, EcdsaSignature, EcdsaPublicKey, + EddsaPrivateKey, + EddsaSignature, + EddsaPublicKey, + EddsaMessage, StoreId, ] diff --git a/tests/test_nillion_client.py b/tests/test_nillion_client.py index fae2710..b3c55e1 100644 --- a/tests/test_nillion_client.py +++ b/tests/test_nillion_client.py @@ -11,6 +11,7 @@ SECP256K1, EllipticCurvePublicKey, ) +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey from nillion_client.errors import PartyError from nillion_client.ids import UserId @@ -118,12 +119,128 @@ async def test_store_retrieve_all_value_types(devnet_setup): "array": nillion_client.Array( [nillion_client.Integer(1), nillion_client.Integer(2)] ), - "key": nillion_client.EcdsaPrivateKey(bytearray(os.urandom(32))), - "message": nillion_client.EcdsaDigestMessage(bytearray(os.urandom(32))), - "signature": nillion_client.EcdsaSignature( + "ecdsa_key": nillion_client.EcdsaPrivateKey(bytearray(os.urandom(32))), + "ecdsa_message": nillion_client.EcdsaDigestMessage(bytearray(os.urandom(32))), + "ecdsa_signature": nillion_client.EcdsaSignature( (bytearray([1, 2, 3]), bytearray([1, 2, 3])) ), - "public_key": nillion_client.EcdsaPublicKey(bytearray(os.urandom(33))), + "ecdsa_public_key": nillion_client.EcdsaPublicKey(bytearray(os.urandom(33))), + "eddsa_key": nillion_client.EddsaPrivateKey(bytearray([1] * 32)), + "eddsa_message": nillion_client.EddsaMessage(bytearray(os.urandom(32))), + "eddsa_signature": nillion_client.EddsaSignature( + ( + bytearray( + [ + 228, + 118, + 63, + 53, + 138, + 161, + 20, + 164, + 93, + 86, + 233, + 11, + 211, + 204, + 186, + 63, + 255, + 174, + 220, + 173, + 222, + 58, + 64, + 79, + 108, + 173, + 130, + 1, + 134, + 44, + 244, + 104, + ] + ), + bytearray( + [ + 137, + 73, + 233, + 168, + 34, + 64, + 148, + 185, + 177, + 91, + 184, + 21, + 246, + 82, + 65, + 207, + 83, + 158, + 44, + 181, + 199, + 94, + 83, + 178, + 88, + 238, + 210, + 220, + 10, + 49, + 154, + 1, + ] + ), + ) + ), + "eddsa_public_key": nillion_client.EddsaPublicKey( + bytearray( + [ + 186, + 236, + 247, + 198, + 7, + 225, + 204, + 147, + 116, + 47, + 207, + 45, + 149, + 49, + 212, + 168, + 136, + 145, + 98, + 150, + 152, + 122, + 50, + 91, + 141, + 227, + 182, + 233, + 8, + 245, + 72, + 38, + ] + ) + ), "store_id": nillion_client.StoreId(bytearray(os.urandom(16))), } # nest all the types above under an array @@ -467,6 +584,193 @@ async def test_ecdsa_compute(devnet_setup): client.close() +@pytest.mark.asyncio +async def test_eddsa_compute(devnet_setup): + """Test that we can generate an eddsa private key, store it, and use it to sign a message""" + + client = await new_client(devnet_setup) + + ########################################### + # # + # EDDSA CONFIG NAMES # + # # + ########################################### + + # program id + teddsa_sign_program_id = "builtin/teddsa_sign" + teddsa_dks_program_id = "builtin/teddsa_dkg" + # input store name + teddsa_message_name = "teddsa_message" + # output store name + teddsa_signature_name = "teddsa_signature" + teddsa_public_key_name = "teddsa_public_key" + teddsa_store_id_name = "teddsa_store_id" + # party names + teddsa_key_party = "teddsa_key_party" + teddsa_message_party = "teddsa_message_party" + teddsa_output_party = "teddsa_output_party" + teddsa_private_key_store_id_party = "teddsa_private_key_store_id_party" + teddsa_public_key_party = "teddsa_public_key_party" + + ########################################### + # # + # EDDSA MESSAGE # + # # + ########################################### + + ##### GENERATE MESSAGE + + # The message to sign + message = b"A deep message with a deep number: 42." + + teddsa_value = bytearray(message) + # eddsa message to be used for signing + my_eddsa_message = { + teddsa_message_name: nillion_client.EddsaMessage(teddsa_value), + } + + ########################################### + # # + # EDDSA DKG # + # # + ########################################### + + ##### EDDSA DKG + print("\n-----EDDSA DKG") + + # Bind the parties in the computation to the client to set input and output parties + input_bindings = [] + output_bindings = [ + nillion_client.OutputPartyBinding( + teddsa_private_key_store_id_party, [client.user_id] + ), + nillion_client.OutputPartyBinding(teddsa_public_key_party, [client.user_id]), + ] + + # Create a computation time secret to use + compute_time_values = {} + + # Compute, passing in the compute time values as well as the previously uploaded value. + print(f"Invoking DKG using program {teddsa_dks_program_id}") + compute_id = await client.compute( + teddsa_dks_program_id, + input_bindings, + output_bindings, + values=compute_time_values, + value_ids=[], + ).invoke() + + # 6. Return the computation result + result = await client.retrieve_compute_results(compute_id).invoke() + # Get the store ID and public key from results + private_key_store_id = result[teddsa_store_id_name].value + teddsa_public_key_value = result[teddsa_public_key_name].value + # Ensure private_key_store_id is a bytearray and convert to bytes + if isinstance(private_key_store_id, bytearray): # this is required to pass pyright + private_key_store_id_bytes = bytes(private_key_store_id) + else: + raise TypeError("private_key_store_id must be a bytearray") + ecdsa_private_key_store_id = uuid.UUID(bytes=private_key_store_id_bytes) + # Ensure tecdsa_public_key_value is a bytearray and convert to bytes + if isinstance( + teddsa_public_key_value, bytearray + ): # this is required to pass pyright + teddsa_public_key = bytes(teddsa_public_key_value) + else: + raise TypeError("teddsa_public_key must be a bytearray") + + ########################################### + # # + # ECDSA SIGNING # + # # + ########################################### + + ##### EDDSA SIGNING + print("-----EDDSA SIGNING") + + # Bind the parties in the computation to the client to set input and output parties + input_bindings = [ + nillion_client.InputPartyBinding(teddsa_key_party, client.user_id), + nillion_client.InputPartyBinding(teddsa_message_party, client.user_id), + ] + output_bindings = [ + nillion_client.OutputPartyBinding(teddsa_output_party, [client.user_id]) + ] + + # Create a computation time secret to use + compute_time_values = my_eddsa_message + + # Compute, passing in the compute time values as well as the previously uploaded value. + compute_id = await client.compute( + teddsa_sign_program_id, + input_bindings, + output_bindings, + values=my_eddsa_message, + value_ids=[ecdsa_private_key_store_id], + ).invoke() + # 6. Return the computation result + result = await client.retrieve_compute_results(compute_id).invoke() + signature_value = result[teddsa_signature_name] + public_key_value = result[teddsa_public_key_name] + message_value = result[teddsa_message_name] + + # Ensure the signature is of the correct type + if isinstance(signature_value, nillion_client.EddsaSignature): + signature_output = signature_value + else: + raise TypeError("Cannot convert to EddsaSignature.") + + # Ensure the public key is of the correct type + if isinstance(public_key_value, nillion_client.EddsaPublicKey): + public_key_output = public_key_value + else: + raise TypeError("Cannot convert to EddsaPublicKey.") + + # Ensure the message is of the correct type + if isinstance(message_value, nillion_client.EddsaMessage): + message_output = message_value + else: + raise TypeError("Cannot convert to EddsaMessage.") + + ########################################### + # # + # ECDSA VERIFICATION # + # # + ########################################### + + ##### OUTPUT VERIFICATION + # Verify the public key output from dkg is the same as the one given by signing + assert teddsa_public_key == public_key_output.value + + # Verify the message output from signing is the same as input for signing + assert message_output.value == message + + ##### EDDSA VERIFICATION + print("-----EDDSA VERIFICATION") + + # Transform the result signature to bytes for verification + (r, z) = signature_output.value + # Convert r and z to bytes - note that EdDSA uses concatenated format (R || Z) + r_bytes = bytes(r) + z_bytes = bytes(z) + # Ed25519 signatures are simply the concatenation of R and Z components + signature_bytes = r_bytes + z_bytes + + # Create the public key object from the raw bytes + try: + ed25519_public_key = Ed25519PublicKey.from_public_bytes(teddsa_public_key) + except Exception as e: + raise ValueError(f"Invalid Ed25519 public key format: {str(e)}") + + # Verify the signature + try: + ed25519_public_key.verify(signature_bytes, message) + except Exception as e: + raise ValueError(f"Signature is invalid: {str(e)}") + + client.close() + + @pytest.mark.asyncio async def test_complex_compute(devnet_setup): client_party1 = await new_client(devnet_setup) From 007fb5c538ce9f116790ed652e1a27b8c50a39e8 Mon Sep 17 00:00:00 2001 From: manel1874 Date: Fri, 28 Feb 2025 17:07:52 +0000 Subject: [PATCH 2/4] check ec2 runner --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1e855b..9f1c759 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: aws-region: "eu-west-1" - name: Start EC2 runner id: start-ec2-runner - uses: NillionNetwork/ec2-github-runner@v2.2 + uses: NillionNetwork/ec2-github-runner@chore/aws_v3_sdk_upgrade with: mode: start github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN_RUNNER }} From 4f61d690f7305336a1ee27e2ff9e9dd1e6a0946d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Hern=C3=A1ndez?= Date: Thu, 6 Mar 2025 08:29:12 +0000 Subject: [PATCH 3/4] feat: point ec2-git-hub-runner to tag v2.4 --- .github/workflows/ci.yml | 4 ++-- nilvm | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9f1c759..cdea796 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: aws-region: "eu-west-1" - name: Start EC2 runner id: start-ec2-runner - uses: NillionNetwork/ec2-github-runner@chore/aws_v3_sdk_upgrade + uses: NillionNetwork/ec2-github-runner@v2.4.0 with: mode: start github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN_RUNNER }} @@ -212,7 +212,7 @@ jobs: aws-region: "eu-west-1" - name: Stop EC2 runner - uses: NillionNetwork/ec2-github-runner@v2.2 + uses: NillionNetwork/ec2-github-runner@v2.4.0 with: mode: stop github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN_RUNNER }} diff --git a/nilvm b/nilvm index ead1c20..d53b729 160000 --- a/nilvm +++ b/nilvm @@ -1 +1 @@ -Subproject commit ead1c20c9448b1b60af7ca8c10b4cb70a67ea2a3 +Subproject commit d53b7295b7cdb3376af1186aa96915768a01223c From e1275bc5510db07c08d415d87e60965ace22e93e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Hern=C3=A1ndez?= Date: Fri, 7 Mar 2025 08:10:11 +0000 Subject: [PATCH 4/4] fix: remove unneeded file --- patch.txt | 1892 ----------------------------------------------------- 1 file changed, 1892 deletions(-) delete mode 100644 patch.txt diff --git a/patch.txt b/patch.txt deleted file mode 100644 index 253eeff..0000000 --- a/patch.txt +++ /dev/null @@ -1,1892 +0,0 @@ -diff --git a/.nil-sdk.toml b/.nil-sdk.toml -index 7dafb23..1d6ec7b 100644 ---- a/.nil-sdk.toml -+++ b/.nil-sdk.toml -@@ -1 +1 @@ --version = "v0.9.0-rc.62" -+version = "v0.10.0-rc.23" -\ No newline at end of file -diff --git a/client-core/Cargo.lock b/client-core/Cargo.lock -index 09d5d29..efb99dd 100644 ---- a/client-core/Cargo.lock -+++ b/client-core/Cargo.lock -@@ -39,12 +39,18 @@ version = "0.2.0" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - -+[[package]] -+name = "base64" -+version = "0.13.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" -+ - [[package]] - name = "basic-types" - version = "0.1.0" - dependencies = [ - "hex", -- "thiserror", -+ "thiserror 1.0.68", - "uuid", - ] - -@@ -96,6 +102,37 @@ version = "1.0.0" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -+[[package]] -+name = "cggmp21-keygen" -+version = "0.5.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "aaa8c850290c494f951abe0350e56c31e4f5664863490197490ff48cb825447d" -+dependencies = [ -+ "digest", -+ "displaydoc", -+ "generic-ec", -+ "generic-ec-zkp", -+ "hex", -+ "key-share", -+ "rand_core", -+ "round-based", -+ "serde", -+ "serde_with", -+ "sha2", -+ "thiserror 1.0.68", -+ "udigest", -+] -+ -+[[package]] -+name = "chrono" -+version = "0.4.40" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" -+dependencies = [ -+ "num-traits", -+ "serde", -+] -+ - [[package]] - name = "cmake" - version = "0.1.52" -@@ -162,6 +199,8 @@ dependencies = [ - "cpufeatures", - "curve25519-dalek-derive", - "fiat-crypto", -+ "group", -+ "rand_core", - "rustc_version", - "subtle", - "zeroize", -@@ -178,6 +217,41 @@ dependencies = [ - "syn 2.0.87", - ] - -+[[package]] -+name = "darling" -+version = "0.20.10" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" -+dependencies = [ -+ "darling_core", -+ "darling_macro", -+] -+ -+[[package]] -+name = "darling_core" -+version = "0.20.10" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" -+dependencies = [ -+ "fnv", -+ "ident_case", -+ "proc-macro2", -+ "quote", -+ "strsim", -+ "syn 2.0.87", -+] -+ -+[[package]] -+name = "darling_macro" -+version = "0.20.10" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" -+dependencies = [ -+ "darling_core", -+ "quote", -+ "syn 2.0.87", -+] -+ - [[package]] - name = "der" - version = "0.7.9" -@@ -188,6 +262,15 @@ dependencies = [ - "zeroize", - ] - -+[[package]] -+name = "deranged" -+version = "0.3.11" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -+dependencies = [ -+ "powerfmt", -+] -+ - [[package]] - name = "digest" - version = "0.10.7" -@@ -196,6 +279,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" - dependencies = [ - "block-buffer", - "crypto-common", -+ "subtle", - ] - - [[package]] -@@ -219,17 +303,6 @@ dependencies = [ - "proc-macro-error", - ] - --[[package]] --name = "ecdsa-keypair" --version = "0.1.0" --dependencies = [ -- "generic-ec", -- "key-share", -- "rand", -- "subtle", -- "thiserror", --] -- - [[package]] - name = "educe" - version = "0.4.23" -@@ -347,12 +420,50 @@ version = "0.4.2" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - -+[[package]] -+name = "fnv" -+version = "1.0.7" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -+ -+[[package]] -+name = "futures-core" -+version = "0.3.31" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" -+ -+[[package]] -+name = "futures-sink" -+version = "0.3.31" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" -+ -+[[package]] -+name = "futures-task" -+version = "0.3.31" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" -+ -+[[package]] -+name = "futures-util" -+version = "0.3.31" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -+dependencies = [ -+ "futures-core", -+ "futures-sink", -+ "futures-task", -+ "pin-project-lite", -+ "pin-utils", -+] -+ - [[package]] - name = "generic-array" - version = "0.14.7" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" - dependencies = [ -+ "serde", - "typenum", - "version_check", - "zeroize", -@@ -365,12 +476,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "50e3268b3f97e2046ebf69e24a1e7e12dad4dec247d87f59268fd57ab514b5f8" - dependencies = [ - "curve25519-dalek", -+ "digest", - "generic-ec-core", - "generic-ec-curves", - "hex", -- "phantom-type", -+ "phantom-type 0.4.2", - "rand_core", -+ "rand_hash", -+ "serde", -+ "serde_with", - "subtle", -+ "udigest", - "zeroize", - ] - -@@ -382,6 +498,7 @@ checksum = "049128cc67cac6176ada5218e294ce46421470d92a7340c93d5cfd3ecfbc29a4" - dependencies = [ - "generic-array", - "rand_core", -+ "serde", - "subtle", - "zeroize", - ] -@@ -392,8 +509,10 @@ version = "0.2.2" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "1e663405a17b229dede990904edcfd2056cf6641f1240869a1f44fa13bd4ee4d" - dependencies = [ -+ "curve25519-dalek", - "elliptic-curve", - "generic-ec-core", -+ "group", - "k256", - "rand_core", - "sha2", -@@ -426,6 +545,24 @@ dependencies = [ - "wasi", - ] - -+[[package]] -+name = "givre" -+version = "0.2.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "af482004b1f70b5b3b584c3fc3733dd24c71f8c0081a6087f49e09746746788a" -+dependencies = [ -+ "cggmp21-keygen", -+ "digest", -+ "generic-ec", -+ "hd-wallet", -+ "k256", -+ "key-share", -+ "rand_core", -+ "serde", -+ "sha2", -+ "static_assertions", -+] -+ - [[package]] - name = "group" - version = "0.13.0" -@@ -443,6 +580,19 @@ version = "0.15.1" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" - -+[[package]] -+name = "hd-wallet" -+version = "0.6.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "6522551bb35937363845f39a6d4c49e60bdb35a8f7154ebdd078cab50be97992" -+dependencies = [ -+ "generic-array", -+ "generic-ec", -+ "hmac", -+ "sha2", -+ "subtle", -+] -+ - [[package]] - name = "heck" - version = "0.4.1" -@@ -460,6 +610,24 @@ name = "hex" - version = "0.4.3" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -+dependencies = [ -+ "serde", -+] -+ -+[[package]] -+name = "hmac" -+version = "0.12.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -+dependencies = [ -+ "digest", -+] -+ -+[[package]] -+name = "ident_case" -+version = "1.0.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - - [[package]] - name = "indexmap" -@@ -487,6 +655,12 @@ dependencies = [ - "either", - ] - -+[[package]] -+name = "itoa" -+version = "1.0.14" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" -+ - [[package]] - name = "jit-compiler" - version = "0.1.0" -@@ -500,7 +674,7 @@ dependencies = [ - "nada-compiler-backend", - "nada-type", - "substring", -- "thiserror", -+ "thiserror 1.0.68", - ] - - [[package]] -@@ -515,14 +689,18 @@ dependencies = [ - - [[package]] - name = "key-share" --version = "0.5.0" -+version = "0.6.0" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "8ea364cb2397405d8c79afd3de173ca7e2e1d83a4ddd94d359263480ad96f06f" -+checksum = "3ee8e510bb9f738ac400b7dedd98aeb23677c6b48cd3b1b682651ea5091f4282" - dependencies = [ - "displaydoc", - "generic-ec", - "generic-ec-zkp", -+ "hex", - "rand_core", -+ "serde", -+ "serde_with", -+ "thiserror 1.0.68", - ] - - [[package]] -@@ -547,14 +725,13 @@ checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" - name = "math_lib" - version = "0.1.0" - dependencies = [ -- "basic-types", - "crypto-bigint", - "num-bigint", - "num-traits", - "paste", - "rand", - "subtle", -- "thiserror", -+ "thiserror 1.0.68", - ] - - [[package]] -@@ -583,7 +760,7 @@ dependencies = [ - "serde", - "serde_repr", - "substring", -- "thiserror", -+ "thiserror 1.0.68", - ] - - [[package]] -@@ -601,12 +778,12 @@ name = "mpc-vm" - version = "0.1.0" - dependencies = [ - "anyhow", -- "ecdsa-keypair", - "generic-ec", - "jit-compiler", - "nada-compiler-backend", - "nada-value", - "strum", -+ "threshold-keypair", - ] - - [[package]] -@@ -621,12 +798,11 @@ version = "0.1.0" - dependencies = [ - "anyhow", - "ariadne", -- "basic-types", - "duplicate", - "mir-model", - "nada-value", - "num-bigint", -- "thiserror", -+ "thiserror 1.0.68", - ] - - [[package]] -@@ -638,7 +814,7 @@ dependencies = [ - "serde", - "strum", - "strum_macros", -- "thiserror", -+ "thiserror 1.0.68", - "types-proc-macros", - ] - -@@ -648,9 +824,9 @@ version = "0.1.0" - dependencies = [ - "anyhow", - "basic-types", -- "ecdsa-keypair", - "enum-as-inner", - "generic-ec", -+ "givre", - "indexmap", - "key-share", - "math_lib", -@@ -659,7 +835,8 @@ dependencies = [ - "num-traits", - "shamir-sharing", - "strum_macros", -- "thiserror", -+ "thiserror 1.0.68", -+ "threshold-keypair", - "types-proc-macros", - ] - -@@ -668,13 +845,13 @@ name = "nillion-client-core" - version = "0.1.0" - dependencies = [ - "basic-types", -- "ecdsa-keypair", - "key-share", - "math_lib", - "mpc-vm", - "nada-value", - "program-auditor", - "shamir-sharing", -+ "threshold-keypair", - ] - - [[package]] -@@ -696,6 +873,12 @@ dependencies = [ - "num-traits", - ] - -+[[package]] -+name = "num-conv" -+version = "0.1.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" -+ - [[package]] - name = "num-integer" - version = "0.1.46" -@@ -736,6 +919,15 @@ dependencies = [ - "indexmap", - ] - -+[[package]] -+name = "phantom-type" -+version = "0.3.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "f710afd11c9711b04f97ab61bb9747d5a04562fdf0f9f44abc3de92490084982" -+dependencies = [ -+ "educe", -+] -+ - [[package]] - name = "phantom-type" - version = "0.4.2" -@@ -745,12 +937,30 @@ dependencies = [ - "educe", - ] - -+[[package]] -+name = "pin-project-lite" -+version = "0.2.16" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" -+ -+[[package]] -+name = "pin-utils" -+version = "0.1.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -+ - [[package]] - name = "portable-atomic" - version = "1.9.0" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" - -+[[package]] -+name = "powerfmt" -+version = "0.2.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -+ - [[package]] - name = "ppv-lite86" - version = "0.2.20" -@@ -810,7 +1020,7 @@ dependencies = [ - "anyhow", - "mpc-vm", - "nada-compiler-backend", -- "thiserror", -+ "thiserror 1.0.68", - ] - - [[package]] -@@ -977,6 +1187,17 @@ dependencies = [ - "getrandom", - ] - -+[[package]] -+name = "rand_hash" -+version = "0.1.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "16bc1dd921383c6564eb0b8252f5b3f6622b84d40c6e35f5e6790e1fd7abb7a9" -+dependencies = [ -+ "digest", -+ "rand_core", -+ "udigest", -+] -+ - [[package]] - name = "regex" - version = "1.11.1" -@@ -1006,6 +1227,30 @@ version = "0.8.5" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -+[[package]] -+name = "round-based" -+version = "0.4.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "da76edf50de0a9d6911fc79261bb04cc9f3f3a375e0201799f5edf58499af341" -+dependencies = [ -+ "futures-util", -+ "phantom-type 0.3.1", -+ "round-based-derive", -+ "thiserror 2.0.11", -+ "tracing", -+] -+ -+[[package]] -+name = "round-based-derive" -+version = "0.2.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "4afa4d5b318bcafae8a7ebc57c1cb7d4b2db7358293e34d71bfd605fd327cc13" -+dependencies = [ -+ "proc-macro2", -+ "quote", -+ "syn 1.0.109", -+] -+ - [[package]] - name = "rustc-hash" - version = "2.1.0" -@@ -1040,6 +1285,12 @@ version = "1.0.18" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" - -+[[package]] -+name = "ryu" -+version = "1.0.19" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" -+ - [[package]] - name = "sec1" - version = "0.7.3" -@@ -1079,6 +1330,18 @@ dependencies = [ - "syn 2.0.87", - ] - -+[[package]] -+name = "serde_json" -+version = "1.0.139" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" -+dependencies = [ -+ "itoa", -+ "memchr", -+ "ryu", -+ "serde", -+] -+ - [[package]] - name = "serde_repr" - version = "0.1.19" -@@ -1090,6 +1353,33 @@ dependencies = [ - "syn 2.0.87", - ] - -+[[package]] -+name = "serde_with" -+version = "2.3.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" -+dependencies = [ -+ "base64", -+ "chrono", -+ "hex", -+ "serde", -+ "serde_json", -+ "serde_with_macros", -+ "time", -+] -+ -+[[package]] -+name = "serde_with_macros" -+version = "2.3.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" -+dependencies = [ -+ "darling", -+ "proc-macro2", -+ "quote", -+ "syn 2.0.87", -+] -+ - [[package]] - name = "sha2" - version = "0.10.8" -@@ -1110,7 +1400,7 @@ dependencies = [ - "math_lib", - "rand", - "rustc-hash", -- "thiserror", -+ "thiserror 1.0.68", - ] - - [[package]] -@@ -1119,6 +1409,18 @@ version = "1.3.0" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -+[[package]] -+name = "static_assertions" -+version = "1.1.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -+ -+[[package]] -+name = "strsim" -+version = "0.11.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -+ - [[package]] - name = "strum" - version = "0.26.3" -@@ -1204,7 +1506,16 @@ version = "1.0.68" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" - dependencies = [ -- "thiserror-impl", -+ "thiserror-impl 1.0.68", -+] -+ -+[[package]] -+name = "thiserror" -+version = "2.0.11" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" -+dependencies = [ -+ "thiserror-impl 2.0.11", - ] - - [[package]] -@@ -1218,6 +1529,64 @@ dependencies = [ - "syn 2.0.87", - ] - -+[[package]] -+name = "thiserror-impl" -+version = "2.0.11" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" -+dependencies = [ -+ "proc-macro2", -+ "quote", -+ "syn 2.0.87", -+] -+ -+[[package]] -+name = "threshold-keypair" -+version = "0.1.0" -+dependencies = [ -+ "generic-ec", -+ "givre", -+ "key-share", -+ "rand", -+ "subtle", -+ "thiserror 1.0.68", -+] -+ -+[[package]] -+name = "time" -+version = "0.3.37" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" -+dependencies = [ -+ "deranged", -+ "num-conv", -+ "powerfmt", -+ "serde", -+ "time-core", -+] -+ -+[[package]] -+name = "time-core" -+version = "0.1.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" -+ -+[[package]] -+name = "tracing" -+version = "0.1.41" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -+dependencies = [ -+ "pin-project-lite", -+ "tracing-core", -+] -+ -+[[package]] -+name = "tracing-core" -+version = "0.1.33" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" -+ - [[package]] - name = "typenum" - version = "1.17.0" -@@ -1240,6 +1609,7 @@ version = "0.2.2" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "81cd61fa9fb78569e9fe34acf0048fd8cb9ebdbacc47af740745487287043ff0" - dependencies = [ -+ "digest", - "udigest-derive", - ] - -diff --git a/client-core/nillion_client_core.pyi b/client-core/nillion_client_core.pyi -index 8a05264..302472f 100644 ---- a/client-core/nillion_client_core.pyi -+++ b/client-core/nillion_client_core.pyi -@@ -14,6 +14,10 @@ NadaValue = Union[ - EcdsaSignature, - EcdsaPublicKey, - StoreId, -+ EddsaPrivateKey, -+ EddsaPublicKey, -+ EddsaSignature, -+ EddsaMessage, - ] - - class SecretUnsignedInteger: -@@ -139,6 +143,42 @@ class StoreId: - def __eq__(self, other: object) -> bool: ... - def __repr__(self) -> str: ... - -+class EddsaPrivateKey: -+ """Encodes a secret as an eddsa private key.""" -+ -+ value: bytearray -+ -+ def __init__(self, value: bytearray) -> None: ... -+ def __eq__(self, other: object) -> bool: ... -+ def __repr__(self) -> str: ... -+ -+class EddsaPublicKey: -+ """Encodes an eddsa public key.""" -+ -+ value: bytearray -+ -+ def __init__(self, value: bytearray) -> None: ... -+ def __eq__(self, other: object) -> bool: ... -+ def __repr__(self) -> str: ... -+ -+class EddsaSignature: -+ """Encodes an eddsa signature.""" -+ -+ value: Tuple[bytearray, bytearray] -+ -+ def __init__(self, value: Tuple[bytearray, bytearray]) -> None: ... -+ def __eq__(self, other: object) -> bool: ... -+ def __repr__(self) -> str: ... -+ -+class EddsaMessage: -+ """Encodes an eddsa message.""" -+ -+ value: bytearray -+ -+ def __init__(self, value: bytearray) -> None: ... -+ def __eq__(self, other: object) -> bool: ... -+ def __repr__(self) -> str: ... -+ - class ProgramRequirements: - """A program preprocessing requirements""" - -@@ -205,6 +245,9 @@ class NadaValuesClassification: - ecdsa_private_key_shares: int - """The number of ecdsa private key shares.""" - -+ ecdsa_signature_shares: int -+ """The number of ecdsa signature shares.""" -+ - class SecretMasker: - """A secret masker. This allows masking and unmasking secrets.""" - -diff --git a/client-core/src/encrypted_value.rs b/client-core/src/encrypted_value.rs -index de1181c..6c9ac62 100644 ---- a/client-core/src/encrypted_value.rs -+++ b/client-core/src/encrypted_value.rs -@@ -1,10 +1,11 @@ - use nillion_client_core::{ -- generic_ec::{curves::Secp256k1, serde::CurveName, NonZero, Point, Scalar, SecretScalar}, -+ generic_ec::{serde::CurveName, Curve, NonZero, Point, Scalar, SecretScalar}, - key_share::{DirtyCoreKeyShare, DirtyKeyInfo, Validate}, -- privatekey::EcdsaPrivateKeyShare, -- signature::EcdsaSignatureShare, -+ privatekey::ThresholdPrivateKeyShare, -+ signature::{EcdsaSignatureShare, EddsaSignature}, - values::{BlobPrimitiveType, Encoded, EncodedModularNumber, EncodedModulo, Encrypted, NadaValue}, - }; -+ - use pyo3::{ - exceptions::PyValueError, - pyclass, pymethods, -@@ -36,6 +37,10 @@ pub enum EncryptedNadaValue { - EcdsaPrivateKey { i: u16, x: Vec, shared_public_key: Vec, public_shares: Vec> }, - EcdsaPublicKey { value: Vec }, - StoreId { value: Vec }, -+ EddsaPrivateKey { i: u16, x: Vec, shared_public_key: Vec, public_shares: Vec> }, -+ EddsaPublicKey { value: Vec }, -+ EddsaSignature { value: Vec }, -+ EddsaMessage { value: Vec }, - } - - impl EncryptedNadaValue { -@@ -76,6 +81,22 @@ impl EncryptedNadaValue { - } - } - NadaValue::StoreId(value) => Self::StoreId { value: value.to_vec() }, -+ NadaValue::EddsaPrivateKey(key) => { -+ let key = key.into_inner(); -+ Self::EddsaPrivateKey { -+ i: key.i, -+ x: key.x.clone().into_inner().as_ref().to_le_bytes().to_vec(), -+ shared_public_key: key.key_info.shared_public_key.to_bytes(true).to_vec(), -+ public_shares: key.key_info.public_shares.iter().map(|s| s.to_bytes(true).to_vec()).collect(), -+ } -+ } -+ NadaValue::EddsaPublicKey(value) => Self::EddsaPublicKey { value: value.to_vec() }, -+ NadaValue::EddsaSignature(signature) => { -+ let mut out = vec![0u8; signature.serialized_len()]; -+ signature.signature.write_to_slice(&mut out); -+ Self::EddsaSignature { value: out } -+ } -+ NadaValue::EddsaMessage(message) => Self::EddsaMessage { value: message }, - _ => Err(PyValueError::new_err("Unsupported NadaValue variant for conversion to PyObject"))?, - }; - Ok(value) -@@ -153,8 +174,38 @@ impl EncryptedNadaValue { - } - .validate() - .map_err(|e| PyValueError::new_err(e.to_string()))?; -- NadaValue::new_ecdsa_private_key(EcdsaPrivateKeyShare::new(share)) -+ NadaValue::new_ecdsa_private_key(ThresholdPrivateKeyShare::new(share)) -+ } -+ E::EddsaPrivateKey { i, x, shared_public_key, public_shares } => { -+ let share = DirtyCoreKeyShare { -+ i, -+ key_info: DirtyKeyInfo { -+ curve: CurveName::new(), -+ shared_public_key: non_zero_point_from_bytes(&shared_public_key)?, -+ public_shares: public_shares -+ .iter() -+ .map(|s| non_zero_point_from_bytes(s)) -+ .collect::>()?, -+ vss_setup: None, -+ }, -+ x: non_zero_secret_scalar_from_bytes(&x)?, -+ } -+ .validate() -+ .map_err(|e| PyValueError::new_err(e.to_string()))?; -+ NadaValue::new_eddsa_private_key(ThresholdPrivateKeyShare::new(share)) -+ } -+ E::EddsaPublicKey { value } => { -+ let value: [u8; 32] = -+ value.try_into().map_err(|_| PyValueError::new_err("invalid public key length"))?; -+ NadaValue::new_eddsa_public_key(value) -+ } -+ E::EddsaSignature { value } => { -+ let signature = EddsaSignature::from_bytes(&value) -+ .map_err(|_| PyValueError::new_err("Failed to deserialize EdDSA signature"))?; -+ -+ NadaValue::new_eddsa_signature(signature) - } -+ E::EddsaMessage { value } => NadaValue::new_eddsa_message(value), - }; - Ok(value) - } -@@ -180,16 +231,20 @@ impl EncryptedNadaValue { - Self::EcdsaPrivateKey { .. } => "EcdsaPrivateKey {..}".into(), - Self::EcdsaPublicKey { .. } => "EcdsaPublicKey {..}".into(), - Self::StoreId { .. } => "StoreId {..}".into(), -+ Self::EddsaPrivateKey { .. } => "EddsaPrivateKey {..}".into(), -+ Self::EddsaPublicKey { .. } => "EddsaPublicKey {..}".into(), -+ Self::EddsaSignature { .. } => "EddsaSignature {..}".into(), -+ Self::EddsaMessage { .. } => "EddsaMessage {..}".into(), - } - } - } - --fn non_zero_point_from_bytes(bytes: &[u8]) -> PyResult>> { -+fn non_zero_point_from_bytes(bytes: &[u8]) -> PyResult>> { - let point = Point::from_bytes(bytes).map_err(|_| PyValueError::new_err("invalid bytes"))?; - NonZero::from_point(point).ok_or_else(|| PyValueError::new_err("point is zero")) - } - --fn non_zero_secret_scalar_from_bytes(bytes: &[u8]) -> PyResult>> { -+fn non_zero_secret_scalar_from_bytes(bytes: &[u8]) -> PyResult>> { - let scalar = SecretScalar::from_le_bytes(bytes).map_err(|_| PyValueError::new_err("invalid bytes"))?; - NonZero::from_secret_scalar(scalar).ok_or_else(|| PyValueError::new_err("scalar is zero")) - } -@@ -211,6 +266,10 @@ pub enum EncryptedNadaType { - EcdsaPrivateKey(), - EcdsaPublicKey(), - StoreId(), -+ EddsaPrivateKey(), -+ EddsaPublicKey(), -+ EddsaSignature(), -+ EddsaMessage(), - } - - impl EncryptedNadaType { -@@ -236,6 +295,10 @@ impl EncryptedNadaType { - T::EcdsaSignature => Self::EcdsaSignature(), - T::EcdsaPublicKey => Self::EcdsaPublicKey(), - T::StoreId => Self::StoreId(), -+ T::EddsaPrivateKey => Self::EddsaPrivateKey(), -+ T::EddsaPublicKey => Self::EddsaPublicKey(), -+ T::EddsaSignature => Self::EddsaSignature(), -+ T::EddsaMessage => Self::EddsaMessage(), - T::SecretInteger | T::SecretUnsignedInteger | T::SecretBoolean | T::NTuple { .. } | T::Object { .. } => { - return Err(PyValueError::new_err(format!("unsupported type: {t}",))); - } -@@ -270,6 +333,10 @@ impl EncryptedNadaType { - EncryptedNadaType::EcdsaPrivateKey() => T::EcdsaPrivateKey, - EncryptedNadaType::EcdsaPublicKey() => T::EcdsaPublicKey, - EncryptedNadaType::StoreId() => T::StoreId, -+ EncryptedNadaType::EddsaPrivateKey() => T::EddsaPrivateKey, -+ EncryptedNadaType::EddsaPublicKey() => T::EddsaPublicKey, -+ EncryptedNadaType::EddsaSignature() => T::EddsaSignature, -+ EncryptedNadaType::EddsaMessage() => T::EddsaMessage, - }; - Ok(output) - } -@@ -293,6 +360,10 @@ impl EncryptedNadaType { - Self::EcdsaPrivateKey { .. } => "EcdsaPrivateKey {..}".into(), - Self::EcdsaPublicKey { .. } => "EcdsaPublicKey {..}".into(), - Self::StoreId { .. } => "StoreId {..}".into(), -+ Self::EddsaPrivateKey { .. } => "EddsaPrivateKey {..}".into(), -+ Self::EddsaPublicKey { .. } => "EddsaPublicKey {..}".into(), -+ Self::EddsaSignature { .. } => "EddsaSignature {..}".into(), -+ Self::EddsaMessage { .. } => "EddsaMessage {..}".into(), - } - } - } -diff --git a/client-core/src/lib.rs b/client-core/src/lib.rs -index ff91bfe..9b1341a 100644 ---- a/client-core/src/lib.rs -+++ b/client-core/src/lib.rs -@@ -118,6 +118,9 @@ struct NadaValuesClassification { - - /// The number of ecdsa key shares - ecdsa_private_key_shares: u64, -+ -+ /// The number of ecdsa signatures shares -+ ecdsa_signature_shares: u64, - } - - #[pymethods] -@@ -132,7 +135,12 @@ impl NadaValuesClassification { - - impl From<::nillion_client_core::values::NadaValuesClassification> for NadaValuesClassification { - fn from(value: ::nillion_client_core::values::NadaValuesClassification) -> Self { -- Self { shares: value.shares, public: value.public, ecdsa_private_key_shares: value.ecdsa_private_key_shares } -+ Self { -+ shares: value.shares, -+ public: value.public, -+ ecdsa_private_key_shares: value.ecdsa_private_key_shares, -+ ecdsa_signature_shares: value.ecdsa_signature_shares, -+ } - } - } - -diff --git a/client-core/src/secrets_tests.rs b/client-core/src/secrets_tests.rs -index 5b8364d..14fa7da 100644 ---- a/client-core/src/secrets_tests.rs -+++ b/client-core/src/secrets_tests.rs -@@ -253,3 +253,114 @@ assert store_id.value == store_id_bytes # Compare against the same bytes - .unwrap(); - }) - } -+ -+#[test] -+fn test_eddsa_private_key() { -+ Python::with_gil(|py| { -+ Python::run_bound( -+ py, -+ r#" -+from nillion_client_core import * -+ -+eddsa_pk_ba = bytearray([84, 104, 105, 115, 32, 109, 101, 115, 115, 97, 103, 101, 32, 105, 115, 32, 101, 120, 97, 99, 116, 108, 121, 32, 51, 50, 32, 98, 121, 116, 101, 0]) -+eddsa_pk = EddsaPrivateKey(eddsa_pk_ba) -+assert eddsa_pk.value == eddsa_pk_ba -+"#, -+ None, -+ None, -+ ) -+ .unwrap(); -+ }) -+} -+ -+#[test] -+fn test_bad_eddsa_private_key() { -+ Python::with_gil(|py| { -+ Python::run_bound( -+ py, -+ r#" -+from nillion_client_core import * -+import os -+ -+# Check eddsa private key creation fails with bytearray size different from 32 -+try: -+ eddsa_pk_ba = bytearray(os.urandom(33)) -+ eddsa_pk = EddsaPrivateKey(eddsa_pk_ba) -+ raise AssertionError("Expected ValueError not raised for invalid key size") -+except ValueError as e: -+ assert "Private key format error" in str(e), "Unexpected error message" -+ -+# Check eddsa private key creation fails with 0 key -+try: -+ zero_key_ba = bytearray([0] * 32) -+ eddsa_pk = EddsaPrivateKey(zero_key_ba) -+ raise AssertionError("Expected ValueError not raised for zero key") -+except ValueError as e: -+ assert "Private key format error" in str(e), "Unexpected error message" -+"#, -+ None, -+ None, -+ ) -+ .unwrap(); -+ }) -+} -+ -+#[test] -+fn test_eddsa_public_key() { -+ Python::with_gil(|py| { -+ Python::run_bound( -+ py, -+ r#" -+from nillion_client_core import * -+import os -+ -+key_bytes = bytearray(os.urandom(32)) -+eddsa_pk = EddsaPublicKey(key_bytes) -+assert eddsa_pk.value == key_bytes # Compare against the same bytes -+"#, -+ None, -+ None, -+ ) -+ .unwrap(); -+ }) -+} -+ -+#[test] -+fn test_eddsa_message() { -+ Python::with_gil(|py| { -+ Python::run_bound( -+ py, -+ r#" -+from nillion_client_core import * -+import os -+ -+eddsa_msg_ba = bytearray(os.urandom(45)) -+eddsa_msg = EddsaMessage(eddsa_msg_ba) -+"#, -+ None, -+ None, -+ ) -+ .unwrap(); -+ }) -+} -+ -+#[test] -+fn test_eddsa_signature() { -+ Python::with_gil(|py| { -+ Python::run_bound( -+ py, -+ r#" -+from nillion_client_core import * -+import os -+ -+r = bytearray([6, 125, 237, 201, 123, 78, 227, 152, 251, 46, 236, 39, 224, 73, 18, 4, 103, 85, 109, 69, 181, 210, 56, 234, 17, 157, 209, 38, 242, 124, 237, 250]) -+z = bytearray(os.urandom(10)) -+eddsa_msg = EddsaSignature((r, z)) -+print("Eddsa signature is: ", eddsa_msg.value) -+"#, -+ None, -+ None, -+ ) -+ .unwrap(); -+ }) -+} -diff --git a/client-core/src/values/ecdsa_private_key.rs b/client-core/src/values/ecdsa_private_key.rs -index 84fd33a..3d459b0 100644 ---- a/client-core/src/values/ecdsa_private_key.rs -+++ b/client-core/src/values/ecdsa_private_key.rs -@@ -62,7 +62,7 @@ impl EcdsaPrivateKey { - /// Returns a new EcdsaPrivateKey. The byte array should be in big-endian format. - #[new] - fn new(value: &Bound<'_, PyByteArray>) -> PyResult { -- let ecdsa_private_key = privatekey::EcdsaPrivateKey::from_bytes(&value.to_vec()).map_err(|_| { -+ let ecdsa_private_key = privatekey::ThresholdPrivateKey::from_be_bytes(&value.to_vec()).map_err(|_| { - PyValueError::new_err( - "Private key format error. Check your ecdsa secret key is exactly 32 bytes and different from 0.", - ) -@@ -101,7 +101,7 @@ impl EcdsaPrivateKey { - .as_ecdsa_private_key() - .ok_or_else(|| PyValueError::new_err("expected ecdsa private key"))? - .clone() -- .to_bytes(); -+ .to_be_bytes(); - Ok(PyByteArray::new_bound(py, &bytes).into()) - } - -diff --git a/client-core/src/values/ecdsa_signature.rs b/client-core/src/values/ecdsa_signature.rs -index e48e0dc..e51e2cf 100644 ---- a/client-core/src/values/ecdsa_signature.rs -+++ b/client-core/src/values/ecdsa_signature.rs -@@ -82,12 +82,12 @@ impl EcdsaSignature { - self.inner.to_string() - } - -- /// Getter for the `r` inside a -+ /// Getter for the tuple `(r, s)` inside a - /// :py:class:`EcdsaSignature` instance. - /// - /// Returns - /// ------- -- /// int -+ /// tuple - /// The value of the private ecdsa key. - /// - /// Example -diff --git a/client-core/src/values/mod.rs b/client-core/src/values/mod.rs -index 5aa9e2f..7cf9766 100644 ---- a/client-core/src/values/mod.rs -+++ b/client-core/src/values/mod.rs -@@ -6,6 +6,10 @@ use self::{ - ecdsa_private_key::EcdsaPrivateKey, - ecdsa_public_key::EcdsaPublicKey, - ecdsa_signature::EcdsaSignature, -+ eddsa_message::EddsaMessage, -+ eddsa_private_key::EddsaPrivateKey, -+ eddsa_public_key::EddsaPublicKey, -+ eddsa_signature::EddsaSignature, - integer::{Integer, SecretInteger}, - store_id::StoreId, - unsigned_integer::{SecretUnsignedInteger, UnsignedInteger}, -@@ -21,6 +25,10 @@ pub mod ecdsa_digest_message; - pub mod ecdsa_private_key; - pub mod ecdsa_public_key; - pub mod ecdsa_signature; -+pub mod eddsa_message; -+pub mod eddsa_private_key; -+pub mod eddsa_public_key; -+pub mod eddsa_signature; - pub mod integer; - pub mod store_id; - pub mod unsigned_integer; -@@ -46,6 +54,10 @@ pub(crate) fn nada_value_clear_to_pyobject(py: Python<'_>, value: NadaValue { - Array::try_from(NadaValue::Array { values, inner_type })?.into_py(py) - } -+ NadaValue::EddsaPrivateKey(value) => EddsaPrivateKey::try_from(NadaValue::EddsaPrivateKey(value))?.into_py(py), -+ NadaValue::EddsaPublicKey(value) => EddsaPublicKey::try_from(NadaValue::EddsaPublicKey(value))?.into_py(py), -+ NadaValue::EddsaSignature(value) => EddsaSignature::try_from(NadaValue::EddsaSignature(value))?.into_py(py), -+ NadaValue::EddsaMessage(value) => EddsaMessage::try_from(NadaValue::EddsaMessage(value))?.into_py(py), - NadaValue::Tuple { .. } - | NadaValue::NTuple { .. } - | NadaValue::Object { .. } -@@ -85,6 +97,14 @@ fn pyany_to_nada_value_clear(value: Bound) -> Result, Py - value.inner - } else if let Ok(value) = value.extract::() { - value.inner -+ } else if let Ok(value) = value.extract::() { -+ value.inner -+ } else if let Ok(value) = value.extract::() { -+ value.inner -+ } else if let Ok(value) = value.extract::() { -+ value.inner -+ } else if let Ok(value) = value.extract::() { -+ value.inner - } else { - Err(PyValueError::new_err("Unsupported NadaValue variant for conversion to PyObject"))? - }; -@@ -127,5 +147,9 @@ pub fn add_module(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; -+ m.add_class::()?; -+ m.add_class::()?; -+ m.add_class::()?; -+ m.add_class::()?; - Ok(()) - } -diff --git a/client-proto/src/nillion_client_proto/nillion/compute/v1/stream/__init__.py b/client-proto/src/nillion_client_proto/nillion/compute/v1/stream/__init__.py -index b6d1945..7ad97dc 100644 ---- a/client-proto/src/nillion_client_proto/nillion/compute/v1/stream/__init__.py -+++ b/client-proto/src/nillion_client_proto/nillion/compute/v1/stream/__init__.py -@@ -8,6 +8,24 @@ from dataclasses import dataclass - import betterproto - - -+class ComputeType(betterproto.Enum): -+ """ -+ The type of compute performed. We currently support three types: -+ - GENERAL: A general compute that computes some Nada program. -+ - ECDSA_DKG: A specific compute operation for ECDSA distributed key generation. -+ - EDDSA_DKG: A specific compute operation for Eddsa distributed key generation. -+ """ -+ -+ GENERAL = 0 -+ """A general compute.""" -+ -+ ECDSA_DKG = 1 -+ """An ECDSA distributed key generation protocol.""" -+ -+ EDDSA_DKG = 2 -+ """An Eddsa distributed key generation protocol.""" -+ -+ - @dataclass(eq=False, repr=False) - class ComputeStreamMessage(betterproto.Message): - """A message for a compute stream.""" -@@ -22,3 +40,6 @@ class ComputeStreamMessage(betterproto.Message): - - bincode_message: bytes = betterproto.bytes_field(2) - """The VM message in bincode format.""" -+ -+ compute_type: "ComputeType" = betterproto.enum_field(3) -+ """The type of compute.""" -diff --git a/client-proto/src/nillion_client_proto/nillion/payments/v1/config/__init__.py b/client-proto/src/nillion_client_proto/nillion/payments/v1/config/__init__.py -index 483eb40..fb3f91b 100644 ---- a/client-proto/src/nillion_client_proto/nillion/payments/v1/config/__init__.py -+++ b/client-proto/src/nillion_client_proto/nillion/payments/v1/config/__init__.py -@@ -16,3 +16,6 @@ class PaymentsConfigResponse(betterproto.Message): - """ - The minimum amount of unil that can be added in a `Payments.add_funds` request. - """ -+ -+ credits_per_nil: int = betterproto.uint64_field(2) -+ """The number of credits one gets for every nil funded to an account.""" -diff --git a/client-proto/src/nillion_client_proto/nillion/values/v1/value/__init__.py b/client-proto/src/nillion_client_proto/nillion/values/v1/value/__init__.py -index 47b0a3d..e2b1dc3 100644 ---- a/client-proto/src/nillion_client_proto/nillion/values/v1/value/__init__.py -+++ b/client-proto/src/nillion_client_proto/nillion/values/v1/value/__init__.py -@@ -77,6 +77,20 @@ class Value(betterproto.Message): - store_id: "StoreId" = betterproto.message_field(14, group="value") - """A store id.""" - -+ eddsa_private_key_share: "EddsaPrivateKeyShare" = betterproto.message_field( -+ 15, group="value" -+ ) -+ """An Eddsa private key share.""" -+ -+ eddsa_signature: "EddsaSignature" = betterproto.message_field(16, group="value") -+ """An Eddsa signature.""" -+ -+ eddsa_message: "EddsaMessage" = betterproto.message_field(17, group="value") -+ """An Eddsa message.""" -+ -+ eddsa_public_key: "EddsaPublicKey" = betterproto.message_field(18, group="value") -+ """An Eddsa public key.""" -+ - - @dataclass(eq=False, repr=False) - class PublicInteger(betterproto.Message): -@@ -161,7 +175,7 @@ class EcdsaPublicKey(betterproto.Message): - """An ECDSA public key.""" - - public_key: bytes = betterproto.bytes_field(1) -- """The public key, in compressed form.""" -+ """The public key.""" - - - @dataclass(eq=False, repr=False) -@@ -172,6 +186,47 @@ class StoreId(betterproto.Message): - """The store id.""" - - -+@dataclass(eq=False, repr=False) -+class EddsaPrivateKeyShare(betterproto.Message): -+ """An Eddsa private key share.""" -+ -+ i: int = betterproto.uint32_field(1) -+ """Index of local party in key generation protocol.""" -+ -+ x: bytes = betterproto.bytes_field(2) -+ """The secret share x.""" -+ -+ shared_public_key: bytes = betterproto.bytes_field(3) -+ """Public key corresponding to shared secret key, in compressed form.""" -+ -+ public_shares: List[bytes] = betterproto.bytes_field(4) -+ """Public shares of all signers sharing the key, in compressed form.""" -+ -+ -+@dataclass(eq=False, repr=False) -+class EddsaSignature(betterproto.Message): -+ """An Eddsa signature.""" -+ -+ signature: bytes = betterproto.bytes_field(1) -+ """The signature.""" -+ -+ -+@dataclass(eq=False, repr=False) -+class EddsaMessage(betterproto.Message): -+ """An Eddsa message.""" -+ -+ message: bytes = betterproto.bytes_field(1) -+ """The message.""" -+ -+ -+@dataclass(eq=False, repr=False) -+class EddsaPublicKey(betterproto.Message): -+ """An Eddsa public key.""" -+ -+ public_key: bytes = betterproto.bytes_field(1) -+ """The public key.""" -+ -+ - @dataclass(eq=False, repr=False) - class ShamirSharesBlob(betterproto.Message): - """Shamir shares of a blob.""" -@@ -248,6 +303,26 @@ class ValueType(betterproto.Message): - ) - """A store id.""" - -+ eddsa_private_key_share: "betterproto_lib_google_protobuf.Empty" = ( -+ betterproto.message_field(14, group="value_type") -+ ) -+ """An Eddsa private key share.""" -+ -+ eddsa_signature: "betterproto_lib_google_protobuf.Empty" = ( -+ betterproto.message_field(15, group="value_type") -+ ) -+ """An Eddsa signature.""" -+ -+ eddsa_message: "betterproto_lib_google_protobuf.Empty" = betterproto.message_field( -+ 16, group="value_type" -+ ) -+ """An Eddsa message.""" -+ -+ eddsa_public_key: "betterproto_lib_google_protobuf.Empty" = ( -+ betterproto.message_field(17, group="value_type") -+ ) -+ """An Eddsa public key.""" -+ - - @dataclass(eq=False, repr=False) - class ArrayType(betterproto.Message): -diff --git a/nilvm b/nilvm -index c92a3b2..b5b4fe4 160000 ---- a/nilvm -+++ b/nilvm -@@ -1 +1 @@ --Subproject commit c92a3b2c3821f25915eb280513f402b3e04f18ff -+Subproject commit b5b4fe4588211a3c5cf7139fd16d2fd0c3492aa0 -diff --git a/src/nillion_client/__init__.py b/src/nillion_client/__init__.py -index 181cc8a..0fb7fce 100644 ---- a/src/nillion_client/__init__.py -+++ b/src/nillion_client/__init__.py -@@ -23,6 +23,10 @@ from nillion_client_core import ( - EcdsaSignature, - EcdsaPublicKey, - StoreId, -+ EddsaPrivateKey, -+ EddsaPublicKey, -+ EddsaSignature, -+ EddsaMessage, - ) - from nillion_client_proto.nillion.preprocessing.v1.element import PreprocessingElement - from cosmpy.crypto.keypairs import PrivateKey as NilChainPrivateKey -@@ -57,6 +61,10 @@ __all__ = [ - "EcdsaSignature", - "EcdsaPublicKey", - "StoreId", -+ "EddsaPrivateKey", -+ "EddsaPublicKey", -+ "EddsaSignature", -+ "EddsaMessage", - "PreprocessingElement", - "PrivateKey", - "NilChainPrivateKey", -diff --git a/src/nillion_client/values.py b/src/nillion_client/values.py -index 476645d..0034e9b 100644 ---- a/src/nillion_client/values.py -+++ b/src/nillion_client/values.py -@@ -12,6 +12,10 @@ from nillion_client_proto.nillion.values.v1.value import ( - EcdsaPublicKey, - EcdsaPrivateKeyShare, - EcdsaSignatureShare, -+ EddsaMessage, -+ EddsaPrivateKeyShare, -+ EddsaPublicKey, -+ EddsaSignature, - NamedValue, - PublicInteger, - ShamirShare, -@@ -103,6 +107,25 @@ def encrypted_nada_value_to_protobuf(value: EncryptedNadaValue) -> Value: - store_id=bytes(value.value), - ) - ) -+ case EncryptedNadaValue.EddsaMessage(): # type: ignore -+ return Value(eddsa_message=EddsaMessage(message=bytes(value.value))) -+ case EncryptedNadaValue.EddsaSignature(): # type: ignore -+ return Value(eddsa_signature=EddsaSignature(signature=bytes(value.value))) -+ case EncryptedNadaValue.EddsaPrivateKey(): # type: ignore -+ return Value( -+ eddsa_private_key_share=EddsaPrivateKeyShare( -+ i=value.i, -+ x=bytes(value.x), -+ shared_public_key=bytes(value.shared_public_key), -+ public_shares=[bytes(s) for s in value.public_shares], -+ ) -+ ) -+ case EncryptedNadaValue.EddsaPublicKey(): # type: ignore -+ return Value( -+ eddsa_public_key=EddsaPublicKey( -+ public_key=bytes(value.value), -+ ) -+ ) - case _: - raise Exception(f"unsupported type: {value}") - -@@ -157,6 +180,23 @@ def encrypted_nada_value_from_protobuf(value: Value) -> EncryptedNadaValue: - return EncryptedNadaValue.EcdsaPublicKey( - value=bytes(value.public_key), - ) -+ case Value(eddsa_message=value): -+ return EncryptedNadaValue.EddsaMessage(value=bytes(value.message)) -+ case Value(eddsa_signature=value): -+ return EncryptedNadaValue.EddsaSignature( -+ value=bytes(value.signature), -+ ) -+ case Value(eddsa_private_key_share=value): -+ return EncryptedNadaValue.EddsaPrivateKey( -+ i=value.i, -+ x=list(value.x), -+ shared_public_key=list(value.shared_public_key), -+ public_shares=[list(s) for s in value.public_shares], -+ ) -+ case Value(eddsa_public_key=value): -+ return EncryptedNadaValue.EddsaPublicKey( -+ value=bytes(value.public_key), -+ ) - case Value(store_id=value): - return EncryptedNadaValue.StoreId( - value=bytes(value.store_id), -@@ -203,6 +243,14 @@ def encrypted_nada_type_to_protobuf(nada_type: EncryptedNadaType) -> ValueType: - return ValueType(ecdsa_public_key=Empty()) - case EncryptedNadaType.StoreId(): # type: ignore - return ValueType(store_id=Empty()) -+ case EncryptedNadaType.EddsaMessage(): # type: ignore -+ return ValueType(eddsa_message=Empty()) -+ case EncryptedNadaType.EddsaSignature(): # type: ignore -+ return ValueType(eddsa_signature=Empty()) -+ case EncryptedNadaType.EddsaPrivateKey(): # type: ignore -+ return ValueType(eddsa_private_key_share=Empty()) -+ case EncryptedNadaType.EddsaPublicKey(): # type: ignore -+ return ValueType(eddsa_public_key=Empty()) - case _: - raise Exception(f"unsupported encrypted type: {nada_type}") - -@@ -241,5 +289,13 @@ def encrypted_nada_type_from_protobuf(nada_type: ValueType) -> EncryptedNadaType - return EncryptedNadaType.EcdsaPublicKey() # type: ignore - case ValueType(store_id=_): - return EncryptedNadaType.StoreId() # type: ignore -+ case ValueType(eddsa_message=_): -+ return EncryptedNadaType.EddsaMessage() # type: ignore -+ case ValueType(eddsa_signature=_): -+ return EncryptedNadaType.EddsaSignature() # type: ignore -+ case ValueType(eddsa_private_key_share=_): -+ return EncryptedNadaType.EddsaPrivateKey() # type: ignore -+ case ValueType(eddsa_public_key=_): -+ return EncryptedNadaType.EddsaPublicKey() # type: ignore - case _: - raise Exception(f"unsupported type: {nada_type}") -diff --git a/src/nillion_client/vm_operation.py b/src/nillion_client/vm_operation.py -index 5c4b138..1545928 100644 ---- a/src/nillion_client/vm_operation.py -+++ b/src/nillion_client/vm_operation.py -@@ -20,6 +20,10 @@ from nillion_client_core import ( - EcdsaDigestMessage, - EcdsaSignature, - EcdsaPublicKey, -+ EddsaPrivateKey, -+ EddsaSignature, -+ EddsaPublicKey, -+ EddsaMessage, - StoreId, - ) - -@@ -55,6 +59,10 @@ NadaValue = Union[ - EcdsaDigestMessage, - EcdsaSignature, - EcdsaPublicKey, -+ EddsaPrivateKey, -+ EddsaSignature, -+ EddsaPublicKey, -+ EddsaMessage, - StoreId, - ] - -diff --git a/tests/test_nillion_client.py b/tests/test_nillion_client.py -index fae2710..b3c55e1 100644 ---- a/tests/test_nillion_client.py -+++ b/tests/test_nillion_client.py -@@ -11,6 +11,7 @@ from cryptography.hazmat.primitives.asymmetric.ec import ( - SECP256K1, - EllipticCurvePublicKey, - ) -+from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey - - from nillion_client.errors import PartyError - from nillion_client.ids import UserId -@@ -118,12 +119,128 @@ async def test_store_retrieve_all_value_types(devnet_setup): - "array": nillion_client.Array( - [nillion_client.Integer(1), nillion_client.Integer(2)] - ), -- "key": nillion_client.EcdsaPrivateKey(bytearray(os.urandom(32))), -- "message": nillion_client.EcdsaDigestMessage(bytearray(os.urandom(32))), -- "signature": nillion_client.EcdsaSignature( -+ "ecdsa_key": nillion_client.EcdsaPrivateKey(bytearray(os.urandom(32))), -+ "ecdsa_message": nillion_client.EcdsaDigestMessage(bytearray(os.urandom(32))), -+ "ecdsa_signature": nillion_client.EcdsaSignature( - (bytearray([1, 2, 3]), bytearray([1, 2, 3])) - ), -- "public_key": nillion_client.EcdsaPublicKey(bytearray(os.urandom(33))), -+ "ecdsa_public_key": nillion_client.EcdsaPublicKey(bytearray(os.urandom(33))), -+ "eddsa_key": nillion_client.EddsaPrivateKey(bytearray([1] * 32)), -+ "eddsa_message": nillion_client.EddsaMessage(bytearray(os.urandom(32))), -+ "eddsa_signature": nillion_client.EddsaSignature( -+ ( -+ bytearray( -+ [ -+ 228, -+ 118, -+ 63, -+ 53, -+ 138, -+ 161, -+ 20, -+ 164, -+ 93, -+ 86, -+ 233, -+ 11, -+ 211, -+ 204, -+ 186, -+ 63, -+ 255, -+ 174, -+ 220, -+ 173, -+ 222, -+ 58, -+ 64, -+ 79, -+ 108, -+ 173, -+ 130, -+ 1, -+ 134, -+ 44, -+ 244, -+ 104, -+ ] -+ ), -+ bytearray( -+ [ -+ 137, -+ 73, -+ 233, -+ 168, -+ 34, -+ 64, -+ 148, -+ 185, -+ 177, -+ 91, -+ 184, -+ 21, -+ 246, -+ 82, -+ 65, -+ 207, -+ 83, -+ 158, -+ 44, -+ 181, -+ 199, -+ 94, -+ 83, -+ 178, -+ 88, -+ 238, -+ 210, -+ 220, -+ 10, -+ 49, -+ 154, -+ 1, -+ ] -+ ), -+ ) -+ ), -+ "eddsa_public_key": nillion_client.EddsaPublicKey( -+ bytearray( -+ [ -+ 186, -+ 236, -+ 247, -+ 198, -+ 7, -+ 225, -+ 204, -+ 147, -+ 116, -+ 47, -+ 207, -+ 45, -+ 149, -+ 49, -+ 212, -+ 168, -+ 136, -+ 145, -+ 98, -+ 150, -+ 152, -+ 122, -+ 50, -+ 91, -+ 141, -+ 227, -+ 182, -+ 233, -+ 8, -+ 245, -+ 72, -+ 38, -+ ] -+ ) -+ ), - "store_id": nillion_client.StoreId(bytearray(os.urandom(16))), - } - # nest all the types above under an array -@@ -467,6 +584,193 @@ async def test_ecdsa_compute(devnet_setup): - client.close() - - -+@pytest.mark.asyncio -+async def test_eddsa_compute(devnet_setup): -+ """Test that we can generate an eddsa private key, store it, and use it to sign a message""" -+ -+ client = await new_client(devnet_setup) -+ -+ ########################################### -+ # # -+ # EDDSA CONFIG NAMES # -+ # # -+ ########################################### -+ -+ # program id -+ teddsa_sign_program_id = "builtin/teddsa_sign" -+ teddsa_dks_program_id = "builtin/teddsa_dkg" -+ # input store name -+ teddsa_message_name = "teddsa_message" -+ # output store name -+ teddsa_signature_name = "teddsa_signature" -+ teddsa_public_key_name = "teddsa_public_key" -+ teddsa_store_id_name = "teddsa_store_id" -+ # party names -+ teddsa_key_party = "teddsa_key_party" -+ teddsa_message_party = "teddsa_message_party" -+ teddsa_output_party = "teddsa_output_party" -+ teddsa_private_key_store_id_party = "teddsa_private_key_store_id_party" -+ teddsa_public_key_party = "teddsa_public_key_party" -+ -+ ########################################### -+ # # -+ # EDDSA MESSAGE # -+ # # -+ ########################################### -+ -+ ##### GENERATE MESSAGE -+ -+ # The message to sign -+ message = b"A deep message with a deep number: 42." -+ -+ teddsa_value = bytearray(message) -+ # eddsa message to be used for signing -+ my_eddsa_message = { -+ teddsa_message_name: nillion_client.EddsaMessage(teddsa_value), -+ } -+ -+ ########################################### -+ # # -+ # EDDSA DKG # -+ # # -+ ########################################### -+ -+ ##### EDDSA DKG -+ print("\n-----EDDSA DKG") -+ -+ # Bind the parties in the computation to the client to set input and output parties -+ input_bindings = [] -+ output_bindings = [ -+ nillion_client.OutputPartyBinding( -+ teddsa_private_key_store_id_party, [client.user_id] -+ ), -+ nillion_client.OutputPartyBinding(teddsa_public_key_party, [client.user_id]), -+ ] -+ -+ # Create a computation time secret to use -+ compute_time_values = {} -+ -+ # Compute, passing in the compute time values as well as the previously uploaded value. -+ print(f"Invoking DKG using program {teddsa_dks_program_id}") -+ compute_id = await client.compute( -+ teddsa_dks_program_id, -+ input_bindings, -+ output_bindings, -+ values=compute_time_values, -+ value_ids=[], -+ ).invoke() -+ -+ # 6. Return the computation result -+ result = await client.retrieve_compute_results(compute_id).invoke() -+ # Get the store ID and public key from results -+ private_key_store_id = result[teddsa_store_id_name].value -+ teddsa_public_key_value = result[teddsa_public_key_name].value -+ # Ensure private_key_store_id is a bytearray and convert to bytes -+ if isinstance(private_key_store_id, bytearray): # this is required to pass pyright -+ private_key_store_id_bytes = bytes(private_key_store_id) -+ else: -+ raise TypeError("private_key_store_id must be a bytearray") -+ ecdsa_private_key_store_id = uuid.UUID(bytes=private_key_store_id_bytes) -+ # Ensure tecdsa_public_key_value is a bytearray and convert to bytes -+ if isinstance( -+ teddsa_public_key_value, bytearray -+ ): # this is required to pass pyright -+ teddsa_public_key = bytes(teddsa_public_key_value) -+ else: -+ raise TypeError("teddsa_public_key must be a bytearray") -+ -+ ########################################### -+ # # -+ # ECDSA SIGNING # -+ # # -+ ########################################### -+ -+ ##### EDDSA SIGNING -+ print("-----EDDSA SIGNING") -+ -+ # Bind the parties in the computation to the client to set input and output parties -+ input_bindings = [ -+ nillion_client.InputPartyBinding(teddsa_key_party, client.user_id), -+ nillion_client.InputPartyBinding(teddsa_message_party, client.user_id), -+ ] -+ output_bindings = [ -+ nillion_client.OutputPartyBinding(teddsa_output_party, [client.user_id]) -+ ] -+ -+ # Create a computation time secret to use -+ compute_time_values = my_eddsa_message -+ -+ # Compute, passing in the compute time values as well as the previously uploaded value. -+ compute_id = await client.compute( -+ teddsa_sign_program_id, -+ input_bindings, -+ output_bindings, -+ values=my_eddsa_message, -+ value_ids=[ecdsa_private_key_store_id], -+ ).invoke() -+ # 6. Return the computation result -+ result = await client.retrieve_compute_results(compute_id).invoke() -+ signature_value = result[teddsa_signature_name] -+ public_key_value = result[teddsa_public_key_name] -+ message_value = result[teddsa_message_name] -+ -+ # Ensure the signature is of the correct type -+ if isinstance(signature_value, nillion_client.EddsaSignature): -+ signature_output = signature_value -+ else: -+ raise TypeError("Cannot convert to EddsaSignature.") -+ -+ # Ensure the public key is of the correct type -+ if isinstance(public_key_value, nillion_client.EddsaPublicKey): -+ public_key_output = public_key_value -+ else: -+ raise TypeError("Cannot convert to EddsaPublicKey.") -+ -+ # Ensure the message is of the correct type -+ if isinstance(message_value, nillion_client.EddsaMessage): -+ message_output = message_value -+ else: -+ raise TypeError("Cannot convert to EddsaMessage.") -+ -+ ########################################### -+ # # -+ # ECDSA VERIFICATION # -+ # # -+ ########################################### -+ -+ ##### OUTPUT VERIFICATION -+ # Verify the public key output from dkg is the same as the one given by signing -+ assert teddsa_public_key == public_key_output.value -+ -+ # Verify the message output from signing is the same as input for signing -+ assert message_output.value == message -+ -+ ##### EDDSA VERIFICATION -+ print("-----EDDSA VERIFICATION") -+ -+ # Transform the result signature to bytes for verification -+ (r, z) = signature_output.value -+ # Convert r and z to bytes - note that EdDSA uses concatenated format (R || Z) -+ r_bytes = bytes(r) -+ z_bytes = bytes(z) -+ # Ed25519 signatures are simply the concatenation of R and Z components -+ signature_bytes = r_bytes + z_bytes -+ -+ # Create the public key object from the raw bytes -+ try: -+ ed25519_public_key = Ed25519PublicKey.from_public_bytes(teddsa_public_key) -+ except Exception as e: -+ raise ValueError(f"Invalid Ed25519 public key format: {str(e)}") -+ -+ # Verify the signature -+ try: -+ ed25519_public_key.verify(signature_bytes, message) -+ except Exception as e: -+ raise ValueError(f"Signature is invalid: {str(e)}") -+ -+ client.close() -+ -+ - @pytest.mark.asyncio - async def test_complex_compute(devnet_setup): - client_party1 = await new_client(devnet_setup)