diff --git a/.gitignore b/.gitignore index 4be3650..49ca3ef 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,12 @@ result # MSVC Windows builds of rustc generate these, which store debugging information *.pdb + +# FFI C examples +rln/ffi_c_examples/main +rln/ffi_c_examples/rln.h +rln/ffi_c_examples/database + +# FFI Nim examples +rln/ffi_nim_examples/main +rln/ffi_nim_examples/database diff --git a/Cargo.lock b/Cargo.lock index 6bd89c8..5db4c2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,9 +16,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -37,15 +37,15 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "ark-bn254" @@ -90,7 +90,7 @@ checksum = "e7e89fe77d1f0f4fe5b96dfc940923d88d17b6a773808124f21e764dfb063c6a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -106,7 +106,7 @@ dependencies = [ "ark-std", "educe", "fnv", - "hashbrown", + "hashbrown 0.15.5", "itertools 0.13.0", "num-bigint", "num-integer", @@ -143,7 +143,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -156,7 +156,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -187,7 +187,7 @@ dependencies = [ "ark-std", "educe", "fnv", - "hashbrown", + "hashbrown 0.15.5", "rayon", ] @@ -242,7 +242,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -288,9 +288,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.4" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "blake2" @@ -336,9 +336,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "ciborium" @@ -369,18 +369,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.47" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" +checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.47" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" +checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" dependencies = [ "anstyle", "clap_lex", @@ -388,9 +388,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "cpufeatures" @@ -508,9 +508,9 @@ dependencies = [ [[package]] name = "document-features" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ "litrs", ] @@ -524,7 +524,7 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -535,24 +535,30 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "enum-ordinalize" -version = "4.3.0" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" dependencies = [ "enum-ordinalize-derive", ] [[package]] name = "enum-ordinalize-derive" -version = "4.3.1" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "errno" version = "0.3.14" @@ -563,6 +569,41 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "ext-trait" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d772df1c1a777963712fb68e014235e80863d6a91a85c4e06ba2d16243a310e5" +dependencies = [ + "ext-trait-proc_macros", +] + +[[package]] +name = "ext-trait-proc_macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab7934152eaf26aa5aa9f7371408ad5af4c31357073c9e84c3b9d7f11ad639a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "extension-traits" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a296e5a895621edf9fa8329c83aa1cb69a964643e36cf54d8d7a69b789089537" +dependencies = [ + "ext-trait", +] + +[[package]] +name = "extern-c" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320bea982e85d42441eb25c49b41218e7eaa2657e8f90bc4eca7437376751e23" + [[package]] name = "fastrand" version = "2.3.0" @@ -596,9 +637,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", @@ -612,29 +653,30 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.7+wasi-0.2.4", + "wasip2", ] [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -646,6 +688,12 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + [[package]] name = "hex" version = "0.4.3" @@ -658,6 +706,16 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[package]] +name = "indexmap" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", +] + [[package]] name = "instant" version = "0.1.13" @@ -667,6 +725,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "inventory" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" +dependencies = [ + "rustversion", +] + [[package]] name = "itertools" version = "0.13.0" @@ -693,9 +760,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" -version = "0.3.80" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -718,9 +785,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libm" @@ -736,17 +803,16 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litrs" -version = "0.4.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] @@ -757,10 +823,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] -name = "memchr" -version = "2.7.5" +name = "macro_rules_attribute" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "cf0c9b980bf4f3a37fd7b1c066941dd1b1d0152ce6ee6e8fe8c49b9f6810d862" +dependencies = [ + "macro_rules_attribute-proc_macro", + "paste", +] + +[[package]] +name = "macro_rules_attribute-proc_macro" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58093314a45e00c77d5c508f76e77c3396afbbc0d01506e7fae47b018bac2b1d" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "merlin" @@ -890,21 +972,40 @@ dependencies = [ ] [[package]] -name = "proc-macro2" -version = "1.0.101" +name = "prettyplease" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" +checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "num-traits", "rand 0.9.2", "rand_chacha 0.9.0", @@ -932,14 +1033,14 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -1045,9 +1146,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.2" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -1057,9 +1158,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.10" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -1068,9 +1169,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rln" @@ -1097,6 +1198,7 @@ dependencies = [ "rand_chacha 0.3.1", "rayon", "ruint", + "safer-ffi", "serde", "serde_json", "tempfile", @@ -1128,13 +1230,22 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.9.4", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys", @@ -1153,6 +1264,38 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "safer-ffi" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435fdd58b61a6f1d8545274c1dfa458e905ff68c166e65e294a0130ef5e675bd" +dependencies = [ + "extern-c", + "inventory", + "libc", + "macro_rules_attribute", + "paste", + "safer_ffi-proc_macros", + "scopeguard", + "stabby", + "uninit", + "unwind_safe", + "with_builtin_macros", +] + +[[package]] +name = "safer_ffi-proc_macros" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f25be5ba5f319542edb31925517e0380245ae37df50a9752cdbc05ef948156" +dependencies = [ + "macro_rules_attribute", + "prettyplease", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "same-file" version = "1.0.6" @@ -1169,10 +1312,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "serde" -version = "1.0.225" +name = "semver" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", @@ -1180,22 +1329,22 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.225" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -1222,6 +1371,12 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + [[package]] name = "sled" version = "0.34.7" @@ -1244,6 +1399,41 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "stabby" +version = "36.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b7e94eaf470c2e76b5f15fb2fb49714471a36cc512df5ee231e62e82ec79f8" +dependencies = [ + "rustversion", + "stabby-abi", +] + +[[package]] +name = "stabby-abi" +version = "36.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc7a63b8276b54e51bfffe3d85da56e7906b2dcfcb29018a8ab666c06734c1a" +dependencies = [ + "rustc_version", + "rustversion", + "sha2-const-stable", + "stabby-macros", +] + +[[package]] +name = "stabby-macros" +version = "36.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eecb7ec5611ec93ec79d120fbe55f31bea234dc1bed1001d4a071bb688651615" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "rand 0.8.5", + "syn 1.0.109", +] + [[package]] name = "subtle" version = "2.6.1" @@ -1263,9 +1453,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" dependencies = [ "proc-macro2", "quote", @@ -1274,12 +1464,12 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.22.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fa4d11fadde498443cca10fd3ac23c951f0dc59e080e9f4b93d4df4e4eea53" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "rustix", "windows-sys", @@ -1287,22 +1477,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -1324,6 +1514,36 @@ dependencies = [ "serde_json", ] +[[package]] +name = "toml_datetime" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.23.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow", +] + [[package]] name = "tracing" version = "0.1.41" @@ -1343,7 +1563,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -1367,9 +1587,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unarray" @@ -1379,9 +1599,24 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.19" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "uninit" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e130f2ed46ca5d8ec13c7ff95836827f92f5f5f37fd2b2bf16f33c408d98bb6" +dependencies = [ + "extension-traits", +] + +[[package]] +name = "unwind_safe" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0976c77def3f1f75c4ef892a292c31c0bbe9e3d0702c63044d7c76db298171a3" [[package]] name = "vacp2p_pmtree" @@ -1417,15 +1652,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" @@ -1437,9 +1663,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.103" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", @@ -1448,25 +1674,11 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.103" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.106", - "wasm-bindgen-shared", -] - [[package]] name = "wasm-bindgen-macro" -version = "0.2.103" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1474,31 +1686,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.103" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.106", - "wasm-bindgen-backend", + "syn 2.0.108", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.103" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.80" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbe734895e869dc429d78c4b433f8d17d95f8d05317440b4fad5ab2d33e596dc" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -1537,25 +1749,54 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-link" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "with_builtin_macros" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a59d55032495429b87f9d69954c6c8602e4d3f3e0a747a12dea6b0b23de685da" +dependencies = [ + "with_builtin_macros-proc_macros", +] + +[[package]] +name = "with_builtin_macros-proc_macros" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15bd7679c15e22924f53aee34d4e448c45b674feb6129689af88593e129f8f42" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "zerocopy" version = "0.8.27" @@ -1573,14 +1814,14 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] @@ -1593,7 +1834,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] diff --git a/rln-cli/README.md b/rln-cli/README.md index 8289e17..c265ba4 100644 --- a/rln-cli/README.md +++ b/rln-cli/README.md @@ -9,23 +9,23 @@ It also contain: ## Configuration -The CLI can be configured using a JSON configuration file (see the [example](example.config.json)). +The CLI can be configured using a JSON configuration file (see the [example](../rln/resources/tree_depth_20/config.json)). You can specify the configuration file path using the `RLN_CONFIG_PATH` environment variable: ```bash -export RLN_CONFIG_PATH=example.config.json +export RLN_CONFIG_PATH=../rln/resources/tree_depth_20/config.json ``` Alternatively, you can provide the configuration file path as an argument for each command: ```bash -RLN_CONFIG_PATH=example.config.json cargo run -- [OPTIONS] +RLN_CONFIG_PATH=../rln/resources/tree_depth_20/config.json cargo run -- [OPTIONS] ``` If the configuration file is empty, default settings will be used, but the tree data folder will be temporary and not saved to the preconfigured path. -We recommend using the example config, as all commands (except `new` and `create-with-params`) require an initialized RLN instance. +We recommend using the default config, as all commands (except `new` and `create-with-params`) require an initialized RLN instance. ## Relay Example diff --git a/rln-wasm/tests/browser.rs b/rln-wasm/tests/browser.rs index 4ec16ee..52fd3aa 100644 --- a/rln-wasm/tests/browser.rs +++ b/rln-wasm/tests/browser.rs @@ -1,7 +1,7 @@ #![cfg(target_arch = "wasm32")] #[cfg(test)] -mod tests { +mod test { use js_sys::{BigInt as JsBigInt, Date, Object, Uint8Array}; use rln::circuit::{Fr, TEST_TREE_DEPTH}; use rln::hashers::{hash_to_field_le, poseidon_hash, PoseidonHash}; diff --git a/rln-wasm/tests/node.rs b/rln-wasm/tests/node.rs index 85ffaa6..9e7ff71 100644 --- a/rln-wasm/tests/node.rs +++ b/rln-wasm/tests/node.rs @@ -2,7 +2,7 @@ #![cfg(target_arch = "wasm32")] #[cfg(test)] -mod tests { +mod test { use js_sys::{BigInt as JsBigInt, Date, Object, Uint8Array}; use rln::circuit::{Fr, TEST_TREE_DEPTH}; use rln::hashers::{hash_to_field_le, poseidon_hash, PoseidonHash}; diff --git a/rln/Cargo.toml b/rln/Cargo.toml index 67adcd6..3ee5ae3 100644 --- a/rln/Cargo.toml +++ b/rln/Cargo.toml @@ -45,6 +45,10 @@ zeroize = "1.8" tempfile = "3.21.0" utils = { package = "zerokit_utils", version = "0.7.0", path = "../utils", default-features = false } +# FFI +safer-ffi.version = "0.1" +safer-ffi.features = [] # you may add some later on. + # serialization prost = "0.14.1" serde_json = "1.0.141" @@ -71,6 +75,7 @@ parallel = [ fullmerkletree = [] # Pre-allocated tree, fastest access optimalmerkletree = [] # Sparse storage, memory efficient pmtree-ft = ["utils/pmtree-ft"] # Persistent storage, disk-based +headers = ["safer-ffi/headers"] # Generate C header file with safer-ffi [[bench]] name = "pmtree_benchmark" @@ -83,3 +88,7 @@ harness = false [package.metadata.docs.rs] all-features = true + +[[bin]] +name = "generate-headers" +required-features = ["headers"] # Do not build unless generating headers. diff --git a/rln/Makefile.toml b/rln/Makefile.toml index 07bf8fd..4af3d6d 100644 --- a/rln/Makefile.toml +++ b/rln/Makefile.toml @@ -8,7 +8,15 @@ args = ["test", "--release", "--", "--nocapture"] [tasks.test_stateless] command = "cargo" -args = ["test", "--release", "--no-default-features", "--features", "stateless"] +args = [ + "test", + "--release", + "--no-default-features", + "--features", + "stateless", + "--", + "--nocapture", +] [tasks.bench] command = "cargo" diff --git a/rln/README.md b/rln/README.md index 88d1f54..8e63e3f 100644 --- a/rln/README.md +++ b/rln/README.md @@ -105,7 +105,7 @@ fn main() { // We get the public outputs returned by the circuit evaluation // The byte vector `proof_data` is serialized as - // `[ zk-proof | tree_root | external_nullifier | share_x | share_y | nullifier ]`. + // `[ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]`. let proof_data = output_buffer.into_inner(); // 8. Verify a RLN proof @@ -304,6 +304,27 @@ chmod +x ./convert_zkey.sh ./convert_zkey.sh ``` +## FFI Interface + +RLN provides C-compatible bindings for integration with C, C++, Nim, and other languages through [safer_ffi](https://getditto.github.io/safer_ffi/). + +The FFI layer is organized into several modules: + +- [`ffi_rln.rs`](./src/ffi/ffi_rln.rs) – Implements core RLN functionality, including initialization functions, proof generation, and proof verification. +- [`ffi_tree.rs`](./src/ffi/ffi_tree.rs) – Provides all tree-related operations and helper functions for Merkle tree management. +- [`ffi_utils.rs`](./src/ffi/ffi_utils.rs) – Contains all utility functions and structure definitions used across the FFI layer. + +### Examples + +Working examples demonstrating proof generation, proof verification and slashing in C and Nim: + +- [C example](./ffi_c_examples/main.c) and [README](./ffi_c_examples/Readme.md) +- [Nim example](./ffi_nim_examples/main.nim) and [README](./ffi_nim_examples/Readme.md) + +### Memory Management + +All heap-allocated objects must be explicitly freed using their corresponding `_free` functions to prevent memory leaks. + ## Get involved Zerokit RLN public and FFI APIs allow interaction with many more features than what briefly showcased above. diff --git a/rln/ffi_c_examples/Readme.md b/rln/ffi_c_examples/Readme.md new file mode 100644 index 0000000..89ba74a --- /dev/null +++ b/rln/ffi_c_examples/Readme.md @@ -0,0 +1,45 @@ +# Compile and Run + +## Non-stateless mode + +### Compile lib non-stateless + +```bash +cargo build -p rln +cargo run --features headers --bin generate-headers +mv -v rln.h rln/ffi_c_examples/ +``` + +### Compile and run example non-stateless + +```bash +cd rln/ffi_c_examples/ +gcc -Wall main.c -o main -lrln -L../../target/debug +./main +``` + +## Stateless mode + +### Compile lib stateless + +```bash +cargo build -p rln --no-default-features --features stateless +cargo run --no-default-features --features stateless,headers --bin generate-headers +mv -v rln.h rln/ffi_c_examples/ +``` + +### Compile example stateless + +```bash +cd rln/ffi_c_examples/ +gcc -Wall -DSTATELESS main.c -o main -lrln -L../../target/debug +./main +``` + +## Note + +### Find C lib used by Rust + +```bash +cargo +nightly rustc --release -p rln -- -Z unstable-options --print native-static-libs +``` diff --git a/rln/ffi_c_examples/main.c b/rln/ffi_c_examples/main.c new file mode 100644 index 0000000..3823aae --- /dev/null +++ b/rln/ffi_c_examples/main.c @@ -0,0 +1,409 @@ +#include +#include +#include + +#include "rln.h" + +int main (int argc, char const * const argv[]) +{ + printf("Creating RLN instance\n"); + +#ifdef STATELESS + CResult_FFI_RLN_ptr_Vec_uint8_t ffi_new_result = ffi_new(); +#else + const char* config_path = "../resources/tree_depth_20/config.json"; + CResult_FFI_RLN_ptr_Vec_uint8_t ffi_new_result = ffi_new(20, config_path); +#endif + + if (!ffi_new_result.ok) { + fprintf(stderr, "%s", ffi_new_result.err.ptr); + return EXIT_FAILURE; + } + + FFI_RLN_t* rln = ffi_new_result.ok; + printf("RLN instance created successfully\n"); + + printf("\nGenerating identity keys\n"); + Vec_CFr_t keys = ffi_key_gen(); + CFr_t* identity_secret = (CFr_t*)vec_cfr_get(&keys, 0); + CFr_t* id_commitment = (CFr_t*)vec_cfr_get(&keys, 1); + printf("Identity generated\n"); + + Vec_uint8_t debug = cfr_debug(identity_secret); + printf(" - identity_secret = %s\n", debug.ptr); + vec_u8_free(debug); + + debug = cfr_debug(id_commitment); + printf(" - id_commitment = %s\n", debug.ptr); + vec_u8_free(debug); + + printf("\nCreating message limit\n"); + CFr_t* user_message_limit = uint_to_cfr(1); + + debug = cfr_debug(user_message_limit); + printf(" - user_message_limit = %s\n", debug.ptr); + vec_u8_free(debug); + + printf("\nComputing rate commitment\n"); + CFr_t* rate_commitment = ffi_poseidon_hash_pair(id_commitment, user_message_limit); + + debug = cfr_debug(rate_commitment); + printf(" - rate_commitment = %s\n", debug.ptr); + vec_u8_free(debug); + + printf("\nCFr serialization: CFr <-> bytes\n"); + Vec_uint8_t ser_rate_commitment = cfr_to_bytes_le(rate_commitment); + + debug = vec_u8_debug(&ser_rate_commitment); + printf(" - serialized rate_commitment = %s\n", debug.ptr); + vec_u8_free(debug); + + CFr_t* deser_rate_commitment = bytes_le_to_cfr(&ser_rate_commitment); + + debug = cfr_debug(deser_rate_commitment); + printf(" - deserialized rate_commitment = %s\n", debug.ptr); + vec_u8_free(debug); + + vec_u8_free(ser_rate_commitment); + cfr_free(deser_rate_commitment); + + printf("\nVec serialization: Vec <-> bytes\n"); + Vec_uint8_t ser_keys = vec_cfr_to_bytes_le(&keys); + + debug = vec_u8_debug(&ser_keys); + printf(" - serialized keys = %s\n", debug.ptr); + vec_u8_free(debug); + + CResult_Vec_CFr_ptr_Vec_uint8_t deser_keys_result = bytes_le_to_vec_cfr(&ser_keys); + if (!deser_keys_result.ok) { + fprintf(stderr, "%s", deser_keys_result.err.ptr); + return EXIT_FAILURE; + } + + debug = vec_cfr_debug(deser_keys_result.ok); + printf(" - deserialized identity_secret = %s\n", debug.ptr); + vec_u8_free(debug); + + vec_u8_free(ser_keys); + vec_cfr_free(*deser_keys_result.ok); + +#ifdef STATELESS + const size_t TREE_DEPTH = 20; + const size_t CFR_SIZE = 32; + + printf("\nBuilding Merkle path for stateless mode\n"); + CFr_t* default_leaf = cfr_zero(); + + CFr_t* default_hashes[TREE_DEPTH - 1]; + default_hashes[0] = ffi_poseidon_hash_pair(default_leaf, default_leaf); + for (size_t i = 1; i < TREE_DEPTH - 1; i++) { + default_hashes[i] = ffi_poseidon_hash_pair(default_hashes[i-1], default_hashes[i-1]); + } + + void* path_elements_buffer = malloc(CFR_SIZE * TREE_DEPTH); + memcpy(path_elements_buffer, default_leaf, CFR_SIZE); + for (size_t i = 1; i < TREE_DEPTH; i++) { + memcpy((char*)path_elements_buffer + (i * CFR_SIZE), default_hashes[i-1], CFR_SIZE); + } + Vec_CFr_t path_elements = { + .ptr = (CFr_t*)path_elements_buffer, + .len = TREE_DEPTH, + .cap = TREE_DEPTH + }; + + printf("\nVec serialization: Vec <-> bytes\n"); + Vec_uint8_t ser_path_elements = vec_cfr_to_bytes_le(&path_elements); + + debug = vec_u8_debug(&ser_path_elements); + printf(" - serialized path_elements = %s\n", debug.ptr); + vec_u8_free(debug); + + CResult_Vec_CFr_ptr_Vec_uint8_t deser_path_elements = bytes_le_to_vec_cfr(&ser_path_elements); + if (!deser_path_elements.ok) { + fprintf(stderr, "%s", deser_path_elements.err.ptr); + return EXIT_FAILURE; + } + + debug = vec_cfr_debug(deser_path_elements.ok); + printf(" - deserialized path_elements = %s\n", debug.ptr); + vec_u8_free(debug); + + vec_cfr_free(*deser_path_elements.ok); + vec_u8_free(ser_path_elements); + + uint8_t* path_index_arr = calloc(TREE_DEPTH, sizeof(uint8_t)); + Vec_uint8_t identity_path_index = { + .ptr = path_index_arr, + .len = TREE_DEPTH, + .cap = TREE_DEPTH + }; + + printf("\nVec serialization: Vec <-> bytes\n"); + Vec_uint8_t ser_path_index = vec_u8_to_bytes_le(&identity_path_index); + + debug = vec_u8_debug(&ser_path_index); + printf(" - serialized path_index = %s\n", debug.ptr); + vec_u8_free(debug); + + CResult_Vec_uint8_ptr_Vec_uint8_t deser_path_index = bytes_le_to_vec_u8(&ser_path_index); + if (!deser_path_index.ok) { + fprintf(stderr, "%s", deser_path_index.err.ptr); + return EXIT_FAILURE; + } + + debug = vec_u8_debug(deser_path_index.ok); + printf(" - deserialized path_index = %s\n", debug.ptr); + vec_u8_free(debug); + + vec_u8_free(*deser_path_index.ok); + vec_u8_free(ser_path_index); + + printf("\nComputing Merkle root for stateless mode\n"); + printf(" - computing root for index 0 with rate_commitment\n"); + CFr_t* computed_root = ffi_poseidon_hash_pair(rate_commitment, default_leaf); + for (size_t i = 1; i < TREE_DEPTH; i++) { + CFr_t* next_root = ffi_poseidon_hash_pair(computed_root, default_hashes[i-1]); + cfr_free(computed_root); + computed_root = next_root; + } + + debug = cfr_debug(computed_root); + printf(" - computed_root = %s\n", debug.ptr); + vec_u8_free(debug); +#else + printf("\nAdding rate_commitment to tree\n"); + CResult_bool_ptr_Vec_uint8_t set_result = ffi_set_next_leaf(&rln, &rate_commitment); + if (!set_result.ok) { + fprintf(stderr, "%s", set_result.err.ptr); + return EXIT_FAILURE; + } + + size_t leaf_index = ffi_leaves_set(&rln) - 1; + printf(" - added to tree at index %zu\n", leaf_index); + + printf("\nGetting Merkle proof\n"); + CResult_FFI_MerkleProof_ptr_Vec_uint8_t proof_result = ffi_get_proof(&rln, leaf_index); + if (!proof_result.ok) { + fprintf(stderr, "%s", proof_result.err.ptr); + return EXIT_FAILURE; + } + FFI_MerkleProof_t* merkle_proof = proof_result.ok; + printf(" - proof obtained (depth: %zu)\n", merkle_proof->path_elements.len); +#endif + + printf("\nHashing signal\n"); + uint8_t signal[32] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + Vec_uint8_t signal_vec = {signal, 32, 32}; + CFr_t* x = ffi_hash_to_field_le(&signal_vec); + + debug = cfr_debug(x); + printf(" - x = %s\n", debug.ptr); + vec_u8_free(debug); + + printf("\nHashing epoch\n"); + const char* epoch_str = "test-epoch"; + Vec_uint8_t epoch_vec = {(uint8_t*)epoch_str, strlen(epoch_str), strlen(epoch_str)}; + CFr_t* epoch = ffi_hash_to_field_le(&epoch_vec); + + debug = cfr_debug(epoch); + printf(" - epoch = %s\n", debug.ptr); + vec_u8_free(debug); + + printf("\nHashing RLN identifier\n"); + const char* rln_id_str = "test-rln-identifier"; + Vec_uint8_t rln_id_vec = {(uint8_t*)rln_id_str, strlen(rln_id_str), strlen(rln_id_str)}; + CFr_t* rln_identifier = ffi_hash_to_field_le(&rln_id_vec); + + debug = cfr_debug(rln_identifier); + printf(" - rln_identifier = %s\n", debug.ptr); + vec_u8_free(debug); + + printf("\nComputing Poseidon hash for external nullifier\n"); + CFr_t* external_nullifier = ffi_poseidon_hash_pair(epoch, rln_identifier); + + debug = cfr_debug(external_nullifier); + printf(" - external_nullifier = %s\n", debug.ptr); + vec_u8_free(debug); + + printf("\nCreating message_id\n"); + CFr_t* message_id = uint_to_cfr(0); + + debug = cfr_debug(message_id); + printf(" - message_id = %s\n", debug.ptr); + vec_u8_free(debug); + + printf("\nGenerating RLN Proof\n"); +#ifdef STATELESS + CResult_FFI_RLNProof_ptr_Vec_uint8_t proof_gen_result = ffi_generate_rln_proof_stateless( + &rln, + identity_secret, + user_message_limit, + message_id, + &path_elements, + &identity_path_index, + x, + external_nullifier + ); +#else + CResult_FFI_RLNProof_ptr_Vec_uint8_t proof_gen_result = ffi_generate_rln_proof( + &rln, + identity_secret, + user_message_limit, + message_id, + x, + external_nullifier, + leaf_index + ); +#endif + FFI_RLNProof_t* rln_proof = NULL; + + if (!proof_gen_result.ok) { + fprintf(stderr, "Proof generation failed: %s\n", proof_gen_result.err.ptr); + return EXIT_FAILURE; + } else { + rln_proof = proof_gen_result.ok; + printf("Proof generated successfully\n"); + + printf("\nVerifying Proof\n"); +#ifdef STATELESS + Vec_CFr_t roots = { + .ptr = computed_root, + .len = 1, + .cap = 1 + }; + CResult_bool_ptr_Vec_uint8_t verify_result = ffi_verify_with_roots(&rln, &rln_proof, &roots, x); +#else + CResult_bool_ptr_Vec_uint8_t verify_result = ffi_verify_rln_proof(&rln, &rln_proof, x); +#endif + if (!verify_result.ok) { + fprintf(stderr, "Proof verification error: %s\n", verify_result.err.ptr); + return EXIT_FAILURE; + } else if (*verify_result.ok) { + printf("Proof verified successfully\n"); + } else { + printf("Proof verification failed\n"); + return EXIT_FAILURE; + } + + printf("\nSimulating double-signaling attack (same epoch, different message)\n"); + + printf("\nHashing second signal\n"); + uint8_t signal2[32] = {11, 12, 13, 14, 15, 16, 17, 18, 19, 20}; + Vec_uint8_t signal2_vec = {signal2, 32, 32}; + CFr_t* x2 = ffi_hash_to_field_le(&signal2_vec); + + debug = cfr_debug(x2); + printf(" - x2 = %s\n", debug.ptr); + vec_u8_free(debug); + + printf("\nCreating second message with the same id\n"); + CFr_t* message_id2 = uint_to_cfr(0); + + debug = cfr_debug(message_id2); + printf(" - message_id2 = %s\n", debug.ptr); + vec_u8_free(debug); + + printf("\nGenerating second RLN Proof\n"); +#ifdef STATELESS + CResult_FFI_RLNProof_ptr_Vec_uint8_t proof_gen_result2 = ffi_generate_rln_proof_stateless( + &rln, + identity_secret, + user_message_limit, + message_id2, + &path_elements, + &identity_path_index, + x2, + external_nullifier + ); +#else + CResult_FFI_RLNProof_ptr_Vec_uint8_t proof_gen_result2 = ffi_generate_rln_proof( + &rln, + identity_secret, + user_message_limit, + message_id2, + x2, + external_nullifier, + leaf_index + ); +#endif + FFI_RLNProof_t* rln_proof2 = NULL; + + if (!proof_gen_result2.ok) { + fprintf(stderr, "Second proof generation failed: %s\n", proof_gen_result2.err.ptr); + return EXIT_FAILURE; + } else { + rln_proof2 = proof_gen_result2.ok; + printf("Second proof generated successfully\n"); + + printf("\nVerifying second proof\n"); +#ifdef STATELESS + CResult_bool_ptr_Vec_uint8_t verify_result2 = ffi_verify_with_roots(&rln, &rln_proof2, &roots, x2); +#else + CResult_bool_ptr_Vec_uint8_t verify_result2 = ffi_verify_rln_proof(&rln, &rln_proof2, x2); +#endif + if (!verify_result2.ok) { + fprintf(stderr, "Second proof verification error: %s\n", verify_result2.err.ptr); + return EXIT_FAILURE; + } else if (*verify_result2.ok) { + printf("Second proof verified successfully\n"); + + printf("\nRecovering identity secret\n"); + CResult_CFr_ptr_Vec_uint8_t recover_result = ffi_recover_id_secret(&rln_proof, &rln_proof2); + if (!recover_result.ok) { + fprintf(stderr, "Identity recovery error: %s\n", recover_result.err.ptr); + return EXIT_FAILURE; + } else { + CFr_t* recovered_secret = recover_result.ok; + + debug = cfr_debug(recovered_secret); + printf(" - recovered_secret = %s\n", debug.ptr); + vec_u8_free(debug); + + debug = cfr_debug(identity_secret); + printf(" - original_secret = %s\n", debug.ptr); + vec_u8_free(debug); + + printf("Slashing successful: Identity is recovered!\n"); + + cfr_free(recovered_secret); + } + } else { + printf("Second proof verification failed\n"); + return EXIT_FAILURE; + } + } + + if (rln_proof2) { + ffi_rln_proof_free(rln_proof2); + } + cfr_free(x2); + cfr_free(message_id2); + } + + if (rln_proof) { + ffi_rln_proof_free(rln_proof); + } + +#ifdef STATELESS + free(path_index_arr); + free(path_elements_buffer); + for (size_t i = 0; i < TREE_DEPTH - 1; i++) { + cfr_free(default_hashes[i]); + } + cfr_free(default_leaf); + cfr_free(computed_root); +#else + ffi_merkle_proof_free(merkle_proof); +#endif + + cfr_free(rate_commitment); + cfr_free(x); + cfr_free(epoch); + cfr_free(rln_identifier); + cfr_free(external_nullifier); + cfr_free(user_message_limit); + cfr_free(message_id); + vec_cfr_free(keys); + ffi_rln_free(rln); + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/rln/ffi_nim_examples/README.md b/rln/ffi_nim_examples/README.md new file mode 100644 index 0000000..808726c --- /dev/null +++ b/rln/ffi_nim_examples/README.md @@ -0,0 +1,106 @@ +# RLN Nim FFI example + +This example shows how to use the RLN C FFI from Nim in stateless mode. It demonstrates: + +- Creating an RLN handle using the stateless constructor +- Building a witness for a mock Merkle path (no exported tree APIs) +- Generating a proof and verifying it + +## Build the RLN library + +From the repository root: + +```bash +# Stateless build (no tree APIs) +cargo build -p rln --release --no-default-features --features stateless + +# Non-stateless build (with tree APIs) +cargo build -p rln --release +``` + +This produces the shared library in `target/release`: + +- macOS: `librln.dylib` +- Linux: `librln.so` +- Windows: `rln.dll` + +## Build the Nim example (two modes) + +From this directory: + +```bash +# Stateless (uses local mock path, no tree exports) +nim c -d:release -d:ffiStateless main.nim + +# Non-stateless (uses exported tree APIs to insert leaf and fetch proof) +nim c -d:release main.nim +``` + +Notes: + +- The example links dynamically. If your OS linker cannot find the library at runtime, + set an rpath or environment variable as shown below. +- The example auto-picks a platform-specific default library name. + You can override it with `-d:RLN_LIB:"/absolute/path/to/lib"` if needed. + +## Run the example + +Ensure the dynamic loader can find the RLN library, then run the binary. + +macOS: + +```bash +DYLD_LIBRARY_PATH=../../target/release ./main +``` + +Linux: + +```bash +LD_LIBRARY_PATH=../../target/release ./main +``` + +Windows (PowerShell): + +```powershell +$env:PATH = "$PWD\..\..\target\release;$env:PATH" +./main.exe +``` + +You should see output similar to: + +```powershell +RLN created +Witness built +Proof generated +Verify: OK +``` + +## What the example does (stateless mode) + +1) Creates an RLN handle via the stateless constructor. + +2) Generates identity keys and sets a `user_message_limit` and `message_id`. + +3) Hashes a signal and external nullifier (`ffi_hash`). + +4) Computes `rateCommitment = Poseidon(id_commitment, user_message_limit)` using `ffi_poseidon_hash`. + +5) Builds a mock Merkle path for an empty depth-20 tree at index 0 (no exported tree APIs): + + - Path siblings: level 0 sibling is `0`, + then each level uses precomputed default hashes `H(0,0)`, `H(H(0,0),H(0,0))`, ... + - Path indices: all zeros (left at every level) + - Root: folds the path upwards with `rateCommitment` at index 0 + +6) Builds the witness, generates the proof, and verifies it with `ffi_verify_with_roots`, + passing a one-element roots vector containing the computed root (length must be 1). + +## What the example does (non-stateless mode) + +1) Creates an RLN handle with a Merkle tree backend and configuration. + +2) Generates identity keys and computes `rateCommitment = Poseidon(id_commitment, user_message_limit)`. + +3) Inserts the leaf with `ffi_set_next_leaf` and fetches a real Merkle path for index 0 via `ffi_get_proof`. + +4) Builds the witness from the exported proof, generates the proof, and verifies with `ffi_verify_rln_proof` using the current tree root. diff --git a/rln/ffi_nim_examples/main.nim b/rln/ffi_nim_examples/main.nim new file mode 100644 index 0000000..3de589c --- /dev/null +++ b/rln/ffi_nim_examples/main.nim @@ -0,0 +1,592 @@ +# Embed rpaths to find Cargo's built library relative to the executable +when defined(macosx): + {.passL: "-Wl,-rpath,@executable_path/../../target/release".} +when defined(linux): + {.passL: "-Wl,-rpath,'$ORIGIN/../../target/release'".} + +# Portable dynlib name with override capability (-d:RLN_LIB:"...") +when defined(macosx): + const RLN_LIB* {.strdefine.} = "librln.dylib" +elif defined(linux): + const RLN_LIB* {.strdefine.} = "librln.so" +elif defined(windows): + const RLN_LIB* {.strdefine.} = "rln.dll" +else: + const RLN_LIB* {.strdefine.} = "rln" + +# FFI objects +type + CSize* = csize_t + CFr* = object + FFI_RLN* = object + FFI_RLNProof* = object + + Vec_CFr* = object + dataPtr*: ptr CFr + len*: CSize + cap*: CSize + + Vec_uint8* = object + dataPtr*: ptr uint8 + len*: CSize + cap*: CSize + + SliceRefU8* = object + dataPtr*: ptr uint8 + len*: CSize + + FFI_MerkleProof* = object + path_elements*: Vec_CFr + path_index*: Vec_uint8 + + CResultBoolPtrVecU8* = object + ok*: ptr bool + err*: Vec_uint8 + + CResultRLNPtrVecU8* = object + ok*: ptr FFI_RLN + err*: Vec_uint8 + + CResultProofPtrVecU8* = object + ok*: ptr FFI_RLNProof + err*: Vec_uint8 + + CResultCFrPtrVecU8* = object + ok*: ptr CFr + err*: Vec_uint8 + + CResultMerkleProofPtrVecU8* = object + ok*: ptr FFI_MerkleProof + err*: Vec_uint8 + + CResultVecCFrPtrVecU8* = object + ok*: ptr Vec_CFr + err*: Vec_uint8 + + CResultVecU8PtrVecU8* = object + ok*: ptr Vec_uint8 + err*: Vec_uint8 + +# CFr functions +proc cfr_zero*(): ptr CFr {.importc: "cfr_zero", cdecl, dynlib: RLN_LIB.} +proc cfr_free*(x: ptr CFr) {.importc: "cfr_free", cdecl, dynlib: RLN_LIB.} +proc uint_to_cfr*(value: uint32): ptr CFr {.importc: "uint_to_cfr", cdecl, + dynlib: RLN_LIB.} +proc cfr_debug*(cfr: ptr CFr): Vec_uint8 {.importc: "cfr_debug", cdecl, + dynlib: RLN_LIB.} +proc cfr_to_bytes_le*(cfr: ptr CFr): Vec_uint8 {.importc: "cfr_to_bytes_le", + cdecl, dynlib: RLN_LIB.} +proc cfr_to_bytes_be*(cfr: ptr CFr): Vec_uint8 {.importc: "cfr_to_bytes_be", + cdecl, dynlib: RLN_LIB.} +proc bytes_le_to_cfr*(bytes: ptr Vec_uint8): ptr CFr {.importc: "bytes_le_to_cfr", + cdecl, dynlib: RLN_LIB.} +proc bytes_be_to_cfr*(bytes: ptr Vec_uint8): ptr CFr {.importc: "bytes_be_to_cfr", + cdecl, dynlib: RLN_LIB.} + +# Vec functions +proc vec_cfr_get*(v: ptr Vec_CFr, i: CSize): ptr CFr {.importc: "vec_cfr_get", + cdecl, dynlib: RLN_LIB.} +proc vec_cfr_to_bytes_le*(v: ptr Vec_CFr): Vec_uint8 {.importc: "vec_cfr_to_bytes_le", + cdecl, dynlib: RLN_LIB.} +proc vec_cfr_to_bytes_be*(v: ptr Vec_CFr): Vec_uint8 {.importc: "vec_cfr_to_bytes_be", + cdecl, dynlib: RLN_LIB.} +proc bytes_le_to_vec_cfr*(bytes: ptr Vec_uint8): CResultVecCFrPtrVecU8 {.importc: "bytes_le_to_vec_cfr", + cdecl, dynlib: RLN_LIB.} +proc bytes_be_to_vec_cfr*(bytes: ptr Vec_uint8): CResultVecCFrPtrVecU8 {.importc: "bytes_be_to_vec_cfr", + cdecl, dynlib: RLN_LIB.} +proc vec_cfr_debug*(v: ptr Vec_CFr): Vec_uint8 {.importc: "vec_cfr_debug", + cdecl, dynlib: RLN_LIB.} +proc vec_cfr_free*(v: Vec_CFr) {.importc: "vec_cfr_free", cdecl, + dynlib: RLN_LIB.} + +# Vec functions +proc vec_u8_to_bytes_le*(v: ptr Vec_uint8): Vec_uint8 {.importc: "vec_u8_to_bytes_le", + cdecl, dynlib: RLN_LIB.} +proc vec_u8_to_bytes_be*(v: ptr Vec_uint8): Vec_uint8 {.importc: "vec_u8_to_bytes_be", + cdecl, dynlib: RLN_LIB.} +proc bytes_le_to_vec_u8*(bytes: ptr Vec_uint8): CResultVecU8PtrVecU8 {.importc: "bytes_le_to_vec_u8", + cdecl, dynlib: RLN_LIB.} +proc bytes_be_to_vec_u8*(bytes: ptr Vec_uint8): CResultVecU8PtrVecU8 {.importc: "bytes_be_to_vec_u8", + cdecl, dynlib: RLN_LIB.} +proc vec_u8_debug*(v: ptr Vec_uint8): Vec_uint8 {.importc: "vec_u8_debug", + cdecl, dynlib: RLN_LIB.} +proc vec_u8_free*(v: Vec_uint8) {.importc: "vec_u8_free", cdecl, + dynlib: RLN_LIB.} + +# Hashing functions +proc ffi_hash_to_field_le*(input: ptr Vec_uint8): ptr CFr {.importc: "ffi_hash_to_field_le", + cdecl, dynlib: RLN_LIB.} +proc ffi_hash_to_field_be*(input: ptr Vec_uint8): ptr CFr {.importc: "ffi_hash_to_field_be", + cdecl, dynlib: RLN_LIB.} +proc ffi_poseidon_hash_pair*(a: ptr CFr, + b: ptr CFr): ptr CFr {.importc: "ffi_poseidon_hash_pair", cdecl, + dynlib: RLN_LIB.} + +# Keygen function +proc ffi_key_gen*(): Vec_CFr {.importc: "ffi_key_gen", cdecl, + dynlib: RLN_LIB.} + +# RLN instance functions +when defined(ffiStateless): + proc ffi_new*(): CResultRLNPtrVecU8 {.importc: "ffi_new", cdecl, + dynlib: RLN_LIB.} +else: + proc ffi_new*(treeDepth: CSize, config: cstring): CResultRLNPtrVecU8 {.importc: "ffi_new", + cdecl, dynlib: RLN_LIB.} + +proc ffi_rln_free*(rln: ptr FFI_RLN) {.importc: "ffi_rln_free", cdecl, + dynlib: RLN_LIB.} + +# Proof generation/verification functions +when defined(ffiStateless): + proc ffi_generate_rln_proof_stateless*( + rln: ptr ptr FFI_RLN, + identity_secret: ptr CFr, + user_message_limit: ptr CFr, + message_id: ptr CFr, + path_elements: ptr Vec_CFr, + identity_path_index: ptr Vec_uint8, + x: ptr CFr, + external_nullifier: ptr CFr + ): CResultProofPtrVecU8 {.importc: "ffi_generate_rln_proof_stateless", cdecl, + dynlib: RLN_LIB.} +else: + proc ffi_generate_rln_proof*( + rln: ptr ptr FFI_RLN, + identity_secret: ptr CFr, + user_message_limit: ptr CFr, + message_id: ptr CFr, + x: ptr CFr, + external_nullifier: ptr CFr, + leaf_index: CSize + ): CResultProofPtrVecU8 {.importc: "ffi_generate_rln_proof", cdecl, + dynlib: RLN_LIB.} + +when defined(ffiStateless): + proc ffi_verify_with_roots*( + rln: ptr ptr FFI_RLN, + proof: ptr ptr FFI_RLNProof, + roots: ptr Vec_CFr, + x: ptr CFr + ): CResultBoolPtrVecU8 {.importc: "ffi_verify_with_roots", cdecl, + dynlib: RLN_LIB.} +else: + proc ffi_verify_rln_proof*( + rln: ptr ptr FFI_RLN, + proof: ptr ptr FFI_RLNProof, + x: ptr CFr + ): CResultBoolPtrVecU8 {.importc: "ffi_verify_rln_proof", cdecl, + dynlib: RLN_LIB.} + +proc ffi_rln_proof_free*(p: ptr FFI_RLNProof) {.importc: "ffi_rln_proof_free", + cdecl, dynlib: RLN_LIB.} + +# Merkle tree operations (non-stateless mode) +when not defined(ffiStateless): + proc ffi_set_next_leaf*(rln: ptr ptr FFI_RLN, + value: ptr ptr CFr): CResultBoolPtrVecU8 {.importc: "ffi_set_next_leaf", + cdecl, dynlib: RLN_LIB.} + proc ffi_leaves_set*(rln: ptr ptr FFI_RLN): CSize {.importc: "ffi_leaves_set", + cdecl, dynlib: RLN_LIB.} + proc ffi_get_proof*(rln: ptr ptr FFI_RLN, + index: CSize): CResultMerkleProofPtrVecU8 {.importc: "ffi_get_proof", + cdecl, dynlib: RLN_LIB.} + proc ffi_merkle_proof_free*(p: ptr FFI_MerkleProof) {.importc: "ffi_merkle_proof_free", + cdecl, dynlib: RLN_LIB.} + +# Secret recovery +proc ffi_recover_id_secret*(proof1: ptr ptr FFI_RLNProof, + proof2: ptr ptr FFI_RLNProof): CResultCFrPtrVecU8 {.importc: "ffi_recover_id_secret", + cdecl, dynlib: RLN_LIB.} + +# Helpers +proc asVecU8*(buf: var seq[uint8]): Vec_uint8 = + result.dataPtr = if buf.len == 0: nil else: addr buf[0] + result.len = CSize(buf.len) + result.cap = CSize(buf.len) + +proc asString*(v: Vec_uint8): string = + if v.dataPtr.isNil or v.len == 0: return "" + result = newString(v.len.int) + copyMem(addr result[0], v.dataPtr, v.len.int) + +when isMainModule: + echo "Creating RLN instance" + + var rlnRes: CResultRLNPtrVecU8 + when defined(ffiStateless): + rlnRes = ffi_new() + else: + let config_path = """../resources/tree_depth_20/config.json""".cstring + rlnRes = ffi_new(CSize(20), config_path) + + if rlnRes.ok.isNil: + stderr.writeLine "ffi_new error: ", asString(rlnRes.err) + quit 1 + + var rln = rlnRes.ok + echo "RLN instance created successfully" + + echo "\nGenerating identity keys" + var keys = ffi_key_gen() + let identitySecret = vec_cfr_get(addr keys, CSize(0)) + let idCommitment = vec_cfr_get(addr keys, CSize(1)) + echo "Identity generated" + + block: + let debug = cfr_debug(identitySecret) + echo " - identity_secret = ", asString(debug) + vec_u8_free(debug) + + block: + let debug = cfr_debug(idCommitment) + echo " - id_commitment = ", asString(debug) + vec_u8_free(debug) + + echo "\nCreating message limit" + let userMessageLimit = uint_to_cfr(1'u32) + + block: + let debug = cfr_debug(userMessageLimit) + echo " - user_message_limit = ", asString(debug) + vec_u8_free(debug) + + echo "\nComputing rate commitment" + let rateCommitment = ffi_poseidon_hash_pair(idCommitment, userMessageLimit) + + block: + let debug = cfr_debug(rateCommitment) + echo " - rate_commitment = ", asString(debug) + vec_u8_free(debug) + + echo "\nCFr serialization: CFr <-> bytes" + var serRateCommitment = cfr_to_bytes_be(rateCommitment) + + block: + let debug = vec_u8_debug(addr serRateCommitment) + echo " - serialized rate_commitment = ", asString(debug) + vec_u8_free(debug) + + let deserRateCommitment = bytes_be_to_cfr(addr serRateCommitment) + + block: + let debug = cfr_debug(deserRateCommitment) + echo " - deserialized rate_commitment = ", asString(debug) + vec_u8_free(debug) + + vec_u8_free(serRateCommitment) + cfr_free(deserRateCommitment) + + echo "\nVec serialization: Vec <-> bytes" + var serKeys = vec_cfr_to_bytes_be(addr keys) + + block: + let debug = vec_u8_debug(addr serKeys) + echo " - serialized keys = ", asString(debug) + vec_u8_free(debug) + + let deserKeysResult = bytes_be_to_vec_cfr(addr serKeys) + if deserKeysResult.ok.isNil: + stderr.writeLine "bytes_be_to_vec_cfr error: ", asString( + deserKeysResult.err) + quit 1 + + block: + let debug = vec_cfr_debug(deserKeysResult.ok) + echo " - deserialized identity_secret = ", asString(debug) + vec_u8_free(debug) + + vec_u8_free(serKeys) + vec_cfr_free(deserKeysResult.ok[]) + + when defined(ffiStateless): + const treeDepth = 20 + const CFR_SIZE = 32 + + echo "\nBuilding Merkle path for stateless mode" + + let defaultLeaf = cfr_zero() + var defaultHashes: array[treeDepth-1, ptr CFr] + defaultHashes[0] = ffi_poseidon_hash_pair(defaultLeaf, defaultLeaf) + for i in 1..treeDepth-2: + defaultHashes[i] = ffi_poseidon_hash_pair(defaultHashes[i-1], + defaultHashes[i-1]) + + var pathElemsBuffer = alloc(CFR_SIZE * treeDepth) + copyMem(pathElemsBuffer, defaultLeaf, CFR_SIZE) + for i in 1..treeDepth-1: + copyMem(cast[pointer](cast[int](pathElemsBuffer) + i * CFR_SIZE), + defaultHashes[i-1], CFR_SIZE) + + var pathElements: Vec_CFr + pathElements.dataPtr = cast[ptr CFr](pathElemsBuffer) + pathElements.len = CSize(treeDepth) + pathElements.cap = CSize(treeDepth) + + echo "\nVec serialization: Vec <-> bytes" + var serPathElements = vec_cfr_to_bytes_be(addr pathElements) + + block: + let debug = vec_u8_debug(addr serPathElements) + echo " - serialized path_elements = ", asString(debug) + vec_u8_free(debug) + + let deserPathElements = bytes_be_to_vec_cfr(addr serPathElements) + if deserPathElements.ok.isNil: + stderr.writeLine "bytes_be_to_vec_cfr error: ", asString( + deserPathElements.err) + quit 1 + + block: + let debug = vec_cfr_debug(deserPathElements.ok) + echo " - deserialized path_elements = ", asString(debug) + vec_u8_free(debug) + + vec_cfr_free(deserPathElements.ok[]) + vec_u8_free(serPathElements) + + var pathIndexSeq = newSeq[uint8](treeDepth) + var identityPathIndex = asVecU8(pathIndexSeq) + + echo "\nVec serialization: Vec <-> bytes" + var serPathIndex = vec_u8_to_bytes_be(addr identityPathIndex) + + block: + let debug = vec_u8_debug(addr serPathIndex) + echo " - serialized path_index = ", asString(debug) + vec_u8_free(debug) + + let deserPathIndex = bytes_be_to_vec_u8(addr serPathIndex) + if deserPathIndex.ok.isNil: + stderr.writeLine "bytes_be_to_vec_u8 error: ", asString( + deserPathIndex.err) + quit 1 + + block: + let debug = vec_u8_debug(deserPathIndex.ok) + echo " - deserialized path_index = ", asString(debug) + vec_u8_free(debug) + + vec_u8_free(deserPathIndex.ok[]) + vec_u8_free(serPathIndex) + + echo "\nComputing Merkle root for stateless mode" + echo " - computing root for index 0 with rate_commitment" + var computedRoot = ffi_poseidon_hash_pair(rateCommitment, defaultLeaf) + for i in 1..treeDepth-1: + let next = ffi_poseidon_hash_pair(computedRoot, defaultHashes[i-1]) + cfr_free(computedRoot) + computedRoot = next + + block: + let debug = cfr_debug(computedRoot) + echo " - computed_root = ", asString(debug) + vec_u8_free(debug) + else: + echo "\nAdding rate_commitment to tree" + var rcPtr = rateCommitment + let setRes = ffi_set_next_leaf(addr rln, addr rcPtr) + if setRes.ok.isNil or not setRes.ok[]: + stderr.writeLine "set_next_leaf error: ", asString(setRes.err) + vec_cfr_free(keys) + ffi_rln_free(rln) + quit 1 + + let leafIndex = ffi_leaves_set(addr rln) - 1 + echo " - added to tree at index ", leafIndex + + echo "\nGetting Merkle proof" + let proofResult = ffi_get_proof(addr rln, leafIndex) + if proofResult.ok.isNil: + stderr.writeLine "get_proof error: ", asString(proofResult.err) + vec_cfr_free(keys) + ffi_rln_free(rln) + quit 1 + let merkleProof = proofResult.ok + echo " - proof obtained (depth: ", merkleProof.path_elements.len, ")" + + echo "\nHashing signal" + var signal: array[32, uint8] = [1'u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + var signalVec = Vec_uint8(dataPtr: cast[ptr uint8](addr signal[0]), + len: CSize(signal.len), cap: CSize(signal.len)) + let x = ffi_hash_to_field_le(addr signalVec) + + block: + let debug = cfr_debug(x) + echo " - x = ", asString(debug) + vec_u8_free(debug) + + echo "\nHashing epoch" + let epochStr = "test-epoch" + var epochBytes = newSeq[uint8](epochStr.len) + for i in 0.. ::std::io::Result<()> { + ffi::generate_headers() +} diff --git a/rln/src/circuit/iden3calc/graph.rs b/rln/src/circuit/iden3calc/graph.rs index 5dbacfb..b666aba 100644 --- a/rln/src/circuit/iden3calc/graph.rs +++ b/rln/src/circuit/iden3calc/graph.rs @@ -816,7 +816,7 @@ fn u_lt(a: &U256, b: &U256) -> U256 { } #[cfg(test)] -mod tests { +mod test { use super::*; use ruint::uint; use std::ops::Div; diff --git a/rln/src/circuit/iden3calc/storage.rs b/rln/src/circuit/iden3calc/storage.rs index c22c087..f0bed5f 100644 --- a/rln/src/circuit/iden3calc/storage.rs +++ b/rln/src/circuit/iden3calc/storage.rs @@ -331,7 +331,7 @@ impl Write for WriteBackReader { } #[cfg(test)] -mod tests { +mod test { use super::*; use byteorder::ByteOrder; use core::str::FromStr; diff --git a/rln/src/error.rs b/rln/src/error.rs index 7a19228..7fe2748 100644 --- a/rln/src/error.rs +++ b/rln/src/error.rs @@ -43,6 +43,8 @@ pub enum ProtocolError { JsonError(#[from] serde_json::Error), #[error("Message id ({0}) is not within user_message_limit ({1})")] InvalidMessageId(Fr, Fr), + #[error("Merkle proof length mismatch: expected {0}, got {1}")] + InvalidMerkleProofLength(usize, usize), } #[derive(Debug, thiserror::Error)] diff --git a/rln/src/ffi.rs b/rln/src/ffi.rs deleted file mode 100644 index 9451067..0000000 --- a/rln/src/ffi.rs +++ /dev/null @@ -1,615 +0,0 @@ -// This crate implements the public Foreign Function Interface (FFI) for the RLN module - -use std::slice; - -use crate::public::{ - extended_key_gen as public_extended_key_gen, hash as public_hash, key_gen as public_key_gen, - poseidon_hash as public_poseidon_hash, - seeded_extended_key_gen as public_seeded_extended_key_gen, - seeded_key_gen as public_seeded_key_gen, RLN, -}; - -// Macro to call methods with arbitrary amount of arguments, -// First argument to the macro is context, -// second is the actual method on `RLN` -// rest are all other arguments to the method -#[cfg(not(feature = "stateless"))] -macro_rules! call { - ($instance:expr, $method:ident $(, $arg:expr)*) => { - { - let new_instance: &mut RLN = $instance.process(); - match new_instance.$method($($arg.process()),*) { - Ok(()) => { - true - } - Err(err) => { - eprintln!("execution error: {err}"); - false - } - } - } - } -} - -// Macro to call methods with arbitrary amount of arguments, -// which have the last argument is output buffer pointer -// First argument to the macro is context, -// second is the actual method on `RLN` -// third is the aforementioned output buffer argument -// rest are all other arguments to the method -macro_rules! call_with_output_arg { - // this variant is needed for the case when - // there are zero other arguments - ($instance:expr, $method:ident, $output_arg:expr) => { - { - let mut output_data: Vec = Vec::new(); - let new_instance = $instance.process(); - match new_instance.$method(&mut output_data) { - Ok(()) => { - unsafe { *$output_arg = Buffer::from(&output_data[..]) }; - std::mem::forget(output_data); - true - } - Err(err) => { - std::mem::forget(output_data); - eprintln!("execution error: {err}"); - false - } - } - } - }; - ($instance:expr, $method:ident, $output_arg:expr, $( $arg:expr ),* ) => { - { - let mut output_data: Vec = Vec::new(); - let new_instance = $instance.process(); - match new_instance.$method($($arg.process()),*, &mut output_data) { - Ok(()) => { - unsafe { *$output_arg = Buffer::from(&output_data[..]) }; - std::mem::forget(output_data); - true - } - Err(err) => { - std::mem::forget(output_data); - eprintln!("execution error: {err}"); - false - } - } - } - }; - -} - -// Macro to call methods with arbitrary amount of arguments, -// which are not implemented in a ctx RLN object -// First argument is the method to call -// Second argument is the output buffer argument -// The remaining arguments are all other inputs to the method -macro_rules! no_ctx_call_with_output_arg { - ($method:ident, $output_arg:expr, $input_arg:expr, $endianness_arg:expr) => {{ - let mut output_data: Vec = Vec::new(); - match $method( - $input_arg.process(), - &mut output_data, - $endianness_arg.process(), - ) { - Ok(()) => { - unsafe { *$output_arg = Buffer::from(&output_data[..]) }; - std::mem::forget(output_data); - true - } - Err(err) => { - std::mem::forget(output_data); - eprintln!("execution error: {err}"); - false - } - } - }}; -} - -// Macro to call methods with arbitrary amount of arguments, -// which are not implemented in a ctx RLN object -// First argument is the method to call -// Second argument is the output buffer argument -// The remaining arguments are all other inputs to the method -macro_rules! no_ctx_call_with_output_arg_and_endianness { - ($method:ident, $output_arg:expr, $endianness_arg:expr) => {{ - let mut output_data: Vec = Vec::new(); - match $method(&mut output_data, $endianness_arg.process()) { - Ok(()) => { - unsafe { *$output_arg = Buffer::from(&output_data[..]) }; - std::mem::forget(output_data); - true - } - Err(err) => { - std::mem::forget(output_data); - eprintln!("execution error: {err}"); - false - } - } - }}; -} - -// Macro to call methods with arbitrary amount of arguments, -// which have the last argument as bool -// First argument to the macro is context, -// second is the actual method on `RLN` -// third is the aforementioned bool argument -// rest are all other arguments to the method -macro_rules! call_with_bool_arg { - ($instance:expr, $method:ident, $bool_arg:expr, $( $arg:expr ),* ) => { - { - let new_instance = $instance.process(); - if match new_instance.$method($($arg.process()),*,) { - Ok(result) => result, - Err(err) => { - eprintln!("execution error: {err}"); - return false - }, - } { - unsafe { *$bool_arg = true }; - } else { - unsafe { *$bool_arg = false }; - }; - true - } - } -} - -trait ProcessArg { - type ReturnType; - fn process(self) -> Self::ReturnType; -} - -impl ProcessArg for usize { - type ReturnType = usize; - fn process(self) -> Self::ReturnType { - self - } -} - -impl ProcessArg for *const Buffer { - type ReturnType = &'static [u8]; - fn process(self) -> Self::ReturnType { - <&[u8]>::from(unsafe { &*self }) - } -} - -impl ProcessArg for *const RLN { - type ReturnType = &'static RLN; - fn process(self) -> Self::ReturnType { - unsafe { &*self } - } -} - -impl ProcessArg for *mut RLN { - type ReturnType = &'static mut RLN; - fn process(self) -> Self::ReturnType { - unsafe { &mut *self } - } -} - -impl ProcessArg for bool { - type ReturnType = bool; - fn process(self) -> Self::ReturnType { - self - } -} - -///// Buffer struct is taken from -///// -///// -///// Also heavily inspired by - -#[repr(C)] -#[derive(Clone, Debug, PartialEq)] -pub struct Buffer { - pub ptr: *const u8, - pub len: usize, -} - -impl From<&[u8]> for Buffer { - fn from(src: &[u8]) -> Self { - Self { - ptr: src.as_ptr(), - len: src.len(), - } - } -} - -impl<'a> From<&Buffer> for &'a [u8] { - fn from(src: &Buffer) -> &'a [u8] { - unsafe { slice::from_raw_parts(src.ptr, src.len) } - } -} - -// TODO: check if there are security implications by using this clippy -// #[allow(clippy::not_unsafe_ptr_arg_deref)] - -//////////////////////////////////////////////////////// -// RLN APIs -//////////////////////////////////////////////////////// - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[cfg(not(feature = "stateless"))] -#[no_mangle] -pub extern "C" fn new(tree_depth: usize, input_buffer: *const Buffer, ctx: *mut *mut RLN) -> bool { - match RLN::new(tree_depth, input_buffer.process()) { - Ok(rln) => { - unsafe { *ctx = Box::into_raw(Box::new(rln)) }; - true - } - Err(err) => { - eprintln!("could not instantiate rln: {err}"); - false - } - } -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[cfg(feature = "stateless")] -#[no_mangle] -pub extern "C" fn new(ctx: *mut *mut RLN) -> bool { - match RLN::new() { - Ok(rln) => { - unsafe { *ctx = Box::into_raw(Box::new(rln)) }; - true - } - Err(err) => { - eprintln!("could not instantiate rln: {err}"); - false - } - } -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[cfg(not(feature = "stateless"))] -#[no_mangle] -pub extern "C" fn new_with_params( - tree_depth: usize, - zkey_buffer: *const Buffer, - graph_data: *const Buffer, - tree_config: *const Buffer, - ctx: *mut *mut RLN, -) -> bool { - match RLN::new_with_params( - tree_depth, - zkey_buffer.process().to_vec(), - graph_data.process().to_vec(), - tree_config.process(), - ) { - Ok(rln) => { - unsafe { *ctx = Box::into_raw(Box::new(rln)) }; - true - } - Err(err) => { - eprintln!("could not instantiate rln: {err}"); - false - } - } -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[cfg(feature = "stateless")] -#[no_mangle] -pub extern "C" fn new_with_params( - zkey_buffer: *const Buffer, - graph_buffer: *const Buffer, - ctx: *mut *mut RLN, -) -> bool { - match RLN::new_with_params( - zkey_buffer.process().to_vec(), - graph_buffer.process().to_vec(), - ) { - Ok(rln) => { - unsafe { *ctx = Box::into_raw(Box::new(rln)) }; - true - } - Err(err) => { - eprintln!("could not instantiate rln: {err}"); - false - } - } -} - -//////////////////////////////////////////////////////// -// Merkle tree APIs -//////////////////////////////////////////////////////// -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -#[cfg(not(feature = "stateless"))] -pub extern "C" fn set_tree(ctx: *mut RLN, tree_depth: usize) -> bool { - call!(ctx, set_tree, tree_depth) -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -#[cfg(not(feature = "stateless"))] -pub extern "C" fn delete_leaf(ctx: *mut RLN, index: usize) -> bool { - call!(ctx, delete_leaf, index) -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -#[cfg(not(feature = "stateless"))] -pub extern "C" fn set_leaf(ctx: *mut RLN, index: usize, input_buffer: *const Buffer) -> bool { - call!(ctx, set_leaf, index, input_buffer) -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -#[cfg(not(feature = "stateless"))] -pub extern "C" fn get_leaf(ctx: *mut RLN, index: usize, output_buffer: *mut Buffer) -> bool { - call_with_output_arg!(ctx, get_leaf, output_buffer, index) -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -#[cfg(not(feature = "stateless"))] -pub extern "C" fn leaves_set(ctx: *mut RLN) -> usize { - ctx.process().leaves_set() -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -#[cfg(not(feature = "stateless"))] -pub extern "C" fn set_next_leaf(ctx: *mut RLN, input_buffer: *const Buffer) -> bool { - call!(ctx, set_next_leaf, input_buffer) -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -#[cfg(not(feature = "stateless"))] -pub extern "C" fn set_leaves_from( - ctx: *mut RLN, - index: usize, - input_buffer: *const Buffer, -) -> bool { - call!(ctx, set_leaves_from, index, input_buffer) -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -#[cfg(not(feature = "stateless"))] -pub extern "C" fn init_tree_with_leaves(ctx: *mut RLN, input_buffer: *const Buffer) -> bool { - call!(ctx, init_tree_with_leaves, input_buffer) -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -#[cfg(not(feature = "stateless"))] -pub extern "C" fn atomic_operation( - ctx: *mut RLN, - index: usize, - leaves_buffer: *const Buffer, - indices_buffer: *const Buffer, -) -> bool { - call!(ctx, atomic_operation, index, leaves_buffer, indices_buffer) -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -#[cfg(not(feature = "stateless"))] -pub extern "C" fn seq_atomic_operation( - ctx: *mut RLN, - leaves_buffer: *const Buffer, - indices_buffer: *const Buffer, -) -> bool { - call!( - ctx, - atomic_operation, - ctx.process().leaves_set(), - leaves_buffer, - indices_buffer - ) -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -#[cfg(not(feature = "stateless"))] -pub extern "C" fn get_root(ctx: *const RLN, output_buffer: *mut Buffer) -> bool { - call_with_output_arg!(ctx, get_root, output_buffer) -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -#[cfg(not(feature = "stateless"))] -pub extern "C" fn get_proof(ctx: *const RLN, index: usize, output_buffer: *mut Buffer) -> bool { - call_with_output_arg!(ctx, get_proof, output_buffer, index) -} - -//////////////////////////////////////////////////////// -// zkSNARKs APIs -//////////////////////////////////////////////////////// -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -pub extern "C" fn prove( - ctx: *mut RLN, - input_buffer: *const Buffer, - output_buffer: *mut Buffer, -) -> bool { - call_with_output_arg!(ctx, prove, output_buffer, input_buffer) -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -pub extern "C" fn verify( - ctx: *const RLN, - proof_buffer: *const Buffer, - proof_is_valid_ptr: *mut bool, -) -> bool { - call_with_bool_arg!(ctx, verify, proof_is_valid_ptr, proof_buffer) -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -#[cfg(not(feature = "stateless"))] -pub extern "C" fn generate_rln_proof( - ctx: *mut RLN, - input_buffer: *const Buffer, - output_buffer: *mut Buffer, -) -> bool { - call_with_output_arg!(ctx, generate_rln_proof, output_buffer, input_buffer) -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -pub extern "C" fn generate_rln_proof_with_witness( - ctx: *mut RLN, - input_buffer: *const Buffer, - output_buffer: *mut Buffer, -) -> bool { - call_with_output_arg!( - ctx, - generate_rln_proof_with_witness, - output_buffer, - input_buffer - ) -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -#[cfg(not(feature = "stateless"))] -pub extern "C" fn verify_rln_proof( - ctx: *const RLN, - proof_buffer: *const Buffer, - proof_is_valid_ptr: *mut bool, -) -> bool { - call_with_bool_arg!(ctx, verify_rln_proof, proof_is_valid_ptr, proof_buffer) -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -pub extern "C" fn verify_with_roots( - ctx: *const RLN, - proof_buffer: *const Buffer, - roots_buffer: *const Buffer, - proof_is_valid_ptr: *mut bool, -) -> bool { - call_with_bool_arg!( - ctx, - verify_with_roots, - proof_is_valid_ptr, - proof_buffer, - roots_buffer - ) -} - -//////////////////////////////////////////////////////// -// Utils -//////////////////////////////////////////////////////// -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -pub extern "C" fn recover_id_secret( - ctx: *const RLN, - input_proof_buffer_1: *const Buffer, - input_proof_buffer_2: *const Buffer, - output_buffer: *mut Buffer, -) -> bool { - call_with_output_arg!( - ctx, - recover_id_secret, - output_buffer, - input_proof_buffer_1, - input_proof_buffer_2 - ) -} - -//////////////////////////////////////////////////////// -// Persistent metadata APIs -//////////////////////////////////////////////////////// - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -#[cfg(not(feature = "stateless"))] -pub extern "C" fn set_metadata(ctx: *mut RLN, input_buffer: *const Buffer) -> bool { - call!(ctx, set_metadata, input_buffer) -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -#[cfg(not(feature = "stateless"))] -pub extern "C" fn get_metadata(ctx: *const RLN, output_buffer: *mut Buffer) -> bool { - call_with_output_arg!(ctx, get_metadata, output_buffer) -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -#[cfg(not(feature = "stateless"))] -pub extern "C" fn flush(ctx: *mut RLN) -> bool { - call!(ctx, flush) -} - -//////////////////////////////////////////////////////// -// Utils APIs -//////////////////////////////////////////////////////// - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -pub extern "C" fn hash( - input_buffer: *const Buffer, - output_buffer: *mut Buffer, - is_little_endian: bool, -) -> bool { - no_ctx_call_with_output_arg!(public_hash, output_buffer, input_buffer, is_little_endian) -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -pub extern "C" fn poseidon_hash( - input_buffer: *const Buffer, - output_buffer: *mut Buffer, - is_little_endian: bool, -) -> bool { - no_ctx_call_with_output_arg!( - public_poseidon_hash, - output_buffer, - input_buffer, - is_little_endian - ) -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -pub extern "C" fn key_gen(output_buffer: *mut Buffer, is_little_endian: bool) -> bool { - no_ctx_call_with_output_arg_and_endianness!(public_key_gen, output_buffer, is_little_endian) -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -pub extern "C" fn seeded_key_gen( - input_buffer: *const Buffer, - output_buffer: *mut Buffer, - is_little_endian: bool, -) -> bool { - no_ctx_call_with_output_arg!( - public_seeded_key_gen, - output_buffer, - input_buffer, - is_little_endian - ) -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -pub extern "C" fn extended_key_gen(output_buffer: *mut Buffer, is_little_endian: bool) -> bool { - no_ctx_call_with_output_arg_and_endianness!( - public_extended_key_gen, - output_buffer, - is_little_endian - ) -} - -#[allow(clippy::not_unsafe_ptr_arg_deref)] -#[no_mangle] -pub extern "C" fn seeded_extended_key_gen( - input_buffer: *const Buffer, - output_buffer: *mut Buffer, - is_little_endian: bool, -) -> bool { - no_ctx_call_with_output_arg!( - public_seeded_extended_key_gen, - output_buffer, - input_buffer, - is_little_endian - ) -} diff --git a/rln/src/ffi/ffi_rln.rs b/rln/src/ffi/ffi_rln.rs new file mode 100644 index 0000000..35359ec --- /dev/null +++ b/rln/src/ffi/ffi_rln.rs @@ -0,0 +1,472 @@ +#![allow(non_camel_case_types)] + +use super::ffi_utils::{CFr, CResult}; +use crate::{ + circuit::{graph_from_folder, zkey_from_folder, zkey_from_raw, Curve}, + protocol::{ + compute_id_secret, generate_proof, proof_values_from_witness, verify_proof, RLNProofValues, + RLNWitnessInput, + }, + utils::IdSecret, +}; +use ark_bn254::Fr; +use ark_groth16::{Proof as ArkProof, ProvingKey}; +use ark_relations::r1cs::ConstraintMatrices; +use safer_ffi::{boxed::Box_, derive_ReprC, ffi_export, prelude::repr_c}; + +#[cfg(not(feature = "stateless"))] +use { + crate::poseidon_tree::PoseidonTree, + safer_ffi::prelude::char_p, + std::{fs::File, io::Read, str::FromStr}, + utils::{Hasher, ZerokitMerkleProof, ZerokitMerkleTree}, +}; + +// FFI_RLN + +#[derive_ReprC] +#[repr(opaque)] +pub struct FFI_RLN { + pub(crate) proving_key: (ProvingKey, ConstraintMatrices), + #[cfg(not(target_arch = "wasm32"))] + pub(crate) graph_data: Vec, + #[cfg(not(feature = "stateless"))] + pub(crate) tree: PoseidonTree, +} + +// RLN initialization APIs + +#[cfg(not(feature = "stateless"))] +#[ffi_export] +pub fn ffi_new( + tree_depth: usize, + config_path: char_p::Ref<'_>, +) -> CResult, repr_c::String> { + let tree_config = match File::open(config_path.to_str()).and_then(|mut file| { + let mut config_str = String::new(); + file.read_to_string(&mut config_str)?; + Ok(config_str) + }) { + Ok(config_str) if !config_str.is_empty() => { + match ::Config::from_str(&config_str) { + Ok(config) => config, + Err(err) => { + return CResult { + ok: None, + err: Some(err.to_string().into()), + }; + } + } + } + _ => ::Config::default(), + }; + + let proving_key = zkey_from_folder().to_owned(); + let graph_data = graph_from_folder().to_owned(); + + // We compute a default empty tree + let tree = match PoseidonTree::new( + tree_depth, + ::Hasher::default_leaf(), + tree_config, + ) { + Ok(tree) => tree, + Err(err) => { + return CResult { + ok: None, + err: Some(err.to_string().into()), + }; + } + }; + + let rln = FFI_RLN { + proving_key: proving_key.to_owned(), + graph_data: graph_data.to_vec(), + #[cfg(not(feature = "stateless"))] + tree, + }; + + CResult { + ok: Some(Box_::new(rln)), + err: None, + } +} + +#[cfg(feature = "stateless")] +#[ffi_export] +pub fn ffi_new() -> CResult, repr_c::String> { + let proving_key = zkey_from_folder().to_owned(); + let graph_data = graph_from_folder().to_owned(); + + let rln = FFI_RLN { + proving_key: proving_key.to_owned(), + graph_data: graph_data.to_vec(), + }; + + CResult { + ok: Some(Box_::new(rln)), + err: None, + } +} + +#[cfg(not(feature = "stateless"))] +#[ffi_export] +pub fn ffi_new_with_params( + tree_depth: usize, + zkey_buffer: &repr_c::Vec, + graph_data: &repr_c::Vec, + config_path: char_p::Ref<'_>, +) -> CResult, repr_c::String> { + let tree_config = match File::open(config_path.to_str()).and_then(|mut file| { + let mut config_str = String::new(); + file.read_to_string(&mut config_str)?; + Ok(config_str) + }) { + Ok(config_str) if !config_str.is_empty() => { + match ::Config::from_str(&config_str) { + Ok(config) => config, + Err(err) => { + return CResult { + ok: None, + err: Some(err.to_string().into()), + }; + } + } + } + _ => ::Config::default(), + }; + + let proving_key = match zkey_from_raw(zkey_buffer) { + Ok(pk) => pk, + Err(err) => { + return CResult { + ok: None, + err: Some(err.to_string().into()), + }; + } + }; + + // We compute a default empty tree + let tree = match PoseidonTree::new( + tree_depth, + ::Hasher::default_leaf(), + tree_config, + ) { + Ok(tree) => tree, + Err(err) => { + return CResult { + ok: None, + err: Some(err.to_string().into()), + }; + } + }; + + let rln = FFI_RLN { + proving_key, + graph_data: graph_data.to_vec(), + #[cfg(not(feature = "stateless"))] + tree, + }; + + CResult { + ok: Some(Box_::new(rln)), + err: None, + } +} + +#[cfg(feature = "stateless")] +#[ffi_export] +pub fn ffi_new_with_params( + zkey_buffer: &repr_c::Vec, + graph_data: &repr_c::Vec, +) -> CResult, repr_c::String> { + let proving_key = match zkey_from_raw(zkey_buffer) { + Ok(pk) => pk, + Err(err) => { + return CResult { + ok: None, + err: Some(err.to_string().into()), + }; + } + }; + + let rln = FFI_RLN { + proving_key, + graph_data: graph_data.to_vec(), + }; + + CResult { + ok: Some(Box_::new(rln)), + err: None, + } +} + +#[ffi_export] +pub fn ffi_rln_free(rln: Option>) { + drop(rln); +} + +// RLNProof + +#[derive_ReprC] +#[repr(opaque)] +pub struct FFI_RLNProof { + pub(crate) proof: ArkProof, + pub(crate) proof_values: RLNProofValues, +} + +#[ffi_export] +pub fn ffi_rln_proof_free(rln: Option>) { + drop(rln); +} + +// Proof generation APIs + +#[cfg(not(feature = "stateless"))] +#[ffi_export] +pub fn ffi_generate_rln_proof( + rln: &repr_c::Box, + identity_secret: &CFr, + user_message_limit: &CFr, + message_id: &CFr, + x: &CFr, + external_nullifier: &CFr, + leaf_index: usize, +) -> CResult, repr_c::String> { + let proof = match rln.tree.proof(leaf_index) { + Ok(proof) => proof, + Err(err) => { + return CResult { + ok: None, + err: Some(err.to_string().into()), + }; + } + }; + + let path_elements: Vec = proof.get_path_elements(); + let identity_path_index: Vec = proof.get_path_index(); + + let mut identity_secret_fr = identity_secret.0; + let rln_witness = match RLNWitnessInput::new( + IdSecret::from(&mut identity_secret_fr), + user_message_limit.0, + message_id.0, + path_elements, + identity_path_index, + x.0, + external_nullifier.0, + ) { + Ok(witness) => witness, + Err(err) => { + return CResult { + ok: None, + err: Some(err.to_string().into()), + }; + } + }; + + let proof_values = match proof_values_from_witness(&rln_witness) { + Ok(pv) => pv, + Err(err) => { + return CResult { + ok: None, + err: Some(err.to_string().into()), + }; + } + }; + + let proof = match generate_proof(&rln.proving_key, &rln_witness, &rln.graph_data) { + Ok(proof) => proof, + Err(err) => { + return CResult { + ok: None, + err: Some(err.to_string().into()), + }; + } + }; + + CResult { + ok: Some(Box_::new(FFI_RLNProof { + proof_values, + proof, + })), + err: None, + } +} + +#[cfg(feature = "stateless")] +#[ffi_export] +pub fn ffi_generate_rln_proof_stateless( + rln: &repr_c::Box, + identity_secret: &CFr, + user_message_limit: &CFr, + message_id: &CFr, + path_elements: &repr_c::Vec, + identity_path_index: &repr_c::Vec, + x: &CFr, + external_nullifier: &CFr, +) -> CResult, repr_c::String> { + let mut identity_secret_fr = identity_secret.0; + let path_elements: Vec = path_elements.iter().map(|cfr| cfr.0).collect(); + let identity_path_index: Vec = identity_path_index.iter().copied().collect(); + let rln_witness = match RLNWitnessInput::new( + IdSecret::from(&mut identity_secret_fr), + user_message_limit.0, + message_id.0, + path_elements, + identity_path_index, + x.0, + external_nullifier.0, + ) { + Ok(witness) => witness, + Err(err) => { + return CResult { + ok: None, + err: Some(err.to_string().into()), + }; + } + }; + + let proof_values = match proof_values_from_witness(&rln_witness) { + Ok(pv) => pv, + Err(err) => { + return CResult { + ok: None, + err: Some(err.to_string().into()), + }; + } + }; + + let proof = match generate_proof(&rln.proving_key, &rln_witness, &rln.graph_data) { + Ok(proof) => proof, + Err(err) => { + return CResult { + ok: None, + err: Some(err.to_string().into()), + }; + } + }; + + CResult { + ok: Some(Box_::new(FFI_RLNProof { + proof_values, + proof, + })), + err: None, + } +} + +// Proof verification APIs + +#[cfg(not(feature = "stateless"))] +#[ffi_export] +pub fn ffi_verify_rln_proof( + rln: &repr_c::Box, + proof: &repr_c::Box, + x: &CFr, +) -> CResult, repr_c::String> { + // Verify the proof + match verify_proof(&rln.proving_key.0.vk, &proof.proof, &proof.proof_values) { + Ok(proof_verified) => { + // Verify the root and signal + let roots_verified = rln.tree.root() == proof.proof_values.root; + let signal_verified = *x == proof.proof_values.x; + CResult { + ok: Some(Box_::new( + proof_verified && roots_verified && signal_verified, + )), + err: None, + } + } + Err(err) => CResult { + ok: None, + err: Some(err.to_string().into()), + }, + } +} + +#[ffi_export] +pub fn ffi_verify_with_roots( + rln: &repr_c::Box, + proof: &repr_c::Box, + roots: &repr_c::Vec, + x: &CFr, +) -> CResult, repr_c::String> { + // Verify the proof + let proof_verified = + match verify_proof(&rln.proving_key.0.vk, &proof.proof, &proof.proof_values) { + Ok(v) => v, + Err(err) => { + return CResult { + ok: None, + err: Some(err.to_string().into()), + }; + } + }; + + // If proof verification failed, return early + if !proof_verified { + return CResult { + ok: Some(Box_::new(false)), + err: None, + }; + } + + // Verify the root + let roots_verified: bool = if roots.is_empty() { + // If no root is passed in roots_buffer, we skip proof's root check + true + } else { + // We check if the proof's root is in roots + roots.iter().any(|root| root.0 == proof.proof_values.root) + }; + + // Verify the signal + let signal_verified = *x == proof.proof_values.x; + + CResult { + ok: Some(Box_::new( + proof_verified && roots_verified && signal_verified, + )), + err: None, + } +} + +// Identity secret recovery API + +#[ffi_export] +pub fn ffi_recover_id_secret( + proof_1: &repr_c::Box, + proof_2: &repr_c::Box, +) -> CResult, repr_c::String> { + let external_nullifier_1 = proof_1.proof_values.external_nullifier; + let external_nullifier_2 = proof_2.proof_values.external_nullifier; + + // We continue only if the proof values are for the same external nullifier + if external_nullifier_1 != external_nullifier_2 { + return CResult { + ok: None, + err: Some("External nullifiers do not match".to_string().into()), + }; + } + + // We extract the two shares + let share1 = (proof_1.proof_values.x, proof_1.proof_values.y); + let share2 = (proof_2.proof_values.x, proof_2.proof_values.y); + + // We recover the secret + let recovered_identity_secret_hash = match compute_id_secret(share1, share2) { + Ok(secret) => secret, + Err(err) => { + return CResult { + ok: None, + err: Some(err.to_string().into()), + }; + } + }; + + CResult { + ok: Some(CFr::from(*recovered_identity_secret_hash).into()), + err: None, + } +} diff --git a/rln/src/ffi/ffi_tree.rs b/rln/src/ffi/ffi_tree.rs new file mode 100644 index 0000000..0a0aec8 --- /dev/null +++ b/rln/src/ffi/ffi_tree.rs @@ -0,0 +1,330 @@ +#![allow(non_camel_case_types)] + +#[cfg(not(feature = "stateless"))] +use { + super::ffi_rln::FFI_RLN, + super::ffi_utils::{CFr, CResult}, + crate::poseidon_tree::PoseidonTree, + safer_ffi::{boxed::Box_, derive_ReprC, ffi_export, prelude::repr_c}, + utils::{ZerokitMerkleProof, ZerokitMerkleTree}, +}; + +// MerkleProof + +#[cfg(not(feature = "stateless"))] +#[derive_ReprC] +#[repr(C)] +pub struct FFI_MerkleProof { + pub path_elements: repr_c::Vec, + pub path_index: repr_c::Vec, +} + +#[cfg(not(feature = "stateless"))] +#[ffi_export] +pub fn ffi_merkle_proof_free(proof: Option>) { + drop(proof); +} + +// Merkle tree management APIs + +#[cfg(not(feature = "stateless"))] +#[ffi_export] +pub fn ffi_set_tree( + rln: &mut repr_c::Box, + tree_depth: usize, +) -> CResult, repr_c::String> { + // We compute a default empty tree of desired depth + match PoseidonTree::default(tree_depth) { + Ok(tree) => { + rln.tree = tree; + CResult { + ok: Some(Box_::new(true)), + err: None, + } + } + Err(err) => CResult { + ok: None, + err: Some(err.to_string().into()), + }, + } +} + +// Merkle tree leaf operations + +#[cfg(not(feature = "stateless"))] +#[ffi_export] +pub fn ffi_delete_leaf( + rln: &mut repr_c::Box, + index: usize, +) -> CResult, repr_c::String> { + match rln.tree.delete(index) { + Ok(_) => CResult { + ok: Some(Box_::new(true)), + err: None, + }, + Err(err) => CResult { + ok: None, + err: Some(err.to_string().into()), + }, + } +} + +#[cfg(not(feature = "stateless"))] +#[ffi_export] +pub fn ffi_set_leaf( + rln: &mut repr_c::Box, + index: usize, + value: &repr_c::Box, +) -> CResult, repr_c::String> { + match rln.tree.set(index, value.0) { + Ok(_) => CResult { + ok: Some(Box_::new(true)), + err: None, + }, + Err(err) => CResult { + ok: None, + err: Some(err.to_string().into()), + }, + } +} + +#[cfg(not(feature = "stateless"))] +#[ffi_export] +pub fn ffi_get_leaf( + rln: &repr_c::Box, + index: usize, +) -> CResult, repr_c::String> { + match rln.tree.get(index) { + Ok(leaf) => CResult { + ok: Some(CFr::from(leaf).into()), + err: None, + }, + Err(err) => CResult { + ok: None, + err: Some(err.to_string().into()), + }, + } +} + +#[cfg(not(feature = "stateless"))] +#[ffi_export] +pub fn ffi_leaves_set(rln: &repr_c::Box) -> usize { + rln.tree.leaves_set() +} + +#[cfg(not(feature = "stateless"))] +#[ffi_export] +pub fn ffi_set_next_leaf( + rln: &mut repr_c::Box, + value: &repr_c::Box, +) -> CResult, repr_c::String> { + match rln.tree.update_next(value.0) { + Ok(_) => CResult { + ok: Some(Box_::new(true)), + err: None, + }, + Err(err) => CResult { + ok: None, + err: Some(err.to_string().into()), + }, + } +} + +#[cfg(not(feature = "stateless"))] +#[ffi_export] +pub fn ffi_set_leaves_from( + rln: &mut repr_c::Box, + index: usize, + leaves: &repr_c::Vec, +) -> CResult, repr_c::String> { + match rln + .tree + .override_range(index, leaves.iter().map(|cfr| cfr.0), [].into_iter()) + { + Ok(_) => CResult { + ok: Some(Box_::new(true)), + err: None, + }, + Err(err) => CResult { + ok: None, + err: Some(err.to_string().into()), + }, + } +} + +#[cfg(not(feature = "stateless"))] +#[ffi_export] +pub fn ffi_init_tree_with_leaves( + rln: &mut repr_c::Box, + leaves: &repr_c::Vec, +) -> CResult, repr_c::String> { + // Reset tree to default + let tree_depth = rln.tree.depth(); + match PoseidonTree::default(tree_depth) { + Ok(tree) => { + rln.tree = tree; + } + Err(err) => { + return CResult { + ok: None, + err: Some(err.to_string().into()), + } + } + } + + match rln + .tree + .override_range(0, leaves.iter().map(|cfr| cfr.0), [].into_iter()) + { + Ok(_) => CResult { + ok: Some(Box_::new(true)), + err: None, + }, + Err(err) => CResult { + ok: None, + err: Some(err.to_string().into()), + }, + } +} + +// Atomic operations + +#[cfg(not(feature = "stateless"))] +#[ffi_export] +pub fn ffi_atomic_operation( + rln: &mut repr_c::Box, + index: usize, + leaves: &repr_c::Vec, + indices: &repr_c::Vec, +) -> CResult, repr_c::String> { + match rln.tree.override_range( + index, + leaves.iter().map(|cfr| cfr.0), + indices.iter().copied(), + ) { + Ok(_) => CResult { + ok: Some(Box_::new(true)), + err: None, + }, + Err(err) => CResult { + ok: None, + err: Some(err.to_string().into()), + }, + } +} + +#[cfg(not(feature = "stateless"))] +#[ffi_export] +pub fn ffi_seq_atomic_operation( + rln: &mut repr_c::Box, + leaves: &repr_c::Vec, + indices: &repr_c::Vec, +) -> CResult, repr_c::String> { + let index = rln.tree.leaves_set(); + match rln.tree.override_range( + index, + leaves.iter().map(|cfr| cfr.0), + indices.iter().map(|x| *x as usize), + ) { + Ok(_) => CResult { + ok: Some(Box_::new(true)), + err: None, + }, + Err(err) => CResult { + ok: None, + err: Some(err.to_string().into()), + }, + } +} + +// Root and proof operations + +#[cfg(not(feature = "stateless"))] +#[ffi_export] +pub fn ffi_get_root(rln: &repr_c::Box) -> repr_c::Box { + CFr::from(rln.tree.root()).into() +} + +#[cfg(not(feature = "stateless"))] +#[ffi_export] +pub fn ffi_get_proof( + rln: &repr_c::Box, + index: usize, +) -> CResult, repr_c::String> { + match rln.tree.proof(index) { + Ok(proof) => { + let path_elements: repr_c::Vec = proof + .get_path_elements() + .iter() + .map(|fr| CFr::from(*fr)) + .collect::>() + .into(); + + let path_index: repr_c::Vec = proof.get_path_index().into(); + + let merkle_proof = FFI_MerkleProof { + path_elements, + path_index, + }; + + CResult { + ok: Some(Box_::new(merkle_proof)), + err: None, + } + } + Err(err) => CResult { + ok: None, + err: Some(err.to_string().into()), + }, + } +} + +// Persistent metadata APIs + +#[cfg(not(feature = "stateless"))] +#[ffi_export] +pub fn ffi_set_metadata( + rln: &mut repr_c::Box, + metadata: &repr_c::Vec, +) -> CResult, repr_c::String> { + match rln.tree.set_metadata(metadata) { + Ok(_) => CResult { + ok: Some(Box_::new(true)), + err: None, + }, + Err(err) => CResult { + ok: None, + err: Some(err.to_string().into()), + }, + } +} + +#[cfg(not(feature = "stateless"))] +#[ffi_export] +pub fn ffi_get_metadata(rln: &repr_c::Box) -> CResult, repr_c::String> { + match rln.tree.metadata() { + Ok(metadata) => CResult { + ok: Some(metadata.into()), + err: None, + }, + Err(err) => CResult { + ok: None, + err: Some(err.to_string().into()), + }, + } +} + +#[cfg(not(feature = "stateless"))] +#[ffi_export] +pub fn ffi_flush(rln: &mut repr_c::Box) -> CResult, repr_c::String> { + match rln.tree.close_db_connection() { + Ok(_) => CResult { + ok: Some(Box_::new(true)), + err: None, + }, + Err(err) => CResult { + ok: None, + err: Some(err.to_string().into()), + }, + } +} diff --git a/rln/src/ffi/ffi_utils.rs b/rln/src/ffi/ffi_utils.rs new file mode 100644 index 0000000..2f7e23a --- /dev/null +++ b/rln/src/ffi/ffi_utils.rs @@ -0,0 +1,284 @@ +#![allow(non_camel_case_types)] + +use crate::{ + circuit::Fr, + hashers::{hash_to_field_be, hash_to_field_le, poseidon_hash}, + protocol::{extended_keygen, extended_seeded_keygen, keygen, seeded_keygen}, + utils::{bytes_be_to_fr, bytes_le_to_fr, fr_to_bytes_be, fr_to_bytes_le}, +}; +use safer_ffi::prelude::ReprC; +use safer_ffi::{boxed::Box_, derive_ReprC, ffi_export, prelude::repr_c}; +use std::ops::Deref; + +// CResult + +#[derive_ReprC] +#[repr(C)] +pub struct CResult { + pub ok: Option, + pub err: Option, +} + +// CFr + +#[derive_ReprC] +#[repr(opaque)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct CFr(pub(crate) Fr); + +impl Deref for CFr { + type Target = Fr; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for CFr { + fn from(fr: Fr) -> Self { + Self(fr) + } +} + +impl From for repr_c::Box { + fn from(cfr: CFr) -> Self { + Box_::new(cfr) + } +} + +impl From<&CFr> for repr_c::Box { + fn from(cfr: &CFr) -> Self { + Box_::new(CFr(cfr.0)) + } +} + +impl PartialEq for CFr { + fn eq(&self, other: &Fr) -> bool { + self.0 == *other + } +} + +#[ffi_export] +pub fn cfr_zero() -> repr_c::Box { + Box_::new(CFr::from(Fr::from(0))) +} + +#[ffi_export] +pub fn cfr_one() -> repr_c::Box { + Box_::new(CFr::from(Fr::from(1))) +} + +#[ffi_export] +pub fn cfr_to_bytes_le(cfr: &CFr) -> repr_c::Vec { + fr_to_bytes_le(&cfr.0).into() +} + +#[ffi_export] +pub fn cfr_to_bytes_be(cfr: &CFr) -> repr_c::Vec { + fr_to_bytes_be(&cfr.0).into() +} + +#[ffi_export] +pub fn bytes_le_to_cfr(bytes: &repr_c::Vec) -> repr_c::Box { + let (cfr, _) = bytes_le_to_fr(bytes); + Box_::new(CFr(cfr)) +} + +#[ffi_export] +pub fn bytes_be_to_cfr(bytes: &repr_c::Vec) -> repr_c::Box { + let (cfr, _) = bytes_be_to_fr(bytes); + Box_::new(CFr(cfr)) +} + +#[ffi_export] +pub fn uint_to_cfr(value: u32) -> repr_c::Box { + Box_::new(CFr::from(Fr::from(value))) +} + +#[ffi_export] +pub fn cfr_debug(cfr: Option<&CFr>) -> repr_c::String { + format!("{:?}", cfr.map(|c| c.0.to_string())).into() +} + +#[ffi_export] +pub fn cfr_free(cfr: Option>) { + drop(cfr); +} + +// Vec + +#[ffi_export] +pub fn vec_cfr_get(v: &repr_c::Vec, i: usize) -> Option<&CFr> { + v.get(i) +} + +#[ffi_export] +pub fn vec_cfr_to_bytes_le(vec: &repr_c::Vec) -> repr_c::Vec { + let vec_fr: Vec = vec.iter().map(|cfr| cfr.0).collect(); + crate::utils::vec_fr_to_bytes_le(&vec_fr).into() +} + +#[ffi_export] +pub fn vec_cfr_to_bytes_be(vec: &repr_c::Vec) -> repr_c::Vec { + let vec_fr: Vec = vec.iter().map(|cfr| cfr.0).collect(); + crate::utils::vec_fr_to_bytes_be(&vec_fr).into() +} + +#[ffi_export] +pub fn bytes_le_to_vec_cfr( + bytes: &repr_c::Vec, +) -> CResult>, repr_c::String> { + match crate::utils::bytes_le_to_vec_fr(bytes) { + Ok((vec_fr, _)) => { + let vec_cfr: Vec = vec_fr.into_iter().map(CFr).collect(); + CResult { + ok: Some(Box_::new(vec_cfr.into())), + err: None, + } + } + Err(err) => CResult { + ok: None, + err: Some(err.to_string().into()), + }, + } +} + +#[ffi_export] +pub fn bytes_be_to_vec_cfr( + bytes: &repr_c::Vec, +) -> CResult>, repr_c::String> { + match crate::utils::bytes_be_to_vec_fr(bytes) { + Ok((vec_fr, _)) => { + let vec_cfr: Vec = vec_fr.into_iter().map(CFr).collect(); + CResult { + ok: Some(Box_::new(vec_cfr.into())), + err: None, + } + } + Err(err) => CResult { + ok: None, + err: Some(err.to_string().into()), + }, + } +} + +#[ffi_export] +pub fn vec_cfr_debug(v: &repr_c::Vec) -> repr_c::String { + format!("{:?}", v.iter().map(|cfr| cfr.0).collect::>()).into() +} + +#[ffi_export] +pub fn vec_cfr_free(v: repr_c::Vec) { + drop(v); +} + +// Vec + +#[ffi_export] +pub fn vec_u8_to_bytes_le(vec: &repr_c::Vec) -> repr_c::Vec { + crate::utils::vec_u8_to_bytes_le(vec).into() +} + +#[ffi_export] +pub fn vec_u8_to_bytes_be(vec: &repr_c::Vec) -> repr_c::Vec { + crate::utils::vec_u8_to_bytes_be(vec).into() +} + +#[ffi_export] +pub fn bytes_le_to_vec_u8( + bytes: &repr_c::Vec, +) -> CResult>, repr_c::String> { + match crate::utils::bytes_le_to_vec_u8(bytes) { + Ok((vec, _)) => CResult { + ok: Some(Box_::new(vec.into())), + err: None, + }, + Err(err) => CResult { + ok: None, + err: Some(err.to_string().into()), + }, + } +} + +#[ffi_export] +pub fn bytes_be_to_vec_u8( + bytes: &repr_c::Vec, +) -> CResult>, repr_c::String> { + match crate::utils::bytes_be_to_vec_u8(bytes) { + Ok((vec, _)) => CResult { + ok: Some(Box_::new(vec.into())), + err: None, + }, + Err(err) => CResult { + ok: None, + err: Some(err.to_string().into()), + }, + } +} + +#[ffi_export] +pub fn vec_u8_debug(v: &repr_c::Vec) -> repr_c::String { + format!("{:?}", v.iter().copied().collect::>()).into() +} + +#[ffi_export] +pub fn vec_u8_free(v: repr_c::Vec) { + drop(v); +} + +// Utility APIs + +#[ffi_export] +pub fn ffi_hash_to_field_le(input: &repr_c::Vec) -> repr_c::Box { + let hash_result = hash_to_field_le(input); + CFr::from(hash_result).into() +} + +#[ffi_export] +pub fn ffi_hash_to_field_be(input: &repr_c::Vec) -> repr_c::Box { + let hash_result = hash_to_field_be(input); + CFr::from(hash_result).into() +} + +#[ffi_export] +pub fn ffi_poseidon_hash_pair(a: &CFr, b: &CFr) -> repr_c::Box { + let hash_result = poseidon_hash(&[a.0, b.0]); + CFr::from(hash_result).into() +} + +#[ffi_export] +pub fn ffi_key_gen() -> repr_c::Vec { + let (identity_secret_hash, id_commitment) = keygen(); + vec![CFr(*identity_secret_hash), CFr(id_commitment)].into() +} + +#[ffi_export] +pub fn ffi_seeded_key_gen(seed: &repr_c::Vec) -> repr_c::Vec { + let (identity_secret_hash, id_commitment) = seeded_keygen(seed); + vec![CFr(identity_secret_hash), CFr(id_commitment)].into() +} + +#[ffi_export] +pub fn ffi_extended_key_gen() -> repr_c::Vec { + let (identity_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) = + extended_keygen(); + vec![ + CFr(identity_trapdoor), + CFr(identity_nullifier), + CFr(identity_secret_hash), + CFr(id_commitment), + ] + .into() +} + +#[ffi_export] +pub fn ffi_seeded_extended_key_gen(seed: &repr_c::Vec) -> repr_c::Vec { + let (identity_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) = + extended_seeded_keygen(seed); + vec![ + CFr(identity_trapdoor), + CFr(identity_nullifier), + CFr(identity_secret_hash), + CFr(id_commitment), + ] + .into() +} diff --git a/rln/src/ffi/mod.rs b/rln/src/ffi/mod.rs new file mode 100644 index 0000000..fb75a2b --- /dev/null +++ b/rln/src/ffi/mod.rs @@ -0,0 +1,10 @@ +#![cfg(not(target_arch = "wasm32"))] + +pub mod ffi_rln; +pub mod ffi_tree; +pub mod ffi_utils; + +#[cfg(feature = "headers")] +pub fn generate_headers() -> ::std::io::Result<()> { + ::safer_ffi::headers::builder().to_file("rln.h")?.generate() +} diff --git a/rln/src/lib.rs b/rln/src/lib.rs index f50b4b9..b7bfd39 100644 --- a/rln/src/lib.rs +++ b/rln/src/lib.rs @@ -1,6 +1,5 @@ pub mod circuit; pub mod error; -#[cfg(not(target_arch = "wasm32"))] pub mod ffi; pub mod hashers; #[cfg(feature = "pmtree-ft")] diff --git a/rln/src/pm_tree_adapter.rs b/rln/src/pm_tree_adapter.rs index 99253d1..d3b6957 100644 --- a/rln/src/pm_tree_adapter.rs +++ b/rln/src/pm_tree_adapter.rs @@ -476,7 +476,7 @@ impl ZerokitMerkleProof for PmTreeProof { } #[cfg(test)] -mod tests { +mod test { use super::*; #[test] diff --git a/rln/src/protocol.rs b/rln/src/protocol.rs index 3858636..724cf37 100644 --- a/rln/src/protocol.rs +++ b/rln/src/protocol.rs @@ -51,6 +51,31 @@ pub struct RLNWitnessInput { external_nullifier: Fr, } +impl RLNWitnessInput { + pub fn new( + identity_secret: IdSecret, + user_message_limit: Fr, + message_id: Fr, + path_elements: Vec, + identity_path_index: Vec, + x: Fr, + external_nullifier: Fr, + ) -> Result { + merkle_proof_len_check(&path_elements, &identity_path_index)?; + message_id_range_check(&message_id, &user_message_limit)?; + + Ok(Self { + identity_secret, + user_message_limit, + message_id, + path_elements, + identity_path_index, + x, + external_nullifier, + }) + } +} + #[derive(Debug, PartialEq)] pub struct RLNProofValues { // Public outputs: @@ -864,10 +889,22 @@ pub fn rln_witness_to_bigint_json( Ok(inputs) } -pub fn message_id_range_check( - message_id: &Fr, - user_message_limit: &Fr, +fn merkle_proof_len_check( + path_elements: &[Fr], + identity_path_index: &[u8], ) -> Result<(), ProtocolError> { + let path_elements_len = path_elements.len(); + let identity_path_index_len = identity_path_index.len(); + if path_elements_len != identity_path_index_len { + return Err(ProtocolError::InvalidMerkleProofLength( + path_elements_len, + identity_path_index_len, + )); + } + Ok(()) +} + +fn message_id_range_check(message_id: &Fr, user_message_limit: &Fr) -> Result<(), ProtocolError> { if message_id > user_message_limit { return Err(ProtocolError::InvalidMessageId( *message_id, diff --git a/rln/src/public.rs b/rln/src/public.rs index 82620d7..51c1e39 100644 --- a/rln/src/public.rs +++ b/rln/src/public.rs @@ -1,5 +1,5 @@ use crate::circuit::{zkey_from_raw, Curve, Fr}; -use crate::hashers::{hash_to_field_le, poseidon_hash as utils_poseidon_hash}; +use crate::hashers::{hash_to_field_be, hash_to_field_le, poseidon_hash as utils_poseidon_hash}; use crate::protocol::{ compute_id_secret, deserialize_proof_values, deserialize_witness, extended_keygen, extended_seeded_keygen, keygen, proof_values_from_witness, rln_witness_to_bigint_json, @@ -789,7 +789,7 @@ impl RLN { /// /// Input values are: /// - `input_data`: a reader for the serialization of the RLN zkSNARK proof concatenated with a serialization of the circuit output values, - /// i.e. `[ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32>]`, where <_> indicates the byte length. + /// i.e. `[ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]`, where <_> indicates the byte length. /// /// The function returns true if the zkSNARK proof is valid with respect to the provided circuit output values, false otherwise. /// @@ -842,7 +842,7 @@ impl RLN { /// - `input_data`: a reader for the serialization of `[ identity_secret<32> | id_index<8> | user_message_limit<32> | message_id<32> | external_nullifier<32> | signal_len<8> | signal ]` /// /// Output values are: - /// - `output_data`: a writer receiving the serialization of the zkSNARK proof and the circuit evaluations outputs, i.e. `[ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32>]` + /// - `output_data`: a writer receiving the serialization of the zkSNARK proof and the circuit evaluations outputs, i.e. `[ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]` /// /// Example /// ``` @@ -884,7 +884,7 @@ impl RLN { /// rln.generate_rln_proof(&mut input_buffer, &mut output_buffer) /// .unwrap(); /// - /// // proof_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32>] + /// // proof_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ] /// let mut proof_data = output_buffer.into_inner(); /// ``` #[cfg(all(not(target_arch = "wasm32"), not(feature = "stateless")))] @@ -911,7 +911,7 @@ impl RLN { /// Generate RLN Proof using a witness calculated from outside zerokit /// - /// output_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32>] + /// output_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ] #[cfg(not(target_arch = "wasm32"))] pub fn generate_rln_proof_with_witness( &mut self, @@ -934,7 +934,7 @@ impl RLN { /// Generate RLN Proof using a witness calculated from outside zerokit /// - /// output_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32>] + /// output_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ] #[cfg(target_arch = "wasm32")] pub fn generate_rln_proof_with_witness( &mut self, @@ -958,7 +958,7 @@ impl RLN { /// /// Input values are: /// - `input_data`: a reader for the serialization of the RLN zkSNARK proof concatenated with a serialization of the circuit output values and the signal information, - /// i.e. `[ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal]`, where <_> indicates the byte length. + /// i.e. `[ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal ]`, where <_> indicates the byte length. /// /// The function returns true if the zkSNARK proof is valid with respect to the provided circuit output values and signal. Returns false otherwise. /// @@ -972,7 +972,7 @@ impl RLN { /// // proof_data is computed as in the example code snippet provided for rln::public::RLN::generate_rln_proof /// /// // We prepare input for verify_rln_proof API - /// // input_data is `[ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal]` + /// // input_data is `[ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal ]` /// // that is [ proof_data || signal_len<8> | signal ] /// let verify_input = prepare_verify_input(proof_data, &signal); /// @@ -1012,7 +1012,7 @@ impl RLN { /// Verifies a zkSNARK RLN proof against the provided proof values and a set of allowed Merkle tree roots. /// /// Input values are: - /// - `input_data`: a reader for the serialization of the RLN zkSNARK proof concatenated with a serialization of the circuit output values and the signal information, i.e. `[ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal]` + /// - `input_data`: a reader for the serialization of the RLN zkSNARK proof concatenated with a serialization of the circuit output values and the signal information, i.e. `[ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal ]` /// - `roots_data`: a reader for the serialization of a vector of roots, i.e. `[ number_of_roots<8> | root_1<32> | ... | root_n<32> ]` (number_of_roots is an uint64 in little-endian, roots are serialized using `rln::utils::fr_to_bytes_le`) /// /// The function returns true if the zkSNARK proof is valid with respect to the provided circuit output values, signal and roots. Returns false otherwise. @@ -1133,7 +1133,7 @@ impl RLN { /// /// Input values are: /// - `input_proof_data_1`: a reader for the serialization of a RLN zkSNARK proof concatenated with a serialization of the circuit output values and -optionally- the signal information, - /// i.e. either `[proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32>]` + /// i.e. either `[proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ]` /// or `[ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal ]` (to maintain compatibility with both output of [`generate_rln_proof`](crate::public::RLN::generate_rln_proof) and input of [`verify_rln_proof`](crate::public::RLN::verify_rln_proof)) /// - `input_proof_data_2`: same as `input_proof_data_1` /// @@ -1311,7 +1311,7 @@ pub fn hash( let hash = hash_to_field_le(&serialized); output_data.write_all(&fr_to_bytes_le(&hash))?; } else { - let hash = hash_to_field_le(&serialized); + let hash = hash_to_field_be(&serialized); output_data.write_all(&fr_to_bytes_be(&hash))?; } diff --git a/rln/tests/ffi.rs b/rln/tests/ffi.rs index f71ebe1..edee801 100644 --- a/rln/tests/ffi.rs +++ b/rln/tests/ffi.rs @@ -4,35 +4,54 @@ mod test { use ark_std::{rand::thread_rng, UniformRand}; use rand::Rng; use rln::circuit::{Fr, TEST_TREE_DEPTH}; - use rln::ffi::*; + use rln::ffi::{ffi_rln::*, ffi_tree::*, ffi_utils::*}; use rln::hashers::{hash_to_field_le, poseidon_hash as utils_poseidon_hash}; - use rln::protocol::{deserialize_identity_tuple_le, *}; - use rln::public::RLN; + use rln::protocol::*; use rln::utils::*; + use safer_ffi::boxed::Box_; + use safer_ffi::prelude::repr_c; use serde_json::json; use std::fs::File; use std::io::Read; - use std::mem::MaybeUninit; - use std::time::{Duration, Instant}; use zeroize::Zeroize; const NO_OF_LEAVES: usize = 256; - fn create_rln_instance() -> &'static mut RLN { - let mut rln_pointer = MaybeUninit::<*mut RLN>::uninit(); + fn create_rln_instance() -> repr_c::Box { let input_config = json!({}).to_string(); - let input_buffer = &Buffer::from(input_config.as_bytes()); - let success = new(TEST_TREE_DEPTH, input_buffer, rln_pointer.as_mut_ptr()); - assert!(success, "RLN object creation failed"); - unsafe { &mut *rln_pointer.assume_init() } + let c_str = std::ffi::CString::new(input_config).unwrap(); + match ffi_new(TEST_TREE_DEPTH, c_str.as_c_str().into()) { + CResult { + ok: Some(rln), + err: None, + } => rln, + CResult { + ok: None, + err: Some(err), + } => panic!("RLN object creation failed: {}", err), + _ => unreachable!(), + } } - fn set_leaves_init(rln_pointer: &mut RLN, leaves: &[Fr]) { - let leaves_ser = vec_fr_to_bytes_le(leaves); - let input_buffer = &Buffer::from(leaves_ser.as_ref()); - let success = init_tree_with_leaves(rln_pointer, input_buffer); - assert!(success, "init tree with leaves call failed"); - assert_eq!(rln_pointer.leaves_set(), leaves.len()); + fn set_leaves_init(ffi_rln_instance: &mut repr_c::Box, leaves: &[Fr]) { + let leaves_vec: repr_c::Vec = leaves + .iter() + .map(|fr| CFr::from(*fr)) + .collect::>() + .into(); + match ffi_init_tree_with_leaves(ffi_rln_instance, &leaves_vec) { + CResult { + ok: Some(_), + err: None, + } => { + assert_eq!(ffi_leaves_set(ffi_rln_instance), leaves.len()); + } + CResult { + ok: None, + err: Some(err), + } => panic!("init tree with leaves call failed: {}", err), + _ => unreachable!(), + } } fn get_random_leaves() -> Vec { @@ -40,34 +59,47 @@ mod test { (0..NO_OF_LEAVES).map(|_| Fr::rand(&mut rng)).collect() } - fn get_tree_root(rln_pointer: &mut RLN) -> Fr { - let mut output_buffer = MaybeUninit::::uninit(); - let success = get_root(rln_pointer, output_buffer.as_mut_ptr()); - assert!(success, "get root call failed"); - let output_buffer = unsafe { output_buffer.assume_init() }; - let result_data = <&[u8]>::from(&output_buffer).to_vec(); - let (root, _) = bytes_le_to_fr(&result_data); - root + fn get_tree_root(ffi_rln_instance: &repr_c::Box) -> Fr { + let root_cfr = ffi_get_root(ffi_rln_instance); + **root_cfr } fn identity_pair_gen() -> (IdSecret, Fr) { - let mut output_buffer = MaybeUninit::::uninit(); - let success = key_gen(output_buffer.as_mut_ptr(), true); - assert!(success, "key gen call failed"); - let output_buffer = unsafe { output_buffer.assume_init() }; - let result_data = <&[u8]>::from(&output_buffer).to_vec(); - let (identity_secret_hash, read) = IdSecret::from_bytes_le(&result_data); - let (id_commitment, _) = bytes_le_to_fr(&result_data[read..]); - (identity_secret_hash, id_commitment) + let key_gen = ffi_key_gen(); + let mut id_secret_fr = *key_gen[0]; + let id_secret_hash = IdSecret::from(&mut id_secret_fr); + let id_commitment = *key_gen[1]; + (id_secret_hash, id_commitment) } - fn rln_proof_gen(rln_pointer: &mut RLN, serialized: &[u8]) -> Vec { - let input_buffer = &Buffer::from(serialized); - let mut output_buffer = MaybeUninit::::uninit(); - let success = generate_rln_proof(rln_pointer, input_buffer, output_buffer.as_mut_ptr()); - assert!(success, "generate rln proof call failed"); - let output_buffer = unsafe { output_buffer.assume_init() }; - <&[u8]>::from(&output_buffer).to_vec() + fn rln_proof_gen( + ffi_rln_instance: &repr_c::Box, + identity_secret: &CFr, + user_message_limit: &CFr, + message_id: &CFr, + x: &CFr, + external_nullifier: &CFr, + leaf_index: usize, + ) -> repr_c::Box { + match ffi_generate_rln_proof( + ffi_rln_instance, + identity_secret, + user_message_limit, + message_id, + x, + external_nullifier, + leaf_index, + ) { + CResult { + ok: Some(proof), + err: None, + } => proof, + CResult { + ok: None, + err: Some(err), + } => panic!("generate rln proof call failed: {}", err), + _ => unreachable!(), + } } #[test] @@ -76,47 +108,79 @@ mod test { // We generate a vector of random leaves let leaves = get_random_leaves(); // We create a RLN instance - let rln_pointer = create_rln_instance(); + let mut ffi_rln_instance = create_rln_instance(); // We first add leaves one by one specifying the index for (i, leaf) in leaves.iter().enumerate() { // We prepare the rate_commitment and we set the leaf at provided index - let leaf_ser = fr_to_bytes_le(leaf); - let input_buffer = &Buffer::from(leaf_ser.as_ref()); - let success = set_leaf(rln_pointer, i, input_buffer); - assert!(success, "set leaf call failed"); + match ffi_set_leaf(&mut ffi_rln_instance, i, &Box_::new(CFr::from(*leaf))) { + CResult { + ok: Some(_), + err: None, + } => {} + CResult { + ok: None, + err: Some(err), + } => panic!("set leaf call failed: {}", err), + _ => unreachable!(), + } } // We get the root of the tree obtained adding one leaf per time - let root_single = get_tree_root(rln_pointer); + let root_single = get_tree_root(&ffi_rln_instance); // We reset the tree to default - let success = set_tree(rln_pointer, TEST_TREE_DEPTH); - assert!(success, "set tree call failed"); + match ffi_set_tree(&mut ffi_rln_instance, TEST_TREE_DEPTH) { + CResult { + ok: Some(_), + err: None, + } => {} + CResult { + ok: None, + err: Some(err), + } => panic!("set tree call failed: {}", err), + _ => unreachable!(), + } // We add leaves one by one using the internal index (new leaves goes in next available position) for leaf in &leaves { - let leaf_ser = fr_to_bytes_le(leaf); - let input_buffer = &Buffer::from(leaf_ser.as_ref()); - let success = set_next_leaf(rln_pointer, input_buffer); - assert!(success, "set next leaf call failed"); + match ffi_set_next_leaf(&mut ffi_rln_instance, &Box_::new(CFr::from(*leaf))) { + CResult { + ok: Some(_), + err: None, + } => {} + CResult { + ok: None, + err: Some(err), + } => panic!("set next leaf call failed: {}", err), + _ => unreachable!(), + } } // We get the root of the tree obtained adding leaves using the internal index - let root_next = get_tree_root(rln_pointer); + let root_next = get_tree_root(&ffi_rln_instance); // We check if roots are the same assert_eq!(root_single, root_next); // We reset the tree to default - let success = set_tree(rln_pointer, TEST_TREE_DEPTH); - assert!(success, "set tree call failed"); + match ffi_set_tree(&mut ffi_rln_instance, TEST_TREE_DEPTH) { + CResult { + ok: Some(_), + err: None, + } => {} + CResult { + ok: None, + err: Some(err), + } => panic!("set tree call failed: {}", err), + _ => unreachable!(), + } // We add leaves in a batch into the tree - set_leaves_init(rln_pointer, &leaves); + set_leaves_init(&mut ffi_rln_instance, &leaves); // We get the root of the tree obtained adding leaves in batch - let root_batch = get_tree_root(rln_pointer); + let root_batch = get_tree_root(&ffi_rln_instance); // We check if roots are the same assert_eq!(root_single, root_batch); @@ -124,19 +188,37 @@ mod test { // We now delete all leaves set and check if the root corresponds to the empty tree root // delete calls over indexes higher than no_of_leaves are ignored and will not increase self.tree.next_index for i in 0..NO_OF_LEAVES { - let success = delete_leaf(rln_pointer, i); - assert!(success, "delete leaf call failed"); + match ffi_delete_leaf(&mut ffi_rln_instance, i) { + CResult { + ok: Some(_), + err: None, + } => {} + CResult { + ok: None, + err: Some(err), + } => panic!("delete leaf call failed: {}", err), + _ => unreachable!(), + } } // We get the root of the tree obtained deleting all leaves - let root_delete = get_tree_root(rln_pointer); + let root_delete = get_tree_root(&ffi_rln_instance); // We reset the tree to default - let success = set_tree(rln_pointer, TEST_TREE_DEPTH); - assert!(success, "set tree call failed"); + match ffi_set_tree(&mut ffi_rln_instance, TEST_TREE_DEPTH) { + CResult { + ok: Some(_), + err: None, + } => {} + CResult { + ok: None, + err: Some(err), + } => panic!("set tree call failed: {}", err), + _ => unreachable!(), + } // We get the root of the empty tree - let root_empty = get_tree_root(rln_pointer); + let root_empty = get_tree_root(&ffi_rln_instance); // We check if roots are the same assert_eq!(root_delete, root_empty); @@ -147,8 +229,8 @@ mod test { // Uses `set_leaves_from` to set leaves in a batch fn test_leaf_setting_with_index_ffi() { // We create a RLN instance - let rln_pointer = create_rln_instance(); - assert_eq!(rln_pointer.leaves_set(), 0); + let mut ffi_rln_instance = create_rln_instance(); + assert_eq!(ffi_leaves_set(&ffi_rln_instance), 0); // We generate a vector of random leaves let leaves = get_random_leaves(); @@ -160,43 +242,71 @@ mod test { println!("set_index: {set_index}"); // We add leaves in a batch into the tree - set_leaves_init(rln_pointer, &leaves); + set_leaves_init(&mut ffi_rln_instance, &leaves); // We get the root of the tree obtained adding leaves in batch - let root_batch_with_init = get_tree_root(rln_pointer); + let root_batch_with_init = get_tree_root(&ffi_rln_instance); // `init_tree_with_leaves` resets the tree to the depth it was initialized with, using `set_tree` // We add leaves in a batch starting from index 0..set_index - set_leaves_init(rln_pointer, &leaves[0..set_index]); + set_leaves_init(&mut ffi_rln_instance, &leaves[0..set_index]); // We add the remaining n leaves in a batch starting from index set_index - let leaves_n = vec_fr_to_bytes_le(&leaves[set_index..]); - let buffer = &Buffer::from(leaves_n.as_ref()); - let success = set_leaves_from(rln_pointer, set_index, buffer); - assert!(success, "set leaves from call failed"); + let leaves_vec: repr_c::Vec = leaves[set_index..] + .iter() + .map(|fr| CFr::from(*fr)) + .collect::>() + .into(); + match ffi_set_leaves_from(&mut ffi_rln_instance, set_index, &leaves_vec) { + CResult { + ok: Some(_), + err: None, + } => {} + CResult { + ok: None, + err: Some(err), + } => panic!("set leaves from call failed: {}", err), + _ => unreachable!(), + } // We get the root of the tree obtained adding leaves in batch - let root_batch_with_custom_index = get_tree_root(rln_pointer); + let root_batch_with_custom_index = get_tree_root(&ffi_rln_instance); assert_eq!( root_batch_with_init, root_batch_with_custom_index, "root batch !=" ); // We reset the tree to default - let success = set_tree(rln_pointer, TEST_TREE_DEPTH); - assert!(success, "set tree call failed"); + match ffi_set_tree(&mut ffi_rln_instance, TEST_TREE_DEPTH) { + CResult { + ok: Some(_), + err: None, + } => {} + CResult { + ok: None, + err: Some(err), + } => panic!("set tree call failed: {}", err), + _ => unreachable!(), + } // We add leaves one by one using the internal index (new leaves goes in next available position) for leaf in &leaves { - let leaf_ser = fr_to_bytes_le(leaf); - let input_buffer = &Buffer::from(leaf_ser.as_ref()); - let success = set_next_leaf(rln_pointer, input_buffer); - assert!(success, "set next leaf call failed"); + match ffi_set_next_leaf(&mut ffi_rln_instance, &Box_::new(CFr::from(*leaf))) { + CResult { + ok: Some(_), + err: None, + } => {} + CResult { + ok: None, + err: Some(err), + } => panic!("set next leaf call failed: {}", err), + _ => unreachable!(), + } } // We get the root of the tree obtained adding leaves using the internal index - let root_single_additions = get_tree_root(rln_pointer); + let root_single_additions = get_tree_root(&ffi_rln_instance); assert_eq!( root_batch_with_init, root_single_additions, "root single additions !=" @@ -209,28 +319,38 @@ mod test { // We generate a vector of random leaves let leaves = get_random_leaves(); // We create a RLN instance - let rln_pointer = create_rln_instance(); + let mut ffi_rln_instance = create_rln_instance(); // We add leaves in a batch into the tree - set_leaves_init(rln_pointer, &leaves); + set_leaves_init(&mut ffi_rln_instance, &leaves); // We get the root of the tree obtained adding leaves in batch - let root_after_insertion = get_tree_root(rln_pointer); + let root_after_insertion = get_tree_root(&ffi_rln_instance); let last_leaf = leaves.last().unwrap(); let last_leaf_index = NO_OF_LEAVES - 1; - let indices = vec![last_leaf_index as u8]; - let last_leaf = vec![*last_leaf]; - let indices = vec_u8_to_bytes_le(&indices); - let indices_buffer = &Buffer::from(indices.as_ref()); - let leaves = vec_fr_to_bytes_le(&last_leaf); - let leaves_buffer = &Buffer::from(leaves.as_ref()); + let indices: repr_c::Vec = vec![last_leaf_index].into(); + let last_leaf_vec: repr_c::Vec = vec![CFr::from(*last_leaf)].into(); - let success = atomic_operation(rln_pointer, last_leaf_index, leaves_buffer, indices_buffer); - assert!(success, "atomic operation call failed"); + match ffi_atomic_operation( + &mut ffi_rln_instance, + last_leaf_index, + &last_leaf_vec, + &indices, + ) { + CResult { + ok: Some(_), + err: None, + } => {} + CResult { + ok: None, + err: Some(err), + } => panic!("atomic operation call failed: {}", err), + _ => unreachable!(), + } // We get the root of the tree obtained after a no-op - let root_after_noop = get_tree_root(rln_pointer); + let root_after_noop = get_tree_root(&ffi_rln_instance); assert_eq!(root_after_insertion, root_after_noop); } @@ -240,22 +360,30 @@ mod test { // We generate a vector of random leaves let leaves = get_random_leaves(); // We create a RLN instance - let rln_pointer = create_rln_instance(); + let mut ffi_rln_instance = create_rln_instance(); let mut rng = thread_rng(); let bad_index = (1 << TEST_TREE_DEPTH) - rng.gen_range(0..NO_OF_LEAVES) as usize; // Get root of empty tree - let root_empty = get_tree_root(rln_pointer); + let root_empty = get_tree_root(&ffi_rln_instance); // We add leaves in a batch into the tree - let leaves = vec_fr_to_bytes_le(&leaves); - let buffer = &Buffer::from(leaves.as_ref()); - let success = set_leaves_from(rln_pointer, bad_index, buffer); - assert!(!success, "set leaves from call succeeded"); + let leaves_vec: repr_c::Vec = leaves + .iter() + .map(|fr| CFr::from(*fr)) + .collect::>() + .into(); + match ffi_set_leaves_from(&mut ffi_rln_instance, bad_index, &leaves_vec) { + CResult { + ok: None, + err: Some(_), + } => {} + _ => panic!("set leaves from call should have failed"), + } // Get root of tree after attempted set - let root_after_bad_set = get_tree_root(rln_pointer); + let root_after_bad_set = get_tree_root(&ffi_rln_instance); assert_eq!(root_empty, root_after_bad_set); } @@ -264,7 +392,7 @@ mod test { fn test_merkle_proof_ffi() { let leaf_index = 3; // We create a RLN instance - let rln_pointer = create_rln_instance(); + let mut ffi_rln_instance = create_rln_instance(); // generate identity let mut identity_secret_hash_ = hash_to_field_le(b"test-merkle-proof"); @@ -276,13 +404,24 @@ mod test { let rate_commitment = utils_poseidon_hash(&[id_commitment, user_message_limit]); // We prepare id_commitment and we set the leaf at provided index - let leaf_ser = fr_to_bytes_le(&rate_commitment); - let input_buffer = &Buffer::from(leaf_ser.as_ref()); - let success = set_leaf(rln_pointer, leaf_index, input_buffer); - assert!(success, "set leaf call failed"); + match ffi_set_leaf( + &mut ffi_rln_instance, + leaf_index, + &Box_::new(CFr::from(rate_commitment)), + ) { + CResult { + ok: Some(_), + err: None, + } => {} + CResult { + ok: None, + err: Some(err), + } => panic!("set leaf call failed: {}", err), + _ => unreachable!(), + } // We obtain the Merkle tree root - let root = get_tree_root(rln_pointer); + let root = get_tree_root(&ffi_rln_instance); use ark_ff::BigInt; assert_eq!( @@ -296,15 +435,21 @@ mod test { .into() ); - // We obtain the Merkle tree root - let mut output_buffer = MaybeUninit::::uninit(); - let success = get_proof(rln_pointer, leaf_index, output_buffer.as_mut_ptr()); - assert!(success, "get merkle proof call failed"); - let output_buffer = unsafe { output_buffer.assume_init() }; - let result_data = <&[u8]>::from(&output_buffer).to_vec(); + // We obtain the Merkle proof + let proof = match ffi_get_proof(&ffi_rln_instance, leaf_index) { + CResult { + ok: Some(proof), + err: None, + } => proof, + CResult { + ok: None, + err: Some(err), + } => panic!("get merkle proof call failed: {}", err), + _ => unreachable!(), + }; - let (path_elements, read) = bytes_le_to_vec_fr(&result_data).unwrap(); - let (identity_path_index, _) = bytes_le_to_vec_u8(&result_data[read..]).unwrap(); + let path_elements: Vec = proof.path_elements.iter().map(|cfr| **cfr).collect(); + let identity_path_index: Vec = proof.path_index.iter().copied().collect(); // We check correct computation of the path and indexes let expected_path_elements: Vec = [ @@ -349,70 +494,14 @@ mod test { assert_eq!(root, root_from_proof); } - #[test] - // Benchmarks proof generation and verification - fn test_groth16_proofs_performance_ffi() { - // We create a RLN instance - let rln_pointer = create_rln_instance(); - - // We compute some benchmarks regarding proof and verify API calls - // Note that circuit loading requires some initial overhead. - // Once the circuit is loaded (i.e., when the RLN object is created), proof generation and verification times should be similar at each call. - let sample_size = 100; - let mut prove_time: u128 = 0; - let mut verify_time: u128 = 0; - - for _ in 0..sample_size { - // We generate random witness instances and relative proof values - let rln_witness = random_rln_witness(TEST_TREE_DEPTH); - let proof_values = proof_values_from_witness(&rln_witness).unwrap(); - - // We prepare id_commitment and we set the leaf at provided index - let rln_witness_ser = serialize_witness(&rln_witness).unwrap(); - let input_buffer = &Buffer::from(rln_witness_ser.as_ref()); - let mut output_buffer = MaybeUninit::::uninit(); - let now = Instant::now(); - let success = prove(rln_pointer, input_buffer, output_buffer.as_mut_ptr()); - prove_time += now.elapsed().as_nanos(); - assert!(success, "prove call failed"); - let output_buffer = unsafe { output_buffer.assume_init() }; - - // We read the returned proof and we append proof values for verify - let serialized_proof = <&[u8]>::from(&output_buffer).to_vec(); - let serialized_proof_values = serialize_proof_values(&proof_values); - let mut verify_data = Vec::::new(); - verify_data.extend(&serialized_proof); - verify_data.extend(&serialized_proof_values); - - // We prepare input proof values and we call verify - let input_buffer = &Buffer::from(verify_data.as_ref()); - let mut proof_is_valid: bool = false; - let proof_is_valid_ptr = &mut proof_is_valid as *mut bool; - let now = Instant::now(); - let success = verify(rln_pointer, input_buffer, proof_is_valid_ptr); - verify_time += now.elapsed().as_nanos(); - assert!(success, "verify call failed"); - assert!(proof_is_valid); - } - - println!( - "Average prove API call time: {:?}", - Duration::from_nanos((prove_time / sample_size).try_into().unwrap()) - ); - println!( - "Average verify API call time: {:?}", - Duration::from_nanos((verify_time / sample_size).try_into().unwrap()) - ); - } - #[test] // Creating a RLN with raw data should generate same results as using a path to resources fn test_rln_raw_ffi() { // We create a RLN instance - let rln_pointer = create_rln_instance(); + let ffi_rln_instance = create_rln_instance(); // We obtain the root from the RLN instance - let root_rln_folder = get_tree_root(rln_pointer); + let root_rln_folder = get_tree_root(&ffi_rln_instance); let zkey_path = "./resources/tree_depth_20/rln_final.arkzkey"; let mut zkey_file = File::open(zkey_path).expect("no file found"); @@ -422,8 +511,6 @@ mod test { .read_exact(&mut zkey_buffer) .expect("buffer overflow"); - let zkey_data = &Buffer::from(&zkey_buffer[..]); - let graph_data = "./resources/tree_depth_20/graph.bin"; let mut graph_file = File::open(graph_data).expect("no file found"); let metadata = std::fs::metadata(graph_data).expect("unable to read metadata"); @@ -432,25 +519,29 @@ mod test { .read_exact(&mut graph_buffer) .expect("buffer overflow"); - let graph_data = &Buffer::from(&graph_buffer[..]); - // Creating a RLN instance passing the raw data - let mut rln_pointer_raw_bytes = MaybeUninit::<*mut RLN>::uninit(); let tree_config = "".to_string(); - let tree_config_buffer = &Buffer::from(tree_config.as_bytes()); - let success = new_with_params( + let c_str = std::ffi::CString::new(tree_config).unwrap(); + let ffi_rln_instance2 = match ffi_new_with_params( TEST_TREE_DEPTH, - zkey_data, - graph_data, - tree_config_buffer, - rln_pointer_raw_bytes.as_mut_ptr(), - ); - assert!(success, "RLN object creation failed"); - let rln_pointer2 = unsafe { &mut *rln_pointer_raw_bytes.assume_init() }; + &zkey_buffer.into(), + &graph_buffer.into(), + c_str.as_c_str().into(), + ) { + CResult { + ok: Some(rln), + err: None, + } => rln, + CResult { + ok: None, + err: Some(err), + } => panic!("RLN object creation failed: {}", err), + _ => unreachable!(), + }; // We obtain the root from the RLN instance containing raw data // And compare that the same root was generated - let root_rln_raw = get_tree_root(rln_pointer2); + let root_rln_raw = get_tree_root(&ffi_rln_instance2); assert_eq!(root_rln_folder, root_rln_raw); } @@ -466,10 +557,10 @@ mod test { .collect(); // We create a RLN instance - let rln_pointer = create_rln_instance(); + let mut ffi_rln_instance = create_rln_instance(); // We add leaves in a batch into the tree - set_leaves_init(rln_pointer, &leaves); + set_leaves_init(&mut ffi_rln_instance, &leaves); // We generate a new identity pair let (identity_secret_hash, id_commitment) = identity_pair_gen(); @@ -491,52 +582,76 @@ mod test { let rate_commitment = utils_poseidon_hash(&[id_commitment, user_message_limit]); // We set as leaf rate_commitment, its index would be equal to no_of_leaves - let leaf_ser = fr_to_bytes_le(&rate_commitment); - let input_buffer = &Buffer::from(leaf_ser.as_ref()); - let success = set_next_leaf(rln_pointer, input_buffer); - assert!(success, "set next leaf call failed"); + match ffi_set_next_leaf( + &mut ffi_rln_instance, + &Box_::new(CFr::from(rate_commitment)), + ) { + CResult { + ok: Some(_), + err: None, + } => {} + CResult { + ok: None, + err: Some(err), + } => panic!("set next leaf call failed: {}", err), + _ => unreachable!(), + } - // We prepare input for generate_rln_proof API - // input_data is [ identity_secret<32> | id_index<8> | user_message_limit<32> | message_id<32> | external_nullifier<32> | signal_len<8> | signal ] - let prove_input = prepare_prove_input( - identity_secret_hash, + // Get the merkle proof for the identity + let _merkle_proof = match ffi_get_proof(&ffi_rln_instance, identity_index) { + CResult { + ok: Some(proof), + err: None, + } => proof, + CResult { + ok: None, + err: Some(err), + } => panic!("get merkle proof call failed: {}", err), + _ => unreachable!(), + }; + + // Hash the signal to get x + let x = hash_to_field_le(&signal); + + // path_elements and identity_path_index are not needed in non-stateless mode + let rln_proof = rln_proof_gen( + &ffi_rln_instance, + &CFr::from(*identity_secret_hash), + &CFr::from(user_message_limit), + &CFr::from(message_id), + &CFr::from(x), + &CFr::from(external_nullifier), identity_index, - user_message_limit, - message_id, - external_nullifier, - &signal, ); - // We call generate_rln_proof - // result_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ] - let proof_data = rln_proof_gen(rln_pointer, prove_input.as_ref()); - // We prepare input for verify_rln_proof API - // input_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal ] - // that is [ proof_data || signal_len<8> | signal ] - let verify_input = prepare_verify_input(proof_data, &signal); + let proof_is_valid = + match ffi_verify_rln_proof(&ffi_rln_instance, &rln_proof, &CFr::from(x)) { + CResult { + ok: Some(success), + err: None, + } => *success, + CResult { + ok: None, + err: Some(err), + } => panic!("verify rln proof call failed: {}", err), + _ => unreachable!(), + }; - // We call verify_rln_proof - let input_buffer = &Buffer::from(verify_input.as_ref()); - let mut proof_is_valid: bool = false; - let proof_is_valid_ptr = &mut proof_is_valid as *mut bool; - let success = verify_rln_proof(rln_pointer, input_buffer, proof_is_valid_ptr); - assert!(success, "verify call failed"); assert!(proof_is_valid); } #[test] // Computes and verifies an RLN ZK proof by checking proof's root against an input roots buffer fn test_verify_with_roots_ffi() { - // First part similar to test_rln_proof_ffi let user_message_limit = Fr::from(100); // We generate a vector of random leaves let leaves = get_random_leaves(); // We create a RLN instance - let rln_pointer = create_rln_instance(); + let mut ffi_rln_instance = create_rln_instance(); // We add leaves in a batch into the tree - set_leaves_init(rln_pointer, &leaves); + set_leaves_init(&mut ffi_rln_instance, &leaves); // We generate a new identity pair let (identity_secret_hash, id_commitment) = identity_pair_gen(); @@ -557,83 +672,135 @@ mod test { let message_id = Fr::from(1); // We set as leaf rate_commitment, its index would be equal to no_of_leaves - let leaf_ser = fr_to_bytes_le(&rate_commitment); - let input_buffer = &Buffer::from(leaf_ser.as_ref()); - let success = set_next_leaf(rln_pointer, input_buffer); - assert!(success, "set next leaf call failed"); + match ffi_set_next_leaf( + &mut ffi_rln_instance, + &Box_::new(CFr::from(rate_commitment)), + ) { + CResult { + ok: Some(_), + err: None, + } => {} + CResult { + ok: None, + err: Some(err), + } => panic!("set next leaf call failed: {}", err), + _ => unreachable!(), + } - // We prepare input for generate_rln_proof API - // input_data is [ identity_secret<32> | id_index<8> | user_message_limit<32> | message_id<32> | external_nullifier<32> | signal_len<8> | signal ] - let prove_input = prepare_prove_input( - identity_secret_hash, + // Get the merkle proof for the identity + let _merkle_proof = match ffi_get_proof(&ffi_rln_instance, identity_index) { + CResult { + ok: Some(proof), + err: None, + } => proof, + CResult { + ok: None, + err: Some(err), + } => panic!("get merkle proof call failed: {}", err), + _ => unreachable!(), + }; + + // Hash the signal to get x + let x = hash_to_field_le(&signal); + + // path_elements and identity_path_index are not needed in non-stateless mode + // witness input is now passed directly as parameters + + let rln_proof = rln_proof_gen( + &ffi_rln_instance, + &CFr::from(*identity_secret_hash), + &CFr::from(user_message_limit), + &CFr::from(message_id), + &CFr::from(x), + &CFr::from(external_nullifier), identity_index, - user_message_limit, - message_id, - external_nullifier, - &signal, ); - // We call generate_rln_proof - // result_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ] - let proof_data = rln_proof_gen(rln_pointer, prove_input.as_ref()); - - // We prepare input for verify_rln_proof API - // input_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> | signal_len<8> | signal ] - // that is [ proof_data || signal_len<8> | signal ] - let verify_input = prepare_verify_input(proof_data.clone(), &signal); - // We test verify_with_roots // We first try to verify against an empty buffer of roots. // In this case, since no root is provided, proof's root check is skipped and proof is verified if other proof values are valid - let mut roots_data: Vec = Vec::new(); + let roots_empty: repr_c::Vec = vec![].into(); - let input_buffer = &Buffer::from(verify_input.as_ref()); - let roots_buffer = &Buffer::from(roots_data.as_ref()); - let mut proof_is_valid: bool = false; - let proof_is_valid_ptr = &mut proof_is_valid as *mut bool; - let success = - verify_with_roots(rln_pointer, input_buffer, roots_buffer, proof_is_valid_ptr); - assert!(success, "verify call failed"); + let proof_is_valid = + match ffi_verify_with_roots(&ffi_rln_instance, &rln_proof, &roots_empty, &CFr::from(x)) + { + CResult { + ok: Some(valid), + err: None, + } => *valid, + CResult { + ok: None, + err: Some(err), + } => panic!("verify with roots call failed: {}", err), + _ => unreachable!(), + }; // Proof should be valid assert!(proof_is_valid); // We then try to verify against some random values not containing the correct one. + let mut roots_random: Vec = Vec::new(); for _ in 0..5 { - roots_data.append(&mut fr_to_bytes_le(&Fr::rand(&mut rng))); + roots_random.push(CFr::from(Fr::rand(&mut rng))); } - let input_buffer = &Buffer::from(verify_input.as_ref()); - let roots_buffer = &Buffer::from(roots_data.as_ref()); - let mut proof_is_valid: bool = false; - let proof_is_valid_ptr = &mut proof_is_valid as *mut bool; - let success = - verify_with_roots(rln_pointer, input_buffer, roots_buffer, proof_is_valid_ptr); - assert!(success, "verify call failed"); + let roots_random_vec: repr_c::Vec = roots_random.into(); + + let proof_is_valid = match ffi_verify_with_roots( + &ffi_rln_instance, + &rln_proof, + &roots_random_vec, + &CFr::from(x), + ) { + CResult { + ok: Some(valid), + err: None, + } => *valid, + CResult { + ok: None, + err: Some(err), + } => panic!("verify with roots call failed: {}", err), + _ => unreachable!(), + }; // Proof should be invalid. assert!(!proof_is_valid); // We finally include the correct root // We get the root of the tree obtained adding one leaf per time - let root = get_tree_root(rln_pointer); + let root = get_tree_root(&ffi_rln_instance); // We include the root and verify the proof - roots_data.append(&mut fr_to_bytes_le(&root)); - let input_buffer = &Buffer::from(verify_input.as_ref()); - let roots_buffer = &Buffer::from(roots_data.as_ref()); - let mut proof_is_valid: bool = false; - let proof_is_valid_ptr = &mut proof_is_valid as *mut bool; - let success = - verify_with_roots(rln_pointer, input_buffer, roots_buffer, proof_is_valid_ptr); - assert!(success, "verify call failed"); + let mut roots_with_correct: Vec = Vec::new(); + for _ in 0..5 { + roots_with_correct.push(CFr::from(Fr::rand(&mut rng))); + } + roots_with_correct.push(CFr::from(root)); + let roots_correct_vec: repr_c::Vec = roots_with_correct.into(); + + let proof_is_valid = match ffi_verify_with_roots( + &ffi_rln_instance, + &rln_proof, + &roots_correct_vec, + &CFr::from(x), + ) { + CResult { + ok: Some(valid), + err: None, + } => *valid, + CResult { + ok: None, + err: Some(err), + } => panic!("verify with roots call failed: {}", err), + _ => unreachable!(), + }; // Proof should be valid. assert!(proof_is_valid); } #[test] - // Computes and verifies an RLN ZK proof using FFI APIs + // Computes and verifies an RLN ZK proof using FFI APIs and recovers identity secret fn test_recover_id_secret_ffi() { // We create a RLN instance - let rln_pointer = create_rln_instance(); + let mut ffi_rln_instance = create_rln_instance(); // We generate a new identity pair let (identity_secret_hash, id_commitment) = identity_pair_gen(); @@ -642,10 +809,20 @@ mod test { let rate_commitment = utils_poseidon_hash(&[id_commitment, user_message_limit]); // We set as leaf rate_commitment, its index would be equal to 0 since tree is empty - let leaf_ser = fr_to_bytes_le(&rate_commitment); - let input_buffer = &Buffer::from(leaf_ser.as_ref()); - let success = set_next_leaf(rln_pointer, input_buffer); - assert!(success, "set next leaf call failed"); + match ffi_set_next_leaf( + &mut ffi_rln_instance, + &Box_::new(CFr::from(rate_commitment)), + ) { + CResult { + ok: Some(_), + err: None, + } => {} + CResult { + ok: None, + err: Some(err), + } => panic!("set next leaf call failed: {}", err), + _ => unreachable!(), + } let identity_index: usize = 0; @@ -654,8 +831,6 @@ mod test { // We generate two random signals let mut rng = rand::thread_rng(); let signal1: [u8; 32] = rng.gen(); - - // We generate two random signals let signal2: [u8; 32] = rng.gen(); // We generate a random epoch @@ -667,53 +842,62 @@ mod test { // We choose a message_id satisfy 0 <= message_id < MESSAGE_LIMIT let message_id = Fr::from(1); - // We prepare input for generate_rln_proof API - // input_data is [ identity_secret<32> | id_index<8> | user_message_limit<32> | message_id<32> | external_nullifier<32> | signal_len<8> | signal ] - let prove_input1 = prepare_prove_input( - identity_secret_hash.clone(), - identity_index, - user_message_limit, - message_id, - external_nullifier, - &signal1, - ); + // Get the merkle proof for the identity + let _merkle_proof = match ffi_get_proof(&ffi_rln_instance, identity_index) { + CResult { + ok: Some(proof), + err: None, + } => proof, + CResult { + ok: None, + err: Some(err), + } => panic!("get merkle proof call failed: {}", err), + _ => unreachable!(), + }; - let prove_input2 = prepare_prove_input( - identity_secret_hash.clone(), - identity_index, - user_message_limit, - message_id, - external_nullifier, - &signal2, - ); + // Hash the signals to get x + let x1 = hash_to_field_le(&signal1); + let x2 = hash_to_field_le(&signal2); + + // path_elements and identity_path_index are not needed in non-stateless mode + // witness input is now passed directly as parameters // We call generate_rln_proof for first proof values - // result_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ] - let proof_data_1 = rln_proof_gen(rln_pointer, prove_input1.as_ref()); - - // We call generate_rln_proof - // result_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ] - let proof_data_2 = rln_proof_gen(rln_pointer, prove_input2.as_ref()); - - let input_proof_buffer_1 = &Buffer::from(proof_data_1.as_ref()); - let input_proof_buffer_2 = &Buffer::from(proof_data_2.as_ref()); - let mut output_buffer = MaybeUninit::::uninit(); - let success = recover_id_secret( - rln_pointer, - input_proof_buffer_1, - input_proof_buffer_2, - output_buffer.as_mut_ptr(), + let rln_proof1 = rln_proof_gen( + &ffi_rln_instance, + &CFr::from(*identity_secret_hash.clone()), + &CFr::from(user_message_limit), + &CFr::from(message_id), + &CFr::from(x1), + &CFr::from(external_nullifier), + identity_index, ); - assert!(success, "recover id secret call failed"); - let output_buffer = unsafe { output_buffer.assume_init() }; - let serialized_identity_secret_hash = <&[u8]>::from(&output_buffer).to_vec(); - // We passed two shares for the same secret, so recovery should be successful - // To check it, we ensure that recovered identity secret hash is empty - assert!(!serialized_identity_secret_hash.is_empty()); + // We call generate_rln_proof for second proof values + let rln_proof2 = rln_proof_gen( + &ffi_rln_instance, + &CFr::from(*identity_secret_hash.clone()), + &CFr::from(user_message_limit), + &CFr::from(message_id), + &CFr::from(x2), + &CFr::from(external_nullifier), + identity_index, + ); + + let recovered_id_secret_cfr = match ffi_recover_id_secret(&rln_proof1, &rln_proof2) { + CResult { + ok: Some(secret), + err: None, + } => secret, + CResult { + ok: None, + err: Some(err), + } => panic!("recover id secret call failed: {}", err), + _ => unreachable!(), + }; // We check if the recovered identity secret hash corresponds to the original one - let (recovered_identity_secret_hash, _) = bytes_le_to_fr(&serialized_identity_secret_hash); + let recovered_identity_secret_hash = *recovered_id_secret_cfr; assert_eq!(recovered_identity_secret_hash, *identity_secret_hash); // We now test that computing identity_secret_hash is unsuccessful if shares computed from two different identity secret hashes but within same epoch are passed @@ -723,53 +907,74 @@ mod test { let rate_commitment_new = utils_poseidon_hash(&[id_commitment_new, user_message_limit]); // We set as leaf id_commitment, its index would be equal to 1 since at 0 there is id_commitment - let leaf_ser = fr_to_bytes_le(&rate_commitment_new); - let input_buffer = &Buffer::from(leaf_ser.as_ref()); - let success = set_next_leaf(rln_pointer, input_buffer); - assert!(success, "set next leaf call failed"); + match ffi_set_next_leaf( + &mut ffi_rln_instance, + &Box_::new(CFr::from(rate_commitment_new)), + ) { + CResult { + ok: Some(_), + err: None, + } => {} + CResult { + ok: None, + err: Some(err), + } => panic!("set next leaf call failed: {}", err), + _ => unreachable!(), + } let identity_index_new: usize = 1; - // We generate a random signals + // We generate a random signal let signal3: [u8; 32] = rng.gen(); + let x3 = hash_to_field_le(&signal3); - // We prepare input for generate_rln_proof API - // input_data is [ identity_secret<32> | id_index<8> | epoch<32> | signal_len<8> | signal ] - // Note that epoch is the same as before - let prove_input3 = prepare_prove_input( - identity_secret_hash, - identity_index_new, - user_message_limit, - message_id, - external_nullifier, - &signal3, - ); + // Get the merkle proof for the new identity + let _merkle_proof_new = match ffi_get_proof(&ffi_rln_instance, identity_index_new) { + CResult { + ok: Some(proof), + err: None, + } => proof, + CResult { + ok: None, + err: Some(err), + } => panic!("get merkle proof call failed: {}", err), + _ => unreachable!(), + }; + + // path_elements_new and identity_path_index_new are not needed in non-stateless mode + // witness input is now passed directly as parameters // We call generate_rln_proof - // result_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ] - let proof_data_3 = rln_proof_gen(rln_pointer, prove_input3.as_ref()); + let rln_proof3 = rln_proof_gen( + &ffi_rln_instance, + &CFr::from(*identity_secret_hash_new.clone()), + &CFr::from(user_message_limit), + &CFr::from(message_id), + &CFr::from(x3), + &CFr::from(external_nullifier), + identity_index_new, + ); // We attempt to recover the secret using share1 (coming from identity_secret_hash) and share3 (coming from identity_secret_hash_new) - let input_proof_buffer_1 = &Buffer::from(proof_data_1.as_ref()); - let input_proof_buffer_3 = &Buffer::from(proof_data_3.as_ref()); - let mut output_buffer = MaybeUninit::::uninit(); - let success = recover_id_secret( - rln_pointer, - input_proof_buffer_1, - input_proof_buffer_3, - output_buffer.as_mut_ptr(), - ); - assert!(success, "recover id secret call failed"); - let output_buffer = unsafe { output_buffer.assume_init() }; - let serialized_identity_secret_hash = <&[u8]>::from(&output_buffer).to_vec(); - let (recovered_identity_secret_hash_new, _) = - bytes_le_to_fr(&serialized_identity_secret_hash); + let recovered_id_secret_new_cfr = match ffi_recover_id_secret(&rln_proof1, &rln_proof3) { + CResult { + ok: Some(secret), + err: None, + } => secret, + CResult { + ok: None, + err: Some(err), + } => panic!("recover id secret call failed: {}", err), + _ => unreachable!(), + }; + + let recovered_identity_secret_hash_new = recovered_id_secret_new_cfr; // ensure that the recovered secret does not match with either of the // used secrets in proof generation assert_ne!( - recovered_identity_secret_hash_new, + *recovered_identity_secret_hash_new, *identity_secret_hash_new ); } @@ -780,34 +985,46 @@ mod test { let no_of_leaves = 1 << TEST_TREE_DEPTH; // We create a RLN instance - let rln_pointer = create_rln_instance(); + let mut ffi_rln_instance = create_rln_instance(); // We generate a new identity tuple from an input seed - let seed_bytes: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - let input_buffer = &Buffer::from(seed_bytes); - let mut output_buffer = MaybeUninit::::uninit(); - let success = seeded_extended_key_gen(input_buffer, output_buffer.as_mut_ptr(), true); - assert!(success, "seeded key gen call failed"); - - let output_buffer = unsafe { output_buffer.assume_init() }; - let result_data = <&[u8]>::from(&output_buffer).to_vec(); - let (_, _, _, id_commitment) = deserialize_identity_tuple_le(result_data); + let seed_bytes: Vec = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + let key_gen = ffi_seeded_extended_key_gen(&seed_bytes.into()); + assert_eq!(key_gen.len(), 4, "seeded extended key gen call failed"); + let id_commitment = *key_gen[3]; // We insert the id_commitment into the tree at a random index let mut rng = thread_rng(); let index = rng.gen_range(0..no_of_leaves) as usize; - let leaf = fr_to_bytes_le(&id_commitment); - let input_buffer = &Buffer::from(leaf.as_ref()); - let success = set_leaf(rln_pointer, index, input_buffer); - assert!(success, "set leaf call failed"); + match ffi_set_leaf( + &mut ffi_rln_instance, + index, + &Box_::new(CFr::from(id_commitment)), + ) { + CResult { + ok: Some(_), + err: None, + } => {} + CResult { + ok: None, + err: Some(err), + } => panic!("set leaf call failed: {}", err), + _ => unreachable!(), + } // We get the leaf at the same index - let mut output_buffer = MaybeUninit::::uninit(); - let success = get_leaf(rln_pointer, index, output_buffer.as_mut_ptr()); - assert!(success, "get leaf call failed"); - let output_buffer = unsafe { output_buffer.assume_init() }; - let result_data = <&[u8]>::from(&output_buffer).to_vec(); - let (received_id_commitment, _) = bytes_le_to_fr(&result_data); + let received_id_commitment_cfr = match ffi_get_leaf(&ffi_rln_instance, index) { + CResult { + ok: Some(leaf), + err: None, + } => leaf, + CResult { + ok: None, + err: Some(err), + } => panic!("get leaf call failed: {}", err), + _ => unreachable!(), + }; + let received_id_commitment = *received_id_commitment_cfr; // We check that the received id_commitment is the same as the one we inserted assert_eq!(received_id_commitment, id_commitment); @@ -816,629 +1033,54 @@ mod test { #[test] fn test_valid_metadata_ffi() { // We create a RLN instance - let rln_pointer = create_rln_instance(); + let mut ffi_rln_instance = create_rln_instance(); - let seed_bytes: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - let input_buffer = &Buffer::from(seed_bytes); + let seed_bytes: Vec = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - let success = set_metadata(rln_pointer, input_buffer); - assert!(success, "set_metadata call failed"); + match ffi_set_metadata(&mut ffi_rln_instance, &seed_bytes.clone().into()) { + CResult { + ok: Some(_), + err: None, + } => {} + CResult { + ok: None, + err: Some(err), + } => panic!("set_metadata call failed: {}", err), + _ => unreachable!(), + } - let mut output_buffer = MaybeUninit::::uninit(); - let success = get_metadata(rln_pointer, output_buffer.as_mut_ptr()); - assert!(success, "get_metadata call failed"); + let metadata = match ffi_get_metadata(&ffi_rln_instance) { + CResult { + ok: Some(data), + err: None, + } => data, + CResult { + ok: None, + err: Some(err), + } => panic!("get_metadata call failed: {}", err), + _ => unreachable!(), + }; - let output_buffer = unsafe { output_buffer.assume_init() }; - let result_data = <&[u8]>::from(&output_buffer).to_vec(); - - assert_eq!(result_data, seed_bytes.to_vec()); + assert_eq!(metadata.iter().copied().collect::>(), seed_bytes); } #[test] fn test_empty_metadata_ffi() { // We create a RLN instance - let rln_pointer = create_rln_instance(); + let ffi_rln_instance = create_rln_instance(); - let mut output_buffer = MaybeUninit::::uninit(); - let success = get_metadata(rln_pointer, output_buffer.as_mut_ptr()); - assert!(success, "get_metadata call failed"); + let metadata = match ffi_get_metadata(&ffi_rln_instance) { + CResult { + ok: Some(data), + err: None, + } => data, + CResult { + ok: None, + err: Some(err), + } => panic!("get_metadata call failed: {}", err), + _ => unreachable!(), + }; - let output_buffer = unsafe { output_buffer.assume_init() }; - assert_eq!(output_buffer.len, 0); - } -} - -#[cfg(test)] -#[cfg(feature = "stateless")] -mod stateless_test { - use ark_std::{rand::thread_rng, UniformRand}; - use rand::Rng; - use rln::circuit::*; - use rln::ffi::generate_rln_proof_with_witness; - use rln::ffi::*; - use rln::hashers::{hash_to_field_le, poseidon_hash as utils_poseidon_hash, PoseidonHash}; - use rln::protocol::*; - use rln::public::RLN; - use rln::utils::*; - use std::mem::MaybeUninit; - use std::time::{Duration, Instant}; - use utils::{OptimalMerkleTree, ZerokitMerkleProof, ZerokitMerkleTree}; - - type ConfigOf = ::Config; - - fn create_rln_instance() -> &'static mut RLN { - let mut rln_pointer = MaybeUninit::<*mut RLN>::uninit(); - let success = new(rln_pointer.as_mut_ptr()); - assert!(success, "RLN object creation failed"); - unsafe { &mut *rln_pointer.assume_init() } - } - - fn identity_pair_gen() -> (IdSecret, Fr) { - let mut output_buffer = MaybeUninit::::uninit(); - let success = key_gen(output_buffer.as_mut_ptr(), true); - assert!(success, "key gen call failed"); - let output_buffer = unsafe { output_buffer.assume_init() }; - let result_data = <&[u8]>::from(&output_buffer).to_vec(); - let (identity_secret_hash, read) = IdSecret::from_bytes_le(&result_data); - let (id_commitment, _) = bytes_le_to_fr(&result_data[read..]); - (identity_secret_hash, id_commitment) - } - - fn rln_proof_gen_with_witness(rln_pointer: &mut RLN, serialized: &[u8]) -> Vec { - let input_buffer = &Buffer::from(serialized); - let mut output_buffer = MaybeUninit::::uninit(); - let success = - generate_rln_proof_with_witness(rln_pointer, input_buffer, output_buffer.as_mut_ptr()); - assert!(success, "generate rln proof call failed"); - let output_buffer = unsafe { output_buffer.assume_init() }; - <&[u8]>::from(&output_buffer).to_vec() - } - - #[test] - fn test_recover_id_secret_stateless_ffi() { - let default_leaf = Fr::from(0); - let mut tree: OptimalMerkleTree = OptimalMerkleTree::new( - TEST_TREE_DEPTH, - default_leaf, - ConfigOf::>::default(), - ) - .unwrap(); - - let rln_pointer = create_rln_instance(); - - // We generate a new identity pair - let (identity_secret_hash, id_commitment) = identity_pair_gen(); - - let user_message_limit = Fr::from(100); - let rate_commitment = utils_poseidon_hash(&[id_commitment, user_message_limit]); - tree.update_next(rate_commitment).unwrap(); - - // We generate a random epoch - let epoch = hash_to_field_le(b"test-epoch"); - let rln_identifier = hash_to_field_le(b"test-rln-identifier"); - let external_nullifier = utils_poseidon_hash(&[epoch, rln_identifier]); - - // We generate two proofs using same epoch but different signals. - // We generate a random signal - let mut rng = thread_rng(); - let signal1: [u8; 32] = rng.gen(); - let x1 = hash_to_field_le(&signal1); - - let signal2: [u8; 32] = rng.gen(); - let x2 = hash_to_field_le(&signal2); - - let identity_index = tree.leaves_set(); - let merkle_proof = tree.proof(identity_index).expect("proof should exist"); - - // We prepare input for generate_rln_proof API - let rln_witness1 = rln_witness_from_values( - identity_secret_hash.clone(), - merkle_proof.get_path_elements(), - merkle_proof.get_path_index(), - x1, - external_nullifier, - user_message_limit, - Fr::from(1), - ) - .unwrap(); - let serialized1 = serialize_witness(&rln_witness1).unwrap(); - - let rln_witness2 = rln_witness_from_values( - identity_secret_hash.clone(), - merkle_proof.get_path_elements(), - merkle_proof.get_path_index(), - x2, - external_nullifier, - user_message_limit, - Fr::from(1), - ) - .unwrap(); - let serialized2 = serialize_witness(&rln_witness2).unwrap(); - - // We call generate_rln_proof for first proof values - // result_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ] - let proof_data_1 = rln_proof_gen_with_witness(rln_pointer, serialized1.as_ref()); - - // We call generate_rln_proof - // result_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ] - let proof_data_2 = rln_proof_gen_with_witness(rln_pointer, serialized2.as_ref()); - - let input_proof_buffer_1 = &Buffer::from(proof_data_1.as_ref()); - let input_proof_buffer_2 = &Buffer::from(proof_data_2.as_ref()); - let mut output_buffer = MaybeUninit::::uninit(); - let success = recover_id_secret( - rln_pointer, - input_proof_buffer_1, - input_proof_buffer_2, - output_buffer.as_mut_ptr(), - ); - assert!(success, "recover id secret call failed"); - let output_buffer = unsafe { output_buffer.assume_init() }; - let serialized_identity_secret_hash = <&[u8]>::from(&output_buffer).to_vec(); - - // We passed two shares for the same secret, so recovery should be successful - // To check it, we ensure that recovered identity secret hash is empty - assert!(!serialized_identity_secret_hash.is_empty()); - - // We check if the recovered identity secret hash corresponds to the original one - let (recovered_identity_secret_hash, _) = - IdSecret::from_bytes_le(&serialized_identity_secret_hash); - assert_eq!(recovered_identity_secret_hash, identity_secret_hash); - - // We now test that computing identity_secret_hash is unsuccessful if shares computed from two different identity secret hashes but within same epoch are passed - - // We generate a new identity pair - let (identity_secret_hash_new, id_commitment_new) = identity_pair_gen(); - let rate_commitment_new = utils_poseidon_hash(&[id_commitment_new, user_message_limit]); - tree.update_next(rate_commitment_new).unwrap(); - - // We generate a random signals - let signal3: [u8; 32] = rng.gen(); - let x3 = hash_to_field_le(&signal3); - - let identity_index_new = tree.leaves_set(); - let merkle_proof_new = tree.proof(identity_index_new).expect("proof should exist"); - - let rln_witness3 = rln_witness_from_values( - identity_secret_hash_new.clone(), - merkle_proof_new.get_path_elements(), - merkle_proof_new.get_path_index(), - x3, - external_nullifier, - user_message_limit, - Fr::from(1), - ) - .unwrap(); - let serialized3 = serialize_witness(&rln_witness3).unwrap(); - - // We call generate_rln_proof - // result_data is [ proof<128> | root<32> | external_nullifier<32> | x<32> | y<32> | nullifier<32> ] - let proof_data_3 = rln_proof_gen_with_witness(rln_pointer, serialized3.as_ref()); - - // We attempt to recover the secret using share1 (coming from identity_secret_hash) and share3 (coming from identity_secret_hash_new) - - let input_proof_buffer_1 = &Buffer::from(proof_data_1.as_ref()); - let input_proof_buffer_3 = &Buffer::from(proof_data_3.as_ref()); - let mut output_buffer = MaybeUninit::::uninit(); - let success = recover_id_secret( - rln_pointer, - input_proof_buffer_1, - input_proof_buffer_3, - output_buffer.as_mut_ptr(), - ); - assert!(success, "recover id secret call failed"); - let output_buffer = unsafe { output_buffer.assume_init() }; - let serialized_identity_secret_hash = <&[u8]>::from(&output_buffer).to_vec(); - let (recovered_identity_secret_hash_new, _) = - bytes_le_to_fr(&serialized_identity_secret_hash); - - // ensure that the recovered secret does not match with either of the - // used secrets in proof generation - assert_ne!( - recovered_identity_secret_hash_new, - *identity_secret_hash_new - ); - } - - #[test] - fn test_verify_with_roots_stateless_ffi() { - let default_leaf = Fr::from(0); - let mut tree: OptimalMerkleTree = OptimalMerkleTree::new( - TEST_TREE_DEPTH, - default_leaf, - ConfigOf::>::default(), - ) - .unwrap(); - - let rln_pointer = create_rln_instance(); - - // We generate a new identity pair - let (identity_secret_hash, id_commitment) = identity_pair_gen(); - - let identity_index = tree.leaves_set(); - let user_message_limit = Fr::from(100); - let rate_commitment = utils_poseidon_hash(&[id_commitment, user_message_limit]); - tree.update_next(rate_commitment).unwrap(); - - // We generate a random epoch - let epoch = hash_to_field_le(b"test-epoch"); - let rln_identifier = hash_to_field_le(b"test-rln-identifier"); - let external_nullifier = utils_poseidon_hash(&[epoch, rln_identifier]); - - // We generate two proofs using same epoch but different signals. - // We generate a random signal - let mut rng = thread_rng(); - let signal: [u8; 32] = rng.gen(); - let x = hash_to_field_le(&signal); - - let merkle_proof = tree.proof(identity_index).expect("proof should exist"); - - // We prepare input for generate_rln_proof API - let rln_witness = rln_witness_from_values( - identity_secret_hash, - merkle_proof.get_path_elements(), - merkle_proof.get_path_index(), - x, - external_nullifier, - user_message_limit, - Fr::from(1), - ) - .unwrap(); - - let serialized = serialize_witness(&rln_witness).unwrap(); - let proof_data = rln_proof_gen_with_witness(rln_pointer, serialized.as_ref()); - - let verify_input = prepare_verify_input(proof_data.clone(), &signal); - - // If no roots is provided, proof validation is skipped and if the remaining proof values are valid, the proof will be correctly verified - let mut roots_data: Vec = Vec::new(); - - let input_buffer = &Buffer::from(verify_input.as_ref()); - let roots_buffer = &Buffer::from(roots_data.as_ref()); - let mut proof_is_valid: bool = false; - let proof_is_valid_ptr = &mut proof_is_valid as *mut bool; - let success = - verify_with_roots(rln_pointer, input_buffer, roots_buffer, proof_is_valid_ptr); - assert!(success, "verify call failed"); - // Proof should be valid - assert!(proof_is_valid); - - // We serialize in the roots buffer some random values and we check that the proof is not verified since doesn't contain the correct root the proof refers to - for _ in 0..5 { - roots_data.append(&mut fr_to_bytes_le(&Fr::rand(&mut rng))); - } - let input_buffer = &Buffer::from(verify_input.as_ref()); - let roots_buffer = &Buffer::from(roots_data.as_ref()); - let mut proof_is_valid: bool = false; - let proof_is_valid_ptr = &mut proof_is_valid as *mut bool; - let success = - verify_with_roots(rln_pointer, input_buffer, roots_buffer, proof_is_valid_ptr); - assert!(success, "verify call failed"); - // Proof should be invalid. - assert!(!proof_is_valid); - - // We get the root of the tree obtained adding one leaf per time - let root = tree.root(); - - // We add the real root and we check if now the proof is verified - roots_data.append(&mut fr_to_bytes_le(&root)); - let input_buffer = &Buffer::from(verify_input.as_ref()); - let roots_buffer = &Buffer::from(roots_data.as_ref()); - let mut proof_is_valid: bool = false; - let proof_is_valid_ptr = &mut proof_is_valid as *mut bool; - let success = - verify_with_roots(rln_pointer, input_buffer, roots_buffer, proof_is_valid_ptr); - assert!(success, "verify call failed"); - // Proof should be valid. - assert!(proof_is_valid); - } - - #[test] - fn test_groth16_proofs_performance_stateless_ffi() { - // We create a RLN instance - let rln_pointer = create_rln_instance(); - - // We compute some benchmarks regarding proof and verify API calls - // Note that circuit loading requires some initial overhead. - // Once the circuit is loaded (i.e., when the RLN object is created), proof generation and verification times should be similar at each call. - let sample_size = 100; - let mut prove_time: u128 = 0; - let mut verify_time: u128 = 0; - - for _ in 0..sample_size { - // We generate random witness instances and relative proof values - let rln_witness = random_rln_witness(TEST_TREE_DEPTH); - let proof_values = proof_values_from_witness(&rln_witness).unwrap(); - - // We prepare id_commitment and we set the leaf at provided index - let rln_witness_ser = serialize_witness(&rln_witness).unwrap(); - let input_buffer = &Buffer::from(rln_witness_ser.as_ref()); - let mut output_buffer = MaybeUninit::::uninit(); - let now = Instant::now(); - let success = prove(rln_pointer, input_buffer, output_buffer.as_mut_ptr()); - prove_time += now.elapsed().as_nanos(); - assert!(success, "prove call failed"); - let output_buffer = unsafe { output_buffer.assume_init() }; - - // We read the returned proof and we append proof values for verify - let serialized_proof = <&[u8]>::from(&output_buffer).to_vec(); - let serialized_proof_values = serialize_proof_values(&proof_values); - let mut verify_data = Vec::::new(); - verify_data.extend(&serialized_proof); - verify_data.extend(&serialized_proof_values); - - // We prepare input proof values and we call verify - let input_buffer = &Buffer::from(verify_data.as_ref()); - let mut proof_is_valid: bool = false; - let proof_is_valid_ptr = &mut proof_is_valid as *mut bool; - let now = Instant::now(); - let success = verify(rln_pointer, input_buffer, proof_is_valid_ptr); - verify_time += now.elapsed().as_nanos(); - assert!(success, "verify call failed"); - assert!(proof_is_valid); - } - - println!( - "Average prove API call time: {:?}", - Duration::from_nanos((prove_time / sample_size).try_into().unwrap()) - ); - println!( - "Average verify API call time: {:?}", - Duration::from_nanos((verify_time / sample_size).try_into().unwrap()) - ); - } -} - -#[cfg(test)] -mod general_tests { - use ark_std::{rand::thread_rng, UniformRand}; - use rand::Rng; - use rln::circuit::*; - use rln::ffi::{hash as ffi_hash, poseidon_hash as ffi_poseidon_hash, *}; - use rln::hashers::{ - hash_to_field_be, hash_to_field_le, poseidon_hash as utils_poseidon_hash, ROUND_PARAMS, - }; - use rln::protocol::*; - use rln::utils::*; - use std::mem::MaybeUninit; - - #[test] - // Tests hash to field using FFI APIs - fn test_seeded_keygen_stateless_ffi() { - // We generate a new identity pair from an input seed - let seed_bytes: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - let input_buffer = &Buffer::from(seed_bytes); - let mut output_buffer = MaybeUninit::::uninit(); - let success = seeded_key_gen(input_buffer, output_buffer.as_mut_ptr(), true); - assert!(success, "seeded key gen call failed"); - let output_buffer = unsafe { output_buffer.assume_init() }; - let result_data = <&[u8]>::from(&output_buffer).to_vec(); - let (identity_secret_hash, read) = bytes_le_to_fr(&result_data); - let (id_commitment, _) = bytes_le_to_fr(&result_data[read..]); - - // We check against expected values - let expected_identity_secret_hash_seed_bytes = str_to_fr( - "0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716", - 16, - ); - let expected_id_commitment_seed_bytes = str_to_fr( - "0xbf16d2b5c0d6f9d9d561e05bfca16a81b4b873bb063508fae360d8c74cef51f", - 16, - ); - - assert_eq!( - identity_secret_hash, - expected_identity_secret_hash_seed_bytes.unwrap() - ); - assert_eq!(id_commitment, expected_id_commitment_seed_bytes.unwrap()); - } - - #[test] - fn test_seeded_keygen_big_endian_ffi() { - let seed_bytes: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - let input_buffer = &Buffer::from(seed_bytes); - let mut output_buffer = MaybeUninit::::uninit(); - let success = seeded_key_gen(input_buffer, output_buffer.as_mut_ptr(), false); - assert!(success, "seeded key gen call failed"); - let output_buffer = unsafe { output_buffer.assume_init() }; - let result_data = <&[u8]>::from(&output_buffer).to_vec(); - let (identity_secret_hash, read) = bytes_be_to_fr(&result_data); - let (id_commitment, _) = bytes_be_to_fr(&result_data[read..]); - - let expected_identity_secret_hash_seed_bytes = str_to_fr( - "0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716", - 16, - ); - let expected_id_commitment_seed_bytes = str_to_fr( - "0xbf16d2b5c0d6f9d9d561e05bfca16a81b4b873bb063508fae360d8c74cef51f", - 16, - ); - - assert_eq!( - identity_secret_hash, - expected_identity_secret_hash_seed_bytes.unwrap() - ); - assert_eq!(id_commitment, expected_id_commitment_seed_bytes.unwrap()); - } - - #[test] - // Tests hash to field using FFI APIs - fn test_seeded_extended_keygen_stateless_ffi() { - // We generate a new identity tuple from an input seed - let seed_bytes: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - let input_buffer = &Buffer::from(seed_bytes); - let mut output_buffer = MaybeUninit::::uninit(); - let success = seeded_extended_key_gen(input_buffer, output_buffer.as_mut_ptr(), true); - assert!(success, "seeded key gen call failed"); - let output_buffer = unsafe { output_buffer.assume_init() }; - let result_data = <&[u8]>::from(&output_buffer).to_vec(); - let (identity_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) = - deserialize_identity_tuple_le(result_data); - - // We check against expected values - let expected_identity_trapdoor_seed_bytes = str_to_fr( - "0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716", - 16, - ); - let expected_identity_nullifier_seed_bytes = str_to_fr( - "0x1f18714c7bc83b5bca9e89d404cf6f2f585bc4c0f7ed8b53742b7e2b298f50b4", - 16, - ); - let expected_identity_secret_hash_seed_bytes = str_to_fr( - "0x2aca62aaa7abaf3686fff2caf00f55ab9462dc12db5b5d4bcf3994e671f8e521", - 16, - ); - let expected_id_commitment_seed_bytes = str_to_fr( - "0x68b66aa0a8320d2e56842581553285393188714c48f9b17acd198b4f1734c5c", - 16, - ); - - assert_eq!( - identity_trapdoor, - expected_identity_trapdoor_seed_bytes.unwrap() - ); - assert_eq!( - identity_nullifier, - expected_identity_nullifier_seed_bytes.unwrap() - ); - assert_eq!( - identity_secret_hash, - expected_identity_secret_hash_seed_bytes.unwrap() - ); - assert_eq!(id_commitment, expected_id_commitment_seed_bytes.unwrap()); - } - - #[test] - fn test_seeded_extended_keygen_big_endian_ffi() { - let seed_bytes: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - let input_buffer = &Buffer::from(seed_bytes); - let mut output_buffer = MaybeUninit::::uninit(); - let success = seeded_extended_key_gen(input_buffer, output_buffer.as_mut_ptr(), false); - assert!(success, "seeded key gen call failed"); - let output_buffer = unsafe { output_buffer.assume_init() }; - let result_data = <&[u8]>::from(&output_buffer).to_vec(); - let (identity_trapdoor, identity_nullifier, identity_secret_hash, id_commitment) = - deserialize_identity_tuple_be(result_data); - - let expected_identity_trapdoor_seed_bytes = str_to_fr( - "0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716", - 16, - ); - let expected_identity_nullifier_seed_bytes = str_to_fr( - "0x1f18714c7bc83b5bca9e89d404cf6f2f585bc4c0f7ed8b53742b7e2b298f50b4", - 16, - ); - let expected_identity_secret_hash_seed_bytes = str_to_fr( - "0x2aca62aaa7abaf3686fff2caf00f55ab9462dc12db5b5d4bcf3994e671f8e521", - 16, - ); - let expected_id_commitment_seed_bytes = str_to_fr( - "0x68b66aa0a8320d2e56842581553285393188714c48f9b17acd198b4f1734c5c", - 16, - ); - - assert_eq!( - identity_trapdoor, - expected_identity_trapdoor_seed_bytes.unwrap() - ); - assert_eq!( - identity_nullifier, - expected_identity_nullifier_seed_bytes.unwrap() - ); - assert_eq!( - identity_secret_hash, - expected_identity_secret_hash_seed_bytes.unwrap() - ); - assert_eq!(id_commitment, expected_id_commitment_seed_bytes.unwrap()); - } - - #[test] - // Tests hash to field using FFI APIs - fn test_hash_to_field_stateless_ffi() { - let mut rng = rand::thread_rng(); - let signal: [u8; 32] = rng.gen(); - - // We prepare id_commitment and we set the leaf at provided index - let input_buffer = &Buffer::from(signal.as_ref()); - let mut output_buffer = MaybeUninit::::uninit(); - let success = ffi_hash(input_buffer, output_buffer.as_mut_ptr(), true); - assert!(success, "hash call failed"); - let output_buffer = unsafe { output_buffer.assume_init() }; - - // We read the returned proof and we append proof values for verify - let serialized_hash = <&[u8]>::from(&output_buffer).to_vec(); - let (hash1, _) = bytes_le_to_fr(&serialized_hash); - - let hash2 = hash_to_field_le(&signal); - - assert_eq!(hash1, hash2); - } - - #[test] - fn test_hash_to_field_big_endian_ffi() { - let mut rng = rand::thread_rng(); - let signal: [u8; 32] = rng.gen(); - - let input_buffer = &Buffer::from(signal.as_ref()); - let mut output_buffer = MaybeUninit::::uninit(); - let success = ffi_hash(input_buffer, output_buffer.as_mut_ptr(), false); - assert!(success, "hash call failed"); - let output_buffer = unsafe { output_buffer.assume_init() }; - let serialized_hash = <&[u8]>::from(&output_buffer).to_vec(); - let (hash1, _) = bytes_be_to_fr(&serialized_hash); - - let hash2 = hash_to_field_be(&signal); - - assert_eq!(hash1, hash2); - } - - #[test] - // Test Poseidon hash FFI - fn test_poseidon_hash_stateless_ffi() { - // generate random number between 1..ROUND_PARAMS.len() - let mut rng = thread_rng(); - let number_of_inputs = rng.gen_range(1..ROUND_PARAMS.len()); - let mut inputs = Vec::with_capacity(number_of_inputs); - for _ in 0..number_of_inputs { - inputs.push(Fr::rand(&mut rng)); - } - let inputs_ser = vec_fr_to_bytes_le(&inputs); - let input_buffer = &Buffer::from(inputs_ser.as_ref()); - - let expected_hash = utils_poseidon_hash(inputs.as_ref()); - - let mut output_buffer = MaybeUninit::::uninit(); - let success = ffi_poseidon_hash(input_buffer, output_buffer.as_mut_ptr(), true); - assert!(success, "poseidon hash call failed"); - - let output_buffer = unsafe { output_buffer.assume_init() }; - let result_data = <&[u8]>::from(&output_buffer).to_vec(); - let (received_hash, _) = bytes_le_to_fr(&result_data); - - assert_eq!(received_hash, expected_hash); - } - - #[test] - fn test_poseidon_hash_big_endian_ffi() { - let mut rng = thread_rng(); - let number_of_inputs = rng.gen_range(1..ROUND_PARAMS.len()); - let mut inputs = Vec::with_capacity(number_of_inputs); - for _ in 0..number_of_inputs { - inputs.push(Fr::rand(&mut rng)); - } - let inputs_ser = vec_fr_to_bytes_be(&inputs); - let input_buffer = &Buffer::from(inputs_ser.as_ref()); - - let expected_hash = utils_poseidon_hash(inputs.as_ref()); - - let mut output_buffer = MaybeUninit::::uninit(); - let success = ffi_poseidon_hash(input_buffer, output_buffer.as_mut_ptr(), false); - assert!(success, "poseidon hash call failed"); - - let output_buffer = unsafe { output_buffer.assume_init() }; - let result_data = <&[u8]>::from(&output_buffer).to_vec(); - let (received_hash, _) = bytes_be_to_fr(&result_data); - - assert_eq!(received_hash, expected_hash); + assert_eq!(metadata.len(), 0); } } diff --git a/rln/tests/ffi_stateless.rs b/rln/tests/ffi_stateless.rs new file mode 100644 index 0000000..b45095c --- /dev/null +++ b/rln/tests/ffi_stateless.rs @@ -0,0 +1,349 @@ +#[cfg(test)] +#[cfg(feature = "stateless")] +mod test { + use ark_std::{rand::thread_rng, UniformRand}; + use rand::Rng; + use rln::circuit::{Fr, TEST_TREE_DEPTH}; + use rln::ffi::{ffi_rln::*, ffi_utils::*}; + use rln::hashers::{hash_to_field_le, poseidon_hash as utils_poseidon_hash, PoseidonHash}; + use rln::utils::*; + use safer_ffi::prelude::repr_c; + use utils::{OptimalMerkleTree, ZerokitMerkleProof, ZerokitMerkleTree}; + + type ConfigOf = ::Config; + + fn create_rln_instance() -> repr_c::Box { + match ffi_new() { + CResult { + ok: Some(rln), + err: None, + } => rln, + CResult { + ok: None, + err: Some(err), + } => panic!("RLN object creation failed: {}", err), + _ => unreachable!(), + } + } + + fn identity_pair_gen() -> (IdSecret, Fr) { + let key_gen = ffi_key_gen(); + let mut id_secret_fr = *key_gen[0]; + let id_secret_hash = IdSecret::from(&mut id_secret_fr); + let id_commitment = *key_gen[1]; + (id_secret_hash, id_commitment) + } + + // ...existing code... + + #[test] + fn test_recover_id_secret_stateless_ffi() { + let default_leaf = Fr::from(0); + let mut tree: OptimalMerkleTree = OptimalMerkleTree::new( + TEST_TREE_DEPTH, + default_leaf, + ConfigOf::>::default(), + ) + .unwrap(); + + let ffi_rln_instance = create_rln_instance(); + + // We generate a new identity pair + let (identity_secret_hash, id_commitment) = identity_pair_gen(); + + let user_message_limit = Fr::from(100); + let rate_commitment = utils_poseidon_hash(&[id_commitment, user_message_limit]); + tree.update_next(rate_commitment).unwrap(); + + // We generate a random epoch + let epoch = hash_to_field_le(b"test-epoch"); + let rln_identifier = hash_to_field_le(b"test-rln-identifier"); + let external_nullifier = utils_poseidon_hash(&[epoch, rln_identifier]); + + // We generate two proofs using same epoch but different signals. + // We generate a random signal + let mut rng = thread_rng(); + let signal1: [u8; 32] = rng.gen(); + let x1 = hash_to_field_le(&signal1); + + let signal2: [u8; 32] = rng.gen(); + let x2 = hash_to_field_le(&signal2); + + let identity_index = tree.leaves_set(); + let merkle_proof = tree.proof(identity_index).expect("proof should exist"); + + let path_elements: repr_c::Vec = merkle_proof + .get_path_elements() + .iter() + .map(|fr| CFr::from(*fr)) + .collect::>() + .into(); + let identity_path_index: repr_c::Vec = merkle_proof.get_path_index().to_vec().into(); + + // We call generate_rln_proof for first proof values + let rln_proof1 = match ffi_generate_rln_proof_stateless( + &ffi_rln_instance, + &CFr::from(*identity_secret_hash.clone()), + &CFr::from(user_message_limit), + &CFr::from(Fr::from(1)), + &path_elements, + &identity_path_index, + &CFr::from(x1), + &CFr::from(external_nullifier), + ) { + CResult { + ok: Some(proof), + err: None, + } => proof, + CResult { + ok: None, + err: Some(err), + } => panic!("generate rln proof with witness call failed: {}", err), + _ => unreachable!(), + }; + + // We call generate_rln_proof for second proof values + let rln_proof2 = match ffi_generate_rln_proof_stateless( + &ffi_rln_instance, + &CFr::from(*identity_secret_hash.clone()), + &CFr::from(user_message_limit), + &CFr::from(Fr::from(1)), + &path_elements, + &identity_path_index, + &CFr::from(x2), + &CFr::from(external_nullifier), + ) { + CResult { + ok: Some(proof), + err: None, + } => proof, + CResult { + ok: None, + err: Some(err), + } => panic!("generate rln proof with witness call failed: {}", err), + _ => unreachable!(), + }; + + let recovered_id_secret_cfr = match ffi_recover_id_secret(&rln_proof1, &rln_proof2) { + CResult { + ok: Some(secret), + err: None, + } => secret, + CResult { + ok: None, + err: Some(err), + } => panic!("recover id secret call failed: {}", err), + _ => unreachable!(), + }; + + // We check if the recovered identity secret hash corresponds to the original one + let recovered_identity_secret_hash = *recovered_id_secret_cfr; + assert_eq!(recovered_identity_secret_hash, *identity_secret_hash); + + // We now test that computing identity_secret_hash is unsuccessful if shares computed from two different identity secret hashes but within same epoch are passed + + // We generate a new identity pair + let (identity_secret_hash_new, id_commitment_new) = identity_pair_gen(); + let rate_commitment_new = utils_poseidon_hash(&[id_commitment_new, user_message_limit]); + tree.update_next(rate_commitment_new).unwrap(); + + // We generate a random signal + let signal3: [u8; 32] = rng.gen(); + let x3 = hash_to_field_le(&signal3); + + let identity_index_new = tree.leaves_set(); + let merkle_proof_new = tree.proof(identity_index_new).expect("proof should exist"); + + let path_elements_new: repr_c::Vec = merkle_proof_new + .get_path_elements() + .iter() + .map(|fr| CFr::from(*fr)) + .collect::>() + .into(); + let identity_path_index_new: repr_c::Vec = + merkle_proof_new.get_path_index().to_vec().into(); + + // We call generate_rln_proof + let rln_proof3 = match ffi_generate_rln_proof_stateless( + &ffi_rln_instance, + &CFr::from(*identity_secret_hash_new.clone()), + &CFr::from(user_message_limit), + &CFr::from(Fr::from(1)), + &path_elements_new, + &identity_path_index_new, + &CFr::from(x3), + &CFr::from(external_nullifier), + ) { + CResult { + ok: Some(proof), + err: None, + } => proof, + CResult { + ok: None, + err: Some(err), + } => panic!("generate rln proof with witness call failed: {}", err), + _ => unreachable!(), + }; + + // We attempt to recover the secret using share1 (coming from identity_secret_hash) and share3 (coming from identity_secret_hash_new) + + let recovered_id_secret_new_cfr = match ffi_recover_id_secret(&rln_proof1, &rln_proof3) { + CResult { + ok: Some(secret), + err: None, + } => secret, + CResult { + ok: None, + err: Some(err), + } => panic!("recover id secret call failed: {}", err), + _ => unreachable!(), + }; + + let recovered_identity_secret_hash_new = recovered_id_secret_new_cfr; + + // ensure that the recovered secret does not match with either of the + // used secrets in proof generation + assert_ne!( + *recovered_identity_secret_hash_new, + *identity_secret_hash_new + ); + } + + #[test] + fn test_verify_with_roots_stateless_ffi() { + let default_leaf = Fr::from(0); + let mut tree: OptimalMerkleTree = OptimalMerkleTree::new( + TEST_TREE_DEPTH, + default_leaf, + ConfigOf::>::default(), + ) + .unwrap(); + + let ffi_rln_instance = create_rln_instance(); + + // We generate a new identity pair + let (identity_secret_hash, id_commitment) = identity_pair_gen(); + + let identity_index = tree.leaves_set(); + let user_message_limit = Fr::from(100); + let rate_commitment = utils_poseidon_hash(&[id_commitment, user_message_limit]); + tree.update_next(rate_commitment).unwrap(); + + // We generate a random epoch + let epoch = hash_to_field_le(b"test-epoch"); + let rln_identifier = hash_to_field_le(b"test-rln-identifier"); + let external_nullifier = utils_poseidon_hash(&[epoch, rln_identifier]); + + // We generate a random signal + let mut rng = thread_rng(); + let signal: [u8; 32] = rng.gen(); + let x = hash_to_field_le(&signal); + + let merkle_proof = tree.proof(identity_index).expect("proof should exist"); + + // We prepare input for generate_rln_proof API + let path_elements: repr_c::Vec = merkle_proof + .get_path_elements() + .iter() + .map(|fr| CFr::from(*fr)) + .collect::>() + .into(); + let identity_path_index: repr_c::Vec = merkle_proof.get_path_index().to_vec().into(); + + let rln_proof = match ffi_generate_rln_proof_stateless( + &ffi_rln_instance, + &CFr::from(*identity_secret_hash.clone()), + &CFr::from(user_message_limit), + &CFr::from(Fr::from(1)), + &path_elements, + &identity_path_index, + &CFr::from(x), + &CFr::from(external_nullifier), + ) { + CResult { + ok: Some(proof), + err: None, + } => proof, + CResult { + ok: None, + err: Some(err), + } => panic!("generate rln proof with witness call failed: {}", err), + _ => unreachable!(), + }; + + // If no roots is provided, proof validation is skipped and if the remaining proof values are valid, the proof will be correctly verified + let roots_empty: repr_c::Vec = vec![].into(); + + let proof_is_valid = + match ffi_verify_with_roots(&ffi_rln_instance, &rln_proof, &roots_empty, &CFr::from(x)) + { + CResult { + ok: Some(valid), + err: None, + } => *valid, + CResult { + ok: None, + err: Some(err), + } => panic!("verify with roots call failed: {}", err), + _ => unreachable!(), + }; + // Proof should be valid + assert!(proof_is_valid); + + // We serialize in the roots buffer some random values and we check that the proof is not verified since doesn't contain the correct root the proof refers to + let mut roots_random: Vec = Vec::new(); + for _ in 0..5 { + roots_random.push(CFr::from(Fr::rand(&mut rng))); + } + let roots_random_vec: repr_c::Vec = roots_random.into(); + + let proof_is_valid = match ffi_verify_with_roots( + &ffi_rln_instance, + &rln_proof, + &roots_random_vec, + &CFr::from(x), + ) { + CResult { + ok: Some(valid), + err: None, + } => *valid, + CResult { + ok: None, + err: Some(err), + } => panic!("verify with roots call failed: {}", err), + _ => unreachable!(), + }; + // Proof should be invalid. + assert!(!proof_is_valid); + + // We get the root of the tree obtained adding one leaf per time + let root = tree.root(); + + // We add the real root and we check if now the proof is verified + let mut roots_with_correct: Vec = Vec::new(); + for _ in 0..5 { + roots_with_correct.push(CFr::from(Fr::rand(&mut rng))); + } + roots_with_correct.push(CFr::from(root)); + let roots_correct_vec: repr_c::Vec = roots_with_correct.into(); + + let proof_is_valid = match ffi_verify_with_roots( + &ffi_rln_instance, + &rln_proof, + &roots_correct_vec, + &CFr::from(x), + ) { + CResult { + ok: Some(valid), + err: None, + } => *valid, + CResult { + ok: None, + err: Some(err), + } => panic!("verify with roots call failed: {}", err), + _ => unreachable!(), + }; + // Proof should be valid. + assert!(proof_is_valid); + } +} diff --git a/rln/tests/ffi_utils.rs b/rln/tests/ffi_utils.rs new file mode 100644 index 0000000..2b5a3b1 --- /dev/null +++ b/rln/tests/ffi_utils.rs @@ -0,0 +1,238 @@ +#[cfg(test)] +mod test { + use rand::Rng; + use rln::circuit::Fr; + use rln::ffi::ffi_utils::*; + use rln::hashers::poseidon_hash; + use rln::utils::{fr_to_bytes_be, fr_to_bytes_le, str_to_fr, IdSecret}; + + #[test] + // Tests hash to field using FFI APIs + fn test_seeded_keygen_ffi() { + // We generate a new identity pair from an input seed + let seed_bytes: Vec = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + let res = ffi_seeded_key_gen(&seed_bytes.into()); + assert_eq!(res.len(), 2, "seeded key gen call failed"); + let identity_secret_hash = res.first().unwrap(); + let id_commitment = res.get(1).unwrap(); + + // We check against expected values + let expected_identity_secret_hash_seed_bytes = str_to_fr( + "0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716", + 16, + ) + .unwrap(); + let expected_id_commitment_seed_bytes = str_to_fr( + "0xbf16d2b5c0d6f9d9d561e05bfca16a81b4b873bb063508fae360d8c74cef51f", + 16, + ) + .unwrap(); + + assert_eq!( + *identity_secret_hash, + expected_identity_secret_hash_seed_bytes + ); + assert_eq!(*id_commitment, expected_id_commitment_seed_bytes); + } + + #[test] + // Tests hash to field using FFI APIs + fn test_seeded_extended_keygen_ffi() { + // We generate a new identity tuple from an input seed + let seed_bytes: Vec = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + let key_gen = ffi_seeded_extended_key_gen(&seed_bytes.into()); + assert_eq!(key_gen.len(), 4, "seeded extended key gen call failed"); + let identity_trapdoor = *key_gen[0]; + let identity_nullifier = *key_gen[1]; + let identity_secret_hash = *key_gen[2]; + let id_commitment = *key_gen[3]; + + // We check against expected values + let expected_identity_trapdoor_seed_bytes = str_to_fr( + "0x766ce6c7e7a01bdf5b3f257616f603918c30946fa23480f2859c597817e6716", + 16, + ) + .unwrap(); + let expected_identity_nullifier_seed_bytes = str_to_fr( + "0x1f18714c7bc83b5bca9e89d404cf6f2f585bc4c0f7ed8b53742b7e2b298f50b4", + 16, + ) + .unwrap(); + let expected_identity_secret_hash_seed_bytes = str_to_fr( + "0x2aca62aaa7abaf3686fff2caf00f55ab9462dc12db5b5d4bcf3994e671f8e521", + 16, + ) + .unwrap(); + let expected_id_commitment_seed_bytes = str_to_fr( + "0x68b66aa0a8320d2e56842581553285393188714c48f9b17acd198b4f1734c5c", + 16, + ) + .unwrap(); + + assert_eq!(identity_trapdoor, expected_identity_trapdoor_seed_bytes); + assert_eq!(identity_nullifier, expected_identity_nullifier_seed_bytes); + assert_eq!( + identity_secret_hash, + expected_identity_secret_hash_seed_bytes + ); + assert_eq!(id_commitment, expected_id_commitment_seed_bytes); + } + + #[test] + // Test CFr FFI functions + fn test_cfr_ffi() { + let cfr_zero = cfr_zero(); + let fr_zero = rln::circuit::Fr::from(0u8); + assert_eq!(*cfr_zero, fr_zero); + + let cfr_one = cfr_one(); + let fr_one = rln::circuit::Fr::from(1u8); + assert_eq!(*cfr_one, fr_one); + + let cfr_int = uint_to_cfr(42); + let fr_int = rln::circuit::Fr::from(42u8); + assert_eq!(*cfr_int, fr_int); + + let cfr_debug_str = cfr_debug(Some(&cfr_int)); + assert_eq!(cfr_debug_str.to_string(), "Some(\"42\")"); + + let key_gen = ffi_key_gen(); + let mut id_secret_fr = *key_gen[0]; + let id_secret_hash = IdSecret::from(&mut id_secret_fr); + let id_commitment = *key_gen[1]; + let cfr_id_secret_hash = vec_cfr_get(&key_gen, 0).unwrap(); + assert_eq!(*cfr_id_secret_hash, *id_secret_hash); + let cfr_id_commitment = vec_cfr_get(&key_gen, 1).unwrap(); + assert_eq!(*cfr_id_commitment, id_commitment); + } + + #[test] + // Test Vec FFI functions + fn test_vec_u8_ffi() { + let mut rng = rand::thread_rng(); + let signal_gen: [u8; 32] = rng.gen(); + let signal: Vec = signal_gen.to_vec(); + + let bytes_le = vec_u8_to_bytes_le(&signal.clone().into()); + let expected_le = rln::utils::vec_u8_to_bytes_le(&signal); + assert_eq!(bytes_le.iter().copied().collect::>(), expected_le); + + let bytes_be = vec_u8_to_bytes_be(&signal.clone().into()); + let expected_be = rln::utils::vec_u8_to_bytes_be(&signal); + assert_eq!(bytes_be.iter().copied().collect::>(), expected_be); + + let signal_from_le = match bytes_le_to_vec_u8(&bytes_le) { + CResult { + ok: Some(vec_u8), + err: None, + } => vec_u8, + CResult { + ok: None, + err: Some(err), + } => panic!("bytes_le_to_vec_u8 call failed: {}", err), + _ => unreachable!(), + }; + assert_eq!(signal_from_le.iter().copied().collect::>(), signal); + + let signal_from_be = match bytes_be_to_vec_u8(&bytes_be) { + CResult { + ok: Some(vec_u8), + err: None, + } => vec_u8, + CResult { + ok: None, + err: Some(err), + } => panic!("bytes_be_to_vec_u8 call failed: {}", err), + _ => unreachable!(), + }; + assert_eq!(signal_from_be.iter().copied().collect::>(), signal); + } + + #[test] + // Test Vec FFI functions + fn test_vec_cfr_ffi() { + let vec_fr = [Fr::from(1u8), Fr::from(2u8), Fr::from(3u8), Fr::from(4u8)]; + let vec_cfr: Vec = vec_fr.iter().map(|fr| CFr::from(*fr)).collect(); + + let bytes_le = vec_cfr_to_bytes_le(&vec_cfr.clone().into()); + let expected_le = rln::utils::vec_fr_to_bytes_le(&vec_fr); + assert_eq!(bytes_le.iter().copied().collect::>(), expected_le); + + let bytes_be = vec_cfr_to_bytes_be(&vec_cfr.clone().into()); + let expected_be = rln::utils::vec_fr_to_bytes_be(&vec_fr); + assert_eq!(bytes_be.iter().copied().collect::>(), expected_be); + + let vec_cfr_from_le = match bytes_le_to_vec_cfr(&bytes_le) { + CResult { + ok: Some(vec_cfr), + err: None, + } => vec_cfr, + CResult { + ok: None, + err: Some(err), + } => panic!("bytes_le_to_vec_cfr call failed: {}", err), + _ => unreachable!(), + }; + assert_eq!(vec_cfr_from_le.iter().copied().collect::>(), vec_cfr); + + let vec_cfr_from_be = match bytes_be_to_vec_cfr(&bytes_be) { + CResult { + ok: Some(vec_cfr), + err: None, + } => vec_cfr, + CResult { + ok: None, + err: Some(err), + } => panic!("bytes_be_to_vec_cfr call failed: {}", err), + _ => unreachable!(), + }; + assert_eq!(vec_cfr_from_be.iter().copied().collect::>(), vec_cfr); + } + + #[test] + // Tests hash to field using FFI APIs + fn test_hash_to_field_ffi() { + let mut rng = rand::thread_rng(); + let signal_gen: [u8; 32] = rng.gen(); + let signal: Vec = signal_gen.to_vec(); + + let cfr_le_1 = ffi_hash_to_field_le(&signal.clone().into()); + let fr_le_2 = rln::hashers::hash_to_field_le(&signal); + assert_eq!(*cfr_le_1, fr_le_2); + + let cfr_be_1 = ffi_hash_to_field_be(&signal.clone().into()); + let fr_be_2 = rln::hashers::hash_to_field_be(&signal); + assert_eq!(*cfr_be_1, fr_be_2); + + assert_eq!(*cfr_le_1, *cfr_be_1); + assert_eq!(fr_le_2, fr_be_2); + + let hash_cfr_le_1 = cfr_to_bytes_le(&cfr_le_1) + .iter() + .copied() + .collect::>(); + let hash_fr_le_2 = fr_to_bytes_le(&fr_le_2); + assert_eq!(hash_cfr_le_1, hash_fr_le_2); + + let hash_cfr_be_1 = cfr_to_bytes_be(&cfr_be_1) + .iter() + .copied() + .collect::>(); + let hash_fr_be_2 = fr_to_bytes_be(&fr_be_2); + assert_eq!(hash_cfr_be_1, hash_fr_be_2); + + assert_ne!(hash_cfr_le_1, hash_cfr_be_1); + assert_ne!(hash_fr_le_2, hash_fr_be_2); + } + + #[test] + // Test Poseidon hash FFI + fn test_poseidon_hash_pair_ffi() { + let input_1 = Fr::from(42u8); + let input_2 = Fr::from(99u8); + + let expected_hash = poseidon_hash(&[input_1, input_2]); + let received_hash_cfr = ffi_poseidon_hash_pair(&CFr::from(input_1), &CFr::from(input_2)); + assert_eq!(*received_hash_cfr, expected_hash); + } +} diff --git a/utils/Makefile.toml b/utils/Makefile.toml index 89aeb4a..99ce82e 100644 --- a/utils/Makefile.toml +++ b/utils/Makefile.toml @@ -4,7 +4,7 @@ args = ["build", "--release"] [tasks.test] command = "cargo" -args = ["test", "--release"] +args = ["test", "--release", "--", "--nocapture"] [tasks.bench] command = "cargo" diff --git a/utils/src/pm_tree/sled_adapter.rs b/utils/src/pm_tree/sled_adapter.rs index c35533f..531bab2 100644 --- a/utils/src/pm_tree/sled_adapter.rs +++ b/utils/src/pm_tree/sled_adapter.rs @@ -19,17 +19,17 @@ impl SledDB { } match config.open() { Ok(db) => Ok(SledDB(db)), - Err(e) if e.to_string().contains("WouldBlock") => { + Err(err) if err.to_string().contains("WouldBlock") => { // try till the fd is freed // sleep for 10^tries milliseconds, then recursively try again thread::sleep(Duration::from_millis(10u64.pow(tries))); Self::new_with_tries(config, tries + 1) } - Err(e) => { + Err(err) => { // On any other error, we return immediately. Err(PmtreeErrorKind::DatabaseError( DatabaseErrorKind::CustomError(format!( - "Cannot create database: {e} {config:#?}" + "Cannot create database: {err} {config:#?}" )), )) } @@ -48,9 +48,9 @@ impl Database for SledDB { fn load(config: Self::Config) -> PmtreeResult { let db = match config.open() { Ok(db) => db, - Err(e) => { + Err(err) => { return Err(PmtreeErrorKind::DatabaseError( - DatabaseErrorKind::CustomError(format!("Cannot load database: {e}")), + DatabaseErrorKind::CustomError(format!("Cannot load database: {err}")), )) } }; diff --git a/utils/tests/merkle_tree.rs b/utils/tests/merkle_tree.rs index e3abc17..175c475 100644 --- a/utils/tests/merkle_tree.rs +++ b/utils/tests/merkle_tree.rs @@ -1,6 +1,6 @@ // Tests adapted from https://github.com/worldcoin/semaphore-rs/blob/d462a4372f1fd9c27610f2acfe4841fab1d396aa/src/merkle_tree.rs #[cfg(test)] -pub mod test { +mod test { use hex_literal::hex; use std::{fmt::Display, str::FromStr}; use tiny_keccak::{Hasher as _, Keccak};