diff --git a/icicle/backend/cpu/src/hash/cpu_keccak.cpp b/icicle/backend/cpu/src/hash/cpu_keccak.cpp index 4fa6dead3..0d0181277 100644 --- a/icicle/backend/cpu/src/hash/cpu_keccak.cpp +++ b/icicle/backend/cpu/src/hash/cpu_keccak.cpp @@ -6,15 +6,19 @@ namespace icicle { class KeccakBackend : public HashBackend { public: - KeccakBackend(uint64_t input_chunk_size, uint64_t output_size, uint64_t rate, int padding_const) - : HashBackend(output_size, input_chunk_size) + KeccakBackend(uint64_t input_chunk_size, uint64_t output_size, uint64_t rate, int padding_const, const char* name) + : HashBackend(name, output_size, input_chunk_size) { } eIcicleError hash(const std::byte* input, uint64_t size, const HashConfig& config, std::byte* output) const override { - ICICLE_LOG_INFO << "Keccak CPU hash() called"; - // TODO implement + ICICLE_LOG_DEBUG << "Keccak/sha3 CPU hash() called, batch=" << config.batch + << ", single_output_size=" << output_size(); + // TODO implement real logic + for (int i = 0; i < output_size() * config.batch; ++i) { + output[i] = std::byte(i % 256); + } return eIcicleError::SUCCESS; } }; @@ -33,9 +37,10 @@ namespace icicle { Keccak256Backend(int input_chunk_size) : KeccakBackend( input_chunk_size, - KECCAK_256_DIGEST * sizeof(uint64_t) / sizeof(std::byte), + KECCAK_256_DIGEST * sizeof(uint64_t), KECCAK_256_RATE, - KECCAK_PADDING_CONST) + KECCAK_PADDING_CONST, + "Keccak-256-CPU") { } }; @@ -46,9 +51,10 @@ namespace icicle { Keccak512Backend(int input_chunk_size) : KeccakBackend( input_chunk_size, - KECCAK_512_DIGEST * sizeof(uint64_t) / sizeof(std::byte), + KECCAK_512_DIGEST * sizeof(uint64_t), KECCAK_512_RATE, - KECCAK_PADDING_CONST) + KECCAK_PADDING_CONST, + "Keccak-512-CPU") { } }; @@ -58,10 +64,7 @@ namespace icicle { public: Sha3_256Backend(int input_chunk_size) : KeccakBackend( - input_chunk_size, - KECCAK_256_DIGEST * sizeof(uint64_t) / sizeof(std::byte), - KECCAK_256_RATE, - SHA3_PADDING_CONST) + input_chunk_size, KECCAK_256_DIGEST * sizeof(uint64_t), KECCAK_256_RATE, SHA3_PADDING_CONST, "SHA3-256-CPU") { } }; @@ -71,10 +74,7 @@ namespace icicle { public: Sha3_512Backend(int input_chunk_size) : KeccakBackend( - input_chunk_size, - KECCAK_512_DIGEST * sizeof(uint64_t) / sizeof(std::byte), - KECCAK_512_RATE, - SHA3_PADDING_CONST) + input_chunk_size, KECCAK_512_DIGEST * sizeof(uint64_t), KECCAK_512_RATE, SHA3_PADDING_CONST, "SHA3-512-CPU") { } }; diff --git a/icicle/backend/cpu/src/hash/cpu_merkle_tree.cpp b/icicle/backend/cpu/src/hash/cpu_merkle_tree.cpp index c144fce10..e2e1a91de 100644 --- a/icicle/backend/cpu/src/hash/cpu_merkle_tree.cpp +++ b/icicle/backend/cpu/src/hash/cpu_merkle_tree.cpp @@ -11,17 +11,21 @@ namespace icicle { const std::vector& layer_hashes, uint64_t leaf_element_size, uint64_t output_store_min_layer = 0) : MerkleTreeBackend(layer_hashes, leaf_element_size, output_store_min_layer) { + ICICLE_LOG_DEBUG << "in CPUMerkleTreeBackend, have " << layer_hashes.size() << " layer hashes"; + for (const auto& layer_hash : layer_hashes) { + ICICLE_LOG_DEBUG << "layer hash (" << &layer_hash << "), name: " << layer_hash.name(); + } } eIcicleError build(const std::byte* leaves, uint64_t size, const MerkleTreeConfig& config) override { - ICICLE_LOG_INFO << "CPU CPUMerkleTreeBackend::build() called with " << size << " bytes of leaves"; + ICICLE_LOG_DEBUG << "CPU CPUMerkleTreeBackend::build() called with " << size << " bytes of leaves"; return eIcicleError::SUCCESS; // TODO: Implement tree-building logic } std::pair get_merkle_root() const override { - ICICLE_LOG_INFO << "CPU CPUMerkleTreeBackend::get_merkle_root() called"; + ICICLE_LOG_DEBUG << "CPU CPUMerkleTreeBackend::get_merkle_root() called"; return {nullptr, 0}; // TODO: Implement root retrieval logic } @@ -31,7 +35,12 @@ namespace icicle { const MerkleTreeConfig& config, MerkleProof& merkle_proof) const override { - ICICLE_LOG_INFO << "CPU CPUMerkleTreeBackend::get_merkle_proof() called for element index " << element_idx; + ICICLE_LOG_DEBUG << "CPU CPUMerkleTreeBackend::get_merkle_proof() called for element index " << element_idx; + // Dummy implementation. TODO implement + uint64_t root = 123; + merkle_proof.allocate( + false /*pruned*/, element_idx, leaves + element_idx * get_leaf_element_size(), get_leaf_element_size(), + (const std::byte*)&root, sizeof(root)); return eIcicleError::SUCCESS; // TODO: Implement proof generation logic } }; @@ -43,7 +52,7 @@ namespace icicle { uint64_t output_store_min_layer, std::shared_ptr& backend) { - ICICLE_LOG_INFO << "Creating CPU MerkleTreeBackend"; + ICICLE_LOG_DEBUG << "Creating CPU MerkleTreeBackend"; backend = std::make_shared(layer_hashes, leaf_element_size, output_store_min_layer); return eIcicleError::SUCCESS; } diff --git a/icicle/backend/cpu/src/hash/cpu_poseidon.cpp b/icicle/backend/cpu/src/hash/cpu_poseidon.cpp index b2bafd73d..deb26a631 100644 --- a/icicle/backend/cpu/src/hash/cpu_poseidon.cpp +++ b/icicle/backend/cpu/src/hash/cpu_poseidon.cpp @@ -3,37 +3,19 @@ namespace icicle { - template - class PoseidonConstantsCPU : PoseidonConstants - { - // TODO add field here - S* m_dummy_poseidon_constant; - }; - - static eIcicleError cpu_poseidon_init_constants( - const Device& device, - unsigned arity, - unsigned alpha, - unsigned nof_partial_rounds, - unsigned nof_upper_full_rounds, - unsigned nof_end_full_rounds, - const scalar_t* rounds_constants, - const scalar_t* mds_matrix, - const scalar_t* pre_matrix, - const scalar_t* sparse_matrix, - std::shared_ptr>& constants /*out*/) + static eIcicleError + cpu_poseidon_init_constants(const Device& device, const PoseidonConstantsInitOptions* options) { - ICICLE_LOG_INFO << "in cpu_poseidon_init_constants()"; + ICICLE_LOG_DEBUG << "in cpu_poseidon_init_constants() for type " << demangle(); // TODO implement return eIcicleError::SUCCESS; } REGISTER_POSEIDON_INIT_CONSTANTS_BACKEND("CPU", cpu_poseidon_init_constants); - static eIcicleError cpu_poseidon_init_default_constants( - const Device& device, unsigned arity, std::shared_ptr>& constants /*out*/) + static eIcicleError cpu_poseidon_init_default_constants(const Device& device, const scalar_t& phantom) { - ICICLE_LOG_INFO << "in cpu_poseidon_init_default_constants()"; + ICICLE_LOG_DEBUG << "in cpu_poseidon_init_default_constants() for type " << demangle(); // TODO implement return eIcicleError::SUCCESS; } @@ -44,28 +26,22 @@ namespace icicle { class PoseidonBackendCPU : public HashBackend { public: - PoseidonBackendCPU(std::shared_ptr> constants) - : HashBackend(sizeof(S), 0 /*TODO get from constants arity of whatever*/), m_constants{constants} - { - } + PoseidonBackendCPU(unsigned arity) : HashBackend("Poseidon-CPU", sizeof(S), arity * sizeof(S)) {} eIcicleError hash(const std::byte* input, uint64_t size, const HashConfig& config, std::byte* output) const override { - ICICLE_LOG_INFO << "Poseidon CPU hash() " << size << " bytes, for type " << demangle(); + ICICLE_LOG_DEBUG << "Poseidon CPU hash() " << size << " bytes, for type " << demangle() + << ", batch=" << config.batch; // TODO implement return eIcicleError::SUCCESS; } - - private: - std::shared_ptr> m_constants = nullptr; }; static eIcicleError create_cpu_poseidon_hash_backend( - const Device& device, - std::shared_ptr> constants, - std::shared_ptr& backend /*OUT*/) + const Device& device, unsigned arity, std::shared_ptr& backend /*OUT*/, const scalar_t& phantom) { - backend = std::make_shared>(constants); + ICICLE_LOG_DEBUG << "in create_cpu_poseidon_hash_backend(arity=" << arity << ")"; + backend = std::make_shared>(arity); return eIcicleError::SUCCESS; } diff --git a/icicle/cmake/hash.cmake b/icicle/cmake/hash.cmake index 2007aa2b7..660d2e2a3 100644 --- a/icicle/cmake/hash.cmake +++ b/icicle/cmake/hash.cmake @@ -5,6 +5,8 @@ function(setup_hash_target) target_sources(icicle_hash PRIVATE src/hash/keccak.cpp src/hash/merkle_tree.cpp + src/hash/hash_c_api.cpp + src/hash/merkle_c_api.cpp ) target_link_libraries(icicle_hash PUBLIC icicle_device) diff --git a/icicle/cmake/target_editor.cmake b/icicle/cmake/target_editor.cmake index 09d1932a3..4ac91ed78 100644 --- a/icicle/cmake/target_editor.cmake +++ b/icicle/cmake/target_editor.cmake @@ -73,7 +73,7 @@ endfunction() function(handle_poseidon TARGET FEATURE_LIST) if(POSEIDON AND "POSEIDON" IN_LIST FEATURE_LIST) target_compile_definitions(${TARGET} PUBLIC POSEIDON=${POSEIDON}) - target_sources(${TARGET} PRIVATE src/hash/poseidon.cpp) + target_sources(${TARGET} PRIVATE src/hash/poseidon.cpp src/hash/poseidon_c_api.cpp) set(POSEIDON ON CACHE BOOL "Enable POSEIDON feature" FORCE) else() set(POSEIDON OFF CACHE BOOL "POSEIDON not available for this field" FORCE) diff --git a/icicle/include/icicle/backend/hash/hash_backend.h b/icicle/include/icicle/backend/hash/hash_backend.h index 9fa4310df..b2c8c7a7c 100644 --- a/icicle/include/icicle/backend/hash/hash_backend.h +++ b/icicle/include/icicle/backend/hash/hash_backend.h @@ -23,8 +23,8 @@ namespace icicle { * @param output_size The size of the output in bytes. * @param default_input_chunk_size The default size of a single input chunk in bytes. Useful for Merkle trees. */ - HashBackend(uint64_t output_size, uint64_t default_input_chunk_size = 0) - : m_output_size{output_size}, m_default_input_chunk_size{default_input_chunk_size} + HashBackend(const char* name, uint64_t output_size, uint64_t default_input_chunk_size = 0) + : m_name_hint(name), m_output_size{output_size}, m_default_input_chunk_size{default_input_chunk_size} { } @@ -58,9 +58,12 @@ namespace icicle { */ uint64_t output_size() const { return m_output_size; } + const std::string& name() { return m_name_hint; } + protected: const uint64_t m_output_size; ///< The number of output bytes produced by the hash. const uint64_t m_default_input_chunk_size; ///< Expected input chunk size for hashing operations. + const std::string m_name_hint; ///< Name of hash, for debug purposes inline uint64_t get_single_chunk_size(uint64_t size_or_zero) const { diff --git a/icicle/include/icicle/backend/hash/poseidon_backend.h b/icicle/include/icicle/backend/hash/poseidon_backend.h index 485fe3858..84bc9ead7 100644 --- a/icicle/include/icicle/backend/hash/poseidon_backend.h +++ b/icicle/include/icicle/backend/hash/poseidon_backend.h @@ -12,18 +12,8 @@ namespace icicle { /*************************** Backend registration ***************************/ - using InitPoseidonConstantsImpl = std::function>& constants /*out*/)>; + using InitPoseidonConstantsImpl = + std::function* options)>; // poseidon init constants void register_poseidon_init_constants(const std::string& deviceType, InitPoseidonConstantsImpl impl); @@ -36,8 +26,9 @@ namespace icicle { }(); \ } - using InitPoseidonDefaultConstantsImpl = std::function>& constants /*out*/)>; + // Note: 'phantom' is a workaround for the function required per field but need to differentiate by type when + // calling. + using InitPoseidonDefaultConstantsImpl = std::function; // poseidon init constants void register_poseidon_init_default_constants(const std::string& deviceType, InitPoseidonDefaultConstantsImpl impl); @@ -51,7 +42,7 @@ namespace icicle { } using CreatePoseidonImpl = std::function>, std::shared_ptr& /*OUT*/)>; + const Device& device, unsigned arity, std::shared_ptr& /*OUT*/, const scalar_t& phantom)>; // poseidon init constants void register_create_poseidon(const std::string& deviceType, CreatePoseidonImpl impl); diff --git a/icicle/include/icicle/backend/ntt_backend.h b/icicle/include/icicle/backend/ntt_backend.h index 9d61c56d2..7ea010044 100644 --- a/icicle/include/icicle/backend/ntt_backend.h +++ b/icicle/include/icicle/backend/ntt_backend.h @@ -63,9 +63,9 @@ namespace icicle { } /*************************** RELEASE DOMAIN ***************************/ - // Note: 'dummy' is a workaround for the function required per field but need to differentiate by type when - // calling. TODO Yuval: avoid this param somehow - using NttReleaseDomainImpl = std::function; + // Note: 'phantom' is a workaround for the function required per field but need to differentiate by type when + // calling. + using NttReleaseDomainImpl = std::function; void register_ntt_release_domain(const std::string& deviceType, NttReleaseDomainImpl); diff --git a/icicle/include/icicle/device_api.h b/icicle/include/icicle/device_api.h index b4356b0f0..e42faea23 100644 --- a/icicle/include/icicle/device_api.h +++ b/icicle/include/icicle/device_api.h @@ -15,7 +15,7 @@ namespace icicle { /** * @brief Enum for specifying the direction of data copy. */ - enum eCopyDirection { HostToDevice, DeviceToHost, DeviceToDevice }; + enum eCopyDirection { HostToDevice, DeviceToHost, DeviceToDevice, HostToHost }; /** * @brief Typedef for an abstract stream used for asynchronous operations. diff --git a/icicle/include/icicle/fields/host_math.h b/icicle/include/icicle/fields/host_math.h index e256aa922..9ced242d3 100644 --- a/icicle/include/icicle/fields/host_math.h +++ b/icicle/include/icicle/fields/host_math.h @@ -288,7 +288,7 @@ namespace host_math { r = left_shift(r); r.limbs[0] |= ((num.limbs[limb_idx] >> bit_idx) & 1); uint32_t c = add_sub_limbs(r, denom, temp); - if (limb_idx < NLIMBS_Q & !c) { + if ((limb_idx < NLIMBS_Q) & !c) { r = temp; q.limbs[limb_idx] |= 1 << bit_idx; } diff --git a/icicle/include/icicle/hash/hash.h b/icicle/include/icicle/hash/hash.h index 5656d865c..39e9f2c97 100644 --- a/icicle/include/icicle/hash/hash.h +++ b/icicle/include/icicle/hash/hash.h @@ -73,6 +73,8 @@ namespace icicle { */ inline uint64_t output_size() const { return m_backend->output_size(); } + const std::string& name() const { return m_backend->name(); } + private: std::shared_ptr m_backend; ///< Shared pointer to the backend performing the hash operation. }; diff --git a/icicle/include/icicle/hash/poseidon.h b/icicle/include/icicle/hash/poseidon.h index 7330dc678..4365b10ba 100644 --- a/icicle/include/icicle/hash/poseidon.h +++ b/icicle/include/icicle/hash/poseidon.h @@ -4,84 +4,73 @@ namespace icicle { - /** - * @brief Class representing the constants for Poseidon hash. - * - * This class will store the necessary constants for Poseidon hashing operations. - * It will be managed and allocated by the backend and used when initializing the Poseidon hash. - */ + // Options for generating the on-device Poseidon constants. + // This struct will hold parameters needed to initialize Poseidon constants with custom settings. + // The fields will include: + // - `arity`: The arity (branching factor) of the Poseidon hash. + // - `alpha`: The exponent used in the S-box function. + // - `nof_rounds`: The number of rounds (both full and partial) for the Poseidon hash. + // - `mds_matrix`: The Maximum Distance Separable (MDS) matrix used for mixing the state. + // The struct should be FFI (Foreign Function Interface) compatible, meaning it should use basic types or pointers + // that can easily be shared between languages. The template parameter `S` represents the field type for which the + // Poseidon constants are being initialized. template - class PoseidonConstants - { - // The backend will populate this class with Poseidon-specific constants. - // This is intentionally left opaque and managed by the backend. + struct PoseidonConstantsInitOptions { + // TODO: Define the struct with fields such as arity, alpha, nof_rounds, mds_matrix, etc. + // It must be compatible with FFI, so make sure to use only types like integers, arrays, and pointers. }; - /** - * @brief Initialize Poseidon constants with full configuration parameters. - * - * This function initializes a PoseidonConstants object with the provided configuration parameters. - * The user provides arity, alpha, round constants, matrices, and other Poseidon-specific parameters. - * The backend allocates and returns the initialized constants as a shared object. - * - * @param arity The arity (branching factor) of the Poseidon hash. - * @param alpha Exponent used in the S-box function. - * @param nof_partial_rounds Number of partial rounds. - * @param nof_upper_full_rounds Number of full rounds at the beginning. - * @param nof_end_full_rounds Number of full rounds at the end. - * @param rounds_constants Array of round constants for Poseidon. - * @param mds_matrix Array representing the MDS matrix. - * @param pre_matrix Array representing the pre-processing matrix. - * @param sparse_matrix Array representing the sparse matrix. - * @param constants [OUT] Shared pointer to the initialized PoseidonConstants object. - * - * @return eIcicleError Error code indicating success or failure of the initialization. - */ + // Function to generate and initialize Poseidon constants based on user-defined options. + // This function allows the user to customize the initialization of Poseidon constants by providing their own + // parameters. It is important to call this function per arity (branching factor) because the constants depend on the + // arity. The template parameter `S` represents the field type (e.g., scalar field) for which the constants are being + // initialized. template - eIcicleError poseidon_init_constants( - unsigned arity, - unsigned alpha, - unsigned nof_partial_rounds, - unsigned nof_upper_full_rounds, - unsigned nof_end_full_rounds, - const S* rounds_constants, - const S* mds_matrix, - const S* pre_matrix, - const S* sparse_matrix, - std::shared_ptr>& constants /*out*/); + eIcicleError poseidon_init_constants(const PoseidonConstantsInitOptions* options); - /** - * @brief Initialize Poseidon constants with default values based on arity. - * - * This function initializes a PoseidonConstants object with default values, based only on the arity. - * The backend will populate the constants object with pre-determined default values for Poseidon parameters. - * - * @param arity The arity (branching factor) of the Poseidon hash. - * @param constants [OUT] Shared pointer to the initialized PoseidonConstants object. - * - * @return eIcicleError Error code indicating success or failure of the initialization. - */ + // Function to initialize Poseidon constants using default, precomputed values. + // These constants are optimized and precomputed for the given field and arity. + // The arity must be supported by the implementation (i.e., predefined sets of constants exist for the supported + // arities). This function simplifies initialization when custom constants are not needed, and the user can rely on + // default values. template - eIcicleError - poseidon_init_default_constants(unsigned arity, std::shared_ptr>& constants /*out*/); + eIcicleError poseidon_init_default_constants(); - /** - * @brief Create a Poseidon hash object using the shared PoseidonConstants. - * - * This function creates a Poseidon hash object, using the shared constants that were initialized by - * the backend. The constants will be shared between the user and the PoseidonHasher. - * - * @param constants Shared pointer to the PoseidonConstants object, which holds hash-specific data. - * - * @return Hash Poseidon Hash object ready to perform hashing operations. - */ + // Function to create a Poseidon hash object for a given arity. + // This function returns a `Hash` object configured to use the Poseidon hash for the specified arity. + // The arity controls the number of inputs the hash function can take (branching factor). template - Hash create_poseidon_hash(std::shared_ptr> constants); + Hash create_poseidon_hash(unsigned arity); + + // Poseidon struct providing a static interface to Poseidon-related operations. struct Poseidon { + // Static method to create a Poseidon hash object. + // This method provides a simple API for creating a Poseidon hash object, hiding the complexity of template + // parameters from the user. It uses the specified `arity` to create the Poseidon hash. + template + inline static Hash create(unsigned arity) + { + return create_poseidon_hash(arity); + } + + // Static method to initialize Poseidon constants based on user-defined options. + // This method abstracts away the complexity of calling the `poseidon_init_constants` function directly, + // providing a clean interface to initialize Poseidon constants. + // The user provides a pointer to `PoseidonConstantsInitOptions` to customize the constants. + template + inline static eIcicleError init_constants(const PoseidonConstantsInitOptions* options) + { + return poseidon_init_constants(options); + } + + // Static method to initialize Poseidon constants with default values. + // This provides a clean interface for initializing Poseidon with precomputed default constants for the given field + // and arity. Useful when the user doesn't need to customize the constants and wants to use pre-optimized + // parameters. template - inline static Hash create(std::shared_ptr> constants) + inline static eIcicleError init_default_constants() { - return create_poseidon_hash(constants); + return poseidon_init_default_constants(); } }; diff --git a/icicle/include/icicle/merkle/merkle_tree.h b/icicle/include/icicle/merkle/merkle_tree.h index ac746abe7..813173266 100644 --- a/icicle/include/icicle/merkle/merkle_tree.h +++ b/icicle/include/icicle/merkle/merkle_tree.h @@ -118,6 +118,8 @@ namespace icicle { { // TODO: Implement Merkle path verification here + ICICLE_LOG_DEBUG << "In MerkleTree::verify()"; + // can access path by offset or all of it // auto digest = merkle_proof.access_path_at_offset(offset); auto [path, path_size] = merkle_proof.get_path(); diff --git a/icicle/src/hash/hash_c_api.cpp b/icicle/src/hash/hash_c_api.cpp new file mode 100644 index 000000000..ac8dbf6e0 --- /dev/null +++ b/icicle/src/hash/hash_c_api.cpp @@ -0,0 +1,112 @@ +#include +#include "icicle/hash/hash.h" +#include "icicle/errors.h" +#include "icicle/hash/keccak.h" + +extern "C" { +// Define a type for the HasherHandle (which is a pointer to Hash) +typedef icicle::Hash* HasherHandle; + +/** + * @brief Hashes input data and stores the result in the output buffer. + * + * @param hash_ptr Handle to the Hash object. + * @param input_ptr Pointer to the input data. + * @param input_len Length of the input data in bytes. + * @param config Pointer to the HashConfig object. + * @param output_ptr Pointer to the output buffer to store the result. + * @return eIcicleError indicating success or failure. + */ +eIcicleError icicle_hasher_hash( + HasherHandle hash_ptr, + const uint8_t* input_ptr, + uint64_t input_len, + const icicle::HashConfig* config, + uint8_t* output_ptr) +{ + if (!hash_ptr) return eIcicleError::INVALID_POINTER; + return hash_ptr->hash( + reinterpret_cast(input_ptr), input_len, *config, reinterpret_cast(output_ptr)); +} + +/** + * @brief Returns the output size in bytes for the hash operation. + * + * @param hash_ptr Handle to the Hash object. + * @return uint64_t The size of the output in bytes. + */ +uint64_t icicle_hasher_output_size(HasherHandle hash_ptr) +{ + if (!hash_ptr) return 0; + return hash_ptr->output_size(); +} + +/** + * @brief Deletes the Hash object and cleans up its resources. + * + * @param hash_ptr Handle to the Hash object. + * @return eIcicleError indicating success or failure. + */ +eIcicleError icicle_hasher_delete(HasherHandle hash_ptr) +{ + if (!hash_ptr) return eIcicleError::INVALID_POINTER; + delete hash_ptr; + return eIcicleError::SUCCESS; +} + +/*=================================================================================* + The following are factories to create hasher objects of general-purpose hashes. + *=================================================================================*/ + +/** + * @brief Creates a Keccak-256 hash object. + * + * This function constructs a Hash object configured for Keccak-256. + * + * @param input_chunk_size Size of the input in bytes for the Keccak-256 hash. + * @return HasherHandle A handle to the created Keccak-256 Hash object. + */ +HasherHandle icicle_create_keccak_256(uint64_t input_chunk_size) +{ + return new icicle::Hash(icicle::create_keccak_256_hash(input_chunk_size)); +} + +/** + * @brief Creates a Keccak-512 hash object. + * + * This function constructs a Hash object configured for Keccak-512. + * + * @param input_chunk_size Size of the input in bytes for the Keccak-512 hash. + * @return HasherHandle A handle to the created Keccak-512 Hash object. + */ +HasherHandle icicle_create_keccak_512(uint64_t input_chunk_size) +{ + return new icicle::Hash(icicle::create_keccak_512_hash(input_chunk_size)); +} + +/** + * @brief Creates a SHA3-256 hash object. + * + * This function constructs a Hash object configured for SHA3-256. + * + * @param input_chunk_size Size of the input in bytes for the SHA3-256 hash. + * @return HasherHandle A handle to the created SHA3-256 Hash object. + */ +HasherHandle icicle_create_sha3_256(uint64_t input_chunk_size) +{ + return new icicle::Hash(icicle::create_sha3_256_hash(input_chunk_size)); +} + +/** + * @brief Creates a SHA3-512 hash object. + * + * This function constructs a Hash object configured for SHA3-512. + * + * @param input_chunk_size Size of the input in bytes for the SHA3-512 hash. + * @return HasherHandle A handle to the created SHA3-512 Hash object. + */ +HasherHandle icicle_create_sha3_512(uint64_t input_chunk_size) +{ + return new icicle::Hash(icicle::create_sha3_512_hash(input_chunk_size)); +} +} \ No newline at end of file diff --git a/icicle/src/hash/keccak.cpp b/icicle/src/hash/keccak.cpp index dd7f415da..a4cdf7243 100644 --- a/icicle/src/hash/keccak.cpp +++ b/icicle/src/hash/keccak.cpp @@ -27,46 +27,25 @@ namespace icicle { } // Sha3 256 - ICICLE_DISPATCHER_INST(Sah3_256Dispatcher, sha3_256_factory, KeccakFactoryImpl); + ICICLE_DISPATCHER_INST(Sha3_256Dispatcher, sha3_256_factory, KeccakFactoryImpl); Hash create_sha3_256_hash(uint64_t input_chunk_size) { std::shared_ptr backend; - ICICLE_CHECK(Keccak256Dispatcher::execute(input_chunk_size, backend)); + ICICLE_CHECK(Sha3_256Dispatcher::execute(input_chunk_size, backend)); Hash keccak{backend}; return keccak; } // Keccak 512 - ICICLE_DISPATCHER_INST(Sah3_512Dispatcher, sha3_512_factory, KeccakFactoryImpl); + ICICLE_DISPATCHER_INST(Sha3_512Dispatcher, sha3_512_factory, KeccakFactoryImpl); Hash create_sha3_512_hash(uint64_t input_chunk_size) { std::shared_ptr backend; - ICICLE_CHECK(Keccak512Dispatcher::execute(input_chunk_size, backend)); + ICICLE_CHECK(Sha3_512Dispatcher::execute(input_chunk_size, backend)); Hash keccak{backend}; return keccak; } - /*************************** C API ***************************/ - - extern "C" Hash* create_keccak_256_hash_c_api(uint64_t input_chunk_size) - { - return new Hash(create_keccak_256_hash(input_chunk_size)); - } - extern "C" Hash* create_keccak_512_hash_c_api(uint64_t input_chunk_size) - { - return new Hash(create_keccak_512_hash(input_chunk_size)); - } - extern "C" Hash* create_sha3_256_hash_c_api(uint64_t input_chunk_size) - { - return new Hash(create_sha3_256_hash(input_chunk_size)); - } - extern "C" Hash* create_sha3_512_hash_c_api(uint64_t input_chunk_size) - { - return new Hash(create_sha3_512_hash(input_chunk_size)); - } - - // TODO Yuval : need to expose one deleter from C++. This will be used to drop any object - } // namespace icicle \ No newline at end of file diff --git a/icicle/src/hash/merkle_c_api.cpp b/icicle/src/hash/merkle_c_api.cpp new file mode 100644 index 000000000..b51673bbb --- /dev/null +++ b/icicle/src/hash/merkle_c_api.cpp @@ -0,0 +1,147 @@ +#include "icicle/utils/log.h" +#include "icicle/errors.h" +#include "icicle/merkle/merkle_proof.h" +#include "icicle/merkle/merkle_tree.h" + +extern "C" { +// Define an opaque pointer type for MerkleProof (similar to a handle in C) +typedef icicle::MerkleProof* MerkleProofHandle; +typedef icicle::MerkleTree* MerkleTreeHandle; + +// Create a new MerkleProof object and return a handle to it. +MerkleProofHandle icicle_merkle_proof_create() { return new icicle::MerkleProof(); } + +// Delete the MerkleProof object and free its resources. +eIcicleError icicle_merkle_proof_delete(MerkleProofHandle proof) +{ + if (!proof) { return eIcicleError::INVALID_POINTER; } + delete proof; + return eIcicleError::SUCCESS; +} + +// Check if the Merkle path is pruned. +bool icicle_merkle_proof_is_pruned(MerkleProofHandle proof) +{ + if (!proof) { return false; } + return proof->is_pruned(); +} + +// Get the path data and its size. +const std::byte* icicle_merkle_proof_get_path(MerkleProofHandle proof, std::size_t* out_size) +{ + if (!proof || !out_size) { return nullptr; } + auto [path, size] = proof->get_path(); + *out_size = size; + return path; +} + +// Get the leaf data, its size, and its index. +const std::byte* icicle_merkle_proof_get_leaf(MerkleProofHandle proof, std::size_t* out_size, uint64_t* out_leaf_idx) +{ + if (!proof || !out_size || !out_leaf_idx) { return nullptr; } + auto [leaf, size, leaf_idx] = proof->get_leaf(); + *out_size = size; + *out_leaf_idx = leaf_idx; + return leaf; +} + +// Get the root data and its size. +const std::byte* icicle_merkle_proof_get_root(MerkleProofHandle proof, std::size_t* out_size) +{ + if (!proof || !out_size) { return nullptr; } + auto [root, size] = proof->get_root(); + *out_size = size; + return root; +} + +// Create a new MerkleTree object +icicle::MerkleTree* icicle_merkle_tree_create( + const icicle::Hash** layer_hashes, + size_t layer_hashes_len, + uint64_t leaf_element_size, + uint64_t output_store_min_layer) +{ + try { + // Convert the array of Hash pointers to a vector of Hash objects + std::vector hash_vector; + hash_vector.reserve(layer_hashes_len); + + // Dereference each pointer and push the Hash object into the vector + for (size_t i = 0; i < layer_hashes_len; ++i) { + if (layer_hashes[i] == nullptr) { + // Handle the case where one of the pointers is null, if needed + throw std::invalid_argument("Null pointer found in layer_hashes."); + } + hash_vector.push_back(*layer_hashes[i]); + } + + // Create a MerkleTree instance using the static factory method + return new icicle::MerkleTree(icicle::MerkleTree::create(hash_vector, leaf_element_size, output_store_min_layer)); + } catch (...) { + return nullptr; + } +} + +// Delete the MerkleTree object and free its resources +void icicle_merkle_tree_delete(icicle::MerkleTree* tree) +{ + if (tree) { delete tree; } +} + +// Build the Merkle tree from the provided leaves +eIcicleError icicle_merkle_tree_build( + icicle::MerkleTree* tree, const std::byte* leaves, uint64_t size, const icicle::MerkleTreeConfig* config) +{ + if (!tree || !leaves || !config) { return eIcicleError::INVALID_POINTER; } + + try { + return tree->build(leaves, size, *config); + } catch (...) { + return eIcicleError::UNKNOWN_ERROR; + } +} + +// Get the Merkle root as a pointer to the root data and its size +const std::byte* icicle_merkle_tree_get_root(icicle::MerkleTree* tree, size_t* out_size) +{ + if (!tree || !out_size) { return nullptr; } + + try { + auto [root_ptr, root_size] = tree->get_merkle_root(); + *out_size = root_size; + return root_ptr; + } catch (...) { + return nullptr; + } +} + +// Retrieve the Merkle proof for a specific element +eIcicleError icicle_merkle_tree_get_proof( + icicle::MerkleTree* tree, + const std::byte* leaves, + uint64_t leaf_idx, + const icicle::MerkleTreeConfig* config, + icicle::MerkleProof* merkle_proof) +{ + if (!tree || !leaves || !config || !merkle_proof) { return eIcicleError::INVALID_POINTER; } + + try { + return tree->get_merkle_proof(leaves, leaf_idx, *config, *merkle_proof); + } catch (...) { + return eIcicleError::UNKNOWN_ERROR; + } +} + +// Verify a Merkle proof +eIcicleError icicle_merkle_tree_verify(icicle::MerkleTree* tree, const icicle::MerkleProof* merkle_proof, bool* valid) +{ + if (!tree || !merkle_proof || !valid) { return eIcicleError::INVALID_POINTER; } + + try { + return tree->verify(*merkle_proof, *valid); + } catch (...) { + return eIcicleError::UNKNOWN_ERROR; + } +} + +} // extern "C" \ No newline at end of file diff --git a/icicle/src/hash/merkle_tree.cpp b/icicle/src/hash/merkle_tree.cpp index b5695df3d..00ef57482 100644 --- a/icicle/src/hash/merkle_tree.cpp +++ b/icicle/src/hash/merkle_tree.cpp @@ -16,12 +16,4 @@ namespace icicle { return merkle_tree; } - /*************************** C API ***************************/ - - MerkleTree* create_merkle_tree_c_api( - const std::vector& layer_hashes, uint64_t leaf_element_size, uint64_t output_store_min_layer) - { - return new MerkleTree(create_merkle_tree(layer_hashes, leaf_element_size, output_store_min_layer)); - } - } // namespace icicle \ No newline at end of file diff --git a/icicle/src/hash/poseidon.cpp b/icicle/src/hash/poseidon.cpp index 4d5084163..b7e9de17f 100644 --- a/icicle/src/hash/poseidon.cpp +++ b/icicle/src/hash/poseidon.cpp @@ -7,40 +7,27 @@ namespace icicle { ICICLE_DISPATCHER_INST(InitPoseidonConstantsDispatcher, poseidon_init_constants, InitPoseidonConstantsImpl); template <> - eIcicleError poseidon_init_constants( - unsigned arity, - unsigned alpha, - unsigned nof_partial_rounds, - unsigned nof_upper_full_rounds, - unsigned nof_end_full_rounds, - const scalar_t* rounds_constants, - const scalar_t* mds_matrix, - const scalar_t* pre_matrix, - const scalar_t* sparse_matrix, - std::shared_ptr>& constants /*out*/) + eIcicleError poseidon_init_constants(const PoseidonConstantsInitOptions* options) { - return InitPoseidonConstantsDispatcher::execute( - arity, alpha, nof_partial_rounds, nof_upper_full_rounds, nof_end_full_rounds, rounds_constants, mds_matrix, - pre_matrix, sparse_matrix, constants); + return InitPoseidonConstantsDispatcher::execute(options); } ICICLE_DISPATCHER_INST( InitPoseidonDefaultConstantsDispatcher, poseidon_init_default_constants, InitPoseidonDefaultConstantsImpl); template <> - eIcicleError - poseidon_init_default_constants(unsigned arity, std::shared_ptr>& constants /*out*/) + eIcicleError poseidon_init_default_constants() { - return InitPoseidonDefaultConstantsDispatcher::execute(arity, constants); + return InitPoseidonDefaultConstantsDispatcher::execute(scalar_t::zero()); } ICICLE_DISPATCHER_INST(CreatePoseidonHasherDispatcher, create_poseidon, CreatePoseidonImpl); template <> - Hash create_poseidon_hash(std::shared_ptr> constants) + Hash create_poseidon_hash(unsigned arity) { std::shared_ptr backend; - ICICLE_CHECK(CreatePoseidonHasherDispatcher::execute(constants, backend)); + ICICLE_CHECK(CreatePoseidonHasherDispatcher::execute(arity, backend, scalar_t::zero())); Hash poseidon{backend}; return poseidon; } diff --git a/icicle/src/hash/poseidon_c_api.cpp b/icicle/src/hash/poseidon_c_api.cpp new file mode 100644 index 000000000..0042d3d42 --- /dev/null +++ b/icicle/src/hash/poseidon_c_api.cpp @@ -0,0 +1,27 @@ +#include +#include "icicle/hash/hash.h" +#include "icicle/hash/poseidon.h" +#include "icicle/fields/field_config.h" +#include "icicle/utils/utils.h" + +using namespace field_config; +using namespace icicle; + +extern "C" { +typedef icicle::Hash* HasherHandle; + +eIcicleError CONCAT_EXPAND(FIELD, poseidon_init_constants)(const PoseidonConstantsInitOptions* options) +{ + return Poseidon::init_constants(options); +} + +eIcicleError CONCAT_EXPAND(FIELD, poseidon_init_default_constants)() +{ + return Poseidon::init_default_constants(); +} + +HasherHandle CONCAT_EXPAND(FIELD, create_poseidon_hasher)(unsigned arity) +{ + return new icicle::Hash(icicle::create_poseidon_hash(arity)); +} +} \ No newline at end of file diff --git a/icicle/src/runtime.cpp b/icicle/src/runtime.cpp index 007d987e9..d442a8710 100644 --- a/icicle/src/runtime.cpp +++ b/icicle/src/runtime.cpp @@ -157,17 +157,15 @@ static eIcicleError _determine_copy_direction(void* dst, const void* src, eCopyD MemoryType dstType = _get_memory_type(dst); MemoryType srcType = _get_memory_type(src); - // Validate memory combinations if (dstType == MemoryType::Untracked && srcType == MemoryType::Untracked) { - ICICLE_LOG_ERROR << "Host to Host copy, not handled by DeviceAPI"; - return eIcicleError::INVALID_POINTER; + direction = HostToHost; + return eIcicleError::SUCCESS; } if (dstType == MemoryType::NonActiveDevice || srcType == MemoryType::NonActiveDevice) { ICICLE_LOG_ERROR << "Either dst or src is on a non-active device memory"; return eIcicleError::INVALID_POINTER; } - // Determine the copy direction direction = srcType == MemoryType::ActiveDevice && dstType == MemoryType::Untracked ? eCopyDirection::DeviceToHost : srcType == MemoryType::Untracked && dstType == MemoryType::ActiveDevice ? eCopyDirection::HostToDevice : srcType == MemoryType::ActiveDevice && dstType == MemoryType::ActiveDevice @@ -179,9 +177,20 @@ static eIcicleError _determine_copy_direction(void* dst, const void* src, eCopyD extern "C" eIcicleError icicle_copy(void* dst, const void* src, size_t size) { + // NOTE: memory allocated outside of icicle APIs is considered host memory. Do not use it with memory allocated by + // external libs (e.g. a cudaMalloc() call) + eCopyDirection direction; auto err = _determine_copy_direction(dst, src, direction); if (eIcicleError::SUCCESS != err) { return err; } + if (eCopyDirection::HostToHost == direction) { + ICICLE_LOG_DEBUG + << "Host to Host copy, falling back to std::memcpy(). NOTE: memory allocated outside of icicle APIs is " + "considered host memory. Do not use icicle_copy() with memory allocated by external libs (e.g. a cudaMalloc() " + "call)"; + std::memcpy(dst, src, size); + return eIcicleError::SUCCESS; + } // Call the appropriate copy method return DeviceAPI::get_thread_local_deviceAPI()->copy(dst, src, size, direction); } diff --git a/icicle/tests/test_hash_api.cpp b/icicle/tests/test_hash_api.cpp index 9823df9b1..731be009d 100644 --- a/icicle/tests/test_hash_api.cpp +++ b/icicle/tests/test_hash_api.cpp @@ -94,7 +94,10 @@ TEST_F(HashApiTest, Keccak256) class HashSumBackend : public HashBackend { public: - HashSumBackend(uint64_t input_chunk_size, uint64_t output_size) : HashBackend(output_size, input_chunk_size) {} + HashSumBackend(uint64_t input_chunk_size, uint64_t output_size) + : HashBackend("HashSumTest", output_size, input_chunk_size) + { + } eIcicleError hash(const std::byte* input, uint64_t size, const HashConfig& config, std::byte* output) const override { @@ -171,25 +174,23 @@ using namespace field_config; TEST_F(HashApiTest, poseidon12) { - const uint64_t input_size = 12; // Number of input elements - const uint64_t output_size = sizeof(scalar_t); + const uint64_t arity = 12; // Number of input elements // Create unique pointers for input and output arrays - auto input = std::make_unique(input_size); + auto input = std::make_unique(arity); scalar_t output = scalar_t::from(0); - // Randomize the input array - scalar_t::rand_host_many(input.get(), input_size); + scalar_t::rand_host_many(input.get(), arity); - // init poseidon scalars - std::shared_ptr> poseidon_constants; - ICICLE_CHECK(poseidon_init_default_constants(12, poseidon_constants)); + // init poseidon constants on current device + ICICLE_CHECK(Poseidon::init_default_constants()); - auto config = default_hash_config(); // Create Poseidon hash object - auto poseidon = Poseidon::create(poseidon_constants); + auto poseidon = Poseidon::create(arity); + // Run single hash operation - ICICLE_CHECK(poseidon.hash(input.get(), input_size, config, &output)); + auto config = default_hash_config(); + ICICLE_CHECK(poseidon.hash(input.get(), arity, config, &output)); // TODO: Verify output (e.g., check CPU against CUDA) } #endif // POSEIDON \ No newline at end of file diff --git a/wrappers/rust/Cargo.toml b/wrappers/rust/Cargo.toml index 14d21aaf8..efdc5b4aa 100644 --- a/wrappers/rust/Cargo.toml +++ b/wrappers/rust/Cargo.toml @@ -6,11 +6,12 @@ members = [ "icicle-curves/icicle-bw6-761", "icicle-curves/icicle-bls12-377", "icicle-curves/icicle-bls12-381", - "icicle-curves/icicle-bn254", + "icicle-curves/icicle-bn254", "icicle-curves/icicle-grumpkin", "icicle-fields/icicle-babybear", "icicle-fields/icicle-m31", "icicle-fields/icicle-stark252", + "icicle-hash", ] exclude = [] @@ -24,3 +25,4 @@ repository = "https://github.com/ingonyama-zk/icicle" [workspace.dependencies] icicle-core = { path = "icicle-core" } icicle-runtime = { path = "icicle-runtime" } +icicle-hash = { path = "icicle-hash" } diff --git a/wrappers/rust/icicle-core/src/hash/mod.rs b/wrappers/rust/icicle-core/src/hash/mod.rs new file mode 100644 index 000000000..60950b031 --- /dev/null +++ b/wrappers/rust/icicle-core/src/hash/mod.rs @@ -0,0 +1,127 @@ +use icicle_runtime::{ + config::ConfigExtension, errors::eIcicleError, memory::HostOrDeviceSlice, stream::IcicleStreamHandle, +}; +use std::ffi::c_void; + +/// Configuration structure for hash operations. +/// +/// The `HashConfig` structure holds various configuration options that control how hash operations +/// are executed. It supports features such as specifying the execution stream, input/output locations +/// (device or host), batch sizes, and backend-specific extensions. Additionally, it allows +/// synchronous and asynchronous execution modes. +#[repr(C)] +#[derive(Clone)] +pub struct HashConfig { + pub stream_handle: IcicleStreamHandle, + pub batch: u64, + pub are_inputs_on_device: bool, + pub are_outputs_on_device: bool, + pub is_async: bool, + pub ext: ConfigExtension, +} + +impl HashConfig { + /// Create a default configuration (same as the C++ struct's defaults) + pub fn default() -> Self { + Self { + stream_handle: std::ptr::null_mut(), + batch: 1, + are_inputs_on_device: false, + are_outputs_on_device: false, + is_async: false, + ext: ConfigExtension::new(), + } + } +} + +pub type HasherHandle = *const c_void; + +pub struct Hasher { + pub handle: HasherHandle, +} + +// External C functions for hashing and deleting hash objects +extern "C" { + fn icicle_hasher_hash( + hash_ptr: HasherHandle, + input_ptr: *const u8, + input_len: u64, + config: *const HashConfig, + output_ptr: *mut u8, + ) -> eIcicleError; + fn icicle_hasher_output_size(hash_ptr: HasherHandle) -> u64; + fn icicle_hasher_delete(hash_ptr: HasherHandle) -> eIcicleError; +} + +impl Hasher { + pub fn from_handle(_handle: HasherHandle) -> Self { + Hasher { handle: _handle } + } + + pub fn hash( + &self, + input: &(impl HostOrDeviceSlice + ?Sized), + cfg: &HashConfig, + output: &mut (impl HostOrDeviceSlice + ?Sized), + ) -> Result<(), eIcicleError> { + // check device slices are on active device + if input.is_on_device() && !input.is_on_active_device() { + eprintln!("input not allocated on the active device"); + return Err(eIcicleError::InvalidPointer); + } + if output.is_on_device() && !output.is_on_active_device() { + eprintln!("output not allocated on the active device"); + return Err(eIcicleError::InvalidPointer); + } + + let mut local_cfg = cfg.clone(); + local_cfg.are_inputs_on_device = input.is_on_device(); + local_cfg.are_outputs_on_device = output.is_on_device(); + let input_byte_len = (input.len() * std::mem::size_of::()) as u64; + let output_byte_len = (output.len() * std::mem::size_of::()) as u64; + + if output_byte_len as u64 % self.output_size() != 0 { + eprintln!( + "output size (={}Bytes) must divide single hash output size (={}Bytes)", + output_byte_len, + self.output_size() + ); + return Err(eIcicleError::InvalidArgument); + } + local_cfg.batch = output_byte_len / self.output_size(); + + if input_byte_len % local_cfg.batch != 0 { + eprintln!( + "input size (={}Bytes) must divide batch-size={} (inferred from output size)", + input_byte_len, local_cfg.batch, + ); + return Err(eIcicleError::InvalidArgument); + } + + unsafe { + let input_ptr = input.as_ptr() as *const u8; + let output_ptr = output.as_mut_ptr() as *mut u8; + + icicle_hasher_hash( + self.handle, + input_ptr, // Casted to *const u8 + input_byte_len, + &local_cfg, + output_ptr, // Casted to *mut u8 + ) + .wrap() + } + } + + pub fn output_size(&self) -> u64 { + unsafe { icicle_hasher_output_size(self.handle) } + } +} + +impl Drop for Hasher { + fn drop(&mut self) { + unsafe { + icicle_hasher_delete(self.handle); + } + } +} diff --git a/wrappers/rust/icicle-core/src/lib.rs b/wrappers/rust/icicle-core/src/lib.rs index dcb887bec..540c18cc4 100644 --- a/wrappers/rust/icicle-core/src/lib.rs +++ b/wrappers/rust/icicle-core/src/lib.rs @@ -1,9 +1,12 @@ pub mod curve; pub mod ecntt; pub mod field; +pub mod hash; +pub mod merkle; pub mod msm; pub mod ntt; pub mod polynomials; +pub mod poseidon; pub mod vec_ops; #[doc(hidden)] diff --git a/wrappers/rust/icicle-core/src/merkle/mod.rs b/wrappers/rust/icicle-core/src/merkle/mod.rs new file mode 100644 index 000000000..074294728 --- /dev/null +++ b/wrappers/rust/icicle-core/src/merkle/mod.rs @@ -0,0 +1,300 @@ +use crate::hash::{Hasher, HasherHandle}; +use icicle_runtime::{ + config::ConfigExtension, errors::eIcicleError, memory::HostOrDeviceSlice, stream::IcicleStreamHandle, +}; +use std::{ffi::c_void, mem, ptr, slice}; + +/// Enum representing the padding policy when the input is smaller than expected by the tree structure. +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PaddingPolicy { + None, // No padding, assume input is correctly sized. + ZeroPadding, // Pad the input with zeroes to fit the expected input size. + LastValue, // Pad the input by repeating the last value. +} + +/// Configuration structure for Merkle tree operations. +/// +/// This structure holds the configuration options for Merkle tree operations, including tree construction, +/// path computation, and verification. It allows specifying whether the data (leaves, tree, and paths) +/// reside on the device (e.g., GPU) or the host (e.g., CPU), and supports both synchronous and asynchronous +/// execution modes, as well as backend-specific extensions. It also provides a padding policy for handling +/// cases where the input size is smaller than expected by the tree structure. +#[repr(C)] +#[derive(Clone)] +pub struct MerkleTreeConfig { + pub stream_handle: IcicleStreamHandle, // Stream for asynchronous execution. Default is null for synchronous execution. + pub is_leaves_on_device: bool, // True if leaves are on the device (GPU), false if on the host (CPU). + pub is_tree_on_device: bool, // True if the tree results are allocated on the device (GPU), false if on the host (CPU). + pub is_async: bool, // True for asynchronous execution, false for synchronous. + pub padding_policy: PaddingPolicy, // Policy for handling cases where the input is smaller than expected. + pub ext: ConfigExtension, // Backend-specific extensions for advanced configurations. +} + +impl MerkleTreeConfig { + /// Generates a default configuration for Merkle tree operations. + /// + /// This function provides a default configuration for Merkle tree operations with synchronous execution + /// and all data (leaves, tree results, and paths) residing on the host (CPU). + pub fn default() -> Self { + Self { + stream_handle: ptr::null_mut(), // Default stream handle (synchronous). + is_leaves_on_device: false, // Default: leaves on host (CPU). + is_tree_on_device: false, // Default: tree results on host (CPU). + is_async: false, // Default: synchronous execution. + padding_policy: PaddingPolicy::None, // Default: no padding. + ext: ConfigExtension::new(), // Default: no backend-specific extensions. + } + } +} + +type MerkleProofHandle = *const c_void; + +pub struct MerkleProof { + handle: MerkleProofHandle, +} + +// External C functions for merkle proof +extern "C" { + fn icicle_merkle_proof_create() -> MerkleProofHandle; + fn icicle_merkle_proof_delete(proof: MerkleProofHandle) -> eIcicleError; + fn icicle_merkle_proof_is_pruned(proof: MerkleProofHandle) -> bool; + fn icicle_merkle_proof_get_path(proof: MerkleProofHandle, out_size: *mut usize) -> *const u8; + fn icicle_merkle_proof_get_leaf( + proof: MerkleProofHandle, + out_size: *mut usize, + out_leaf_idx: *mut u64, + ) -> *const u8; + fn icicle_merkle_proof_get_root(proof: MerkleProofHandle, out_size: *mut usize) -> *const u8; +} + +impl MerkleProof { + /// Create a new MerkleProof object. + pub fn new() -> Result { + unsafe { + let handle = icicle_merkle_proof_create(); + if handle.is_null() { + Err(eIcicleError::AllocationFailed) + } else { + Ok(MerkleProof { handle }) + } + } + } + + /// Check if the Merkle path is pruned. + pub fn is_pruned(&self) -> bool { + unsafe { icicle_merkle_proof_is_pruned(self.handle) } + } + + /// Get the path data as a slice of type `T`. + pub fn get_path(&self) -> &[T] { + let mut size = 0; + unsafe { + let ptr = icicle_merkle_proof_get_path(self.handle, &mut size); + if ptr.is_null() { + &[] + } else { + // Calculate how many `T` elements fit into the byte buffer + let element_count = size / mem::size_of::(); + slice::from_raw_parts(ptr as *const T, element_count) + } + } + } + + /// Get the leaf data as a slice of type `T` and its index. + pub fn get_leaf(&self) -> (&[T], u64) { + let mut size = 0; + let mut leaf_idx = 0; + unsafe { + let ptr = icicle_merkle_proof_get_leaf(self.handle, &mut size, &mut leaf_idx); + if ptr.is_null() { + (&[], 0) + } else { + // Calculate how many `T` elements fit into the byte buffer + let element_count = size / mem::size_of::(); + (slice::from_raw_parts(ptr as *const T, element_count), leaf_idx) + } + } + } + + /// Get the root data as a slice of type `T`. + pub fn get_root(&self) -> &[T] { + let mut size = 0; + unsafe { + let ptr = icicle_merkle_proof_get_root(self.handle, &mut size); + if ptr.is_null() { + &[] + } else { + // Calculate how many `T` elements fit into the byte buffer + let element_count = size / mem::size_of::(); + slice::from_raw_parts(ptr as *const T, element_count) + } + } + } +} + +impl Drop for MerkleProof { + fn drop(&mut self) { + unsafe { + if !self + .handle + .is_null() + { + let _ = icicle_merkle_proof_delete(self.handle); + } + } + } +} + +type MerkleTreeHandle = *const c_void; + +pub struct MerkleTree { + handle: MerkleTreeHandle, +} + +// External C functions +extern "C" { + fn icicle_merkle_tree_create( + layer_hashes: *const MerkleTreeHandle, // expecting c-style-array of those + layer_hashes_len: u64, + leaf_element_size: u64, + output_store_min_layer: u64, + ) -> MerkleTreeHandle; + + fn icicle_merkle_tree_delete(tree: MerkleTreeHandle); + + fn icicle_merkle_tree_build( + tree: MerkleTreeHandle, + leaves: *const u8, + size: u64, + config: *const MerkleTreeConfig, + ) -> eIcicleError; + + fn icicle_merkle_tree_get_root(tree: MerkleTreeHandle, out_size: *mut u64) -> *const u8; + + fn icicle_merkle_tree_get_proof( + tree: MerkleTreeHandle, + leaves: *const u8, + leaf_idx: u64, + config: *const MerkleTreeConfig, + merkle_proof: MerkleProofHandle, + ) -> eIcicleError; + + fn icicle_merkle_tree_verify( + tree: MerkleTreeHandle, + merkle_proof: MerkleProofHandle, + valid: *mut bool, + ) -> eIcicleError; +} + +impl MerkleTree { + // Create a new MerkleTree with an array/vector of Hasher structs for the layer hashes + pub fn new( + layer_hashes: &[&Hasher], + leaf_element_size: u64, + output_store_min_layer: u64, + ) -> Result { + unsafe { + // Collect the Hasher handles from the Hasher structs + let hash_handles: Vec = layer_hashes + .iter() + .map(|h| h.handle) + .collect(); + + let handle = icicle_merkle_tree_create( + hash_handles.as_ptr(), + hash_handles.len() as u64, + leaf_element_size as u64, + output_store_min_layer as u64, + ); + + if handle.is_null() { + Err(eIcicleError::UnknownError) + } else { + Ok(MerkleTree { handle }) + } + } + } + + // Templated function to build the Merkle tree using any type of leaves + pub fn build( + &self, + leaves: &(impl HostOrDeviceSlice + ?Sized), + cfg: &MerkleTreeConfig, + ) -> Result<(), eIcicleError> { + // check device slices are on active device + if leaves.is_on_device() && !leaves.is_on_active_device() { + eprintln!("leaves not allocated on the active device"); + return Err(eIcicleError::InvalidPointer); + } + + let mut local_cfg = cfg.clone(); + local_cfg.is_leaves_on_device = leaves.is_on_device(); + + let byte_size = (leaves.len() * std::mem::size_of::()) as u64; + unsafe { icicle_merkle_tree_build(self.handle, leaves.as_ptr() as *const u8, byte_size, &local_cfg).wrap() } + } + + // Templated function to get the Merkle root as a slice of type T + pub fn get_root(&self) -> Result<&[T], eIcicleError> { + let mut size: u64 = 0; + let root_ptr = unsafe { icicle_merkle_tree_get_root(self.handle, &mut size) }; + + if root_ptr.is_null() { + Err(eIcicleError::UnknownError) + } else { + let element_size = std::mem::size_of::() as usize; + let num_elements = size as usize / element_size; + unsafe { Ok(slice::from_raw_parts(root_ptr as *const T, num_elements)) } + } + } + + // Templated function to retrieve a Merkle proof for a specific element of type T + pub fn get_proof( + &self, + leaves: &(impl HostOrDeviceSlice + ?Sized), + leaf_idx: u64, + config: &MerkleTreeConfig, + ) -> Result { + // check device slices are on active device + if leaves.is_on_device() && !leaves.is_on_active_device() { + eprintln!("leaves not allocated on the active device"); + return Err(eIcicleError::InvalidPointer); + } + + let proof = MerkleProof::new().unwrap(); + let result = unsafe { + icicle_merkle_tree_get_proof( + self.handle, + leaves.as_ptr() as *const u8, + leaf_idx as u64, + config, + proof.handle, + ) + }; + + if result == eIcicleError::Success { + Ok(proof) + } else { + Err(result) + } + } + + pub fn verify(&self, proof: &MerkleProof) -> Result { + let mut verification_valid: bool = false; + let result = unsafe { icicle_merkle_tree_verify(self.handle, proof.handle, &mut verification_valid) }; + if result == eIcicleError::Success { + Ok(verification_valid) + } else { + Err(result) + } + } +} + +impl Drop for MerkleTree { + fn drop(&mut self) { + unsafe { + icicle_merkle_tree_delete(self.handle); + } + } +} diff --git a/wrappers/rust/icicle-core/src/poseidon/mod.rs b/wrappers/rust/icicle-core/src/poseidon/mod.rs new file mode 100644 index 000000000..20d425ea6 --- /dev/null +++ b/wrappers/rust/icicle-core/src/poseidon/mod.rs @@ -0,0 +1,138 @@ +#[doc(hidden)] +pub mod tests; + +use crate::{hash::Hasher, traits::FieldImpl}; +use icicle_runtime::errors::eIcicleError; +use std::marker::PhantomData; + +pub struct PoseidonConstantsInitOptions { + // TODO: Define the struct with fields such as arity, alpha, nof_rounds, mds_matrix, etc. + // It must be compatible with FFI, so make sure to use only types like integers, arrays, and pointers. + phantom: PhantomData, +} + +/// Trait to define the behavior of a Poseidon hasher for different field types. +/// This allows the implementation of Poseidon hashing for various field types that implement `FieldImpl`. +pub trait PoseidonHasher { + /// Method to initialize Poseidon constants with user-defined options. + fn initialize_constants(options: &PoseidonConstantsInitOptions) -> Result<(), eIcicleError>; + + /// Method to initialize Poseidon constants with default values. + fn initialize_default_constants() -> Result<(), eIcicleError>; + + /// Method to create a new Poseidon hasher for a given arity (branching factor). + fn new(arity: u32) -> Result; +} + +/// Function to initialize default Poseidon constants for a specific field type. +/// It delegates the call to the `initialize_default_constants()` function of the `PoseidonHasher` trait. +pub fn initialize_default_poseidon_constants() -> Result<(), eIcicleError> +where + F: FieldImpl, + ::Config: PoseidonHasher, // Requires that the `Config` associated with `F` implements `PoseidonHasher`. +{ + <::Config as PoseidonHasher>::initialize_default_constants() +} + +/// Function to initialize Poseidon constants based on user-defined options for a specific field type. +/// Currently, this function returns an error as the feature is not yet implemented. +/// TODO: Define PoseidonConstantsInitOptions and implement the function logic. +pub fn initialize_poseidon_constants(_options: &PoseidonConstantsInitOptions) -> Result<(), eIcicleError> +where + F: FieldImpl, + ::Config: PoseidonHasher, // Requires that the `Config` associated with `F` implements `PoseidonHasher`. +{ + return Err(eIcicleError::ApiNotImplemented); // Placeholder error until the function is implemented. +} + +/// Function to create a Poseidon hasher for a specific field type and arity (branching factor). +/// Delegates the creation to the `new` method of the `PoseidonHasher` trait. +pub fn create_poseidon_hasher(arity: u32) -> Result +where + F: FieldImpl, + ::Config: PoseidonHasher, // Requires that the `Config` associated with `F` implements `PoseidonHasher`. +{ + <::Config as PoseidonHasher>::new(arity) +} + +#[macro_export] +macro_rules! impl_poseidon { + ( + $field_prefix:literal, + $field_prefix_ident:ident, + $field:ident, + $field_cfg:ident + ) => { + mod $field_prefix_ident { + use crate::poseidon::{$field, $field_cfg}; + use icicle_core::{ + hash::{Hasher, HasherHandle}, + poseidon::{PoseidonConstantsInitOptions, PoseidonHasher}, + traits::FieldImpl, + }; + use icicle_runtime::errors::eIcicleError; + use std::marker::PhantomData; + + extern "C" { + #[link_name = concat!($field_prefix, "_poseidon_init_default_constants")] + fn poseidon_init_default_constants() -> eIcicleError; + + #[link_name = concat!($field_prefix, "_poseidon_init_constants")] + fn poseidon_init_constants(options: *const PoseidonConstantsInitOptions<$field>) -> eIcicleError; + + #[link_name = concat!($field_prefix, "_create_poseidon_hasher")] + fn create_poseidon_hasher(arity: u32) -> HasherHandle; + } + + // Implement the `PoseidonHasher` trait for the given field configuration. + impl PoseidonHasher<$field> for $field_cfg { + fn initialize_default_constants() -> Result<(), eIcicleError> { + unsafe { poseidon_init_default_constants().wrap() } // Calls the external FFI function and wraps the result. + } + + fn initialize_constants(options: &PoseidonConstantsInitOptions<$field>) -> Result<(), eIcicleError> { + unsafe { poseidon_init_constants(options as *const PoseidonConstantsInitOptions<$field>).wrap() } + // Calls the external FFI function with user-defined options. + } + + fn new(arity: u32) -> Result { + let handle: HasherHandle = unsafe { create_poseidon_hasher(arity) }; // Calls the external FFI function to create the hasher. + if handle.is_null() { + return Err(eIcicleError::UnknownError); // Checks if the handle is null and returns an error if so. + } + Ok(Hasher::from_handle(handle)) // Wraps the handle in a `Hasher` object and returns it. + } + } + } + }; +} + +#[macro_export] +macro_rules! impl_poseidon_tests { + ( + $field:ident + ) => { + use super::*; + + use std::sync::Once; + + static INIT: Once = Once::new(); + + pub fn initialize() { + INIT.call_once(move || { + // TODO load CUDA backend + // test_utilities::test_load_and_init_devices(); + }); + } + + #[test] + fn test_poseidon_hash() { + check_poseidon_hash::<$field>(); + } + + #[test] + fn test_poseidon_tree() { + check_poseidon_tree::<$field>(); + } + }; +} diff --git a/wrappers/rust/icicle-core/src/poseidon/tests.rs b/wrappers/rust/icicle-core/src/poseidon/tests.rs new file mode 100644 index 000000000..808e78621 --- /dev/null +++ b/wrappers/rust/icicle-core/src/poseidon/tests.rs @@ -0,0 +1,74 @@ +use crate::{ + hash::{HashConfig, Hasher}, + merkle::{MerkleProof, MerkleTree, MerkleTreeConfig}, + poseidon::{create_poseidon_hasher, initialize_default_poseidon_constants, PoseidonHasher}, + traits::FieldImpl, +}; +use icicle_runtime::memory::HostSlice; +use std::mem; + +pub fn check_poseidon_hash() +where + ::Config: PoseidonHasher, +{ + let batch = 1 << 10; + let arity = 3; + let mut inputs = vec![F::one(); batch * arity]; + let mut outputs = vec![F::zero(); arity]; + + initialize_default_poseidon_constants::().unwrap(); + let poseidon_hasher = create_poseidon_hasher::(arity as u32).unwrap(); + + poseidon_hasher + .hash( + HostSlice::from_slice(&mut inputs), + &HashConfig::default(), + HostSlice::from_mut_slice(&mut outputs), + ) + .unwrap(); + + // TODO real test for both CPU and CUDA +} + +pub fn check_poseidon_tree() +where + ::Config: PoseidonHasher, +{ + let arity = 9; + let nof_layers = 4; + let num_elements = (1 << nof_layers) * arity; + let mut leaves: Vec = (0..num_elements) + .map(|i| F::from_u32(i)) + .collect(); + + let hasher = create_poseidon_hasher::(arity as u32).unwrap(); + let layer_hashes: Vec<&Hasher> = (0..nof_layers) + .map(|_| &hasher) + .collect(); + let merkle_tree = MerkleTree::new(&layer_hashes[..], mem::size_of::() as u64, 0).unwrap(); + merkle_tree + .build(HostSlice::from_slice(&mut leaves), &MerkleTreeConfig::default()) + .unwrap(); + + let leaf_idx_to_open = num_elements >> 1; + let merkle_proof: MerkleProof = merkle_tree + .get_proof( + HostSlice::from_slice(&leaves), + leaf_idx_to_open as u64, + &MerkleTreeConfig::default(), + ) + .unwrap(); + let root = merkle_proof.get_root::(); + let path = merkle_proof.get_path::(); + let (leaf, leaf_idx) = merkle_proof.get_leaf::(); + println!("root = {:?}", root); + println!("path = {:?}", path); + println!("leaf = {:?}, leaf_idx = {}", leaf, leaf_idx); + + let verification_valid = merkle_tree + .verify(&merkle_proof) + .unwrap(); + assert_eq!(verification_valid, true); + + // TODO real test for both CPU and CUDA +} diff --git a/wrappers/rust/icicle-curves/icicle-bls12-377/Cargo.toml b/wrappers/rust/icicle-curves/icicle-bls12-377/Cargo.toml index ab21c6e42..dbfebff2a 100644 --- a/wrappers/rust/icicle-curves/icicle-bls12-377/Cargo.toml +++ b/wrappers/rust/icicle-curves/icicle-bls12-377/Cargo.toml @@ -10,7 +10,7 @@ repository.workspace = true [dependencies] icicle-core = { workspace = true } icicle-runtime = { workspace = true } -# criterion = "0.3" +icicle-hash = { workspace = true } [dev-dependencies] criterion = "0.3" diff --git a/wrappers/rust/icicle-curves/icicle-bls12-377/build.rs b/wrappers/rust/icicle-curves/icicle-bls12-377/build.rs index 5f21a32a9..783d562d4 100644 --- a/wrappers/rust/icicle-curves/icicle-bls12-377/build.rs +++ b/wrappers/rust/icicle-curves/icicle-bls12-377/build.rs @@ -26,6 +26,7 @@ fn main() { config .define("CURVE", "bls12_377") .define("FIELD", "bls12_377") + .define("HASH", "OFF") .define("CMAKE_INSTALL_PREFIX", &icicle_install_dir); // build (or pull and build) cuda backend if feature enabled. @@ -51,6 +52,7 @@ fn main() { println!("cargo:rustc-link-search={}/lib", icicle_install_dir.display()); println!("cargo:rustc-link-lib=icicle_field_bls12_377"); println!("cargo:rustc-link-lib=icicle_curve_bls12_377"); + println!("cargo:rustc-link-lib=icicle_hash"); if cfg!(feature = "bw6-761") { // Base config @@ -58,6 +60,7 @@ fn main() { config_bw .define("CURVE", "bw6_761") .define("FIELD", "bw6_761") + .define("HASH", "OFF") .define("CMAKE_INSTALL_PREFIX", &icicle_install_dir); // build (or pull and build) cuda backend if feature enabled. @@ -84,6 +87,7 @@ fn main() { println!("cargo:rustc-link-search={}/lib", icicle_install_dir.display()); println!("cargo:rustc-link-lib=icicle_field_bw6_761"); println!("cargo:rustc-link-lib=icicle_curve_bw6_761"); + println!("cargo:rustc-link-lib=icicle_hash"); } println!("cargo:rustc-link-arg=-Wl,-rpath,{}/lib", icicle_install_dir.display()); // Add RPATH linker arguments diff --git a/wrappers/rust/icicle-curves/icicle-bls12-377/src/lib.rs b/wrappers/rust/icicle-curves/icicle-bls12-377/src/lib.rs index 346974b8e..8af19ab97 100644 --- a/wrappers/rust/icicle-curves/icicle-bls12-377/src/lib.rs +++ b/wrappers/rust/icicle-curves/icicle-bls12-377/src/lib.rs @@ -2,6 +2,7 @@ pub mod curve; pub mod msm; pub mod ntt; pub mod polynomials; +pub mod poseidon; pub mod vec_ops; #[cfg(not(feature = "no_ecntt"))] diff --git a/wrappers/rust/icicle-curves/icicle-bls12-377/src/poseidon/mod.rs b/wrappers/rust/icicle-curves/icicle-bls12-377/src/poseidon/mod.rs new file mode 100644 index 000000000..f95e7616d --- /dev/null +++ b/wrappers/rust/icicle-curves/icicle-bls12-377/src/poseidon/mod.rs @@ -0,0 +1,17 @@ +use crate::curve::{BaseCfg, BaseField}; +use crate::curve::{ScalarCfg, ScalarField}; +use icicle_core::impl_poseidon; + +impl_poseidon!("bls12_377", bls12_377, ScalarField, ScalarCfg); + +#[cfg(feature = "bw6-761")] +impl_poseidon!("bw6_761", bw6_761, BaseField, BaseCfg); + +#[cfg(test)] +pub(crate) mod tests { + use crate::curve::ScalarField; + use icicle_core::impl_poseidon_tests; + use icicle_core::poseidon::tests::*; + + impl_poseidon_tests!(ScalarField); +} diff --git a/wrappers/rust/icicle-curves/icicle-bls12-381/Cargo.toml b/wrappers/rust/icicle-curves/icicle-bls12-381/Cargo.toml index 91c4fc353..109026eeb 100644 --- a/wrappers/rust/icicle-curves/icicle-bls12-381/Cargo.toml +++ b/wrappers/rust/icicle-curves/icicle-bls12-381/Cargo.toml @@ -10,7 +10,7 @@ repository.workspace = true [dependencies] icicle-core = { workspace = true } icicle-runtime = { workspace = true } -# criterion = "0.3" +icicle-hash = { workspace = true } [dev-dependencies] criterion = "0.3" diff --git a/wrappers/rust/icicle-curves/icicle-bls12-381/build.rs b/wrappers/rust/icicle-curves/icicle-bls12-381/build.rs index 657e5881b..2be86ce50 100644 --- a/wrappers/rust/icicle-curves/icicle-bls12-381/build.rs +++ b/wrappers/rust/icicle-curves/icicle-bls12-381/build.rs @@ -26,6 +26,7 @@ fn main() { config .define("CURVE", "bls12_381") .define("FIELD", "bls12_381") + .define("HASH", "OFF") .define("CMAKE_INSTALL_PREFIX", &icicle_install_dir); // build (or pull and build) cuda backend if feature enabled. @@ -51,6 +52,7 @@ fn main() { println!("cargo:rustc-link-search={}/lib", icicle_install_dir.display()); println!("cargo:rustc-link-lib=icicle_field_bls12_381"); println!("cargo:rustc-link-lib=icicle_curve_bls12_381"); + println!("cargo:rustc-link-lib=icicle_hash"); println!("cargo:rustc-link-arg=-Wl,-rpath,{}/lib", icicle_install_dir.display()); // Add RPATH linker arguments // default backends dir diff --git a/wrappers/rust/icicle-curves/icicle-bls12-381/src/lib.rs b/wrappers/rust/icicle-curves/icicle-bls12-381/src/lib.rs index 346974b8e..8af19ab97 100644 --- a/wrappers/rust/icicle-curves/icicle-bls12-381/src/lib.rs +++ b/wrappers/rust/icicle-curves/icicle-bls12-381/src/lib.rs @@ -2,6 +2,7 @@ pub mod curve; pub mod msm; pub mod ntt; pub mod polynomials; +pub mod poseidon; pub mod vec_ops; #[cfg(not(feature = "no_ecntt"))] diff --git a/wrappers/rust/icicle-curves/icicle-bls12-381/src/poseidon/mod.rs b/wrappers/rust/icicle-curves/icicle-bls12-381/src/poseidon/mod.rs new file mode 100644 index 000000000..ed8da42f8 --- /dev/null +++ b/wrappers/rust/icicle-curves/icicle-bls12-381/src/poseidon/mod.rs @@ -0,0 +1,13 @@ +use crate::curve::{ScalarCfg, ScalarField}; +use icicle_core::impl_poseidon; + +impl_poseidon!("bls12_381", bls12_381, ScalarField, ScalarCfg); + +#[cfg(test)] +pub(crate) mod tests { + use crate::curve::ScalarField; + use icicle_core::impl_poseidon_tests; + use icicle_core::poseidon::tests::*; + + impl_poseidon_tests!(ScalarField); +} diff --git a/wrappers/rust/icicle-curves/icicle-bn254/Cargo.toml b/wrappers/rust/icicle-curves/icicle-bn254/Cargo.toml index 56ef95bb5..a10b33312 100644 --- a/wrappers/rust/icicle-curves/icicle-bn254/Cargo.toml +++ b/wrappers/rust/icicle-curves/icicle-bn254/Cargo.toml @@ -10,7 +10,7 @@ repository.workspace = true [dependencies] icicle-core = { workspace = true } icicle-runtime = { workspace = true } -# criterion = "0.3" +icicle-hash = { workspace = true } [dev-dependencies] criterion = "0.3" diff --git a/wrappers/rust/icicle-curves/icicle-bn254/build.rs b/wrappers/rust/icicle-curves/icicle-bn254/build.rs index 566f471fd..52a31aa24 100644 --- a/wrappers/rust/icicle-curves/icicle-bn254/build.rs +++ b/wrappers/rust/icicle-curves/icicle-bn254/build.rs @@ -25,6 +25,7 @@ fn main() { }; config .define("CURVE", "bn254") + .define("HASH", "OFF") .define("CMAKE_INSTALL_PREFIX", &icicle_install_dir); // build (or pull and build) cuda backend if feature enabled. @@ -50,6 +51,7 @@ fn main() { println!("cargo:rustc-link-search={}/lib", icicle_install_dir.display()); println!("cargo:rustc-link-lib=icicle_field_bn254"); println!("cargo:rustc-link-lib=icicle_curve_bn254"); + println!("cargo:rustc-link-lib=icicle_hash"); println!("cargo:rustc-link-arg=-Wl,-rpath,{}/lib", icicle_install_dir.display()); // Add RPATH linker arguments // default backends dir diff --git a/wrappers/rust/icicle-curves/icicle-bn254/src/lib.rs b/wrappers/rust/icicle-curves/icicle-bn254/src/lib.rs index 346974b8e..8af19ab97 100644 --- a/wrappers/rust/icicle-curves/icicle-bn254/src/lib.rs +++ b/wrappers/rust/icicle-curves/icicle-bn254/src/lib.rs @@ -2,6 +2,7 @@ pub mod curve; pub mod msm; pub mod ntt; pub mod polynomials; +pub mod poseidon; pub mod vec_ops; #[cfg(not(feature = "no_ecntt"))] diff --git a/wrappers/rust/icicle-curves/icicle-bn254/src/poseidon/mod.rs b/wrappers/rust/icicle-curves/icicle-bn254/src/poseidon/mod.rs new file mode 100644 index 000000000..422896c74 --- /dev/null +++ b/wrappers/rust/icicle-curves/icicle-bn254/src/poseidon/mod.rs @@ -0,0 +1,13 @@ +use crate::curve::{ScalarCfg, ScalarField}; +use icicle_core::impl_poseidon; + +impl_poseidon!("bn254", bn254, ScalarField, ScalarCfg); + +#[cfg(test)] +pub(crate) mod tests { + use crate::curve::ScalarField; + use icicle_core::impl_poseidon_tests; + use icicle_core::poseidon::tests::*; + + impl_poseidon_tests!(ScalarField); +} diff --git a/wrappers/rust/icicle-curves/icicle-bw6-761/Cargo.toml b/wrappers/rust/icicle-curves/icicle-bw6-761/Cargo.toml index 93b98299b..86413b76e 100644 --- a/wrappers/rust/icicle-curves/icicle-bw6-761/Cargo.toml +++ b/wrappers/rust/icicle-curves/icicle-bw6-761/Cargo.toml @@ -10,6 +10,7 @@ repository.workspace = true [dependencies] icicle-core = { workspace = true } icicle-runtime = { workspace = true } +icicle-hash = { workspace = true } icicle-bls12-377 = { path = "../../icicle-curves/icicle-bls12-377", features = ["bw6-761"] } [dev-dependencies] diff --git a/wrappers/rust/icicle-curves/icicle-bw6-761/src/lib.rs b/wrappers/rust/icicle-curves/icicle-bw6-761/src/lib.rs index 15deafbdb..a3e581d5b 100644 --- a/wrappers/rust/icicle-curves/icicle-bw6-761/src/lib.rs +++ b/wrappers/rust/icicle-curves/icicle-bw6-761/src/lib.rs @@ -1,4 +1,5 @@ pub mod curve; pub mod msm; pub mod ntt; +pub mod poseidon; pub mod vec_ops; diff --git a/wrappers/rust/icicle-curves/icicle-bw6-761/src/poseidon/mod.rs b/wrappers/rust/icicle-curves/icicle-bw6-761/src/poseidon/mod.rs new file mode 100644 index 000000000..527c67aeb --- /dev/null +++ b/wrappers/rust/icicle-curves/icicle-bw6-761/src/poseidon/mod.rs @@ -0,0 +1,8 @@ +#[cfg(test)] +pub(crate) mod tests { + use crate::curve::ScalarField; + use icicle_core::impl_poseidon_tests; + use icicle_core::poseidon::tests::*; + + impl_poseidon_tests!(ScalarField); +} diff --git a/wrappers/rust/icicle-curves/icicle-grumpkin/Cargo.toml b/wrappers/rust/icicle-curves/icicle-grumpkin/Cargo.toml index 41b2b0bbe..127cec13e 100644 --- a/wrappers/rust/icicle-curves/icicle-grumpkin/Cargo.toml +++ b/wrappers/rust/icicle-curves/icicle-grumpkin/Cargo.toml @@ -10,7 +10,7 @@ repository.workspace = true [dependencies] icicle-core = { workspace = true } icicle-runtime = { workspace = true } -# criterion = "0.3" +icicle-hash = { workspace = true } [dev-dependencies] criterion = "0.3" diff --git a/wrappers/rust/icicle-curves/icicle-grumpkin/build.rs b/wrappers/rust/icicle-curves/icicle-grumpkin/build.rs index d77e6cf78..4fde4c19a 100644 --- a/wrappers/rust/icicle-curves/icicle-grumpkin/build.rs +++ b/wrappers/rust/icicle-curves/icicle-grumpkin/build.rs @@ -25,6 +25,7 @@ fn main() { }; config .define("CURVE", "grumpkin") + .define("HASH", "OFF") .define("CMAKE_INSTALL_PREFIX", &icicle_install_dir); // build (or pull and build) cuda backend if feature enabled. @@ -43,6 +44,7 @@ fn main() { println!("cargo:rustc-link-search={}/lib", icicle_install_dir.display()); println!("cargo:rustc-link-lib=icicle_field_grumpkin"); println!("cargo:rustc-link-lib=icicle_curve_grumpkin"); + println!("cargo:rustc-link-lib=icicle_hash"); println!("cargo:rustc-link-arg=-Wl,-rpath,{}/lib", icicle_install_dir.display()); // Add RPATH linker arguments // default backends dir diff --git a/wrappers/rust/icicle-curves/icicle-grumpkin/src/lib.rs b/wrappers/rust/icicle-curves/icicle-grumpkin/src/lib.rs index fdd55ba53..a46ceca19 100644 --- a/wrappers/rust/icicle-curves/icicle-grumpkin/src/lib.rs +++ b/wrappers/rust/icicle-curves/icicle-grumpkin/src/lib.rs @@ -1,3 +1,4 @@ pub mod curve; pub mod msm; +pub mod poseidon; pub mod vec_ops; diff --git a/wrappers/rust/icicle-curves/icicle-grumpkin/src/poseidon/mod.rs b/wrappers/rust/icicle-curves/icicle-grumpkin/src/poseidon/mod.rs new file mode 100644 index 000000000..85958dfdd --- /dev/null +++ b/wrappers/rust/icicle-curves/icicle-grumpkin/src/poseidon/mod.rs @@ -0,0 +1,13 @@ +use crate::curve::{ScalarCfg, ScalarField}; +use icicle_core::impl_poseidon; + +impl_poseidon!("grumpkin", grumpkin, ScalarField, ScalarCfg); + +#[cfg(test)] +pub(crate) mod tests { + use crate::curve::ScalarField; + use icicle_core::impl_poseidon_tests; + use icicle_core::poseidon::tests::*; + + impl_poseidon_tests!(ScalarField); +} diff --git a/wrappers/rust/icicle-fields/icicle-babybear/Cargo.toml b/wrappers/rust/icicle-fields/icicle-babybear/Cargo.toml index 289c719b4..220fd84f3 100644 --- a/wrappers/rust/icicle-fields/icicle-babybear/Cargo.toml +++ b/wrappers/rust/icicle-fields/icicle-babybear/Cargo.toml @@ -11,6 +11,7 @@ repository.workspace = true [dependencies] icicle-core = {workspace = true } icicle-runtime = { workspace = true } +icicle-hash = { workspace = true } [dev-dependencies] criterion = "0.3" diff --git a/wrappers/rust/icicle-fields/icicle-babybear/build.rs b/wrappers/rust/icicle-fields/icicle-babybear/build.rs index a7953c348..b86f35124 100644 --- a/wrappers/rust/icicle-fields/icicle-babybear/build.rs +++ b/wrappers/rust/icicle-fields/icicle-babybear/build.rs @@ -25,6 +25,7 @@ fn main() { }; config .define("FIELD", "babybear") + .define("HASH", "OFF") .define("CMAKE_INSTALL_PREFIX", &icicle_install_dir); // build (or pull and build) cuda backend if feature enabled. @@ -42,6 +43,7 @@ fn main() { println!("cargo:rustc-link-search={}/lib", icicle_install_dir.display()); println!("cargo:rustc-link-lib=icicle_field_babybear"); + println!("cargo:rustc-link-lib=icicle_hash"); // not ideal to have this dependency here but need it for poseidon general Hasher APIs println!("cargo:rustc-link-arg=-Wl,-rpath,{}/lib", icicle_install_dir.display()); // Add RPATH linker arguments // default backends dir diff --git a/wrappers/rust/icicle-fields/icicle-babybear/src/lib.rs b/wrappers/rust/icicle-fields/icicle-babybear/src/lib.rs index a95ac089f..607bef230 100644 --- a/wrappers/rust/icicle-fields/icicle-babybear/src/lib.rs +++ b/wrappers/rust/icicle-fields/icicle-babybear/src/lib.rs @@ -1,4 +1,5 @@ pub mod field; pub mod ntt; pub mod polynomials; +pub mod poseidon; pub mod vec_ops; diff --git a/wrappers/rust/icicle-fields/icicle-babybear/src/poseidon/mod.rs b/wrappers/rust/icicle-fields/icicle-babybear/src/poseidon/mod.rs new file mode 100644 index 000000000..90ddf6b32 --- /dev/null +++ b/wrappers/rust/icicle-fields/icicle-babybear/src/poseidon/mod.rs @@ -0,0 +1,13 @@ +use crate::field::{ScalarCfg, ScalarField}; +use icicle_core::impl_poseidon; + +impl_poseidon!("babybear", babybear, ScalarField, ScalarCfg); + +#[cfg(test)] +pub(crate) mod tests { + use crate::field::ScalarField; + use icicle_core::impl_poseidon_tests; + use icicle_core::poseidon::tests::*; + + impl_poseidon_tests!(ScalarField); +} diff --git a/wrappers/rust/icicle-fields/icicle-m31/Cargo.toml b/wrappers/rust/icicle-fields/icicle-m31/Cargo.toml index 540604e7a..dd8c6cdfd 100644 --- a/wrappers/rust/icicle-fields/icicle-m31/Cargo.toml +++ b/wrappers/rust/icicle-fields/icicle-m31/Cargo.toml @@ -10,6 +10,7 @@ repository.workspace = true [dependencies] icicle-core = { workspace = true } icicle-runtime = { workspace = true } +icicle-hash = { workspace = true } [build-dependencies] cmake = "0.1.50" diff --git a/wrappers/rust/icicle-fields/icicle-m31/build.rs b/wrappers/rust/icicle-fields/icicle-m31/build.rs index 746e2fbc2..870fd5662 100644 --- a/wrappers/rust/icicle-fields/icicle-m31/build.rs +++ b/wrappers/rust/icicle-fields/icicle-m31/build.rs @@ -26,7 +26,7 @@ fn main() { config .define("FIELD", "m31") .define("EXT_FIELD", "ON") - .define("CMAKE_BUILD_TYPE", "Release") + .define("HASH", "OFF") .define("CMAKE_INSTALL_PREFIX", &icicle_install_dir); #[cfg(feature = "cuda_backend")] @@ -42,6 +42,7 @@ fn main() { println!("cargo:rustc-link-search={}/lib", icicle_install_dir.display()); println!("cargo:rustc-link-lib=icicle_field_m31"); + println!("cargo:rustc-link-lib=icicle_hash"); println!("cargo:rustc-link-arg=-Wl,-rpath,{}/lib", icicle_install_dir.display()); // Add RPATH linker arguments // default backends dir diff --git a/wrappers/rust/icicle-fields/icicle-m31/src/lib.rs b/wrappers/rust/icicle-fields/icicle-m31/src/lib.rs index 001f51ba9..082dcb2cb 100644 --- a/wrappers/rust/icicle-fields/icicle-m31/src/lib.rs +++ b/wrappers/rust/icicle-fields/icicle-m31/src/lib.rs @@ -1,2 +1,3 @@ pub mod field; +pub mod poseidon; pub mod vec_ops; diff --git a/wrappers/rust/icicle-fields/icicle-m31/src/poseidon/mod.rs b/wrappers/rust/icicle-fields/icicle-m31/src/poseidon/mod.rs new file mode 100644 index 000000000..2ea14e89d --- /dev/null +++ b/wrappers/rust/icicle-fields/icicle-m31/src/poseidon/mod.rs @@ -0,0 +1,13 @@ +use crate::field::{ScalarCfg, ScalarField}; +use icicle_core::impl_poseidon; + +impl_poseidon!("m31", m31, ScalarField, ScalarCfg); + +#[cfg(test)] +pub(crate) mod tests { + use crate::field::ScalarField; + use icicle_core::impl_poseidon_tests; + use icicle_core::poseidon::tests::*; + + impl_poseidon_tests!(ScalarField); +} diff --git a/wrappers/rust/icicle-fields/icicle-stark252/Cargo.toml b/wrappers/rust/icicle-fields/icicle-stark252/Cargo.toml index d6ae5bfcf..df9a902d7 100644 --- a/wrappers/rust/icicle-fields/icicle-stark252/Cargo.toml +++ b/wrappers/rust/icicle-fields/icicle-stark252/Cargo.toml @@ -11,6 +11,7 @@ repository.workspace = true [dependencies] icicle-core = {workspace = true } icicle-runtime = { workspace = true } +icicle-hash = { workspace = true } [dev-dependencies] criterion = "0.3" diff --git a/wrappers/rust/icicle-fields/icicle-stark252/build.rs b/wrappers/rust/icicle-fields/icicle-stark252/build.rs index 5ba83b9b4..e753c77f9 100644 --- a/wrappers/rust/icicle-fields/icicle-stark252/build.rs +++ b/wrappers/rust/icicle-fields/icicle-stark252/build.rs @@ -25,6 +25,7 @@ fn main() { }; config .define("FIELD", "stark252") + .define("HASH", "OFF") .define("CMAKE_INSTALL_PREFIX", &icicle_install_dir); #[cfg(feature = "cuda_backend")] @@ -40,6 +41,7 @@ fn main() { println!("cargo:rustc-link-search={}/lib", icicle_install_dir.display()); println!("cargo:rustc-link-lib=icicle_field_stark252"); + println!("cargo:rustc-link-lib=icicle_hash"); println!("cargo:rustc-link-arg=-Wl,-rpath,{}/lib", icicle_install_dir.display()); // Add RPATH linker arguments // default backends dir diff --git a/wrappers/rust/icicle-fields/icicle-stark252/src/lib.rs b/wrappers/rust/icicle-fields/icicle-stark252/src/lib.rs index a95ac089f..607bef230 100644 --- a/wrappers/rust/icicle-fields/icicle-stark252/src/lib.rs +++ b/wrappers/rust/icicle-fields/icicle-stark252/src/lib.rs @@ -1,4 +1,5 @@ pub mod field; pub mod ntt; pub mod polynomials; +pub mod poseidon; pub mod vec_ops; diff --git a/wrappers/rust/icicle-fields/icicle-stark252/src/poseidon/mod.rs b/wrappers/rust/icicle-fields/icicle-stark252/src/poseidon/mod.rs new file mode 100644 index 000000000..a58015f87 --- /dev/null +++ b/wrappers/rust/icicle-fields/icicle-stark252/src/poseidon/mod.rs @@ -0,0 +1,13 @@ +use crate::field::{ScalarCfg, ScalarField}; +use icicle_core::impl_poseidon; + +impl_poseidon!("stark252", stark252, ScalarField, ScalarCfg); + +#[cfg(test)] +pub(crate) mod tests { + use crate::field::ScalarField; + use icicle_core::impl_poseidon_tests; + use icicle_core::poseidon::tests::*; + + impl_poseidon_tests!(ScalarField); +} diff --git a/wrappers/rust/icicle-hash/Cargo.toml b/wrappers/rust/icicle-hash/Cargo.toml new file mode 100644 index 000000000..61ebb1393 --- /dev/null +++ b/wrappers/rust/icicle-hash/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "icicle-hash" +version.workspace = true +edition.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +icicle-core = {workspace = true } +icicle-runtime = { workspace = true } +rand = "0.8" + +[build-dependencies] +cmake = "0.1.50" + +[features] +cuda_backend = ["icicle-runtime/cuda_backend"] +pull_cuda_backend = ["icicle-runtime/pull_cuda_backend"] diff --git a/wrappers/rust/icicle-hash/build.rs b/wrappers/rust/icicle-hash/build.rs new file mode 100644 index 000000000..46ff31e0a --- /dev/null +++ b/wrappers/rust/icicle-hash/build.rs @@ -0,0 +1,53 @@ +use cmake::Config; +use std::{env, path::PathBuf}; + +fn main() { + // Construct the path to the deps directory + let out_dir = env::var("OUT_DIR").expect("OUT_DIR is not set"); + let build_dir = PathBuf::from(format!("{}/../../../", &out_dir)); + let deps_dir = build_dir.join("deps"); + + let main_dir = env::current_dir().expect("Failed to get current directory"); + let icicle_src_dir = PathBuf::from(format!("{}/../../../icicle", main_dir.display())); + + println!("cargo:rerun-if-env-changed=CXXFLAGS"); + println!("cargo:rerun-if-changed={}", icicle_src_dir.display()); + + // Base config + let mut config = Config::new(format!("{}", icicle_src_dir.display())); + // Check if ICICLE_INSTALL_DIR is defined + let icicle_install_dir = if let Ok(dir) = env::var("ICICLE_INSTALL_DIR") { + PathBuf::from(dir) + } else { + // Define the default install directory to be under the build directory + PathBuf::from(format!("{}/icicle/", deps_dir.display())) + }; + config + .define("HASH", "ON") + .define("CMAKE_INSTALL_PREFIX", &icicle_install_dir); + + // build (or pull and build) cuda backend if feature enabled. + // Note: this requires access to the repo + if cfg!(feature = "cuda_backend") { + config.define("CUDA_BACKEND", "local"); + } else if cfg!(feature = "pull_cuda_backend") { + config.define("CUDA_BACKEND", "main"); + } + + // Build + let _ = config + .build_target("install") + .build(); + + println!("cargo:rustc-link-search={}/lib", icicle_install_dir.display()); + println!("cargo:rustc-link-lib=icicle_hash"); + println!("cargo:rustc-link-arg=-Wl,-rpath,{}/lib", icicle_install_dir.display()); // Add RPATH linker arguments + + // default backends dir + if cfg!(feature = "cuda_backend") || cfg!(feature = "pull_cuda_backend") { + println!( + "cargo:rustc-env=ICICLE_BACKEND_INSTALL_DIR={}/lib/backend", + icicle_install_dir.display() + ); + } +} diff --git a/wrappers/rust/icicle-hash/src/keccak.rs b/wrappers/rust/icicle-hash/src/keccak.rs new file mode 100644 index 000000000..ea66d92e1 --- /dev/null +++ b/wrappers/rust/icicle-hash/src/keccak.rs @@ -0,0 +1,30 @@ +use icicle_core::hash::{Hasher, HasherHandle}; +use icicle_runtime::errors::eIcicleError; + +extern "C" { + fn icicle_create_keccak_256(default_input_chunk_size: u64) -> HasherHandle; + fn icicle_create_keccak_512(default_input_chunk_size: u64) -> HasherHandle; +} + +pub struct Keccak256; +pub struct Keccak512; + +impl Keccak256 { + pub fn new(default_input_chunk_size: u64) -> Result { + let handle: HasherHandle = unsafe { icicle_create_keccak_256(default_input_chunk_size) }; + if handle.is_null() { + return Err(eIcicleError::UnknownError); + } + Ok(Hasher::from_handle(handle)) + } +} + +impl Keccak512 { + pub fn new(default_input_chunk_size: u64) -> Result { + let handle: HasherHandle = unsafe { icicle_create_keccak_512(default_input_chunk_size) }; + if handle.is_null() { + return Err(eIcicleError::UnknownError); + } + Ok(Hasher::from_handle(handle)) + } +} diff --git a/wrappers/rust/icicle-hash/src/lib.rs b/wrappers/rust/icicle-hash/src/lib.rs new file mode 100644 index 000000000..c9a1db871 --- /dev/null +++ b/wrappers/rust/icicle-hash/src/lib.rs @@ -0,0 +1,5 @@ +pub mod keccak; +pub mod sha3; + +#[doc(hidden)] +pub mod tests; diff --git a/wrappers/rust/icicle-hash/src/sha3.rs b/wrappers/rust/icicle-hash/src/sha3.rs new file mode 100644 index 000000000..005952c99 --- /dev/null +++ b/wrappers/rust/icicle-hash/src/sha3.rs @@ -0,0 +1,30 @@ +use icicle_core::hash::{Hasher, HasherHandle}; +use icicle_runtime::errors::eIcicleError; + +extern "C" { + fn icicle_create_sha3_256(default_input_chunk_size: u64) -> HasherHandle; + fn icicle_create_sha3_512(default_input_chunk_size: u64) -> HasherHandle; +} + +pub struct Sha3_256; +pub struct Sha3_512; + +impl Sha3_256 { + pub fn new(default_input_chunk_size: u64) -> Result { + let handle: HasherHandle = unsafe { icicle_create_sha3_256(default_input_chunk_size) }; + if handle.is_null() { + return Err(eIcicleError::UnknownError); + } + Ok(Hasher::from_handle(handle)) + } +} + +impl Sha3_512 { + pub fn new(default_input_chunk_size: u64) -> Result { + let handle: HasherHandle = unsafe { icicle_create_sha3_512(default_input_chunk_size) }; + if handle.is_null() { + return Err(eIcicleError::UnknownError); + } + Ok(Hasher::from_handle(handle)) + } +} diff --git a/wrappers/rust/icicle-hash/src/tests.rs b/wrappers/rust/icicle-hash/src/tests.rs new file mode 100644 index 000000000..c87a18113 --- /dev/null +++ b/wrappers/rust/icicle-hash/src/tests.rs @@ -0,0 +1,119 @@ +#[cfg(test)] +mod tests { + + use crate::{ + keccak::{Keccak256, Keccak512}, + sha3::{Sha3_256, Sha3_512}, + }; + use icicle_core::{ + hash::{HashConfig, Hasher}, + merkle::{MerkleProof, MerkleTree, MerkleTreeConfig}, + test_utilities, + }; + use icicle_runtime::memory::HostSlice; + use rand::Rng; + use std::sync::Once; + + static INIT: Once = Once::new(); + + pub fn initialize() { + INIT.call_once(move || { + // TODO load CUDA backend + // test_utilities::test_load_and_init_devices(); + }); + } + + #[test] + fn keccak_hashing() { + initialize(); + test_utilities::test_set_ref_device(); + let single_hash_input_size = 30; + let batch = 3; + let keccak_hasher = Keccak512::new(0 /*default chunk size */).unwrap(); + let input = vec![0 as u8; single_hash_input_size * batch]; + let mut output = vec![0 as u8; 64 * batch]; // 64B (=512b) is the output size of Keccak512, + keccak_hasher + .hash( + HostSlice::from_slice(&input), + &HashConfig::default(), + HostSlice::from_mut_slice(&mut output), + ) + .unwrap(); + println!("output= {:?}", output); + // TODO compare to main device (CUDA by default) or verify with goldens + } + + #[test] + fn sha3_hashing() { + initialize(); + test_utilities::test_set_ref_device(); + + let sha3_hasher = Sha3_512::new(0 /*default chunk size */).unwrap(); + let input = vec![0 as u8; 90]; + let mut output = vec![0 as u8; 64]; // 256b * batch + sha3_hasher + .hash( + HostSlice::from_slice(&input), + &HashConfig::default(), + HostSlice::from_mut_slice(&mut output), + ) + .unwrap(); + println!("output= {:?}", output); + // TODO compare to main device (CUDA by default) or verify with goldens + } + + #[test] + fn merkle_tree_keccak() { + initialize(); + test_utilities::test_set_ref_device(); + + // Need a &[&Hashers] to build a tree. Can build it like this + { + // build a simple tree with 2 layers + let leaf_element_size = 8; + let hasher_l0 = Keccak256::new(2 * leaf_element_size /*input chunk size*/).unwrap(); + let hasher_l1 = Sha3_256::new(2 * 32 /*input chunk size*/).unwrap(); + let layer_hashes = [&hasher_l0, &hasher_l1]; + let _merkle_tree = MerkleTree::new(&layer_hashes[..], leaf_element_size as u64, 0).unwrap(); + } + + // or any way that ends up with &[&Hashers] + // building a binray tree, each layer takes 2*32B=64B and hashes to 32B + let nof_layers = 4; + let num_elements = 1 << nof_layers; + let leaf_element_size = 32; + let hasher = Keccak256::new(2 * leaf_element_size /*input chunk size*/).unwrap(); + let layer_hashes: Vec<&Hasher> = (0..nof_layers) + .map(|_| &hasher) + .collect(); + let merkle_tree = MerkleTree::new(&layer_hashes[..], leaf_element_size as u64, 0).unwrap(); + + // Create a vector of random bytes efficiently + let mut input: Vec = vec![0; leaf_element_size as usize * num_elements]; + rand::thread_rng().fill(&mut input[..]); // Fill the vector with random data + println!("input = {:?}", input); + + merkle_tree + .build(HostSlice::from_slice(&input), &MerkleTreeConfig::default()) + .unwrap(); + + let merkle_proof: MerkleProof = merkle_tree + .get_proof(HostSlice::from_slice(&input), 1, &MerkleTreeConfig::default()) + .unwrap(); + let root = merkle_proof.get_root::(); + let path = merkle_proof.get_path::(); + let (leaf, leaf_idx) = merkle_proof.get_leaf::(); + println!("root = {:?}", root); + println!("path = {:?}", path); + println!("leaf = {:?}, leaf_idx = {}", leaf, leaf_idx); + + let verification_valid = merkle_tree + .verify(&merkle_proof) + .unwrap(); + assert_eq!(verification_valid, true); + + // TODOs : + // (1) test real backends: CPU + CUDA. Can also compare the proofs to see the root, path and leaf are the same. + // (2) test different cases of input padding + } +} diff --git a/wrappers/rust/icicle-runtime/build.rs b/wrappers/rust/icicle-runtime/build.rs index dfbdbe351..a68c45008 100644 --- a/wrappers/rust/icicle-runtime/build.rs +++ b/wrappers/rust/icicle-runtime/build.rs @@ -23,7 +23,7 @@ fn main() { PathBuf::from(format!("{}/icicle/", deps_dir.display())) }; config - .define("CMAKE_BUILD_TYPE", "Release") + .define("HASH", "OFF") .define("CMAKE_INSTALL_PREFIX", &icicle_install_dir); // build (or pull and build) cuda backend if feature enabled.