diff --git a/.cargo/config.toml b/.cargo/config.toml index b082c8bf8c2b1e082c654a0e79fa26edad066a8d..0259bfb0c6e81bc053c4ea36760bc334d38683dc 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -11,3 +11,6 @@ rustflags = [ "-C", "default-linker-libraries", ] + +[profile.release] +lto = "off" \ No newline at end of file diff --git a/.gitlab/ci/pipelines/before_merging.yml b/.gitlab/ci/pipelines/before_merging.yml index a719b0e5fff28fa86f5ba71ec76a313497ee4b8b..a76738ddb43aa7e999474a3774b875007a70751c 100644 --- a/.gitlab/ci/pipelines/before_merging.yml +++ b/.gitlab/ci/pipelines/before_merging.yml @@ -2023,6 +2023,11 @@ oc.script:test-gen-genesis: dependencies: - oc.docker:ci:amd64 timeout: 60 minutes + cache: + key: cargo-$CI_JOB_NAME_SLUG + paths: + - $CI_PROJECT_DIR/.cargo/registry/cache + policy: pull-push interruptible: true before_script: - SCRIPT_STEP_BEGIN=$(date +%s) @@ -2034,6 +2039,8 @@ oc.script:test-gen-genesis: - . ./scripts/ci/datadog_send_job_script_step_time.sh || true after_script: - . ./scripts/ci/datadog_send_job_cache_info.sh 'after' + variables: + CARGO_NET_OFFLINE: "false" oc.script:snapshot_alpha_and_link: image: ${ci_image_name}/build:${ci_image_tag} @@ -2320,6 +2327,11 @@ oc.unit:webassembly-x86_64: dependencies: - oc.docker:ci:amd64 timeout: 20 minutes + cache: + key: cargo-$CI_JOB_NAME_SLUG + paths: + - $CI_PROJECT_DIR/.cargo/registry/cache + policy: pull-push interruptible: true before_script: - SCRIPT_STEP_BEGIN=$(date +%s) @@ -2332,6 +2344,8 @@ oc.unit:webassembly-x86_64: - . ./scripts/ci/datadog_send_job_script_step_time.sh || true after_script: - . ./scripts/ci/datadog_send_job_cache_info.sh 'after' + variables: + CARGO_NET_OFFLINE: "false" oc.unit:non-proto-x86_64: image: ${ci_image_name}/test:${ci_image_tag} diff --git a/.gitlab/ci/pipelines/merge_train.yml b/.gitlab/ci/pipelines/merge_train.yml index 65489360a969134d6a283e953d7c8c520b2b06fc..544e4bdc3c4289794aab43224ab740724ada79a1 100644 --- a/.gitlab/ci/pipelines/merge_train.yml +++ b/.gitlab/ci/pipelines/merge_train.yml @@ -1938,6 +1938,11 @@ oc.script:test-gen-genesis: dependencies: - oc.docker:ci:amd64 timeout: 60 minutes + cache: + key: cargo-$CI_JOB_NAME_SLUG + paths: + - $CI_PROJECT_DIR/.cargo/registry/cache + policy: pull-push interruptible: true before_script: - SCRIPT_STEP_BEGIN=$(date +%s) @@ -1949,6 +1954,8 @@ oc.script:test-gen-genesis: - . ./scripts/ci/datadog_send_job_script_step_time.sh || true after_script: - . ./scripts/ci/datadog_send_job_cache_info.sh 'after' + variables: + CARGO_NET_OFFLINE: "false" oc.script:snapshot_alpha_and_link: image: ${ci_image_name}/build:${ci_image_tag} @@ -2228,6 +2235,11 @@ oc.unit:webassembly-x86_64: dependencies: - oc.docker:ci:amd64 timeout: 20 minutes + cache: + key: cargo-$CI_JOB_NAME_SLUG + paths: + - $CI_PROJECT_DIR/.cargo/registry/cache + policy: pull-push interruptible: true before_script: - SCRIPT_STEP_BEGIN=$(date +%s) @@ -2240,6 +2252,8 @@ oc.unit:webassembly-x86_64: - . ./scripts/ci/datadog_send_job_script_step_time.sh || true after_script: - . ./scripts/ci/datadog_send_job_cache_info.sh 'after' + variables: + CARGO_NET_OFFLINE: "false" oc.unit:non-proto-x86_64: image: ${ci_image_name}/test:${ci_image_tag} diff --git a/.gitlab/ci/pipelines/opam.daily.yml b/.gitlab/ci/pipelines/opam.daily.yml index 8368eb9f0551b2676133dcfd9bfc870437cf0437..7c20bf93f1931e7d33e2a52d02a04bfb3492e43c 100644 --- a/.gitlab/ci/pipelines/opam.daily.yml +++ b/.gitlab/ci/pipelines/opam.daily.yml @@ -762,8 +762,10 @@ opam:all_7: - octez-rust-deps - octez-riscv-api - octez-protocol-compiler-compat + - octez-ml-dsa - octez-lwt-domain - octez-libs + - octez-libcrux-ml-dsa - octez-lib-upnp-args - octez-internal-libs - octez-igd-next diff --git a/.gitlab/ci/pipelines/schedule_extended_test.yml b/.gitlab/ci/pipelines/schedule_extended_test.yml index b06ddccf5583364bcc95de0841dac5e90713f07c..ef5bdd0a7992819979a0c539b9529f8687269fd2 100644 --- a/.gitlab/ci/pipelines/schedule_extended_test.yml +++ b/.gitlab/ci/pipelines/schedule_extended_test.yml @@ -1266,6 +1266,11 @@ oc.script:test-gen-genesis: dependencies: - oc.docker:ci:amd64 timeout: 60 minutes + cache: + key: cargo-$CI_JOB_NAME_SLUG + paths: + - $CI_PROJECT_DIR/.cargo/registry/cache + policy: pull-push interruptible: false before_script: - SCRIPT_STEP_BEGIN=$(date +%s) @@ -1277,6 +1282,8 @@ oc.script:test-gen-genesis: - . ./scripts/ci/datadog_send_job_script_step_time.sh || true after_script: - . ./scripts/ci/datadog_send_job_cache_info.sh 'after' + variables: + CARGO_NET_OFFLINE: "false" oc.script:snapshot_alpha_and_link: image: ${ci_image_name}/build:${ci_image_tag} @@ -1473,6 +1480,11 @@ oc.unit:webassembly-x86_64: dependencies: - oc.docker:ci:amd64 timeout: 20 minutes + cache: + key: cargo-$CI_JOB_NAME_SLUG + paths: + - $CI_PROJECT_DIR/.cargo/registry/cache + policy: pull-push interruptible: false before_script: - SCRIPT_STEP_BEGIN=$(date +%s) @@ -1485,6 +1497,8 @@ oc.unit:webassembly-x86_64: - . ./scripts/ci/datadog_send_job_script_step_time.sh || true after_script: - . ./scripts/ci/datadog_send_job_cache_info.sh 'after' + variables: + CARGO_NET_OFFLINE: "false" oc.unit:non-proto-x86_64: image: ${ci_image_name}/test:${ci_image_tag} diff --git a/ci/lib_tezos_ci_jobs/misc.ml b/ci/lib_tezos_ci_jobs/misc.ml index b111da0c500cfd0cf6e9c51333c302395c244cbc..ebe00fe8601f32522d3882f5eefe05d469750cb9 100644 --- a/ci/lib_tezos_ci_jobs/misc.ml +++ b/ci/lib_tezos_ci_jobs/misc.ml @@ -78,6 +78,7 @@ let job_script_test_gen_genesis = ~stage:Test ~description:"Check that scripts/gen-genesis/gen_genesis.exe still builds." ~image:Tezos_ci.Images.CI.build + ~cargo_cache:true ~only_if_changed:(Changesets.changeset_octez |> Tezos_ci.Changeset.encode) ["eval $(opam env)"; "dune build scripts/gen-genesis/gen_genesis.exe"] @@ -255,6 +256,7 @@ let job_oc_unit_webassembly_x86_64 = ~description:"Run the tests for WASM." ~arch:Amd64 (* The wasm tests are written in Python *) ~image:Tezos_ci.Images.CI.test + ~cargo_cache:true ~stage:Test ~only_if_changed:(Tezos_ci.Changeset.encode Changesets.changeset_octez) ~timeout:(Minutes 20) diff --git a/dune-project b/dune-project index bed1b1899eb8b6c9fde3cc6a3d2b9841bee0561f..b0d2ec326feebc5d36afa0eefb073b1f8fcfed66 100644 --- a/dune-project +++ b/dune-project @@ -49,8 +49,11 @@ (package (name octez-l2-libs)) (package (name octez-lib-upnp)) (package (name octez-lib-upnp-args)) +(package (name octez-libcrux-ml-dsa)) +(package (name octez-libcrux-ml-dsa-test)(allow_empty)) (package (name octez-libs)) (package (name octez-lwt-domain)) +(package (name octez-ml-dsa)) (package (name octez-node)) (package (name octez-node-config)) (package (name octez-performance-metrics)) diff --git a/manifest/main.ml b/manifest/main.ml index b444f52191f371040aa441b0d02ae0809659f0be..3bf46a9f40ce4ef4077d0d83031696ffacb0c657 100644 --- a/manifest/main.ml +++ b/manifest/main.ml @@ -78,6 +78,7 @@ let exclude filename = (* We need to tell Dune about excluding directories without defining targets in those directories. Therefore we hand write some Dune in these. *) | "src" :: "riscv" :: _ -> true + | ["src"; "rust_libcrux"; "dune"] -> true (* [src/dune] is either absent or copied from [script-inputs/slim-mode-dune]. *) | "src" :: "dune" :: _ -> true (* [manifest/dune] serves for ocaml-lsp to handle the manifest. *) diff --git a/manifest/product_octez.ml b/manifest/product_octez.ml index 3a97892fc78a014e0d654d897e606035fd3e31b6..b5bfac8a378b3c3984ca95554be836d0743eecde 100644 --- a/manifest/product_octez.ml +++ b/manifest/product_octez.ml @@ -834,6 +834,66 @@ let octez_event_logging_test_helpers = ~linkall:true ~bisect_ppx:No +let octez_libcrux_ml_dsa = + public_lib + "octez-libcrux-ml-dsa" + ~internal_name:"octez_libcrux_ml_dsa" + ~path:"src/rust_libcrux/src" + ~opam:"octez-libcrux-ml-dsa" + ~synopsis:"Rust foreign archive for libcrux_ml_dsa" + ~foreign_archives:["octez_libcrux_ml_dsa"] + ~flags:(Flags.standard ~disable_warnings:[9; 27; 66] ()) + ~dune: + Dune. + [ + [ + S "rule"; + [ + S "targets"; + S "liboctez_libcrux_ml_dsa.a"; + S "dlloctez_libcrux_ml_dsa.so"; + ]; + [ + S "deps"; + [S "glob_files"; S "*.rs"]; + [S "file"; S "../Cargo.toml"]; + [S "file"; S "../Cargo.lock"]; + [S "file"; S "../../../rust-toolchain"]; + [S "source_tree"; S "../.cargo"]; + ]; + [ + S "action"; + [ + S "progn"; + [S "run"; S "sh"; S "-c"; S "cargo build --release"]; + [ + S "run"; + S "sh"; + S "-c"; + S + "mv ../target/release/liboctez_libcrux_ml_dsa.so \ + ./dlloctez_libcrux_ml_dsa.so 2> /dev/null || mv \ + ../target/release/liboctez_libcrux_ml_dsa.dylib \ + ./dlloctez_libcrux_ml_dsa.so"; + ]; + [ + S "run"; + S "mv"; + S "../target/release/liboctez_libcrux_ml_dsa.a"; + S "liboctez_libcrux_ml_dsa.a"; + ]; + ]; + ]; + ]; + ] + +let octez_ml_dsa = + public_lib + "octez-ml-dsa" + ~path:"src/lib_ml_dsa" + ~synopsis:"ML-DSA-44 signature scheme" + ~deps:[octez_libcrux_ml_dsa] + let octez_crypto = octez_lib "crypto" @@ -847,6 +907,7 @@ let octez_crypto = octez_lwt_result_stdlib; lwt; octez_hacl; + octez_ml_dsa; secp256k1_internal; octez_error_monad |> open_ |> open_ ~m:"TzLwtreslib"; octez_rpc; @@ -5212,6 +5273,20 @@ let _octez_riscv_pvm_test = octez_riscv_pvm; ] +let _octez_libcrux_ml_dsa_tests = + tezt + ["test_main"; "test_bindings"] + ~path:"src/rust_libcrux/test" + ~opam:"octez-libcrux-ml-dsa-test" + ~synopsis:"Tests for the libcrux ML-DSA bindings" + ~deps: + [ + octez_base |> open_ ~m:"TzPervasives"; + octez_stdlib_unix |> open_; + alcotezt; + octez_ml_dsa; + ] + let octez_layer2_store = octez_l2_lib "layer2_store" diff --git a/opam/octez-libcrux-ml-dsa-test.opam b/opam/octez-libcrux-ml-dsa-test.opam new file mode 100644 index 0000000000000000000000000000000000000000..41e50721cf204f16982a0ccaa0b27dc1425d4832 --- /dev/null +++ b/opam/octez-libcrux-ml-dsa-test.opam @@ -0,0 +1,25 @@ +# This file was automatically generated, do not edit. +# Edit file manifest/main.ml instead. +opam-version: "2.0" +maintainer: "contact@tezos.com" +authors: ["Tezos devteam"] +homepage: "https://www.tezos.com/" +bug-reports: "https://gitlab.com/tezos/tezos/issues" +dev-repo: "git+https://gitlab.com/tezos/tezos.git" +license: "MIT" +depends: [ + "dune" { >= "3.11.1" } + "ocaml" { >= "4.14" } + "tezt" { with-test & >= "4.1.0" & < "5.0.0" } + "bls12-381" {with-test} + "octez-libs" {with-test} + "octez-alcotezt" {with-test} + "octez-ml-dsa" {with-test} +] +build: [ + ["rm" "-rf" "vendors" "contrib"] + ["dune" "build" "-p" name "-j" jobs] + ["dune" "runtest" "-p" name "-j" jobs] {with-test} +] +available: os-family != "windows" +synopsis: "Tests for the libcrux ML-DSA bindings" diff --git a/opam/octez-libcrux-ml-dsa.opam b/opam/octez-libcrux-ml-dsa.opam new file mode 100644 index 0000000000000000000000000000000000000000..985adb9e0b15c34de19a2d5d21412438659f3c61 --- /dev/null +++ b/opam/octez-libcrux-ml-dsa.opam @@ -0,0 +1,20 @@ +# This file was automatically generated, do not edit. +# Edit file manifest/main.ml instead. +opam-version: "2.0" +maintainer: "contact@tezos.com" +authors: ["Tezos devteam"] +homepage: "https://www.tezos.com/" +bug-reports: "https://gitlab.com/tezos/tezos/issues" +dev-repo: "git+https://gitlab.com/tezos/tezos.git" +license: "MIT" +depends: [ + "dune" { >= "3.11.1" } + "ocaml" { >= "4.14" } +] +build: [ + ["rm" "-rf" "vendors" "contrib"] + ["dune" "build" "-p" name "-j" jobs] + ["dune" "runtest" "-p" name "-j" jobs] {with-test} +] +available: os-family != "windows" +synopsis: "Rust foreign archive for libcrux_ml_dsa" diff --git a/opam/octez-libs.opam b/opam/octez-libs.opam index 7056ad6b5e305d882d4085fa057a7ca221092b14..caa2c77d5ec9bc09c45e40ceb2f0cbc29b9bf86f 100644 --- a/opam/octez-libs.opam +++ b/opam/octez-libs.opam @@ -42,6 +42,7 @@ depends: [ "hacl-star-raw" "bls12-381" { = version } "octez-alcotezt" { = version } + "octez-ml-dsa" { = version } "secp256k1-internal" { >= "0.4.0" } "bigarray-compat" "eqaf" diff --git a/opam/octez-ml-dsa.opam b/opam/octez-ml-dsa.opam new file mode 100644 index 0000000000000000000000000000000000000000..79b56ca00833532b8f6ae57c67f0951382ee9dfd --- /dev/null +++ b/opam/octez-ml-dsa.opam @@ -0,0 +1,21 @@ +# This file was automatically generated, do not edit. +# Edit file manifest/main.ml instead. +opam-version: "2.0" +maintainer: "contact@tezos.com" +authors: ["Tezos devteam"] +homepage: "https://www.tezos.com/" +bug-reports: "https://gitlab.com/tezos/tezos/issues" +dev-repo: "git+https://gitlab.com/tezos/tezos.git" +license: "MIT" +depends: [ + "dune" { >= "3.11.1" } + "ocaml" { >= "4.14" } + "octez-libcrux-ml-dsa" { = version } +] +build: [ + ["rm" "-rf" "vendors" "contrib"] + ["dune" "build" "-p" name "-j" jobs] + ["dune" "runtest" "-p" name "-j" jobs] {with-test} +] +available: os-family != "windows" +synopsis: "ML-DSA-44 signature scheme" diff --git a/script-inputs/ci-opam-package-tests b/script-inputs/ci-opam-package-tests index 8d2e125dac40c159b1e615f1fa2712b1440f4ed1..eeef3a7dc024e4c96ea07587cf39a9bc1e0f632b 100644 --- a/script-inputs/ci-opam-package-tests +++ b/script-inputs/ci-opam-package-tests @@ -22,8 +22,10 @@ octez-internal-libs all 7 octez-l2-libs all 6 octez-lib-upnp all 6 octez-lib-upnp-args all 7 +octez-libcrux-ml-dsa all 7 octez-libs all 7 octez-lwt-domain all 7 +octez-ml-dsa all 7 octez-node exec 1 octez-node-config all 4 octez-performance-metrics all 6 diff --git a/scripts/lint.sh b/scripts/lint.sh index 88cf07633b180fdb5e0745f2456ae5ea420be8be..40bd5519452743be544e21ebe7baedfa65cb2505 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -56,6 +56,8 @@ src/rust_deps/rust_tezos_context/ocaml-api/rust_tezedge_gen.ml src/rust_deps/rust_tezos_context/ocaml-api/rust_tezedge_gen.mli etherlink/lwt_domain/lwt_domain.ml etherlink/lwt_domain/lwt_domain.mli +src/rust_libcrux/src/octez_libcrux_ml_dsa.ml +src/rust_libcrux/src/octez_libcrux_ml_dsa.mli EOF ) diff --git a/src/lib_crypto/dune b/src/lib_crypto/dune index f6014c63a1bc3cd7eabbdc8c96dd462bdaef6435..73ec9d454ac69726c0327210eb4be3bb59ff669a 100644 --- a/src/lib_crypto/dune +++ b/src/lib_crypto/dune @@ -11,6 +11,7 @@ octez-libs.lwt-result-stdlib lwt octez-libs.hacl + octez-ml-dsa secp256k1-internal octez-libs.error-monad octez-libs.rpc diff --git a/src/lib_ml_dsa/dune b/src/lib_ml_dsa/dune new file mode 100644 index 0000000000000000000000000000000000000000..d1b504ca8e2a38f8fe36f76832444feb8479a1c2 --- /dev/null +++ b/src/lib_ml_dsa/dune @@ -0,0 +1,9 @@ +; This file was automatically generated, do not edit. +; Edit file manifest/main.ml instead. + +(library + (name octez_ml_dsa) + (public_name octez-ml-dsa) + (instrumentation (backend bisect_ppx)) + (libraries + octez-libcrux-ml-dsa)) diff --git a/src/lib_ml_dsa/ml_dsa_44.ml b/src/lib_ml_dsa/ml_dsa_44.ml new file mode 100644 index 0000000000000000000000000000000000000000..9779f63bceedc1c0d161de70100c787249f76b0a --- /dev/null +++ b/src/lib_ml_dsa/ml_dsa_44.ml @@ -0,0 +1,10 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* SPDX-FileCopyrightText: 2025 Nomadic Labs *) +(* *) +(*****************************************************************************) + +(* TODO: This library currently re-exports the bindings to the libcrux_ml_dsa crate. *) +(* It should instead implement a high-level OCaml interface on top of it. *) +include Octez_libcrux_ml_dsa diff --git a/src/rust_libcrux/.cargo/config.toml b/src/rust_libcrux/.cargo/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..67ba795ec324bab2fa03fc835ffca875fed5ee98 --- /dev/null +++ b/src/rust_libcrux/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.'cfg(target_os = "macos")'] +rustflags = ["-C", "link-args=-Wl,-undefined,dynamic_lookup"] diff --git a/src/rust_libcrux/Cargo.lock b/src/rust_libcrux/Cargo.lock new file mode 100644 index 0000000000000000000000000000000000000000..aeff859d62d5e081a900948ea1ab63655ac6546d --- /dev/null +++ b/src/rust_libcrux/Cargo.lock @@ -0,0 +1,567 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "core-models" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0940496e5c83c54f3b753d5317daec82e8edac71c33aaa1f666d76f518de2444" +dependencies = [ + "hax-lib", + "pastey", + "rand", +] + +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "hax-lib" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d9ba66d1739c68e0219b2b2238b5c4145f491ebf181b9c6ab561a19352ae86" +dependencies = [ + "hax-lib-macros", + "num-bigint", + "num-traits", +] + +[[package]] +name = "hax-lib-macros" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ba777a231a58d1bce1d68313fa6b6afcc7966adef23d60f45b8a2b9b688bf1" +dependencies = [ + "hax-lib-macros-types", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "hax-lib-macros-types" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "867e19177d7425140b417cd27c2e05320e727ee682e98368f88b7194e80ad515" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_json", + "uuid", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "libcrux-intrinsics" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9ee7ef66569dd7516454fe26de4e401c0c62073929803486b96744594b9632" +dependencies = [ + "core-models", + "hax-lib", +] + +[[package]] +name = "libcrux-macros" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffd6aa2dcd5be681662001b81d493f1569c6d49a32361f470b0c955465cd0338" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "libcrux-ml-dsa" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3adca5778a61aa1707ad612e52a02ecbcd7e0b2912e011392def331e6e8b2aa" +dependencies = [ + "core-models", + "hax-lib", + "libcrux-intrinsics", + "libcrux-macros", + "libcrux-platform", + "libcrux-sha3", +] + +[[package]] +name = "libcrux-platform" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db82d058aa76ea315a3b2092f69dfbd67ddb0e462038a206e1dcd73f058c0778" +dependencies = [ + "libc", +] + +[[package]] +name = "libcrux-secrets" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4dbbf6bc9f2bc0f20dc3bea3e5c99adff3bdccf6d2a40488963da69e2ec307" +dependencies = [ + "hax-lib", +] + +[[package]] +name = "libcrux-sha3" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2400bec764d1c75b8a496d5747cffe32f1fb864a12577f0aca2f55a92021c962" +dependencies = [ + "hax-lib", + "libcrux-intrinsics", + "libcrux-platform", + "libcrux-traits", +] + +[[package]] +name = "libcrux-traits" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9adfd58e79d860f6b9e40e35127bfae9e5bd3ade33201d1347459011a2add034" +dependencies = [ + "libcrux-secrets", + "rand", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "ocaml" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8395e6be68fbf7c3d011b0dd3699660d22b17e981596cbad42454ccff041549b" +dependencies = [ + "ocaml-boxroot-sys", + "ocaml-derive", + "ocaml-sys", +] + +[[package]] +name = "ocaml-boxroot-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3c2664a427c8046d334bf3a50fc466170a3dc53c65bc926a9be31a8e8debd1" +dependencies = [ + "cc", +] + +[[package]] +name = "ocaml-build" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d51b05aa0083eaec54b22a3f2a3d49175e04b4fb77ca7abb5a85731736239c3" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "ocaml-derive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3de4a0decff0fd3ee0928dfa15dac08651157f8f814e93b34fdf962190354035" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ocaml-sys" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14e0db66a4df18164b2318518bea3891b6fd05526253a71023267f8955dbaefa" +dependencies = [ + "cc", + "cty", +] + +[[package]] +name = "octez-libcrux-ml-dsa" +version = "0.0.0" +dependencies = [ + "libcrux-ml-dsa", + "ocaml", + "ocaml-boxroot-sys", + "ocaml-build", + "ocaml-sys", + "zeroize", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "uuid" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "getrandom", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" diff --git a/src/rust_libcrux/Cargo.toml b/src/rust_libcrux/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..5770550e88de69ebe4f115a3e7fd77115649b14b --- /dev/null +++ b/src/rust_libcrux/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "octez-libcrux-ml-dsa" +version = "0.0.0" +edition = "2024" + +[lib] +crate-type = ["staticlib", "cdylib"] + +[lints.clippy] +allow_attributes = "deny" +allow_attributes_without_reason = "deny" + +[lints.rustdoc] +broken_intra_doc_links = "deny" +private_intra_doc_links = "allow" + +[dependencies] +zeroize = "1.8" + +[dependencies.libcrux-ml-dsa] +version = "0.0.4" +default-features = false +features = ["mldsa44"] + +[dependencies.ocaml] +version = "1.2.1" +default-features = false + +[dependencies.ocaml-sys] +# We don't care about the version. Ideally this gets pinned to what `ocaml` needs. +version = "*" +default-features = false + +[dependencies.ocaml-boxroot-sys] +# We don't care about the version. Ideally this gets pinned to what `ocaml` needs. +version = "*" +default-features = false + +[build-dependencies] +ocaml-build = "1.0.0" + +[features] +default = ["ocaml/default"] \ No newline at end of file diff --git a/src/rust_libcrux/build.rs b/src/rust_libcrux/build.rs new file mode 100644 index 0000000000000000000000000000000000000000..c3bbd595ca30e3002397997db942f7f9714c8b2f --- /dev/null +++ b/src/rust_libcrux/build.rs @@ -0,0 +1,14 @@ +use std::env; + +fn generate_ocaml_sigs() { + ocaml_build::Sigs::new("src/octez_libcrux_ml_dsa.ml") + .generate() + .unwrap(); +} + +pub fn main() { + if env::var("INSIDE_DUNE").is_err() { + // Generate the OCaml signatures only when building outside the Dune rule. + generate_ocaml_sigs(); + } +} diff --git a/src/rust_libcrux/dune b/src/rust_libcrux/dune new file mode 100644 index 0000000000000000000000000000000000000000..b3312a1a9a83d7c7ad0e0efd040da325bbfdad03 --- /dev/null +++ b/src/rust_libcrux/dune @@ -0,0 +1 @@ +(dirs :standard .cargo (not target)) diff --git a/src/rust_libcrux/src/.ocamlformat-ignore b/src/rust_libcrux/src/.ocamlformat-ignore new file mode 100644 index 0000000000000000000000000000000000000000..72e8ffc0db8aad71a934dd11e5968bd5109e54b4 --- /dev/null +++ b/src/rust_libcrux/src/.ocamlformat-ignore @@ -0,0 +1 @@ +* diff --git a/src/rust_libcrux/src/dune b/src/rust_libcrux/src/dune new file mode 100644 index 0000000000000000000000000000000000000000..d517e5d1bc5dbb7fe1eee57a3dc5bc442156d6ba --- /dev/null +++ b/src/rust_libcrux/src/dune @@ -0,0 +1,31 @@ +; This file was automatically generated, do not edit. +; Edit file manifest/main.ml instead. + +(library + (name octez_libcrux_ml_dsa) + (public_name octez-libcrux-ml-dsa) + (instrumentation (backend bisect_ppx)) + (flags + (:standard) + -w -9-27-66) + (foreign_archives octez_libcrux_ml_dsa)) + +(rule + (targets liboctez_libcrux_ml_dsa.a dlloctez_libcrux_ml_dsa.so) + (deps + (glob_files *.rs) + (file ../Cargo.toml) + (file ../Cargo.lock) + (file ../../../rust-toolchain) + (source_tree ../.cargo)) + (action + (progn + (run sh -c "cargo build --release") + (run + sh + -c + "mv ../target/release/liboctez_libcrux_ml_dsa.so ./dlloctez_libcrux_ml_dsa.so 2> /dev/null || mv ../target/release/liboctez_libcrux_ml_dsa.dylib ./dlloctez_libcrux_ml_dsa.so") + (run + mv + ../target/release/liboctez_libcrux_ml_dsa.a + liboctez_libcrux_ml_dsa.a)))) diff --git a/src/rust_libcrux/src/lib.rs b/src/rust_libcrux/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..afb78a8fd1150819a1a816ad51fdbd71ad1cad51 --- /dev/null +++ b/src/rust_libcrux/src/lib.rs @@ -0,0 +1,279 @@ +// SPDX-FileCopyrightText: 2025 Nomadic Labs +// +// SPDX-License-Identifier: MIT + +//! This module defines OCaml bindings for the ML-DSA-44 signature scheme from +//! the libcrux-ml-dsa crate and serves as a basis for building +//! the octez-libcrux-ml-dsa OCaml library. + +use ocaml::Pointer; +use zeroize::Zeroize; + +// TODO: Use oprimised platform-dependent versions when possible +// c.f. https://github.com/cryspen/libcrux/issues/1201 +use libcrux_ml_dsa::ml_dsa_44::portable::{generate_key_pair, sign, verify}; +use libcrux_ml_dsa::ml_dsa_44::{ + MLDSA44KeyPair, MLDSA44Signature, MLDSA44SigningKey, MLDSA44VerificationKey, +}; + +/// Key generation randomness size +const KEY_GENERATION_RANDOMNESS_SIZE: usize = libcrux_ml_dsa::KEY_GENERATION_RANDOMNESS_SIZE; + +/// Signing randomness size +const SIGNING_RANDOMNESS_SIZE: usize = libcrux_ml_dsa::SIGNING_RANDOMNESS_SIZE; + +/// Maximum context size +const MAX_CONTEXT_SIZE: usize = 255; + +/// Verification key size for ML-DSA-44 +const VERIFICATION_KEY_SIZE: usize = MLDSA44VerificationKey::len(); + +/// Signing key size for ML-DSA-44 +const SIGNING_KEY_SIZE: usize = MLDSA44SigningKey::len(); + +/// Signature size for ML-DSA-44 +const SIGNATURE_SIZE: usize = MLDSA44Signature::len(); + +/// An ML-DSA-44 key pair containing both signing and verification keys +#[ocaml::sig] +pub struct KeyPair(MLDSA44KeyPair); + +impl Drop for KeyPair { + fn drop(&mut self) { + self.0.signing_key.as_ref_mut().zeroize() + } +} + +/// An ML-DSA-44 signing key +#[ocaml::sig] +pub struct SigningKey(MLDSA44SigningKey); + +impl Drop for SigningKey { + fn drop(&mut self) { + self.0.as_ref_mut().zeroize() + } +} + +/// An ML-DSA-44 verification key +#[ocaml::sig] +pub struct VerificationKey(MLDSA44VerificationKey); + +/// An ML-DSA-44 signature +#[ocaml::sig] +pub struct Signature(MLDSA44Signature); + +ocaml::custom!(KeyPair); +ocaml::custom!(SigningKey); +ocaml::custom!(VerificationKey); +ocaml::custom!(Signature); + +/// Generate a new ML-DSA-44 key pair +/// +/// Returns an error if the size of `randomness` is not [`KEY_GENERATION_RANDOMNESS_SIZE`]. +/// +/// # Security +/// +/// `randomness` must be cryptographically secure random bytes. +#[ocaml::func] +#[ocaml::sig("bytes -> (key_pair, string) result")] +pub fn octez_libcrux_ml_dsa_44_generate_key_pair( + randomness: &[u8], +) -> Result, String> { + let randomness_array: [u8; KEY_GENERATION_RANDOMNESS_SIZE] = + randomness.try_into().map_err(|_| { + format!( + "Invalid key generation randomness size: expected {}, got {}", + KEY_GENERATION_RANDOMNESS_SIZE, + randomness.len() + ) + })?; + + let key_pair = generate_key_pair(randomness_array); + Ok(KeyPair(key_pair).into()) +} + +/// Extract the signing key from a key pair +#[ocaml::func] +#[ocaml::sig("key_pair -> signing_key")] +pub fn octez_libcrux_ml_dsa_44_key_pair_get_signing_key( + key_pair: Pointer, +) -> Pointer { + let signing_key = key_pair.as_ref().0.signing_key.clone(); + SigningKey(signing_key).into() +} + +/// Extract the verification key from a key pair +#[ocaml::func] +#[ocaml::sig("key_pair -> verification_key")] +pub fn octez_libcrux_ml_dsa_44_key_pair_get_verification_key( + key_pair: Pointer, +) -> Pointer { + let verification_key = key_pair.as_ref().0.verification_key.clone(); + VerificationKey(verification_key).into() +} + +/// Sign a message using ML-DSA-44 +/// +/// Returns an error if: +/// - The size of `randomness` is not [`SIGNING_RANDOMNESS_SIZE`] +/// - The size of `context` exceeds [`MAX_CONTEXT_SIZE`] +/// - Signing fails +/// +/// # Security +/// +/// `randomness` must be cryptographically secure random bytes. +#[ocaml::func] +#[ocaml::sig("signing_key -> bytes -> bytes -> bytes -> (signature, string) result")] +pub fn octez_libcrux_ml_dsa_44_sign( + signing_key: Pointer, + message: &[u8], + context: &[u8], + randomness: &[u8], +) -> Result, String> { + let randomness_array: [u8; SIGNING_RANDOMNESS_SIZE] = randomness.try_into().map_err(|_| { + format!( + "Invalid signing randomness size: expected {}, got {}", + SIGNING_RANDOMNESS_SIZE, + randomness.len() + ) + })?; + + if context.len() > MAX_CONTEXT_SIZE { + return Err(format!( + "Signing context too long: expected at most {} bytes, got {}", + MAX_CONTEXT_SIZE, + context.len() + )); + } + + match sign(&signing_key.as_ref().0, message, context, randomness_array) { + Ok(signature) => Ok(Signature(signature).into()), + Err(e) => Err(format!("Signing failed: {e:?}")), + } +} + +/// Verify an ML-DSA-44 signature +/// +/// Returns an error if: +/// - The size of `context` exceeds [`MAX_CONTEXT_SIZE`] +/// - Verification fails +#[ocaml::func] +#[ocaml::sig("verification_key -> bytes -> bytes -> signature -> (unit, string) result")] +pub fn octez_libcrux_ml_dsa_44_verify( + verification_key: Pointer, + message: &[u8], + context: &[u8], + signature: Pointer, +) -> Result<(), String> { + if context.len() > MAX_CONTEXT_SIZE { + return Err(format!( + "Context too long: expected at most {} bytes, got {}", + MAX_CONTEXT_SIZE, + context.len() + )); + } + + verify( + &verification_key.as_ref().0, + message, + context, + &signature.as_ref().0, + ) + .map_err(|e| format!("Verification failed: {e:?}")) +} + +/// Return a new byte buffer of size [`VERIFICATION_KEY_SIZE`] containing +/// the verification key bytes. +#[ocaml::func] +#[ocaml::sig("verification_key -> bytes")] +pub fn octez_libcrux_ml_dsa_44_verification_key_to_bytes( + verification_key: Pointer, +) -> [u8; VERIFICATION_KEY_SIZE] { + *verification_key.as_ref().0.as_ref() +} + +/// Create a new verification key from bytes. +/// +/// Returns an error if the size of `bytes` is not [`VERIFICATION_KEY_SIZE`]. +#[ocaml::func] +#[ocaml::sig("bytes -> (verification_key, string) result")] +pub fn octez_libcrux_ml_dsa_44_verification_key_from_bytes( + bytes: &[u8], +) -> Result, String> { + let verification_key_bytes: [u8; VERIFICATION_KEY_SIZE] = bytes.try_into().map_err(|_| { + format!( + "Invalid verification key size: expected {}, got {}", + VERIFICATION_KEY_SIZE, + bytes.len() + ) + })?; + + Ok(VerificationKey(MLDSA44VerificationKey::new(verification_key_bytes)).into()) +} + +/// Return a new byte buffer of size [`SIGNING_KEY_SIZE`] containing +/// the signing key bytes. +/// +/// # Security +/// +/// This function exposes sensitive key material. Callers are responsible for +/// securely handling the returned bytes. +#[ocaml::func] +#[ocaml::sig("signing_key -> bytes")] +pub fn octez_libcrux_ml_dsa_44_signing_key_to_bytes( + signing_key: Pointer, +) -> [u8; SIGNING_KEY_SIZE] { + *signing_key.as_ref().0.as_ref() +} + +/// Create a new signing key from bytes. +/// +/// Returns an error if the size of `bytes` is not [`SIGNING_KEY_SIZE`]. +#[ocaml::func] +#[ocaml::sig("bytes -> (signing_key, string) result")] +pub fn octez_libcrux_ml_dsa_44_signing_key_from_bytes( + bytes: &[u8], +) -> Result, String> { + let mut signing_key_bytes: [u8; SIGNING_KEY_SIZE] = bytes.try_into().map_err(|_| { + format!( + "Invalid signing key size: expected {}, got {}", + SIGNING_KEY_SIZE, + bytes.len() + ) + })?; + + let signing_key = SigningKey(MLDSA44SigningKey::new(signing_key_bytes)).into(); + + signing_key_bytes.zeroize(); + + Ok(signing_key) +} + +/// Return a new byte buffer of size [`SIGNATURE_SIZE`] containing +/// the signature bytes. +#[ocaml::func] +#[ocaml::sig("signature -> bytes")] +pub fn octez_libcrux_ml_dsa_44_signature_to_bytes( + signature: Pointer, +) -> [u8; SIGNATURE_SIZE] { + *signature.as_ref().0.as_ref() +} + +/// Create a new signature from bytes. +/// +/// Returns an error if the size of `bytes` is not [`SIGNATURE_SIZE`]. +#[ocaml::func] +#[ocaml::sig("bytes -> (signature, string) result")] +pub fn octez_libcrux_ml_dsa_44_signature_from_bytes( + bytes: &[u8], +) -> Result, String> { + let signature_bytes: [u8; SIGNATURE_SIZE] = bytes.try_into().map_err(|_| { + format!( + "Invalid signature size: expected {}, got {}", + SIGNATURE_SIZE, + bytes.len() + ) + })?; + + Ok(Signature(MLDSA44Signature::new(signature_bytes)).into()) +} diff --git a/src/rust_libcrux/src/octez_libcrux_ml_dsa.ml b/src/rust_libcrux/src/octez_libcrux_ml_dsa.ml new file mode 100644 index 0000000000000000000000000000000000000000..93556939a786fdf125718451bf3546031e7f0415 --- /dev/null +++ b/src/rust_libcrux/src/octez_libcrux_ml_dsa.ml @@ -0,0 +1,21 @@ +(* Generated by ocaml-rs *) + +open! Bigarray + +(* file: lib.rs *) + +type key_pair +type signing_key +type verification_key +type signature +external octez_libcrux_ml_dsa_44_generate_key_pair: bytes -> (key_pair, string) result = "octez_libcrux_ml_dsa_44_generate_key_pair" +external octez_libcrux_ml_dsa_44_key_pair_get_signing_key: key_pair -> signing_key = "octez_libcrux_ml_dsa_44_key_pair_get_signing_key" +external octez_libcrux_ml_dsa_44_key_pair_get_verification_key: key_pair -> verification_key = "octez_libcrux_ml_dsa_44_key_pair_get_verification_key" +external octez_libcrux_ml_dsa_44_sign: signing_key -> bytes -> bytes -> bytes -> (signature, string) result = "octez_libcrux_ml_dsa_44_sign" +external octez_libcrux_ml_dsa_44_verify: verification_key -> bytes -> bytes -> signature -> (unit, string) result = "octez_libcrux_ml_dsa_44_verify" +external octez_libcrux_ml_dsa_44_verification_key_to_bytes: verification_key -> bytes = "octez_libcrux_ml_dsa_44_verification_key_to_bytes" +external octez_libcrux_ml_dsa_44_verification_key_from_bytes: bytes -> (verification_key, string) result = "octez_libcrux_ml_dsa_44_verification_key_from_bytes" +external octez_libcrux_ml_dsa_44_signing_key_to_bytes: signing_key -> bytes = "octez_libcrux_ml_dsa_44_signing_key_to_bytes" +external octez_libcrux_ml_dsa_44_signing_key_from_bytes: bytes -> (signing_key, string) result = "octez_libcrux_ml_dsa_44_signing_key_from_bytes" +external octez_libcrux_ml_dsa_44_signature_to_bytes: signature -> bytes = "octez_libcrux_ml_dsa_44_signature_to_bytes" +external octez_libcrux_ml_dsa_44_signature_from_bytes: bytes -> (signature, string) result = "octez_libcrux_ml_dsa_44_signature_from_bytes" diff --git a/src/rust_libcrux/src/octez_libcrux_ml_dsa.mli b/src/rust_libcrux/src/octez_libcrux_ml_dsa.mli new file mode 100644 index 0000000000000000000000000000000000000000..93556939a786fdf125718451bf3546031e7f0415 --- /dev/null +++ b/src/rust_libcrux/src/octez_libcrux_ml_dsa.mli @@ -0,0 +1,21 @@ +(* Generated by ocaml-rs *) + +open! Bigarray + +(* file: lib.rs *) + +type key_pair +type signing_key +type verification_key +type signature +external octez_libcrux_ml_dsa_44_generate_key_pair: bytes -> (key_pair, string) result = "octez_libcrux_ml_dsa_44_generate_key_pair" +external octez_libcrux_ml_dsa_44_key_pair_get_signing_key: key_pair -> signing_key = "octez_libcrux_ml_dsa_44_key_pair_get_signing_key" +external octez_libcrux_ml_dsa_44_key_pair_get_verification_key: key_pair -> verification_key = "octez_libcrux_ml_dsa_44_key_pair_get_verification_key" +external octez_libcrux_ml_dsa_44_sign: signing_key -> bytes -> bytes -> bytes -> (signature, string) result = "octez_libcrux_ml_dsa_44_sign" +external octez_libcrux_ml_dsa_44_verify: verification_key -> bytes -> bytes -> signature -> (unit, string) result = "octez_libcrux_ml_dsa_44_verify" +external octez_libcrux_ml_dsa_44_verification_key_to_bytes: verification_key -> bytes = "octez_libcrux_ml_dsa_44_verification_key_to_bytes" +external octez_libcrux_ml_dsa_44_verification_key_from_bytes: bytes -> (verification_key, string) result = "octez_libcrux_ml_dsa_44_verification_key_from_bytes" +external octez_libcrux_ml_dsa_44_signing_key_to_bytes: signing_key -> bytes = "octez_libcrux_ml_dsa_44_signing_key_to_bytes" +external octez_libcrux_ml_dsa_44_signing_key_from_bytes: bytes -> (signing_key, string) result = "octez_libcrux_ml_dsa_44_signing_key_from_bytes" +external octez_libcrux_ml_dsa_44_signature_to_bytes: signature -> bytes = "octez_libcrux_ml_dsa_44_signature_to_bytes" +external octez_libcrux_ml_dsa_44_signature_from_bytes: bytes -> (signature, string) result = "octez_libcrux_ml_dsa_44_signature_from_bytes" diff --git a/src/rust_libcrux/test/dune b/src/rust_libcrux/test/dune new file mode 100644 index 0000000000000000000000000000000000000000..c1d6aec51cd944047268cfe68060b155189c61cc --- /dev/null +++ b/src/rust_libcrux/test/dune @@ -0,0 +1,40 @@ +; This file was automatically generated, do not edit. +; Edit file manifest/main.ml instead. + +(library + (name src_rust_libcrux_test_tezt_lib) + (instrumentation (backend bisect_ppx)) + (libraries + tezt.core + bls12-381.archive + octez-libs.base + octez-libs.stdlib-unix + octez-alcotezt + octez-ml-dsa) + (library_flags (:standard -linkall)) + (flags + (:standard) + -open Tezt_core + -open Tezt_core.Base + -open Tezos_base.TzPervasives + -open Tezos_stdlib_unix + -open Octez_alcotezt) + (modules test_main test_bindings)) + +(executable + (name main) + (instrumentation (backend bisect_ppx --bisect-sigterm)) + (libraries + src_rust_libcrux_test_tezt_lib + tezt) + (modules main)) + +(rule + (alias runtest) + (package octez-libcrux-ml-dsa-test) + (enabled_if (<> false %{env:RUNTEZTALIAS=true})) + (action (run %{dep:./main.exe} /flaky /ci_disabled))) + +(rule + (targets main.ml) + (action (with-stdout-to %{targets} (echo "let () = Tezt.Test.run ()")))) diff --git a/src/rust_libcrux/test/test_bindings.ml b/src/rust_libcrux/test/test_bindings.ml new file mode 100644 index 0000000000000000000000000000000000000000..7d89ed3535db32f0d76f81febb692f158309c2f9 --- /dev/null +++ b/src/rust_libcrux/test/test_bindings.ml @@ -0,0 +1,191 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* SPDX-FileCopyrightText: 2025 Nomadic Labs *) +(* *) +(*****************************************************************************) + +module Bindings = Octez_libcrux_ml_dsa + +let ml_dsa_44_signing_key_size = 2560 + +let ml_dsa_44_verification_key_size = 1312 + +let ml_dsa_44_signature_size = 2420 + +let key_generation_randomness_size = 32 + +let signing_randomness_size = 32 + +let generate_random_bytes size = + Bytes.init size (fun _ -> char_of_int (Random.int 256)) + +let test_generate_key_pair () = + let seed = generate_random_bytes key_generation_randomness_size in + + let key_pair = + match Bindings.octez_libcrux_ml_dsa_44_generate_key_pair seed with + | Ok key_pair -> key_pair + | Error err -> Alcotest.failf "Key pair generation failed: %s" err + in + + (* Extract signing and verification keys from key pair *) + let signing_key = + Bindings.octez_libcrux_ml_dsa_44_key_pair_get_signing_key key_pair + in + let verification_key = + Bindings.octez_libcrux_ml_dsa_44_key_pair_get_verification_key key_pair + in + + (* Convert keys to bytes and verify sizes *) + let signing_key_bytes = + Bindings.octez_libcrux_ml_dsa_44_signing_key_to_bytes signing_key + in + let verification_key_bytes = + Bindings.octez_libcrux_ml_dsa_44_verification_key_to_bytes verification_key + in + Alcotest.(check int) + "signing key size" + ml_dsa_44_signing_key_size + (Bytes.length signing_key_bytes) ; + Alcotest.(check int) + "verification key size" + ml_dsa_44_verification_key_size + (Bytes.length verification_key_bytes) ; + + (* Initialise keys from bytes *) + match + ( Bindings.octez_libcrux_ml_dsa_44_signing_key_from_bytes signing_key_bytes, + Bindings.octez_libcrux_ml_dsa_44_verification_key_from_bytes + verification_key_bytes ) + with + | Ok _signing_key, Ok _verification_key -> Lwt.return_unit + | Error err, _ -> + Alcotest.failf "Initialising signing key from bytes failed: %s" err + | _, Error err -> + Alcotest.failf "Initialising verification key from bytes failed: %s" err + +let test_sign () = + let seed = generate_random_bytes key_generation_randomness_size in + + let key_pair = + match Bindings.octez_libcrux_ml_dsa_44_generate_key_pair seed with + | Ok key_pair -> key_pair + | Error err -> Alcotest.failf "Key pair generation failed: %s" err + in + + let signing_key = + Bindings.octez_libcrux_ml_dsa_44_key_pair_get_signing_key key_pair + in + + let message = generate_random_bytes 16 in + let randomness = generate_random_bytes signing_randomness_size in + + match + Bindings.octez_libcrux_ml_dsa_44_sign + signing_key + message + Bytes.empty + randomness + with + | Ok signature -> ( + let signature_bytes = + Bindings.octez_libcrux_ml_dsa_44_signature_to_bytes signature + in + Alcotest.(check int) + "signature size" + ml_dsa_44_signature_size + (Bytes.length signature_bytes) ; + + match + Bindings.octez_libcrux_ml_dsa_44_signature_from_bytes signature_bytes + with + | Ok _signature -> Lwt.return_unit + | Error err -> + Alcotest.failf "Initialising signature from bytes failed: %s" err) + | Error err -> Alcotest.failf "Signing failed: %s" err + +let test_verify () = + let seed = generate_random_bytes key_generation_randomness_size in + + let key_pair = + match Bindings.octez_libcrux_ml_dsa_44_generate_key_pair seed with + | Ok key_pair -> key_pair + | Error err -> Alcotest.failf "Key pair generation failed: %s" err + in + + let signing_key = + Bindings.octez_libcrux_ml_dsa_44_key_pair_get_signing_key key_pair + in + let verification_key = + Bindings.octez_libcrux_ml_dsa_44_key_pair_get_verification_key key_pair + in + + let message = generate_random_bytes 16 in + let randomness = generate_random_bytes signing_randomness_size in + let context = Bytes.empty in + + match + Bindings.octez_libcrux_ml_dsa_44_sign signing_key message context randomness + with + | Ok signature -> ( + match + Bindings.octez_libcrux_ml_dsa_44_verify + verification_key + message + context + signature + with + | Ok () -> Lwt.return_unit + | Error err -> Alcotest.failf "Verification failed: %s" err) + | Error err -> Alcotest.failf "Signing failed: %s" err + +let test_verify_invalid_signature () = + let seed = generate_random_bytes key_generation_randomness_size in + + let key_pair = + match Bindings.octez_libcrux_ml_dsa_44_generate_key_pair seed with + | Ok key_pair -> key_pair + | Error err -> Alcotest.failf "Key pair generation failed: %s" err + in + + let signing_key = + Bindings.octez_libcrux_ml_dsa_44_key_pair_get_signing_key key_pair + in + let verification_key = + Bindings.octez_libcrux_ml_dsa_44_key_pair_get_verification_key key_pair + in + + (* Sign two different messages *) + let message1 = generate_random_bytes 16 in + let message2 = generate_random_bytes 16 in + let randomness1 = generate_random_bytes signing_randomness_size in + let randomness2 = generate_random_bytes signing_randomness_size in + let context = Bytes.empty in + + match + ( Bindings.octez_libcrux_ml_dsa_44_sign + signing_key + message1 + context + randomness1, + Bindings.octez_libcrux_ml_dsa_44_sign + signing_key + message2 + context + randomness2 ) + with + | Ok _signature1, Ok signature2 -> ( + (* Try to verify message1 with signature2 *) + match + Bindings.octez_libcrux_ml_dsa_44_verify + verification_key + message1 + context + signature2 + with + | Ok () -> + Alcotest.failf "Verification should have failed with wrong signature" + | Error _ -> Lwt.return_unit) + | Error err, _ -> Alcotest.failf "Signing message1 failed: %s" err + | _, Error err -> Alcotest.failf "Signing message2 failed: %s" err diff --git a/src/rust_libcrux/test/test_main.ml b/src/rust_libcrux/test/test_main.ml new file mode 100644 index 0000000000000000000000000000000000000000..5a6554c496c30fee0ef4da57056f01ab325c5c02 --- /dev/null +++ b/src/rust_libcrux/test/test_main.ml @@ -0,0 +1,21 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* SPDX-FileCopyrightText: 2025 Nomadic Labs *) +(* *) +(*****************************************************************************) + +let tests = + [ + ( "Bindings", + [ + ("Generate key pair", `Quick, Test_bindings.test_generate_key_pair); + ("Sign message", `Quick, Test_bindings.test_sign); + ("Verify signature", `Quick, Test_bindings.test_verify); + ( "Verify invalid signature", + `Quick, + Test_bindings.test_verify_invalid_signature ); + ] ); + ] + +let () = Alcotest_lwt.run ~__FILE__ "ML-DSA-44" tests |> Lwt_main.run diff --git a/tezt/tests/dune b/tezt/tests/dune index df4ce072e122ab3d6046ff70ee1cc58b55216e4e..0e161375dcdcda5e175b8eeb53914661b44bb087 100644 --- a/tezt/tests/dune +++ b/tezt/tests/dune @@ -21,6 +21,7 @@ octez-libs.stdlib-unix tezos-protocol-alpha.protocol tezt_self_tests_tezt_lib + src_rust_libcrux_test_tezt_lib src_proto_alpha_lib_sc_rollup_node_test_tezt_lib src_proto_alpha_lib_protocol_test_unit_tezt_lib src_proto_alpha_lib_protocol_test_regression_tezt_lib diff --git a/tobi/config b/tobi/config index e0cf354cf52fb794204813592f2ad5e3981b349e..2668f9e763a52c7b1ffb969f886eff50e17f1c58 100644 --- a/tobi/config +++ b/tobi/config @@ -80,8 +80,11 @@ octez-internal-libs: irmin/lib_irmin, irmin/lib_irmin/data, irmin/lib_irmin/mem, octez-l2-libs: src/lib_layer2_irmin_context, src/lib_layer2_riscv_context, src/lib_layer2_shell, src/lib_layer2_store, src/lib_layer2_store/test/, src/lib_scoru_wasm/bench, src/lib_scoru_wasm/bench/executable, src/lib_scoru_wasm/fast, src/lib_scoru_wasm/fast/test, src/lib_scoru_wasm/helpers, src/lib_scoru_wasm/test, src/lib_scoru_wasm/test/durable_snapshot, src/lib_scoru_wasm/test/helpers, src/lib_smart_rollup, src/lib_smart_rollup_node, src/lib_smart_rollup_node/migrations, src/lib_sqlite, src/lib_wasmer, src/lib_wasmer/test, src/lib_webassembly/extra, src/lib_webassembly/tests octez-lib-upnp: src/lib_upnp octez-lib-upnp-args: src/lib_upnp/args +octez-libcrux-ml-dsa: src/rust_libcrux/src +octez-libcrux-ml-dsa-test: src/rust_libcrux/test octez-libs: brassaia-eio/lib_brassaia, brassaia-eio/lib_brassaia/data, brassaia-eio/lib_brassaia/mem, brassaia-eio/lib_brassaia_pack, brassaia-eio/lib_brassaia_pack/io, brassaia-eio/lib_brassaia_pack/mem, brassaia-eio/lib_brassaia_tezos, brassaia-eio/lib_ppx_brassaia, brassaia-eio/lib_ppx_brassaia/internal, brassaia-eio/test/helpers, brassaia/index/src/, brassaia/index/src/unix, brassaia/lib_brassaia, brassaia/lib_brassaia/data, brassaia/lib_brassaia/mem, brassaia/lib_brassaia_pack, brassaia/lib_brassaia_pack/mem, brassaia/lib_brassaia_pack/unix, brassaia/lib_brassaia_tezos, brassaia/lib_ppx_brassaia, brassaia/lib_ppx_brassaia/internal, brassaia/test/helpers, data-encoding/json-data-encoding/src, data-encoding/json-data-encoding/test, data-encoding/json-data-encoding/test-bson, data-encoding/src, data-encoding/test, data-encoding/test/expect, data-encoding/test/pbt, prometheus/app, prometheus/examples, prometheus/src, prometheus/tests, resto/src, resto/test, src/lib_aplonk, src/lib_aplonk/plonk-aggregation, src/lib_aplonk/test, src/lib_base, src/lib_base/p2p_identity_file, src/lib_base/test, src/lib_base/test_helpers, src/lib_base/unix, src/lib_base/unix/test, src/lib_bees, src/lib_bees/test, src/lib_bls12_381_hash, src/lib_bls12_381_hash/test, src/lib_bls12_381_polynomial, src/lib_bls12_381_polynomial/test, src/lib_bls12_381_signature, src/lib_bls12_381_signature/test, src/lib_clic, src/lib_clic/test, src/lib_clic/unix, src/lib_context, src/lib_context/disk, src/lib_context/encoding, src/lib_context/helpers, src/lib_context/memory, src/lib_context/memory/test, src/lib_context/merkle_proof_encoding, src/lib_context/sigs, src/lib_context/test, src/lib_context_brassaia, src/lib_context_brassaia/disk, src/lib_context_brassaia/encoding, src/lib_context_brassaia/helpers, src/lib_context_brassaia/memory, src/lib_context_brassaia/merkle_proof_encoding, src/lib_context_tezedge, src/lib_crypto, src/lib_crypto/test, src/lib_crypto/test-unix, src/lib_crypto_dal, src/lib_crypto_dal/dal_config, src/lib_crypto_dal/test, src/lib_distributed_plonk, src/lib_distributed_plonk/communication, src/lib_distributed_plonk/distribution, src/lib_distributed_plonk/distribution/test, src/lib_distributed_plonk/test, src/lib_epoxy_tx, src/lib_epoxy_tx/test, src/lib_error_monad, src/lib_error_monad/test, src/lib_error_monad_legacy, src/lib_event_logging, src/lib_event_logging/test_helpers, src/lib_expect_helper, src/lib_expect_helper/test, src/lib_gossipsub, src/lib_gossipsub/test, src/lib_hacl, src/lib_hacl/test, src/lib_kzg, src/lib_lazy_containers, src/lib_lwt_result_stdlib, src/lib_lwt_result_stdlib/bare/functor_outputs, src/lib_lwt_result_stdlib/bare/sigs, src/lib_lwt_result_stdlib/bare/structs, src/lib_lwt_result_stdlib/examples/traces, src/lib_lwt_result_stdlib/test, src/lib_lwt_result_stdlib/traced/functor_outputs, src/lib_lwt_result_stdlib/traced/sigs, src/lib_lwt_result_stdlib/traced/structs, src/lib_mec, src/lib_mec/test, src/lib_micheline, src/lib_micheline/test, src/lib_p2p_services, src/lib_plompiler, src/lib_plonk, src/lib_plonk/test, src/lib_plonk/test_plompiler, src/lib_polynomial, src/lib_polynomial/test, src/lib_ppx_profiler, src/lib_profiler, src/lib_profiler/backends, src/lib_profiler/backends/complex, src/lib_profiler/backends/test, src/lib_profiler/unix, src/lib_protocol_environment/ppinclude, src/lib_rpc, src/lib_rpc_http, src/lib_rpc_http/test, src/lib_sapling, src/lib_sapling/bindings, src/lib_sapling/test, src/lib_scoru_wasm, src/lib_srs_extraction, src/lib_srs_extraction/test, src/lib_stdlib, src/lib_stdlib/test, src/lib_stdlib/test-unix, src/lib_stdlib_unix, src/lib_stdlib_unix/test/, src/lib_telemetry, src/lib_test, src/lib_tree_encoding, src/lib_version, src/lib_version/parser, src/lib_version/test, src/lib_webassembly, src/lib_workers, src/lib_workers/test, tezt/lib_alcotezt_process, tezt/lib_qcheck, tezt/lib_wrapper octez-lwt-domain: lwt_domain +octez-ml-dsa: src/lib_ml_dsa octez-node: src/bin_node octez-node-config: src/lib_node_config octez-performance-metrics: src/lib_performance_metrics