├── .license.ignore ├── fuzz ├── .gitignore ├── fuzz_targets │ ├── randomx_alloc_cache.rs │ ├── randomx_create_vm_with_cache_only.rs │ ├── randomx_create_vm_with_cache_and_dataset.rs │ ├── randomx_vm_calculate_hash_with_cache_only.rs │ └── randomx_vm_calculate_hash_with_cache_and_dataset.rs ├── README.md ├── Cargo.toml └── Cargo.lock ├── .gitmodules ├── scripts ├── install_ubuntu_dependencies.sh ├── code_coverage.sh └── file_license_check.sh ├── .github ├── dependabot.yml ├── PULL_REQUEST_TEMPLATE.md ├── workflows │ ├── pr_title.yml │ ├── pr_signed_commits_check.yml │ ├── audit.yml │ ├── cov.yml │ ├── build_tests.json │ ├── ci.yml │ └── build_tests.yml └── ISSUE_TEMPLATE │ └── bug_report.md ├── rust-toolchain.toml ├── .gitignore ├── rustfmt.toml ├── Cargo.toml ├── LICENSE ├── lints.toml ├── CHANGELOG.md ├── README.md └── src ├── bindings.rs ├── test_utils.rs └── lib.rs /.license.ignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | corpus 3 | artifacts 4 | coverage 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "RandomX"] 2 | path = RandomX 3 | url = https://github.com/tevador/RandomX.git 4 | -------------------------------------------------------------------------------- /scripts/install_ubuntu_dependencies.sh: -------------------------------------------------------------------------------- 1 | apt-get install --no-install-recommends --assume-yes \ 2 | git \ 3 | make \ 4 | cmake \ 5 | libc++-dev \ 6 | libc++abi-dev 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | # Maintain dependencies for GitHub Actions 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "weekly" 9 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | # this should match the nightly version in tari-project/tari 2 | # only used for cargo fmt settings, repo should always compile on stable too 3 | [toolchain] 4 | channel = "stable" 5 | components = ["rustfmt", "clippy"] 6 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/randomx_alloc_cache.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Tari Project 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | 4 | #![no_main] 5 | 6 | use libfuzzer_sys::fuzz_target; 7 | use randomx_rs::test_utils::fuzz_randomx_alloc_cache; 8 | 9 | fuzz_target!(|data: &[u8]| { 10 | fuzz_randomx_alloc_cache(data.to_vec()); 11 | }); 12 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/randomx_create_vm_with_cache_only.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Tari Project 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | 4 | #![no_main] 5 | 6 | use libfuzzer_sys::fuzz_target; 7 | use randomx_rs::test_utils::fuzz_randomx_create_vm_with_cache_only; 8 | 9 | fuzz_target!(|data: &[u8]| { 10 | fuzz_randomx_create_vm_with_cache_only(data.to_vec()); 11 | }); 12 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/randomx_create_vm_with_cache_and_dataset.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Tari Project 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | 4 | #![no_main] 5 | 6 | use libfuzzer_sys::fuzz_target; 7 | use randomx_rs::test_utils::fuzz_randomx_create_vm_with_cache_and_dataset; 8 | 9 | fuzz_target!(|data: &[u8]| { 10 | fuzz_randomx_create_vm_with_cache_and_dataset(data.to_vec()); 11 | }); 12 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/randomx_vm_calculate_hash_with_cache_only.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Tari Project 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | 4 | #![no_main] 5 | 6 | use libfuzzer_sys::fuzz_target; 7 | use randomx_rs::test_utils::fuzz_randomx_vm_calculate_hash_with_cache_only; 8 | 9 | fuzz_target!(|data: &[u8]| { 10 | fuzz_randomx_vm_calculate_hash_with_cache_only(data.to_vec()); 11 | }); 12 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/randomx_vm_calculate_hash_with_cache_and_dataset.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Tari Project 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | 4 | #![no_main] 5 | 6 | use libfuzzer_sys::fuzz_target; 7 | use randomx_rs::test_utils::fuzz_randomx_vm_calculate_hash_with_cache_and_dataset; 8 | 9 | fuzz_target!(|data: &[u8]| { 10 | fuzz_randomx_vm_calculate_hash_with_cache_and_dataset(data.to_vec()); 11 | }); 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | .DS_Store 12 | .idea 13 | *.iml 14 | 15 | cov_raw/ 16 | *.profraw 17 | coverage_report/ 18 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | binop_separator = "Back" 2 | use_small_heuristics = "default" 3 | comment_width = 120 4 | format_strings = true 5 | group_imports = "StdExternalCrate" 6 | hard_tabs = false 7 | imports_layout = "HorizontalVertical" 8 | imports_granularity = "Crate" 9 | match_block_trailing_comma = true 10 | max_width = 120 11 | newline_style = "Native" 12 | normalize_comments = true 13 | reorder_imports = true 14 | reorder_modules = true 15 | reorder_impl_items = true 16 | space_after_colon = true 17 | space_before_colon = false 18 | struct_lit_single_line = true 19 | use_field_init_shorthand = true 20 | use_try_shorthand = true 21 | unstable_features = true 22 | format_code_in_doc_comments = true 23 | where_single_line = true 24 | wrap_comments = true 25 | overflow_delimited_expr = true 26 | edition = "2018" 27 | ignore = [] 28 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Description 2 | --- 3 | 4 | Motivation and Context 5 | --- 6 | 7 | How Has This Been Tested? 8 | --- 9 | 10 | What process can a PR reviewer use to test or verify this change? 11 | --- 12 | 13 | 14 | 16 | 17 | 18 | Breaking Changes 19 | --- 20 | 21 | - [x] None 22 | - [ ] Requires data directory on base node to be deleted 23 | - [ ] Requires hard fork 24 | - [ ] Other - Please specify 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "randomx-rs" 3 | description = "Rust bindings for the RandomX Proof-of-Work" 4 | authors = ["The Tari Development Community"] 5 | repository = "https://github.com/tari-project/randomx-rs" 6 | homepage = "https://tari.com" 7 | readme = "README.md" 8 | license = "BSD-3-Clause" 9 | version = "1.4.1" 10 | edition = "2018" 11 | build = "build.rs" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | [lib] 15 | # Create a dynamic library for C usage and a rust library so it can be called from rust 16 | crate-type = ["cdylib", "lib"] 17 | 18 | [dependencies] 19 | libc = "0.2.121" 20 | bitflags = "1.3.2" 21 | thiserror = "1.0.50" 22 | 23 | [dev-dependencies] 24 | hex = "0.4.3" 25 | quickcheck = "1" 26 | 27 | [build-dependencies] 28 | cmake = "0.1" 29 | 30 | [profile.release] 31 | lto = false 32 | -------------------------------------------------------------------------------- /.github/workflows/pr_title.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Checks that PR titles conform to Conventional Commits 3 | # See https://www.conventionalcommits.org/en/v1.0.0/ for more information 4 | name: PR 5 | 6 | 'on': 7 | pull_request: 8 | types: 9 | - opened 10 | - reopened 11 | - edited 12 | - synchronize 13 | 14 | concurrency: 15 | group: ${{ github.workflow }}-${{ github.ref }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | check-title: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: install 23 | run: | 24 | npm install -g @commitlint/cli @commitlint/config-conventional 25 | echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js 26 | - name: lint 27 | env: 28 | PR_TITLE: ${{github.event.pull_request.title}} 29 | run: | 30 | echo "$PR_TITLE" | commitlint 31 | -------------------------------------------------------------------------------- /fuzz/README.md: -------------------------------------------------------------------------------- 1 | # Fuzzing randomx-rs 2 | 3 | See https://rust-fuzz.github.io/book/cargo-fuzz.html for more information on fuzzing with cargo-fuzz. 4 | Install `cargo-fuzz` as per [installation instructions](https://rust-fuzz.github.io/book/cargo-fuzz/setup.html). 5 | 6 | 7 | **Note:** Fuzzing is not supported on Windows yet. 8 | 9 | To get a list of fuzz targets, from a terminal in the project root, run 10 | ``` 11 | cargo fuzz list 12 | ``` 13 | 14 | To run a fuzz test, from a terminal in the project root, run 15 | ``` 16 | cargo +nightly fuzz run --release 17 | ``` 18 | To run fuzz tests involving a cache and dataset, on error `libFuzzer: out-of-memory (malloc(2181038016))`, pass 19 | `-- -rss_limit_mb=` as argument to allow using more than 2 GB of RAM - 3GB recommended. 20 | ``` 21 | cargo +nightly fuzz run --release -- -rss_limit_mb=3221225472 22 | ``` 23 | -------------------------------------------------------------------------------- /.github/workflows/pr_signed_commits_check.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Checks if the comments are signed or not 3 | name: PR - Signed commits check 4 | 5 | 'on': 6 | pull_request_target 7 | 8 | concurrency: 9 | # https://docs.github.com/en/actions/examples/using-concurrency-expressions-and-a-test-matrix 10 | group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' 11 | cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/v') || github.ref != 'refs/heads/development' || github.ref != 'refs/heads/nextnet' || github.ref != 'refs/heads/stagenet' }} 12 | 13 | permissions: {} 14 | 15 | jobs: 16 | check-signed-commits: 17 | name: Check signed commits in PR 18 | runs-on: ubuntu-latest 19 | permissions: 20 | contents: read 21 | pull-requests: write 22 | steps: 23 | - name: Check signed commits in PR 24 | uses: 1Password/check-signed-commits-action@v1 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[TITLE]" 5 | labels: 'bug-report' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS & Version: [e.g. iOS 10.2.1] 28 | - Browser & Version [e.g. chrome v71.0.12345] 29 | 30 | **Smartphone (please complete the following information):** 31 | - Device: [e.g. iPhone6] 32 | - OS: [e.g. iOS8.1] 33 | - Browser & Version [e.g. stock browser v0.1.2] 34 | 35 | **Additional context** 36 | Add any other context about the problem here. 37 | -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Security audit - weekly 3 | 4 | 'on': 5 | push: 6 | paths: 7 | # Run if workflow changes 8 | - '.github/workflows/audit.yml' 9 | # Run on changed dependencies 10 | - '**/Cargo.toml' 11 | - '**/Cargo.lock' 12 | # Run if the configuration file changes 13 | - '**/audit.toml' 14 | # Rerun periodicly to pick up new advisories 15 | schedule: 16 | - cron: '43 05 * * 0' 17 | # Run manually 18 | workflow_dispatch: 19 | 20 | env: 21 | toolchain: nightly-2022-11-03 22 | 23 | jobs: 24 | security_audit: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v5 28 | with: 29 | submodules: "true" 30 | 31 | - name: toolchain 32 | uses: dtolnay/rust-toolchain@nightly 33 | with: 34 | toolchain: ${{ env.toolchain }} 35 | components: rustfmt, clippy 36 | 37 | - name: cargo check 38 | run: | 39 | cargo check 40 | 41 | - uses: rustsec/audit-check@v2.0.0 42 | with: 43 | token: ${{ secrets.GITHUB_TOKEN }} 44 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "monero-fuzz" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [package.metadata] 8 | cargo-fuzz = true 9 | 10 | [dependencies] 11 | libfuzzer-sys = "0.4" 12 | 13 | [dependencies.randomx-rs] 14 | path = ".." 15 | 16 | # Prevent this from interfering with workspaces 17 | [workspace] 18 | members = ["."] 19 | 20 | [profile.release] 21 | debug = 1 22 | 23 | [[bin]] 24 | name = "randomx_alloc_cache" 25 | path = "fuzz_targets/randomx_alloc_cache.rs" 26 | test = false 27 | doc = false 28 | 29 | [[bin]] 30 | name = "randomx_create_vm_with_cache_only" 31 | path = "fuzz_targets/randomx_create_vm_with_cache_only.rs" 32 | test = false 33 | doc = false 34 | 35 | [[bin]] 36 | name = "randomx_create_vm_with_cache_and_dataset" 37 | path = "fuzz_targets/randomx_create_vm_with_cache_and_dataset.rs" 38 | test = false 39 | doc = false 40 | 41 | [[bin]] 42 | name = "randomx_vm_calculate_hash_with_cache_only" 43 | path = "fuzz_targets/randomx_vm_calculate_hash_with_cache_only.rs" 44 | test = false 45 | doc = false 46 | 47 | [[bin]] 48 | name = "randomx_vm_calculate_hash_with_cache_and_dataset" 49 | path = "fuzz_targets/randomx_vm_calculate_hash_with_cache_and_dataset.rs" 50 | test = false 51 | doc = false 52 | -------------------------------------------------------------------------------- /.github/workflows/cov.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Coverage 3 | 4 | 'on': 5 | push: 6 | branches: 7 | - development 8 | - cov-* 9 | 10 | env: 11 | RUSTUP_TOOLCHAIN: "nightly" 12 | 13 | jobs: 14 | coverage: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v5 19 | with: 20 | submodules: "true" 21 | 22 | - name: Install dependencies 23 | run: | 24 | sudo apt update 25 | sudo apt install --no-install-recommends --assume-yes \ 26 | jq lcov 27 | 28 | - name: toolchain 29 | uses: dtolnay/rust-toolchain@nightly 30 | with: 31 | toolchain: nightly 32 | components: llvm-tools-preview 33 | 34 | - name: Install requirements for code coverage 35 | run: | 36 | cargo install cargo-binutils 37 | cargo install rustfilt 38 | 39 | - name: Run test coverage 40 | id: coverage 41 | env: 42 | SKIP_HTML: "1" 43 | run: | 44 | /bin/bash -c ./scripts/code_coverage.sh 45 | 46 | - name: Coveralls upload 47 | uses: coverallsapp/github-action@master 48 | with: 49 | github-token: ${{ secrets.GITHUB_TOKEN }} 50 | path-to-lcov: ./cov_raw/randomx-rs.lcov 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, The Tari Project 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /scripts/code_coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Prerequisites 3 | # 1. You need LLVM-COV tools: 4 | # $ rustup component add llvm-tools-preview 5 | # 2. and Rust wrappers for llvm-cov: 6 | # $ cargo install cargo-binutils 7 | # 3. The rust name demangler 8 | # $ cargo install rustfilt 9 | # 4. jq 10 | # 5. genhtml 11 | # $ sudo apt install lcov 12 | 13 | NAME="randomx-rs" 14 | RUSTFLAGS="-Zinstrument-coverage" 15 | LLVM_PROFILE_FILE="./cov_raw/$NAME-%m.profraw" 16 | 17 | get_binaries() { 18 | files=$( 19 | RUSTFLAGS=$RUSTFLAGS cargo test --tests --no-run --message-format=json | 20 | jq -r "select(.profile.test == true) | .filenames[]" | 21 | grep -v dSYM - 22 | ) 23 | files=("${files[@]/#/-object }") 24 | } 25 | 26 | get_binaries 27 | 28 | # echo "files: $files" 29 | 30 | # Remove old coverage files 31 | rm cov_raw/*profraw cov_raw/$NAME.profdata cov_raw/$NAME.lcov cov_raw/$NAME.txt 32 | 33 | RUSTFLAGS=$RUSTFLAGS LLVM_PROFILE_FILE=$LLVM_PROFILE_FILE cargo test --tests 34 | 35 | cargo profdata -- \ 36 | merge -sparse ./cov_raw/$NAME*.profraw -o ./cov_raw/$NAME.profdata 37 | 38 | cargo cov -- \ 39 | export \ 40 | --Xdemangler=rustfilt \ 41 | --format=lcov \ 42 | --show-branch-summary \ 43 | --show-instantiation-summary \ 44 | --show-region-summary \ 45 | --ignore-filename-regex='\.cargo' \ 46 | --ignore-filename-regex="rustc" \ 47 | --ignore-filename-regex="\.git" \ 48 | --instr-profile=cov_raw/$NAME.profdata \ 49 | $files \ 50 | >cov_raw/$NAME.lcov 51 | 52 | cargo cov -- \ 53 | show \ 54 | --Xdemangler=rustfilt \ 55 | --show-branch-summary \ 56 | --show-instantiation-summary \ 57 | --show-region-summary \ 58 | --ignore-filename-regex='\.cargo' \ 59 | --ignore-filename-regex="rustc" \ 60 | --ignore-filename-regex="\.git" \ 61 | --instr-profile=cov_raw/$NAME.profdata \ 62 | $files \ 63 | >cov_raw/$NAME.txt 64 | 65 | if [ -z ${SKIP_HTML+x} ]; then 66 | genhtml -o coverage_report cov_raw/$NAME.lcov 67 | else 68 | echo "Skipping html generation" 69 | fi 70 | # open coverage_report/src/index.html 71 | -------------------------------------------------------------------------------- /lints.toml: -------------------------------------------------------------------------------- 1 | deny = [ 2 | # Prevent spelling mistakes in lints 3 | 'unknown_lints', 4 | # clippy groups: 5 | 'clippy::correctness', 6 | 7 | # All clippy allows must have a reason 8 | # TODO: enable lint-reasons feature 9 | #'clippy::allow_attributes_without_reason', 10 | # Docs 11 | #'missing_docs', 12 | #'clippy::missing_errors_doc', 13 | #'clippy::missing_safety_doc', 14 | #'clippy::missing_panics_doc', 15 | 16 | # Common mistakes 17 | 'clippy::await_holding_lock', 18 | 'unused_variables', 19 | 'unused_imports', 20 | 'dead_code', 21 | 'unused_extern_crates', 22 | 'unused_must_use', 23 | 'unreachable_patterns', 24 | 'clippy::cloned_instead_of_copied', 25 | 'clippy::create_dir', 26 | 'clippy::dbg_macro', 27 | 'clippy::else_if_without_else', 28 | 'clippy::enum_glob_use', 29 | # This is 99% not needed 30 | 'clippy::inline_always', 31 | 'let_underscore_drop', 32 | 'clippy::let_unit_value', 33 | 'clippy::match_on_vec_items', 34 | 'clippy::match_wild_err_arm', 35 | 'clippy::similar_names', 36 | 'clippy::needless_borrow', 37 | 38 | # style 39 | 'clippy::explicit_into_iter_loop', 40 | 'clippy::explicit_iter_loop', 41 | 'clippy::if_not_else', 42 | 'clippy::match_bool', 43 | 'clippy::needless_pass_by_value', 44 | 'clippy::range_plus_one', 45 | 'clippy::struct_excessive_bools', 46 | # perhaps this is a bit harsh 47 | 'clippy::too_many_lines', 48 | 'clippy::trivially_copy_pass_by_ref', 49 | 50 | # casting mistakes 51 | 'clippy::cast_lossless', 52 | 'clippy::cast_possible_truncation', 53 | 'clippy::cast_possible_wrap', 54 | 'clippy::cast_precision-loss', 55 | # This is tricky because sqlite uses signed ints and it's used to represent unsigned data 56 | 'clippy::cast_sign_loss' 57 | ] 58 | 59 | allow = [ 60 | # allow Default::default calls 61 | 'clippy::default_trait_access', 62 | # Generally when developers fix this, it can lead to leaky abstractions or worse, so 63 | # too many arguments is generally the lesser of two evils 64 | 'clippy::too_many_arguments', 65 | # we get this inside of the macro, which we cannot ignore there 66 | 'clippy::bad_bit_mask' 67 | ] -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [1.4.1](https://github.com/tari-project/randomx-rs/compare/v1.4.0...v1.4.1) (2025-09-04) 6 | ### Bug Fixes 7 | * Build support for freebsd [#79] 8 | * Build support for Fedora [#78] 9 | 10 | ## [1.4.0](https://github.com/tari-project/randomx-rs/compare/v1.3.2...v1.4.0) (2025-05-21) 11 | ### Feature 12 | 13 | * switch to cmake crate and support less hard-coded build system 14 | 15 | ## [1.3.2](https://github.com/tari-project/randomx-rs/compare/v1.3.1...v1.3.2) (2024-11-13) 16 | ### Feature 17 | 18 | * support windows builds with either visual studio 2022 or visual studio 2019 19 | 20 | ## [1.3.1](https://github.com/tari-project/randomx-rs/compare/v1.3.1...v1.3.0) (2024-10-07) 21 | ### Feature 22 | 23 | * add cross-compile target ```make``` arguments for riscv64 (riscv64gc-unknown-linux-gnu) 24 | 25 | ## [1.3.0](https://github.com/tari-project/randomx-rs/compare/v1.3.0...v1.2.1) (2023-11-01) 26 | ### Feature 27 | 28 | * update randomx 29 | 30 | ### [1.2.1](https://github.com/tari-project/randomx-rs/compare/v1.1.15...v1.2.1) (2023-08-30) 31 | 32 | ### Bug Fixes 33 | 34 | * fix compile on windows ([#61](https://github.com/tari-project/randomx-rs/issues/61)) ([646c615](https://github.com/tari-project/randomx-rs/commit/646c6150a01659417a2cacd9f1db0a7e492bdf1d)) 35 | 36 | ## [1.2.0](https://github.com/tari-project/randomx-rs/compare/v1.1.15...v1.2.0) (2023-08-22) 37 | ### Feature 38 | 39 | * update randomx ([#60](https://github.com/tari-project/randomx-rs/issues/60)) ([5300721](https://github.com/tari-project/randomx-rs/commit/530072139e09a86b7247831f66b6ac6991c729e6)) 40 | 41 | ### [1.1.15](https://github.com/tari-project/randomx-rs/compare/v1.1.14...v1.1.15) (2023-05-19) 42 | 43 | ### [1.1.14](https://github.com/tari-project/randomx-rs/compare/v1.1.13...v1.1.14) (2022-12-07) 44 | 45 | ### [1.1.13](https://github.com/tari-project/randomx-rs/compare/v1.1.12...v1.1.13) (2022-07-19) 46 | 47 | 48 | ### Bug Fixes 49 | 50 | * hard code cross-compile build option for ARM64/M1 ([#46](https://github.com/tari-project/randomx-rs/issues/46)) ([4f88a37](https://github.com/tari-project/randomx-rs/commit/4f88a37f41d4ab5b5a8fe95f8653104dea8e045d)) 51 | -------------------------------------------------------------------------------- /scripts/file_license_check.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Must be run from the repo root 4 | # 5 | 6 | #set -xo pipefail 7 | set -e 8 | 9 | check_for() { 10 | if prog_location=$(which ${1}) ; then 11 | if result=$(${1} --version 2>/dev/null); then 12 | result="${1}: ${result} INSTALLED ✓" 13 | else 14 | result="${1}: INSTALLED ✓" 15 | fi 16 | else 17 | result="${1}: MISSING ⨯" 18 | fi 19 | } 20 | 21 | check_requirements() { 22 | echo "List of requirements and possible test:" 23 | req_progs=( 24 | mktemp 25 | rg 26 | diff 27 | ) 28 | for RProg in "${req_progs[@]}"; do 29 | check_for ${RProg} 30 | echo "${result}" 31 | if [[ "${result}" == "${RProg}: MISSING ⨯" ]]; then 32 | echo "!! Install ${RProg} and try again !!" 33 | exit -1 34 | fi 35 | done 36 | 37 | if [ ! -f .license.ignore ]; then 38 | echo "!! No .license.ignore file !!" 39 | exit -1 40 | fi 41 | } 42 | 43 | check_requirements 44 | 45 | diffparms=${diffparms:-"-u --suppress-blank-empty --strip-trailing-cr --color=never"} 46 | rgTemp=${rgTemp:-$(mktemp)} 47 | 48 | # rg -i "Copyright.*The Tari Project" --files-without-match \ 49 | # -g '!*.{Dockerfile,asc,bat,config,config.js,css,csv,drawio,env,gitkeep,hbs,html,ini,iss,json,lock,md,min.js,ps1,py,rc,scss,sh,sql,svg,toml,txt,yml,vue}' . \ 50 | # | sort > /tmp/rgtemp 51 | 52 | # Exclude files without extensions as well as those with extensions that are not in the list 53 | # 54 | rg -i "Copyright.*The Tari Project" --files-without-match \ 55 | -g '!*.{Dockerfile,asc,bat,config,config.js,css,csv,drawio,env,gitkeep,hbs,html,ini,iss,json,lock,md,min.js,ps1,py,rc,scss,sh,sql,svg,toml,txt,yml,vue,liquid,otf,d.ts,mjs}' . \ 56 | | while IFS= read -r file; do 57 | if [[ -n $(basename "$file" | grep -E '\.') ]]; then 58 | echo "$file" 59 | fi 60 | done | sort > ${rgTemp} 61 | 62 | # Sort the .license.ignore file as sorting seems to behave differently on different platforms 63 | licenseIgnoreTemp=${licenseIgnoreTemp:-$(mktemp)} 64 | cat .license.ignore | sort > ${licenseIgnoreTemp} 65 | 66 | DIFFS=$( diff ${diffparms} ${licenseIgnoreTemp} ${rgTemp} || true ) 67 | 68 | # clean up 69 | rm -vf ${rgTemp} 70 | rm -vf ${licenseIgnoreTemp} 71 | 72 | if [ -n "${DIFFS}" ]; then 73 | echo "New files detected that either need copyright/license identifiers added, " 74 | echo "or they need to be added to .license.ignore" 75 | echo "NB: The ignore file must be sorted alphabetically!" 76 | 77 | echo "Diff:" 78 | echo "${DIFFS}" 79 | exit 1 80 | else 81 | exit 0 82 | fi 83 | -------------------------------------------------------------------------------- /.github/workflows/build_tests.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "linux-x86_64", 4 | "runs-on": "ubuntu-22.04", 5 | "rust": "stable", 6 | "target": "x86_64-unknown-linux-gnu", 7 | "cross": false 8 | }, 9 | { 10 | "name": "linux-arm64", 11 | "runs-on": "ubuntu-22.04-arm", 12 | "rust": "stable", 13 | "target": "aarch64-unknown-linux-gnu", 14 | "cross": false 15 | }, 16 | { 17 | "name": "linux-riscv64", 18 | "runs-on": "ubuntu-latest", 19 | "rust": "stable", 20 | "target": "riscv64gc-unknown-linux-gnu", 21 | "cross": true 22 | }, 23 | { 24 | "name": "macos-x86_64", 25 | "runs-on": "macos-13", 26 | "rust": "stable", 27 | "target": "x86_64-apple-darwin", 28 | "cross": false 29 | }, 30 | { 31 | "name": "macos-arm64", 32 | "runs-on": "macos-14", 33 | "rust": "stable", 34 | "target": "aarch64-apple-darwin", 35 | "cross": false 36 | }, 37 | { 38 | "name": "windows-x64-2022", 39 | "runs-on": "windows-2022", 40 | "rust": "stable", 41 | "target": "x86_64-pc-windows-msvc", 42 | "cross": false, 43 | "build_enabled": true, 44 | "best_effort": true 45 | }, 46 | { 47 | "name": "windows-x64-2025", 48 | "runs-on": "windows-2025", 49 | "rust": "stable", 50 | "target": "x86_64-pc-windows-msvc", 51 | "cross": false, 52 | "build_enabled": true, 53 | "best_effort": true 54 | }, 55 | { 56 | "name": "windows-arm64-cross", 57 | "runs-on": "windows-latest", 58 | "rust": "stable", 59 | "target": "aarch64-pc-windows-msvc", 60 | "cross": false, 61 | "build_enabled": true, 62 | "best_effort": true 63 | }, 64 | { 65 | "name": "windows-arm64", 66 | "runs-on": "windows-11-arm", 67 | "rust": "stable", 68 | "target": "aarch64-pc-windows-msvc", 69 | "cross": false, 70 | "build_enabled": true, 71 | "best_effort": true 72 | }, 73 | { 74 | "name": "ios-x86_64", 75 | "runs-on": "macos-latest", 76 | "rust": "stable", 77 | "target": "x86_64-apple-ios", 78 | "cross": false, 79 | "build_enabled": true, 80 | "best_effort": true 81 | }, 82 | { 83 | "name": "ios-aarch64", 84 | "runs-on": "macos-latest", 85 | "rust": "stable", 86 | "target": "aarch64-apple-ios", 87 | "cross": false, 88 | "build_enabled": true, 89 | "best_effort": true 90 | }, 91 | { 92 | "name": "ios-aarch64-sim", 93 | "runs-on": "macos-latest", 94 | "rust": "stable", 95 | "target": "aarch64-apple-ios-sim", 96 | "cross": false, 97 | "build_enabled": true, 98 | "best_effort": true 99 | }, 100 | { 101 | "name": "android-x86_64", 102 | "runs-on": "ubuntu-latest", 103 | "rust": "stable", 104 | "target": "x86_64-linux-android", 105 | "cross": true, 106 | "build_enabled": true, 107 | "best_effort": true 108 | }, 109 | { 110 | "name": "android-aarch64", 111 | "runs-on": "ubuntu-latest", 112 | "rust": "stable", 113 | "target": "aarch64-linux-android", 114 | "cross": true, 115 | "build_enabled": true, 116 | "best_effort": true 117 | }, 118 | { 119 | "name": "android-riscv64", 120 | "runs-on": "ubuntu-latest", 121 | "rust": "nightly", 122 | "target": "riscv64-linux-android", 123 | "cross": true, 124 | "build_enabled": true, 125 | "best_effort": true 126 | } 127 | ] 128 | -------------------------------------------------------------------------------- /fuzz/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "arbitrary" 7 | version = "1.3.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "1.3.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 16 | 17 | [[package]] 18 | name = "cc" 19 | version = "1.0.83" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 22 | dependencies = [ 23 | "jobserver", 24 | "libc", 25 | ] 26 | 27 | [[package]] 28 | name = "jobserver" 29 | version = "0.1.26" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" 32 | dependencies = [ 33 | "libc", 34 | ] 35 | 36 | [[package]] 37 | name = "libc" 38 | version = "0.2.148" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" 41 | 42 | [[package]] 43 | name = "libfuzzer-sys" 44 | version = "0.4.7" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" 47 | dependencies = [ 48 | "arbitrary", 49 | "cc", 50 | "once_cell", 51 | ] 52 | 53 | [[package]] 54 | name = "monero-fuzz" 55 | version = "0.0.0" 56 | dependencies = [ 57 | "libfuzzer-sys", 58 | "randomx-rs", 59 | ] 60 | 61 | [[package]] 62 | name = "once_cell" 63 | version = "1.18.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" 66 | 67 | [[package]] 68 | name = "proc-macro2" 69 | version = "1.0.67" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" 72 | dependencies = [ 73 | "unicode-ident", 74 | ] 75 | 76 | [[package]] 77 | name = "quote" 78 | version = "1.0.33" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 81 | dependencies = [ 82 | "proc-macro2", 83 | ] 84 | 85 | [[package]] 86 | name = "randomx-rs" 87 | version = "1.2.1" 88 | dependencies = [ 89 | "bitflags", 90 | "libc", 91 | "thiserror", 92 | ] 93 | 94 | [[package]] 95 | name = "syn" 96 | version = "2.0.37" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" 99 | dependencies = [ 100 | "proc-macro2", 101 | "quote", 102 | "unicode-ident", 103 | ] 104 | 105 | [[package]] 106 | name = "thiserror" 107 | version = "1.0.49" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" 110 | dependencies = [ 111 | "thiserror-impl", 112 | ] 113 | 114 | [[package]] 115 | name = "thiserror-impl" 116 | version = "1.0.49" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" 119 | dependencies = [ 120 | "proc-macro2", 121 | "quote", 122 | "syn", 123 | ] 124 | 125 | [[package]] 126 | name = "unicode-ident" 127 | version = "1.0.12" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 130 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 4 | 'on': 5 | workflow_dispatch: 6 | push: 7 | branches: 8 | - "ci-*" 9 | pull_request: 10 | types: 11 | - opened 12 | - reopened 13 | - synchronize 14 | merge_group: 15 | 16 | env: 17 | toolchain: nightly-2022-11-03 18 | 19 | concurrency: 20 | # https://docs.github.com/en/actions/examples/using-concurrency-expressions-and-a-test-matrix 21 | group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' 22 | cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/v') || github.ref != 'refs/heads/development' || github.ref != 'refs/heads/nextnet' || github.ref != 'refs/heads/stagenet' }} 23 | 24 | permissions: {} 25 | 26 | jobs: 27 | clippy: 28 | name: clippy 29 | runs-on: [ ubuntu-latest ] 30 | steps: 31 | - name: checkout 32 | uses: actions/checkout@v5 33 | with: 34 | submodules: "true" 35 | 36 | - name: toolchain 37 | uses: dtolnay/rust-toolchain@nightly 38 | with: 39 | toolchain: ${{ env.toolchain }} 40 | components: rustfmt, clippy 41 | 42 | - name: Cache rust dependencies 43 | uses: swatinem/rust-cache@v2 44 | 45 | - name: cargo fmt 46 | run: | 47 | cargo +${{ env.toolchain }} fmt --all -- --check 48 | 49 | - name: Install cargo-lints 50 | run: | 51 | cargo install cargo-lints 52 | 53 | - name: Clippy check (with lints) 54 | run: | 55 | cargo lints clippy --all-targets --all-features 56 | 57 | machete: 58 | # Checks for unused dependencies. 59 | name: machete 60 | runs-on: [ ubuntu-latest ] 61 | steps: 62 | - name: checkout 63 | uses: actions/checkout@v5 64 | with: 65 | submodules: "true" 66 | 67 | - name: toolchain 68 | uses: dtolnay/rust-toolchain@master 69 | with: 70 | toolchain: ${{ env.toolchain }} 71 | components: clippy, rustfmt 72 | 73 | - name: Cache rust dependencies 74 | uses: swatinem/rust-cache@v2 75 | 76 | - name: cargo machete 77 | run: | 78 | cargo install cargo-machete 79 | cargo machete 80 | 81 | file-licenses: 82 | name: file-licenses 83 | runs-on: [ ubuntu-latest ] 84 | steps: 85 | - name: checkout 86 | uses: actions/checkout@v5 87 | - name: install ripgrep 88 | run: | 89 | # https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep_14.1.1-1_amd64.deb.sha256 90 | wget -v https://github.com/BurntSushi/ripgrep/releases/download/14.1.1/ripgrep_14.1.1-1_amd64.deb 91 | sudo dpkg -i ripgrep_14.1.1-1_amd64.deb 92 | rg --version || exit 1 93 | - name: run the license check 94 | run: ./scripts/file_license_check.sh 95 | 96 | build: 97 | name: build 98 | runs-on: [ ubuntu-latest ] 99 | steps: 100 | - name: checkout 101 | uses: actions/checkout@v5 102 | with: 103 | submodules: "true" 104 | 105 | - name: toolchain 106 | uses: dtolnay/rust-toolchain@stable 107 | with: 108 | toolchain: stable 109 | 110 | - name: Cache rust dependencies 111 | uses: Swatinem/rust-cache@v2 112 | 113 | - name: cargo build 114 | run: | 115 | cargo build --release 116 | 117 | test: 118 | name: test 119 | runs-on: ubuntu-latest 120 | steps: 121 | - name: checkout 122 | uses: actions/checkout@v5 123 | with: 124 | submodules: "true" 125 | 126 | - name: toolchain 127 | uses: dtolnay/rust-toolchain@stable 128 | with: 129 | toolchain: stable 130 | 131 | - name: Cache rust dependencies 132 | uses: Swatinem/rust-cache@v2 133 | 134 | - name: cargo test 135 | run: | 136 | cargo test 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![CI](https://github.com/tari-project/randomx-rs/actions/workflows/ci.yml/badge.svg) 2 | [![Coverage Status](https://coveralls.io/repos/github/delta1/randomx-rs/badge.svg?branch=cov-temp)](https://coveralls.io/github/delta1/randomx-rs?branch=cov-temp) 3 | 4 | # RandomX-rs 5 | 6 | > Rust bindings to the RandomX proof-of-work (Pow) system 7 | 8 | ## Build Dependencies 9 | 10 | This repo makes use of git submodules. 11 | 12 | The first time you compile, or perhaps after a big update after a `git pull`, you need to update the submodules: 13 | 14 | ```bash 15 | git submodule init 16 | git submodule update 17 | ``` 18 | 19 | If you see an error like 20 | 21 | ``` 22 | fatal: Needed a single revision 23 | Unable to find current revision in submodule path 'RandomX' 24 | ``` 25 | 26 | you might want to see if there is a `RandomX` folder in the source tree. (On case insensitive systems, like OsX and Windows, it might 27 | even be `randomx`). Deleting this folder and repeating the commands above should resolve the issue. 28 | 29 | ### Mac 30 | 31 | Install [XCode](https://apps.apple.com/za/app/xcode/id497799835?mt=12) and then the XCode Command Line Tools with the following command 32 | 33 | ``` 34 | xcode-select --install 35 | ``` 36 | 37 | For macOS Mojave additional headers need to be installed, run 38 | 39 | ``` 40 | open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg 41 | ``` 42 | 43 | and follow the prompts 44 | 45 | Install Brew 46 | 47 | ``` 48 | /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 49 | ``` 50 | 51 | Run the following to install needed bottles 52 | 53 | ``` 54 | brew install git 55 | brew install cmake 56 | ``` 57 | 58 | ### Linux 59 | 60 | Run the following to install dependencies 61 | 62 | ``` 63 | apt-get install git cmake libc++-dev libc++abi-dev 64 | ``` 65 | 66 | ### Windows 67 | 68 | Install [Git](https://git-scm.com/download/win) 69 | 70 | Install [CMake](https://cmake.org/download/) 71 | 72 | Install [Build Tools for Visual Studio 2019](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16) 73 | 74 | ### Android 75 | 76 | To build using the Android NDK the `ANDROID_SDK_ROOT` environment variable needs to be set. Other variables are optional as they have defaults. Example build command for ARM64: 77 | ``` 78 | ANDROID_SDK_ROOT=/home/user/Android/Sdk \ 79 | ANDROID_PLATFORM=android-25 \ 80 | ANDROID_CMAKE=/home/user/Android/Sdk/cmake/3.22.1/bin/cmake \ 81 | ANDROID_CMAKE_TOOLCHAIN=/home/user/Android/Sdk/ndk/22.1.7171670/build/cmake/android.toolchain.cmake \ 82 | cargo build --target=aarch64-linux-android 83 | ``` 84 | 85 | # Troubleshooting 86 | 87 | ## Mac/OSX 88 | 89 | If you're experiencing linker issues, or messages like 90 | 91 | `cstdint:153:8: error: no member named 'int8_t' in the global namespace` 92 | 93 | then you might have multiple conflicting versions of clang installed. 94 | 95 | Try: 96 | 97 | - Does `which cc` report more than one binary? If so, uninstalling one of the clang compilers might help. 98 | - Upgrading cmake. `brew uninstall cmake && brew install cmake` 99 | - `cargo clean` 100 | 101 | On Apple ARM64 hardware and newer XCode releases, RandomX might fail the `randomx-tests`. 102 | ``` 103 | [83] Hash test 1e (interpreter) ... PASSED 104 | [84] Hash test 2a (compiler) ... Assertion failed: (equalsHex(hash, "639183aae1bf4c9a35884cb46b09cad9175f04efd7684e7262a0ac1c2f0b4e3f")), function operator(), file tests.cpp, line 966. 105 | zsh: abort ./randomx-tests 106 | ``` 107 | or 108 | ``` 109 | [88] Hash test 2e (compiler) ... PASSED 110 | [89] Cache initialization: SSSE3 ... SKIPPED 111 | [90] Cache initialization: AVX2 ... SKIPPED 112 | [91] Hash batch test ... Assertion failed: (equalsHex(hash3, "c36d4ed4191e617309867ed66a443be4075014e2b061bcdaf9ce7b721d2b77a8")), function operator(), file tests.cpp, line 1074. 113 | zsh: abort ./randomx-tests 114 | ``` 115 | Building using an older SDK might help. Find location of current SDKs with `xcrun --show-sdk-path`, then for example: 116 | ```bash 117 | export RANDOMX_RS_CMAKE_OSX_SYSROOT="/Library/Developer/CommandLineTools/SDKs/MacOSX12.3.sdk" 118 | cargo build 119 | ``` 120 | Quick test with built binaries 121 | ```bash 122 | find target -name randomx-tests -exec {} \; 123 | ``` 124 | -------------------------------------------------------------------------------- /.github/workflows/build_tests.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Build Test Matrix 3 | 4 | 'on': 5 | push: 6 | tags: 7 | - "v[0-9]+.[0-9]+.[0-9]*" 8 | branches: 9 | - "development" 10 | - "build-ci-*" 11 | pull_request: 12 | branches: 13 | - "development" 14 | schedule: 15 | - cron: "05 00 * * 0" 16 | workflow_dispatch: 17 | 18 | env: 19 | toolchain: nightly-2024-07-07 20 | matrix-json-file: ".github/workflows/build_tests.json" 21 | CARGO_HTTP_MULTIPLEXING: false 22 | CARGO_UNSTABLE_SPARSE_REGISTRY: true 23 | CARGO: cargo 24 | #CARGO_OPTIONS: "--locked" 25 | CARGO_OPTIONS: "-vv" 26 | CARGO_CACHE: true 27 | 28 | concurrency: 29 | # https://docs.github.com/en/actions/examples/using-concurrency-expressions-and-a-test-matrix 30 | group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' 31 | cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/v') || github.ref != 'refs/heads/development' }} 32 | 33 | permissions: {} 34 | 35 | jobs: 36 | matrix-prep: 37 | runs-on: ubuntu-latest 38 | outputs: 39 | matrix: ${{ steps.set-matrix.outputs.matrix }} 40 | steps: 41 | - uses: actions/checkout@v5 42 | with: 43 | submodules: false 44 | 45 | - name: Set Matrix 46 | id: set-matrix 47 | run: | 48 | # 49 | # build all targets images 50 | # matrix=$( jq -s -c .[] .github/workflows/build_binaries.json ) 51 | # 52 | # build only single target image 53 | # matrix_selection=$( jq -c '.[] | select( ."name" == "windows-x64" )' ${{ env.matrix-json-file }} ) 54 | # matrix_selection=$( jq -c '.[] | select( ."name" | contains("macos") )' ${{ env.matrix-json-file }} ) 55 | # 56 | # build select target images - build_enabled 57 | matrix_selection=$( jq -c '.[] | select( ."build_enabled" != false )' ${{ env.matrix-json-file }} ) 58 | # 59 | # Setup the json build matrix 60 | matrix=$(echo ${matrix_selection} | jq -s -c '{"builds": .}') 61 | echo $matrix 62 | echo $matrix | jq . 63 | echo "matrix=${matrix}" >> $GITHUB_OUTPUT 64 | 65 | matrix-check: 66 | # Debug matrix 67 | if: ${{ false }} 68 | runs-on: ubuntu-latest 69 | needs: matrix-prep 70 | steps: 71 | - name: Install json2yaml 72 | run: | 73 | sudo npm install -g json2yaml 74 | 75 | - name: Check matrix definition 76 | run: | 77 | matrix='${{ needs.matrix-prep.outputs.matrix }}' 78 | echo $matrix 79 | echo $matrix | jq . 80 | echo $matrix | json2yaml 81 | 82 | builds: 83 | name: Building ${{ matrix.builds.name }} on ${{ matrix.builds.runs-on }} 84 | needs: matrix-prep 85 | continue-on-error: ${{ matrix.builds.best_effort || false }} 86 | strategy: 87 | fail-fast: false 88 | max-parallel: 5 89 | matrix: ${{ fromJson(needs.matrix-prep.outputs.matrix) }} 90 | 91 | runs-on: ${{ matrix.builds.runs-on }} 92 | 93 | steps: 94 | - name: Checkout source code 95 | uses: actions/checkout@v5 96 | with: 97 | submodules: recursive 98 | 99 | - name: Setup Rust toolchain 100 | uses: dtolnay/rust-toolchain@master 101 | with: 102 | components: rustfmt, clippy 103 | toolchain: ${{ matrix.builds.rust }} 104 | targets: ${{ matrix.builds.target }} 105 | 106 | - name: Install Linux dependencies - Ubuntu 107 | if: ${{ startsWith(runner.os,'Linux') && ( ! matrix.builds.cross ) }} 108 | run: | 109 | sudo apt-get update 110 | sudo bash scripts/install_ubuntu_dependencies.sh 111 | rustup target add ${{ matrix.builds.target }} 112 | 113 | - name: Install macOS dependencies 114 | if: startsWith(runner.os,'macOS') 115 | run: | 116 | # openssl, cmake and autoconf already installed 117 | # brew install zip coreutils automake protobuf 118 | brew install zip coreutils automake 119 | rustup target add ${{ matrix.builds.target }} 120 | 121 | - name: Install macOS-14 missing dependencies - hack 122 | if: ${{ startsWith(runner.os,'macOS') && startsWith(runner.arch,'ARM64') }} 123 | run: | 124 | brew install libtool 125 | 126 | - name: Install Windows dependencies 127 | if: startsWith(runner.os,'Windows') 128 | run: | 129 | # choco upgrade protoc -y 130 | rustup target add ${{ matrix.builds.target }} 131 | 132 | - name: Declare Android/iOS envs 133 | shell: bash 134 | run: | 135 | if [[ "${{ matrix.builds.target }}" =~ "android" ]]; then 136 | echo "CFLAGS=-DMDB_USE_ROBUST=0" >> $GITHUB_ENV 137 | echo "TARGET_PLATFORM=android" >> $GITHUB_ENV 138 | elif [[ "${{ matrix.builds.target }}" =~ "ios" ]]; then 139 | echo "TARGET_PLATFORM=ios" >> $GITHUB_ENV 140 | if [[ "${{ matrix.builds.target }}" =~ "-sim" ]]; then 141 | echo "TARGET_SIM=-sim" >> $GITHUB_ENV 142 | fi 143 | elif [[ "${{ matrix.builds.target }}" =~ "linux-gnu" ]]; then 144 | echo "TARGET_PLATFORM=linux" >> $GITHUB_ENV 145 | elif [[ "${{ matrix.builds.target }}" =~ "apple-darwin" ]]; then 146 | echo "TARGET_PLATFORM=macos" >> $GITHUB_ENV 147 | fi 148 | # Strip begining 149 | tempEnv="${{ matrix.builds.target }}" 150 | echo "TARGET_ARCH=${tempEnv/-*/}" >> $GITHUB_ENV 151 | # Strip outside of *_X_* 152 | tempEnv="${{ matrix.libffis }}" 153 | tempEnv=${tempEnv#*_} 154 | echo "TARGET_NAME=${tempEnv%_*}" >> $GITHUB_ENV 155 | 156 | - name: Cache cargo files and outputs 157 | if: ${{ ( ! startsWith(github.ref, 'refs/tags/v') ) && ( ! matrix.builds.cross ) && ( env.CARGO_CACHE ) }} 158 | uses: Swatinem/rust-cache@v2 159 | with: 160 | key: ${{ matrix.builds.runs-on }}-${{ matrix.builds.target }}-${{ matrix.builds.name }} 161 | 162 | - name: Install and setup cargo cross 163 | if: ${{ matrix.builds.cross }} 164 | shell: bash 165 | run: | 166 | #cargo install cross 167 | cargo install cross --git https://github.com/cross-rs/cross 168 | echo "CARGO=cross" >> $GITHUB_ENV 169 | 170 | - name: Setup build release targets 171 | # if: ${{ startsWith(github.ref, 'refs/tags/v') }} 172 | shell: bash 173 | run: | 174 | # echo "TS_BUILD=release" >> $GITHUB_ENV 175 | if [[ "${{ env.TS_BUILD }}" == "release" ]]; then 176 | echo "CARGO_OPTIONS=${{ env.CARGO_OPTIONS }} --${{ env.TS_BUILD }}" >> $GITHUB_ENV 177 | fi 178 | 179 | - name: Show command used for Cargo 180 | shell: bash 181 | run: | 182 | echo "cargo command is: ${{ env.CARGO }}" 183 | echo "cargo options is: ${{ env.CARGO_OPTIONS }}" 184 | echo "cross flag: ${{ matrix.builds.cross }}" 185 | 186 | - name: Build test for ${{ matrix.builds.target }} 187 | shell: bash 188 | run: | 189 | ${{ env.CARGO }} build ${{ env.CARGO_OPTIONS }} \ 190 | --target ${{ matrix.builds.target }} \ 191 | ${{ matrix.builds.flags }} 192 | -------------------------------------------------------------------------------- /src/bindings.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019. The Tari Project 2 | // 3 | // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the 4 | // following conditions are met: 5 | // 6 | // 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following 7 | // disclaimer. 8 | // 9 | // 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the 10 | // following disclaimer in the documentation and/or other materials provided with the distribution. 11 | // 12 | // 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote 13 | // products derived from this software without specific prior written permission. 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 16 | // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 18 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 20 | // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 21 | // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | 23 | use libc::{c_uint, c_ulong, c_void}; 24 | pub const RANDOMX_HASH_SIZE: u32 = 32; 25 | 26 | #[repr(C)] 27 | pub struct randomx_dataset { 28 | _unused: [u8; 0], 29 | } 30 | 31 | #[repr(C)] 32 | pub struct randomx_cache { 33 | _unused: [u8; 0], 34 | } 35 | 36 | #[repr(C)] 37 | pub struct randomx_vm { 38 | _unused: [u8; 0], 39 | } 40 | 41 | extern "C" { 42 | pub fn randomx_alloc_cache(flags: c_uint) -> *mut randomx_cache; 43 | pub fn randomx_init_cache(cache: *mut randomx_cache, key: *const c_void, keySize: usize); 44 | pub fn randomx_release_cache(cache: *mut randomx_cache); 45 | pub fn randomx_alloc_dataset(flags: c_uint) -> *mut randomx_dataset; 46 | pub fn randomx_dataset_item_count() -> c_ulong; 47 | pub fn randomx_init_dataset( 48 | dataset: *mut randomx_dataset, 49 | cache: *mut randomx_cache, 50 | start_item: c_ulong, 51 | item_count: c_ulong, 52 | ); 53 | pub fn randomx_get_dataset_memory(dataset: *mut randomx_dataset) -> *mut c_void; 54 | pub fn randomx_release_dataset(dataset: *mut randomx_dataset); 55 | pub fn randomx_create_vm( 56 | flags: c_uint, 57 | cache: *mut randomx_cache, 58 | dataset: *mut randomx_dataset, 59 | ) -> *mut randomx_vm; 60 | pub fn randomx_vm_set_cache(machine: *mut randomx_vm, cache: *mut randomx_cache); 61 | pub fn randomx_vm_set_dataset(machine: *mut randomx_vm, dataset: *mut randomx_dataset); 62 | pub fn randomx_destroy_vm(machine: *mut randomx_vm); 63 | pub fn randomx_calculate_hash( 64 | machine: *mut randomx_vm, 65 | input: *const c_void, 66 | input_size: usize, 67 | output: *mut c_void, 68 | ); 69 | pub fn randomx_calculate_hash_first(machine: *mut randomx_vm, input: *const c_void, input_size: usize); 70 | pub fn randomx_calculate_hash_next( 71 | machine: *mut randomx_vm, 72 | input_next: *const c_void, 73 | input_size_next: usize, 74 | output: *mut c_void, 75 | ); 76 | pub fn randomx_calculate_hash_last(machine: *mut randomx_vm, output: *mut c_void); 77 | pub fn randomx_get_flags() -> c_uint; 78 | } 79 | 80 | #[cfg(test)] 81 | mod tests { 82 | use std::ptr; 83 | 84 | use libc::{c_uint, c_void}; 85 | 86 | use super::*; 87 | 88 | #[test] 89 | fn alloc_cache() { 90 | let key = b"Key"; 91 | let flag: c_uint = 0; 92 | let cache = unsafe { randomx_alloc_cache(flag) }; 93 | assert!(!cache.is_null(), "Failed to init cache"); 94 | 95 | unsafe { 96 | randomx_init_cache(cache, key.as_ptr() as _, key.len()); 97 | } 98 | unsafe { 99 | randomx_release_cache(cache); 100 | } 101 | } 102 | 103 | #[test] 104 | fn alloc_dataset() { 105 | let key = b"Key"; 106 | let flag: c_uint = 0; 107 | let cache = unsafe { randomx_alloc_cache(flag) }; 108 | 109 | unsafe { 110 | randomx_init_cache(cache, key.as_ptr() as _, key.len()); 111 | } 112 | 113 | let dataset = unsafe { randomx_alloc_dataset(flag) }; 114 | 115 | unsafe { randomx_init_dataset(dataset, cache, 0, 1) }; 116 | 117 | assert_ne!(unsafe { randomx_dataset_item_count() }, 0); 118 | 119 | unsafe { 120 | randomx_release_dataset(dataset); 121 | randomx_release_cache(cache); 122 | } 123 | } 124 | 125 | #[test] 126 | fn alloc_vm() { 127 | let key = b"Key"; 128 | let flag: c_uint = 0; 129 | 130 | let cache = unsafe { randomx_alloc_cache(flag) }; 131 | 132 | unsafe { 133 | randomx_init_cache(cache, key.as_ptr() as _, key.len()); 134 | } 135 | let mut vm = unsafe { randomx_create_vm(flag, cache, ptr::null_mut()) }; 136 | if vm.is_null() { 137 | panic!("Failed to init vm with cache"); 138 | } 139 | unsafe { 140 | randomx_vm_set_cache(vm, cache); 141 | randomx_destroy_vm(vm); 142 | } 143 | 144 | let dataset = unsafe { randomx_alloc_dataset(flag) }; 145 | unsafe { randomx_init_dataset(dataset, cache, 0, 1) } 146 | 147 | vm = unsafe { randomx_create_vm(flag, cache, dataset) }; 148 | if vm.is_null() { 149 | panic!("Failed to init vm with dataset"); 150 | } 151 | unsafe { 152 | randomx_vm_set_dataset(vm, dataset); 153 | } 154 | 155 | unsafe { 156 | randomx_release_dataset(dataset); 157 | randomx_release_cache(cache); 158 | randomx_destroy_vm(vm); 159 | } 160 | } 161 | 162 | #[test] 163 | fn calculate_hash() { 164 | let key = b"test key 000"; 165 | let input = b"This is a test"; 166 | let expected = b"639183aae1bf4c9a35884cb46b09cad9175f04efd7684e7262a0ac1c2f0b4e3f"; 167 | 168 | let flag: c_uint = 0; 169 | 170 | let arr = [0u8; RANDOMX_HASH_SIZE as usize]; 171 | let output_ptr = arr.as_ptr() as *mut c_void; 172 | 173 | let cache = unsafe { randomx_alloc_cache(flag) }; 174 | 175 | unsafe { 176 | randomx_init_cache(cache, key.as_ptr() as _, key.len()); 177 | } 178 | 179 | let vm = unsafe { randomx_create_vm(flag, cache, ptr::null_mut()) }; 180 | 181 | unsafe { 182 | randomx_calculate_hash(vm, input.as_ptr() as _, input.len(), output_ptr); 183 | } 184 | assert_eq!(hex::decode(expected).unwrap(), arr); 185 | 186 | unsafe { 187 | randomx_destroy_vm(vm); 188 | randomx_release_cache(cache); 189 | } 190 | } 191 | 192 | #[allow(clippy::cast_sign_loss)] 193 | #[test] 194 | fn calculate_hash_set() { 195 | let key = b"test key 000"; 196 | let input = b"This is a test"; 197 | let expected = "639183aae1bf4c9a35884cb46b09cad9175f04efd7684e7262a0ac1c2f0b4e3f"; 198 | 199 | let input2 = b"Lorem ipsum dolor sit amet"; 200 | let expected2 = "300a0adb47603dedb42228ccb2b211104f4da45af709cd7547cd049e9489c969"; 201 | 202 | let input3 = b"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"; 203 | let expected3 = "c36d4ed4191e617309867ed66a443be4075014e2b061bcdaf9ce7b721d2b77a8"; 204 | 205 | let flag: c_uint = 0; 206 | 207 | let arr = [0u8; RANDOMX_HASH_SIZE as usize]; 208 | let output_ptr = arr.as_ptr() as *mut c_void; 209 | 210 | let cache = unsafe { randomx_alloc_cache(flag) }; 211 | 212 | unsafe { 213 | randomx_init_cache(cache, key.as_ptr() as _, key.len()); 214 | } 215 | 216 | let vm = unsafe { randomx_create_vm(flag, cache, ptr::null_mut()) }; 217 | 218 | unsafe { 219 | randomx_calculate_hash_first(vm, input.as_ptr() as _, input.len()); 220 | } 221 | 222 | unsafe { 223 | randomx_calculate_hash_next(vm, input2.as_ptr() as _, input2.len(), output_ptr); 224 | } 225 | assert_eq!(hex::decode(expected).unwrap(), arr); 226 | 227 | unsafe { 228 | randomx_calculate_hash_next(vm, input3.as_ptr() as _, input3.len(), output_ptr); 229 | } 230 | assert_eq!(hex::decode(expected2).unwrap(), arr); 231 | 232 | unsafe { 233 | randomx_calculate_hash_last(vm, output_ptr); 234 | } 235 | assert_eq!(hex::decode(expected3).unwrap(), arr); 236 | 237 | unsafe { 238 | randomx_destroy_vm(vm); 239 | randomx_release_cache(cache); 240 | } 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/test_utils.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019. The Tari Project 2 | // 3 | // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the 4 | // following conditions are met: 5 | // 6 | // 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following 7 | // disclaimer. 8 | // 9 | // 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the 10 | // following disclaimer in the documentation and/or other materials provided with the distribution. 11 | // 12 | // 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote 13 | // products derived from this software without specific prior written permission. 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 16 | // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 18 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 20 | // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 21 | // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | 23 | use crate::{RandomXCache, RandomXDataset, RandomXFlag, RandomXVM}; 24 | 25 | /// Fuzzing: 26 | /// - `pub fn randomx_alloc_cache` 27 | /// - `pub fn randomx_get_flags` 28 | /// - `pub fn randomx_init_cache` 29 | /// - `pub fn randomx_release_cache` 30 | #[allow(clippy::needless_pass_by_value)] // This is required by the `QuickCheck` fuzzing framework 31 | pub fn fuzz_randomx_alloc_cache(data: Vec) -> bool { 32 | let flags = if data.is_empty() { 33 | RandomXFlag::default() 34 | } else { 35 | match data[0] % 10 { 36 | 0 => RandomXFlag::get_recommended_flags(), 37 | 1 => RandomXFlag::FLAG_DEFAULT, 38 | 2 => RandomXFlag::FLAG_LARGE_PAGES, 39 | 3 => RandomXFlag::FLAG_HARD_AES, 40 | 4 => RandomXFlag::FLAG_FULL_MEM, 41 | 5 => RandomXFlag::FLAG_JIT, 42 | 6 => RandomXFlag::FLAG_SECURE, 43 | 7 => RandomXFlag::FLAG_ARGON2_SSSE3, 44 | 8 => RandomXFlag::FLAG_ARGON2_AVX2, 45 | _ => RandomXFlag::FLAG_ARGON2, 46 | } 47 | }; 48 | let _unused = RandomXCache::new(flags, &data); 49 | true 50 | } 51 | 52 | /// Fuzzing: 53 | /// - `pub fn randomx_create_vm` 54 | /// - `pub fn randomx_destroy_vm` 55 | /// - `pub fn randomx_vm_set_cache` 56 | /// - `pub fn randomx_alloc_cache` 57 | /// - `pub fn randomx_get_flags` 58 | /// - `pub fn randomx_init_cache` 59 | /// - `pub fn randomx_release_cache` 60 | #[allow(clippy::needless_pass_by_value)] // This is required by the `QuickCheck` fuzzing framework 61 | 62 | pub fn fuzz_randomx_create_vm_with_cache_only(data: Vec) -> bool { 63 | let flags = RandomXFlag::get_recommended_flags(); 64 | if let Ok(cache) = RandomXCache::new(flags, &data) { 65 | if let Ok(mut vm) = RandomXVM::new(flags, Some(cache.clone()), None) { 66 | let _unused = vm.reinit_cache(cache); 67 | } 68 | } 69 | true 70 | } 71 | 72 | /// Fuzzing: 73 | /// - `pub fn randomx_get_flags` 74 | /// - `pub fn randomx_create_vm` 75 | /// - `pub fn randomx_destroy_vm` 76 | /// - `pub fn randomx_dataset_item_count` 77 | /// - `pub fn randomx_alloc_cache` 78 | /// - `pub fn randomx_init_cache` 79 | /// - `pub fn randomx_release_cache` 80 | /// - `pub fn randomx_alloc_dataset` 81 | /// - `pub fn randomx_init_dataset` 82 | /// - `pub fn randomx_release_dataset` 83 | /// - `pub fn randomx_vm_set_cache` 84 | /// - `pub fn randomx_vm_set_dataset` 85 | /// - `pub fn randomx_dataset_item_count` 86 | /// - `pub fn randomx_get_dataset_memory` 87 | #[allow(clippy::needless_pass_by_value)] // This is required by the `QuickCheck` fuzzing framework 88 | pub fn fuzz_randomx_create_vm_with_cache_and_dataset(data: Vec) -> bool { 89 | let flags = RandomXFlag::get_recommended_flags(); 90 | if let Ok(cache) = RandomXCache::new(flags, &data) { 91 | let start = if data.is_empty() { 0u32 } else { u32::from(data[0] % 3) }; 92 | if let Ok(dataset) = RandomXDataset::new(flags, cache.clone(), start) { 93 | for _ in 0..100 { 94 | let _unused = dataset.get_data(); 95 | } 96 | if let Ok(mut vm) = RandomXVM::new(flags, Some(cache.clone()), Some(dataset.clone())) { 97 | let _unused = vm.reinit_cache(cache); 98 | let _unused = vm.reinit_dataset(dataset); 99 | } 100 | } 101 | } 102 | true 103 | } 104 | 105 | // Helper function to calculate hashes 106 | fn calculate_hashes(hash_data: &[u8], vm: &mut RandomXVM, iterations: u8) { 107 | let mut hash_data = hash_data.to_vec(); 108 | for _ in 0..iterations { 109 | if hash_data.len() > 1 { 110 | // Prepare hash input data 111 | let hash_set_len = 12; 112 | let mut hash_set = Vec::with_capacity(hash_set_len); 113 | for _ in 0..hash_set_len { 114 | let mut scratch_data = hash_data.clone(); 115 | let last = scratch_data.pop().unwrap(); 116 | scratch_data.insert(0, last); 117 | hash_set.push(scratch_data); 118 | } 119 | let hash_set_ref = hash_set.iter().map(|v| v.as_slice()).collect::>(); 120 | // Fuzz hash 121 | let _unused = vm.calculate_hash(&hash_data); 122 | let _unused = vm.calculate_hash_set(&hash_set_ref); 123 | // Change data set 124 | hash_data.pop(); 125 | } else { 126 | let _unused = vm.calculate_hash(&hash_data); 127 | let _unused = vm.calculate_hash_set(&[&hash_data]); 128 | } 129 | } 130 | } 131 | 132 | /// Fuzzing: 133 | /// - `pub fn randomx_calculate_hash` 134 | /// - `pub fn randomx_calculate_hash_last` 135 | /// - `pub fn randomx_calculate_hash_first` 136 | /// - `pub fn randomx_calculate_hash_next` 137 | /// Secondary: 138 | /// - `pub fn randomx_create_vm` 139 | /// - `pub fn randomx_destroy_vm` 140 | /// - `pub fn randomx_alloc_cache` 141 | /// - `pub fn randomx_get_flags` 142 | /// - `pub fn randomx_init_cache` 143 | /// - `pub fn randomx_release_cache` 144 | #[allow(clippy::needless_pass_by_value)] // This is required by the `QuickCheck` fuzzing framework 145 | pub fn fuzz_randomx_vm_calculate_hash_with_cache_only(data: Vec) -> bool { 146 | let flags = RandomXFlag::get_recommended_flags(); 147 | if let Ok(cache) = RandomXCache::new(flags, &data) { 148 | let vm = RandomXVM::new(flags, Some(cache), None); 149 | if let Ok(mut vm) = vm { 150 | calculate_hashes(&data, &mut vm, 100); 151 | } 152 | } 153 | true 154 | } 155 | 156 | /// Fuzzing: 157 | /// - `pub fn randomx_calculate_hash` 158 | /// - `pub fn randomx_calculate_hash_last` 159 | /// - `pub fn randomx_calculate_hash_first` 160 | /// - `pub fn randomx_calculate_hash_next` 161 | /// Secondary: 162 | /// - `pub fn randomx_create_vm` 163 | /// - `pub fn randomx_destroy_vm` 164 | /// - `pub fn randomx_alloc_cache` 165 | /// - `pub fn randomx_get_flags` 166 | /// - `pub fn randomx_init_cache` 167 | /// - `pub fn randomx_release_cache` 168 | /// - `pub fn randomx_alloc_dataset` 169 | /// - `pub fn randomx_init_dataset` 170 | /// - `pub fn randomx_dataset_item_count` 171 | /// - `pub fn randomx_get_dataset_memory` 172 | /// - `pub fn randomx_release_dataset` 173 | #[allow(clippy::needless_pass_by_value)] // This is required by the `QuickCheck` fuzzing framework 174 | pub fn fuzz_randomx_vm_calculate_hash_with_cache_and_dataset(data: Vec) -> bool { 175 | let flags = RandomXFlag::get_recommended_flags(); 176 | if let Ok(cache) = RandomXCache::new(flags, &data) { 177 | if let Ok(dataset) = RandomXDataset::new(flags, cache.clone(), 0) { 178 | let vm = RandomXVM::new(flags, Some(cache), Some(dataset.clone())); 179 | if let Ok(mut vm) = vm { 180 | calculate_hashes(&data, &mut vm, 100); 181 | } 182 | } 183 | } 184 | true 185 | } 186 | 187 | #[cfg(test)] 188 | mod tests { 189 | use quickcheck::QuickCheck; 190 | 191 | use crate::test_utils::{ 192 | fuzz_randomx_alloc_cache, 193 | fuzz_randomx_create_vm_with_cache_and_dataset, 194 | fuzz_randomx_create_vm_with_cache_only, 195 | fuzz_randomx_vm_calculate_hash_with_cache_and_dataset, 196 | fuzz_randomx_vm_calculate_hash_with_cache_only, 197 | }; 198 | 199 | #[test] 200 | fn test_fuzz_lib_alloc_cache() { 201 | fuzz_randomx_alloc_cache(vec![]); 202 | const TESTS: u64 = 25; 203 | QuickCheck::new() 204 | .min_tests_passed(TESTS) 205 | .tests(TESTS) 206 | .max_tests(TESTS) 207 | .quickcheck(fuzz_randomx_alloc_cache as fn(Vec) -> bool); 208 | } 209 | 210 | #[test] 211 | fn test_fuzz_randomx_create_vm_with_cache_only() { 212 | fuzz_randomx_create_vm_with_cache_only(vec![]); 213 | const TESTS: u64 = 25; 214 | QuickCheck::new() 215 | .min_tests_passed(TESTS) 216 | .tests(TESTS) 217 | .max_tests(TESTS) 218 | .quickcheck(fuzz_randomx_create_vm_with_cache_only as fn(Vec) -> bool); 219 | } 220 | 221 | #[test] 222 | fn test_fuzz_randomx_create_vm_with_cache_and_dataset() { 223 | fuzz_randomx_create_vm_with_cache_and_dataset(vec![]); 224 | const TESTS: u64 = 1; 225 | QuickCheck::new() 226 | .min_tests_passed(TESTS) 227 | .tests(TESTS) 228 | .max_tests(TESTS) 229 | .quickcheck(fuzz_randomx_create_vm_with_cache_and_dataset as fn(Vec) -> bool); 230 | } 231 | 232 | #[test] 233 | fn test_fuzz_randomx_vm_calculate_hash_with_cache_only() { 234 | fuzz_randomx_vm_calculate_hash_with_cache_only(vec![]); 235 | const TESTS: u64 = 3; 236 | QuickCheck::new() 237 | .min_tests_passed(TESTS) 238 | .tests(TESTS) 239 | .max_tests(TESTS) 240 | .quickcheck(fuzz_randomx_vm_calculate_hash_with_cache_only as fn(Vec) -> bool); 241 | } 242 | 243 | #[test] 244 | fn test_fuzz_randomx_vm_calculate_hash_with_cache_and_dataset() { 245 | fuzz_randomx_vm_calculate_hash_with_cache_and_dataset(vec![]); 246 | const TESTS: u64 = 1; 247 | QuickCheck::new() 248 | .min_tests_passed(TESTS) 249 | .tests(TESTS) 250 | .max_tests(TESTS) 251 | .quickcheck(fuzz_randomx_vm_calculate_hash_with_cache_and_dataset as fn(Vec) -> bool); 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019. The Tari Project 2 | // 3 | // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the 4 | // following conditions are met: 5 | // 6 | // 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following 7 | // disclaimer. 8 | // 9 | // 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the 10 | // following disclaimer in the documentation and/or other materials provided with the distribution. 11 | // 12 | // 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote 13 | // products derived from this software without specific prior written permission. 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 16 | // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 18 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 20 | // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 21 | // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | 23 | //! # RandomX 24 | //! 25 | //! The `randomx-rs` crate provides bindings to the RandomX proof-of-work (PoW) system. 26 | //! 27 | //! From the [RandomX github repo]: 28 | //! 29 | //! "RandomX is a proof-of-work (PoW) algorithm that is optimized for general-purpose CPUs. RandomX uses random code 30 | //! execution together with several memory-hard techniques to minimize the efficiency advantage of specialized 31 | //! hardware." 32 | //! 33 | //! Read more about how RandomX works in the [design document]. 34 | //! 35 | //! [RandomX github repo]: 36 | //! [design document]: 37 | mod bindings; 38 | /// Test utilities for fuzzing 39 | pub mod test_utils; 40 | 41 | use std::{convert::TryFrom, num::TryFromIntError, ptr, sync::Arc}; 42 | 43 | use bindings::{ 44 | randomx_alloc_cache, 45 | randomx_alloc_dataset, 46 | randomx_cache, 47 | randomx_calculate_hash, 48 | randomx_create_vm, 49 | randomx_dataset, 50 | randomx_dataset_item_count, 51 | randomx_destroy_vm, 52 | randomx_get_dataset_memory, 53 | randomx_init_cache, 54 | randomx_init_dataset, 55 | randomx_release_cache, 56 | randomx_release_dataset, 57 | randomx_vm, 58 | randomx_vm_set_cache, 59 | randomx_vm_set_dataset, 60 | RANDOMX_HASH_SIZE, 61 | }; 62 | use bitflags::bitflags; 63 | use libc::{c_ulong, c_void}; 64 | use thiserror::Error; 65 | 66 | use crate::bindings::{ 67 | randomx_calculate_hash_first, 68 | randomx_calculate_hash_last, 69 | randomx_calculate_hash_next, 70 | randomx_get_flags, 71 | }; 72 | 73 | bitflags! { 74 | /// RandomX Flags are used to configure the library. 75 | pub struct RandomXFlag: u32 { 76 | /// No flags set. Works on all platforms, but is the slowest. 77 | const FLAG_DEFAULT = 0b0000_0000; 78 | /// Allocate memory in large pages. 79 | const FLAG_LARGE_PAGES = 0b0000_0001; 80 | /// Use hardware accelerated AES. 81 | const FLAG_HARD_AES = 0b0000_0010; 82 | /// Use the full dataset. 83 | const FLAG_FULL_MEM = 0b0000_0100; 84 | /// Use JIT compilation support. 85 | const FLAG_JIT = 0b0000_1000; 86 | /// When combined with FLAG_JIT, the JIT pages are never writable and executable at the 87 | /// same time. 88 | const FLAG_SECURE = 0b0001_0000; 89 | /// Optimize Argon2 for CPUs with the SSSE3 instruction set. 90 | const FLAG_ARGON2_SSSE3 = 0b0010_0000; 91 | /// Optimize Argon2 for CPUs with the AVX2 instruction set. 92 | const FLAG_ARGON2_AVX2 = 0b0100_0000; 93 | /// Optimize Argon2 for CPUs without the AVX2 or SSSE3 instruction sets. 94 | const FLAG_ARGON2 = 0b0110_0000; 95 | } 96 | } 97 | 98 | impl RandomXFlag { 99 | /// Returns the recommended flags to be used. 100 | /// 101 | /// Does not include: 102 | /// * FLAG_LARGE_PAGES 103 | /// * FLAG_FULL_MEM 104 | /// * FLAG_SECURE 105 | /// 106 | /// The above flags need to be set manually, if required. 107 | pub fn get_recommended_flags() -> RandomXFlag { 108 | RandomXFlag { 109 | bits: unsafe { randomx_get_flags() }, 110 | } 111 | } 112 | } 113 | 114 | impl Default for RandomXFlag { 115 | /// Default value for RandomXFlag 116 | fn default() -> RandomXFlag { 117 | RandomXFlag::FLAG_DEFAULT 118 | } 119 | } 120 | 121 | #[derive(Debug, Clone, Error)] 122 | /// This enum specifies the possible errors that may occur. 123 | pub enum RandomXError { 124 | #[error("Problem creating the RandomX object: {0}")] 125 | CreationError(String), 126 | #[error("Problem with configuration flags: {0}")] 127 | FlagConfigError(String), 128 | #[error("Problem with parameters supplied: {0}")] 129 | ParameterError(String), 130 | #[error("Failed to convert Int to usize")] 131 | TryFromIntError(#[from] TryFromIntError), 132 | #[error("Unknown problem running RandomX: {0}")] 133 | Other(String), 134 | } 135 | 136 | #[derive(Debug)] 137 | struct RandomXCacheInner { 138 | cache_ptr: *mut randomx_cache, 139 | } 140 | 141 | impl Drop for RandomXCacheInner { 142 | /// De-allocates memory for the `cache` object 143 | fn drop(&mut self) { 144 | unsafe { 145 | randomx_release_cache(self.cache_ptr); 146 | } 147 | } 148 | } 149 | 150 | #[derive(Debug, Clone)] 151 | /// The Cache is used for light verification and Dataset construction. 152 | pub struct RandomXCache { 153 | inner: Arc, 154 | } 155 | 156 | impl RandomXCache { 157 | /// Creates and alllcates memory for a new cache object, and initializes it with 158 | /// the key value. 159 | /// 160 | /// `flags` is any combination of the following two flags: 161 | /// * FLAG_LARGE_PAGES 162 | /// * FLAG_JIT 163 | /// 164 | /// and (optionally) one of the following flags (depending on instruction set supported): 165 | /// * FLAG_ARGON2_SSSE3 166 | /// * FLAG_ARGON2_AVX2 167 | /// 168 | /// `key` is a sequence of u8 used to initialize SuperScalarHash. 169 | pub fn new(flags: RandomXFlag, key: &[u8]) -> Result { 170 | if key.is_empty() { 171 | Err(RandomXError::ParameterError("key is empty".to_string())) 172 | } else { 173 | let cache_ptr = unsafe { randomx_alloc_cache(flags.bits) }; 174 | if cache_ptr.is_null() { 175 | Err(RandomXError::CreationError("Could not allocate cache".to_string())) 176 | } else { 177 | let inner = RandomXCacheInner { cache_ptr }; 178 | let result = RandomXCache { inner: Arc::new(inner) }; 179 | let key_ptr = key.as_ptr() as *mut c_void; 180 | let key_size = key.len(); 181 | unsafe { 182 | randomx_init_cache(result.inner.cache_ptr, key_ptr, key_size); 183 | } 184 | Ok(result) 185 | } 186 | } 187 | } 188 | } 189 | 190 | #[derive(Debug)] 191 | struct RandomXDatasetInner { 192 | dataset_ptr: *mut randomx_dataset, 193 | dataset_count: u32, 194 | #[allow(dead_code)] 195 | cache: RandomXCache, 196 | } 197 | 198 | impl Drop for RandomXDatasetInner { 199 | /// De-allocates memory for the `dataset` object. 200 | fn drop(&mut self) { 201 | unsafe { 202 | randomx_release_dataset(self.dataset_ptr); 203 | } 204 | } 205 | } 206 | 207 | #[derive(Debug, Clone)] 208 | /// The Dataset is a read-only memory structure that is used during VM program execution. 209 | pub struct RandomXDataset { 210 | inner: Arc, 211 | } 212 | 213 | impl RandomXDataset { 214 | /// Creates a new dataset object, allocates memory to the `dataset` object and initializes it. 215 | /// 216 | /// `flags` is one of the following: 217 | /// * FLAG_DEFAULT 218 | /// * FLAG_LARGE_PAGES 219 | /// 220 | /// `cache` is a cache object. 221 | /// 222 | /// `start` is the item number where initialization should start, recommended to pass in 0. 223 | // Conversions may be lossy on Windows or Linux 224 | #[allow(clippy::useless_conversion)] 225 | pub fn new(flags: RandomXFlag, cache: RandomXCache, start: u32) -> Result { 226 | let item_count = RandomXDataset::count() 227 | .map_err(|e| RandomXError::CreationError(format!("Could not get dataset count: {e:?}")))?; 228 | 229 | let test = unsafe { randomx_alloc_dataset(flags.bits) }; 230 | if test.is_null() { 231 | Err(RandomXError::CreationError("Could not allocate dataset".to_string())) 232 | } else { 233 | let inner = RandomXDatasetInner { 234 | dataset_ptr: test, 235 | dataset_count: item_count, 236 | cache, 237 | }; 238 | let result = RandomXDataset { inner: Arc::new(inner) }; 239 | 240 | if start < item_count { 241 | unsafe { 242 | randomx_init_dataset( 243 | result.inner.dataset_ptr, 244 | result.inner.cache.inner.cache_ptr, 245 | c_ulong::from(start), 246 | c_ulong::from(item_count), 247 | ); 248 | } 249 | Ok(result) 250 | } else { 251 | Err(RandomXError::CreationError(format!( 252 | "start must be less than item_count: start: {start}, item_count: {item_count}", 253 | ))) 254 | } 255 | } 256 | } 257 | 258 | /// Returns the number of items in the `dataset` or an error on failure. 259 | pub fn count() -> Result { 260 | match unsafe { randomx_dataset_item_count() } { 261 | 0 => Err(RandomXError::Other("Dataset item count was 0".to_string())), 262 | x => { 263 | // This weirdness brought to you by c_ulong being different on Windows and Linux 264 | #[cfg(target_os = "windows")] 265 | return Ok(x); 266 | #[cfg(not(target_os = "windows"))] 267 | return Ok(u32::try_from(x)?); 268 | }, 269 | } 270 | } 271 | 272 | /// Returns the values of the internal memory buffer of the `dataset` or an error on failure. 273 | pub fn get_data(&self) -> Result, RandomXError> { 274 | let memory = unsafe { randomx_get_dataset_memory(self.inner.dataset_ptr) }; 275 | if memory.is_null() { 276 | Err(RandomXError::Other("Could not get dataset memory".into())) 277 | } else { 278 | let count = usize::try_from(self.inner.dataset_count)?; 279 | let mut result: Vec = vec![0u8; count]; 280 | let n = usize::try_from(self.inner.dataset_count)?; 281 | unsafe { 282 | libc::memcpy(result.as_mut_ptr() as *mut c_void, memory, n); 283 | } 284 | Ok(result) 285 | } 286 | } 287 | } 288 | 289 | #[derive(Debug)] 290 | /// The RandomX Virtual Machine (VM) is a complex instruction set computer that executes generated programs. 291 | pub struct RandomXVM { 292 | flags: RandomXFlag, 293 | vm: *mut randomx_vm, 294 | linked_cache: Option, 295 | linked_dataset: Option, 296 | } 297 | 298 | impl Drop for RandomXVM { 299 | /// De-allocates memory for the `VM` object. 300 | fn drop(&mut self) { 301 | unsafe { 302 | randomx_destroy_vm(self.vm); 303 | } 304 | } 305 | } 306 | 307 | impl RandomXVM { 308 | /// Creates a new `VM` and initializes it, error on failure. 309 | /// 310 | /// `flags` is any combination of the following 5 flags: 311 | /// * FLAG_LARGE_PAGES 312 | /// * FLAG_HARD_AES 313 | /// * FLAG_FULL_MEM 314 | /// * FLAG_JIT 315 | /// * FLAG_SECURE 316 | /// 317 | /// Or 318 | /// 319 | /// * FLAG_DEFAULT 320 | /// 321 | /// `cache` is a cache object, optional if FLAG_FULL_MEM is set. 322 | /// 323 | /// `dataset` is a dataset object, optional if FLAG_FULL_MEM is not set. 324 | pub fn new( 325 | flags: RandomXFlag, 326 | cache: Option, 327 | dataset: Option, 328 | ) -> Result { 329 | let is_full_mem = flags.contains(RandomXFlag::FLAG_FULL_MEM); 330 | match (cache, dataset) { 331 | (None, None) => Err(RandomXError::CreationError("Failed to allocate VM".to_string())), 332 | (None, _) if !is_full_mem => Err(RandomXError::FlagConfigError( 333 | "No cache and FLAG_FULL_MEM not set".to_string(), 334 | )), 335 | (_, None) if is_full_mem => Err(RandomXError::FlagConfigError( 336 | "No dataset and FLAG_FULL_MEM set".to_string(), 337 | )), 338 | (cache, dataset) => { 339 | let cache_ptr = cache 340 | .as_ref() 341 | .map(|stash| stash.inner.cache_ptr) 342 | .unwrap_or_else(ptr::null_mut); 343 | let dataset_ptr = dataset 344 | .as_ref() 345 | .map(|data| data.inner.dataset_ptr) 346 | .unwrap_or_else(ptr::null_mut); 347 | let vm = unsafe { randomx_create_vm(flags.bits, cache_ptr, dataset_ptr) }; 348 | Ok(RandomXVM { 349 | vm, 350 | flags, 351 | linked_cache: cache, 352 | linked_dataset: dataset, 353 | }) 354 | }, 355 | } 356 | } 357 | 358 | /// Re-initializes the `VM` with a new cache that was initialised without 359 | /// RandomXFlag::FLAG_FULL_MEM. 360 | pub fn reinit_cache(&mut self, cache: RandomXCache) -> Result<(), RandomXError> { 361 | if self.flags.contains(RandomXFlag::FLAG_FULL_MEM) { 362 | Err(RandomXError::FlagConfigError( 363 | "Cannot reinit cache with FLAG_FULL_MEM set".to_string(), 364 | )) 365 | } else { 366 | unsafe { 367 | randomx_vm_set_cache(self.vm, cache.inner.cache_ptr); 368 | } 369 | self.linked_cache = Some(cache); 370 | Ok(()) 371 | } 372 | } 373 | 374 | /// Re-initializes the `VM` with a new dataset that was initialised with 375 | /// RandomXFlag::FLAG_FULL_MEM. 376 | pub fn reinit_dataset(&mut self, dataset: RandomXDataset) -> Result<(), RandomXError> { 377 | if self.flags.contains(RandomXFlag::FLAG_FULL_MEM) { 378 | unsafe { 379 | randomx_vm_set_dataset(self.vm, dataset.inner.dataset_ptr); 380 | } 381 | self.linked_dataset = Some(dataset); 382 | Ok(()) 383 | } else { 384 | Err(RandomXError::FlagConfigError( 385 | "Cannot reinit dataset without FLAG_FULL_MEM set".to_string(), 386 | )) 387 | } 388 | } 389 | 390 | /// Calculates a RandomX hash value and returns it, error on failure. 391 | /// 392 | /// `input` is a sequence of u8 to be hashed. 393 | pub fn calculate_hash(&self, input: &[u8]) -> Result, RandomXError> { 394 | if input.is_empty() { 395 | Err(RandomXError::ParameterError("input was empty".to_string())) 396 | } else { 397 | let size_input = input.len(); 398 | let input_ptr = input.as_ptr() as *mut c_void; 399 | let arr = [0; RANDOMX_HASH_SIZE as usize]; 400 | let output_ptr = arr.as_ptr() as *mut c_void; 401 | unsafe { 402 | randomx_calculate_hash(self.vm, input_ptr, size_input, output_ptr); 403 | } 404 | // if this failed, arr should still be empty 405 | if arr == [0; RANDOMX_HASH_SIZE as usize] { 406 | Err(RandomXError::Other("RandomX calculated hash was empty".to_string())) 407 | } else { 408 | let result = arr.to_vec(); 409 | Ok(result) 410 | } 411 | } 412 | } 413 | 414 | /// Calculates hashes from a set of inputs. 415 | /// 416 | /// `input` is an array of a sequence of u8 to be hashed. 417 | #[allow(clippy::needless_range_loop)] // Range loop is not only for indexing `input` 418 | pub fn calculate_hash_set(&self, input: &[&[u8]]) -> Result>, RandomXError> { 419 | if input.is_empty() { 420 | // Empty set 421 | return Err(RandomXError::ParameterError("input was empty".to_string())); 422 | } 423 | 424 | let mut result = Vec::new(); 425 | // For single input 426 | if input.len() == 1 { 427 | let hash = self.calculate_hash(input[0])?; 428 | result.push(hash); 429 | return Ok(result); 430 | } 431 | 432 | // For multiple inputs 433 | let mut output_ptr: *mut c_void = ptr::null_mut(); 434 | let arr = [0; RANDOMX_HASH_SIZE as usize]; 435 | 436 | // Not len() as last iteration assigns final hash 437 | let iterations = input.len() + 1; 438 | for i in 0..iterations { 439 | if i == iterations - 1 { 440 | // For last iteration 441 | unsafe { 442 | randomx_calculate_hash_last(self.vm, output_ptr); 443 | } 444 | } else { 445 | if input[i].is_empty() { 446 | // Stop calculations 447 | if arr != [0; RANDOMX_HASH_SIZE as usize] { 448 | // Complete what was started 449 | unsafe { 450 | randomx_calculate_hash_last(self.vm, output_ptr); 451 | } 452 | } 453 | return Err(RandomXError::ParameterError("input was empty".to_string())); 454 | }; 455 | let size_input = input[i].len(); 456 | let input_ptr = input[i].as_ptr() as *mut c_void; 457 | output_ptr = arr.as_ptr() as *mut c_void; 458 | if i == 0 { 459 | // For first iteration 460 | unsafe { 461 | randomx_calculate_hash_first(self.vm, input_ptr, size_input); 462 | } 463 | } else { 464 | unsafe { 465 | // For every other iteration 466 | randomx_calculate_hash_next(self.vm, input_ptr, size_input, output_ptr); 467 | } 468 | } 469 | } 470 | 471 | if i != 0 { 472 | // First hash is only available in 2nd iteration 473 | if arr == [0; RANDOMX_HASH_SIZE as usize] { 474 | return Err(RandomXError::Other("RandomX hash was zero".to_string())); 475 | } 476 | let output: Vec = arr.to_vec(); 477 | result.push(output); 478 | } 479 | } 480 | Ok(result) 481 | } 482 | } 483 | 484 | #[cfg(test)] 485 | mod tests { 486 | use std::{ptr, sync::Arc}; 487 | 488 | use crate::{RandomXCache, RandomXCacheInner, RandomXDataset, RandomXDatasetInner, RandomXFlag, RandomXVM}; 489 | 490 | #[test] 491 | fn lib_alloc_cache() { 492 | let flags = RandomXFlag::default(); 493 | let key = "Key"; 494 | let cache = RandomXCache::new(flags, key.as_bytes()).expect("Failed to allocate cache"); 495 | drop(cache); 496 | } 497 | 498 | #[test] 499 | fn lib_alloc_dataset() { 500 | let flags = RandomXFlag::default(); 501 | let key = "Key"; 502 | let cache = RandomXCache::new(flags, key.as_bytes()).unwrap(); 503 | let dataset = RandomXDataset::new(flags, cache.clone(), 0).expect("Failed to allocate dataset"); 504 | drop(dataset); 505 | drop(cache); 506 | } 507 | 508 | #[test] 509 | fn lib_alloc_vm() { 510 | let flags = RandomXFlag::default(); 511 | let key = "Key"; 512 | let cache = RandomXCache::new(flags, key.as_bytes()).unwrap(); 513 | let mut vm = RandomXVM::new(flags, Some(cache.clone()), None).expect("Failed to allocate VM"); 514 | drop(vm); 515 | let dataset = RandomXDataset::new(flags, cache.clone(), 0).unwrap(); 516 | vm = RandomXVM::new(flags, Some(cache.clone()), Some(dataset.clone())).expect("Failed to allocate VM"); 517 | drop(dataset); 518 | drop(cache); 519 | drop(vm); 520 | } 521 | 522 | #[test] 523 | fn lib_dataset_memory() { 524 | let flags = RandomXFlag::default(); 525 | let key = "Key"; 526 | let cache = RandomXCache::new(flags, key.as_bytes()).unwrap(); 527 | let dataset = RandomXDataset::new(flags, cache.clone(), 0).unwrap(); 528 | let memory = dataset.get_data().unwrap_or_else(|_| std::vec::Vec::new()); 529 | assert!(!memory.is_empty(), "Failed to get dataset memory"); 530 | let vec = vec![0u8; memory.len()]; 531 | assert_ne!(memory, vec); 532 | drop(dataset); 533 | drop(cache); 534 | } 535 | 536 | #[test] 537 | fn test_null_assignments() { 538 | let flags = RandomXFlag::get_recommended_flags(); 539 | if let Ok(mut vm) = RandomXVM::new(flags, None, None) { 540 | let cache = RandomXCache { 541 | inner: Arc::new(RandomXCacheInner { 542 | cache_ptr: ptr::null_mut(), 543 | }), 544 | }; 545 | assert!(vm.reinit_cache(cache.clone()).is_err()); 546 | let dataset = RandomXDataset { 547 | inner: Arc::new(RandomXDatasetInner { 548 | dataset_ptr: ptr::null_mut(), 549 | dataset_count: 0, 550 | cache, 551 | }), 552 | }; 553 | assert!(vm.reinit_dataset(dataset.clone()).is_err()); 554 | } 555 | } 556 | 557 | #[test] 558 | fn lib_calculate_hash() { 559 | let flags = RandomXFlag::get_recommended_flags(); 560 | let flags2 = flags | RandomXFlag::FLAG_FULL_MEM; 561 | let key = "Key"; 562 | let input = "Input"; 563 | let cache1 = RandomXCache::new(flags, key.as_bytes()).unwrap(); 564 | let mut vm1 = RandomXVM::new(flags, Some(cache1.clone()), None).unwrap(); 565 | let hash1 = vm1.calculate_hash(input.as_bytes()).expect("no data"); 566 | let vec = vec![0u8; hash1.len()]; 567 | assert_ne!(hash1, vec); 568 | let reinit_cache = vm1.reinit_cache(cache1.clone()); 569 | assert!(reinit_cache.is_ok()); 570 | let hash2 = vm1.calculate_hash(input.as_bytes()).expect("no data"); 571 | assert_ne!(hash2, vec); 572 | assert_eq!(hash1, hash2); 573 | 574 | let cache2 = RandomXCache::new(flags, key.as_bytes()).unwrap(); 575 | let vm2 = RandomXVM::new(flags, Some(cache2.clone()), None).unwrap(); 576 | let hash3 = vm2.calculate_hash(input.as_bytes()).expect("no data"); 577 | assert_eq!(hash2, hash3); 578 | 579 | let cache3 = RandomXCache::new(flags, key.as_bytes()).unwrap(); 580 | let dataset3 = RandomXDataset::new(flags, cache3.clone(), 0).unwrap(); 581 | let mut vm3 = RandomXVM::new(flags2, None, Some(dataset3.clone())).unwrap(); 582 | let hash4 = vm3.calculate_hash(input.as_bytes()).expect("no data"); 583 | assert_ne!(hash3, vec); 584 | let reinit_dataset = vm3.reinit_dataset(dataset3.clone()); 585 | assert!(reinit_dataset.is_ok()); 586 | let hash5 = vm3.calculate_hash(input.as_bytes()).expect("no data"); 587 | assert_ne!(hash4, vec); 588 | assert_eq!(hash4, hash5); 589 | 590 | let cache4 = RandomXCache::new(flags, key.as_bytes()).unwrap(); 591 | let dataset4 = RandomXDataset::new(flags, cache4.clone(), 0).unwrap(); 592 | let vm4 = RandomXVM::new(flags2, Some(cache4), Some(dataset4.clone())).unwrap(); 593 | let hash6 = vm3.calculate_hash(input.as_bytes()).expect("no data"); 594 | assert_eq!(hash5, hash6); 595 | 596 | drop(dataset3); 597 | drop(dataset4); 598 | drop(cache1); 599 | drop(cache2); 600 | drop(cache3); 601 | drop(vm1); 602 | drop(vm2); 603 | drop(vm3); 604 | drop(vm4); 605 | } 606 | 607 | #[test] 608 | fn lib_calculate_hash_set() { 609 | let flags = RandomXFlag::default(); 610 | let key = "Key"; 611 | let inputs = vec!["Input".as_bytes(), "Input 2".as_bytes(), "Inputs 3".as_bytes()]; 612 | let cache = RandomXCache::new(flags, key.as_bytes()).unwrap(); 613 | let vm = RandomXVM::new(flags, Some(cache.clone()), None).unwrap(); 614 | let hashes = vm.calculate_hash_set(inputs.as_slice()).expect("no data"); 615 | assert_eq!(inputs.len(), hashes.len()); 616 | let mut prev_hash = Vec::new(); 617 | for (i, hash) in hashes.into_iter().enumerate() { 618 | let vec = vec![0u8; hash.len()]; 619 | assert_ne!(hash, vec); 620 | assert_ne!(hash, prev_hash); 621 | let compare = vm.calculate_hash(inputs[i]).unwrap(); // sanity check 622 | assert_eq!(hash, compare); 623 | prev_hash = hash; 624 | } 625 | drop(cache); 626 | drop(vm); 627 | } 628 | 629 | #[test] 630 | fn lib_calculate_hash_is_consistent() { 631 | let flags = RandomXFlag::get_recommended_flags(); 632 | let key = "Key"; 633 | let input = "Input"; 634 | let cache = RandomXCache::new(flags, key.as_bytes()).unwrap(); 635 | let dataset = RandomXDataset::new(flags, cache.clone(), 0).unwrap(); 636 | let vm = RandomXVM::new(flags, Some(cache.clone()), Some(dataset.clone())).unwrap(); 637 | let hash = vm.calculate_hash(input.as_bytes()).expect("no data"); 638 | assert_eq!(hash, [ 639 | 114, 81, 192, 5, 165, 242, 107, 100, 184, 77, 37, 129, 52, 203, 217, 227, 65, 83, 215, 213, 59, 71, 32, 640 | 172, 253, 155, 204, 111, 183, 213, 157, 155 641 | ]); 642 | drop(vm); 643 | drop(dataset); 644 | drop(cache); 645 | 646 | let cache1 = RandomXCache::new(flags, key.as_bytes()).unwrap(); 647 | let dataset1 = RandomXDataset::new(flags, cache1.clone(), 0).unwrap(); 648 | let vm1 = RandomXVM::new(flags, Some(cache1.clone()), Some(dataset1.clone())).unwrap(); 649 | let hash1 = vm1.calculate_hash(input.as_bytes()).expect("no data"); 650 | assert_eq!(hash1, [ 651 | 114, 81, 192, 5, 165, 242, 107, 100, 184, 77, 37, 129, 52, 203, 217, 227, 65, 83, 215, 213, 59, 71, 32, 652 | 172, 253, 155, 204, 111, 183, 213, 157, 155 653 | ]); 654 | drop(vm1); 655 | drop(dataset1); 656 | drop(cache1); 657 | } 658 | 659 | #[test] 660 | fn lib_check_cache_and_dataset_lifetimes() { 661 | let flags = RandomXFlag::get_recommended_flags(); 662 | let key = "Key"; 663 | let input = "Input"; 664 | let cache = RandomXCache::new(flags, key.as_bytes()).unwrap(); 665 | let dataset = RandomXDataset::new(flags, cache.clone(), 0).unwrap(); 666 | let vm = RandomXVM::new(flags, Some(cache.clone()), Some(dataset.clone())).unwrap(); 667 | drop(dataset); 668 | drop(cache); 669 | let hash = vm.calculate_hash(input.as_bytes()).expect("no data"); 670 | assert_eq!(hash, [ 671 | 114, 81, 192, 5, 165, 242, 107, 100, 184, 77, 37, 129, 52, 203, 217, 227, 65, 83, 215, 213, 59, 71, 32, 672 | 172, 253, 155, 204, 111, 183, 213, 157, 155 673 | ]); 674 | drop(vm); 675 | 676 | let cache1 = RandomXCache::new(flags, key.as_bytes()).unwrap(); 677 | let dataset1 = RandomXDataset::new(flags, cache1.clone(), 0).unwrap(); 678 | let vm1 = RandomXVM::new(flags, Some(cache1.clone()), Some(dataset1.clone())).unwrap(); 679 | drop(dataset1); 680 | drop(cache1); 681 | let hash1 = vm1.calculate_hash(input.as_bytes()).expect("no data"); 682 | assert_eq!(hash1, [ 683 | 114, 81, 192, 5, 165, 242, 107, 100, 184, 77, 37, 129, 52, 203, 217, 227, 65, 83, 215, 213, 59, 71, 32, 684 | 172, 253, 155, 204, 111, 183, 213, 157, 155 685 | ]); 686 | drop(vm1); 687 | } 688 | 689 | #[test] 690 | fn randomx_hash_fast_vs_light() { 691 | let input = b"input"; 692 | let key = b"key"; 693 | 694 | let flags = RandomXFlag::get_recommended_flags() | RandomXFlag::FLAG_FULL_MEM; 695 | let cache = RandomXCache::new(flags, key).unwrap(); 696 | let dataset = RandomXDataset::new(flags, cache, 0).unwrap(); 697 | let fast_vm = RandomXVM::new(flags, None, Some(dataset)).unwrap(); 698 | 699 | let flags = RandomXFlag::get_recommended_flags(); 700 | let cache = RandomXCache::new(flags, key).unwrap(); 701 | let light_vm = RandomXVM::new(flags, Some(cache), None).unwrap(); 702 | 703 | let fast = fast_vm.calculate_hash(input).unwrap(); 704 | let light = light_vm.calculate_hash(input).unwrap(); 705 | assert_eq!(fast, light); 706 | } 707 | 708 | #[test] 709 | fn test_vectors_fast_mode() { 710 | // test vectors from https://github.com/tevador/RandomX/blob/040f4500a6e79d54d84a668013a94507045e786f/src/tests/tests.cpp#L963-L979 711 | let key = b"test key 000"; 712 | let vectors = [ 713 | ( 714 | b"This is a test".as_slice(), 715 | "639183aae1bf4c9a35884cb46b09cad9175f04efd7684e7262a0ac1c2f0b4e3f", 716 | ), 717 | ( 718 | b"Lorem ipsum dolor sit amet".as_slice(), 719 | "300a0adb47603dedb42228ccb2b211104f4da45af709cd7547cd049e9489c969", 720 | ), 721 | ( 722 | b"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua".as_slice(), 723 | "c36d4ed4191e617309867ed66a443be4075014e2b061bcdaf9ce7b721d2b77a8", 724 | ), 725 | ]; 726 | 727 | let flags = RandomXFlag::get_recommended_flags() | RandomXFlag::FLAG_FULL_MEM; 728 | let cache = RandomXCache::new(flags, key).unwrap(); 729 | let dataset = RandomXDataset::new(flags, cache, 0).unwrap(); 730 | let vm = RandomXVM::new(flags, None, Some(dataset)).unwrap(); 731 | 732 | for (input, expected) in vectors { 733 | let hash = vm.calculate_hash(input).unwrap(); 734 | assert_eq!(hex::decode(expected).unwrap(), hash); 735 | } 736 | } 737 | 738 | #[test] 739 | fn test_vectors_light_mode() { 740 | // test vectors from https://github.com/tevador/RandomX/blob/040f4500a6e79d54d84a668013a94507045e786f/src/tests/tests.cpp#L963-L985 741 | let vectors = [ 742 | ( 743 | b"test key 000", 744 | b"This is a test".as_slice(), 745 | "639183aae1bf4c9a35884cb46b09cad9175f04efd7684e7262a0ac1c2f0b4e3f", 746 | ), 747 | ( 748 | b"test key 000", 749 | b"Lorem ipsum dolor sit amet".as_slice(), 750 | "300a0adb47603dedb42228ccb2b211104f4da45af709cd7547cd049e9489c969", 751 | ), 752 | ( 753 | b"test key 000", 754 | b"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua".as_slice(), 755 | "c36d4ed4191e617309867ed66a443be4075014e2b061bcdaf9ce7b721d2b77a8", 756 | ), 757 | ( 758 | b"test key 001", 759 | b"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua".as_slice(), 760 | "e9ff4503201c0c2cca26d285c93ae883f9b1d30c9eb240b820756f2d5a7905fc", 761 | ), 762 | ]; 763 | 764 | let flags = RandomXFlag::get_recommended_flags(); 765 | for (key, input, expected) in vectors { 766 | let cache = RandomXCache::new(flags, key).unwrap(); 767 | let vm = RandomXVM::new(flags, Some(cache), None).unwrap(); 768 | let hash = vm.calculate_hash(input).unwrap(); 769 | assert_eq!(hex::decode(expected).unwrap(), hash); 770 | } 771 | } 772 | } 773 | --------------------------------------------------------------------------------