diff --git a/CHANGELOG.md b/CHANGELOG.md
index baf44e462b82..2c1dfa04780a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Added
+- Native CCF applications can now be written in Rust through a minimal, experimental API for registering endpoints and accessing raw-byte KV maps (#8200).
- COSE Sign1 verification now accepts the fully-specified ECDSA algorithm identifiers introduced by [RFC 9864](https://www.rfc-editor.org/rfc/rfc9864.html): `ESP256` (-9), `ESP384` (-51) and `ESP512` (-52), in addition to the deprecated `ES256` (-7), `ES384` (-35) and `ES512` (-36) they replace. `ESn` and `ESPn` are treated as equivalent for the curve they denote, which CCF already requires to match the verification key. Signatures produced by CCF continue to use the `ES` identifiers (#8267).
### Fixed
diff --git a/CMakeLists.txt b/CMakeLists.txt
index dee27f1f816e..8a4fc6dad627 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -174,6 +174,27 @@ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/tools.cmake DESTINATION cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/ccf_app.cmake)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/cmake/ccf_app.cmake DESTINATION cmake)
+install(
+ DIRECTORY ${CCF_DIR}/src/rust/
+ DESTINATION share/ccf/src/rust
+ PATTERN target EXCLUDE
+)
+install(
+ DIRECTORY ${CCF_DIR}/src/cose/cose_rs/
+ DESTINATION share/ccf/src/cose/cose_rs
+ PATTERN target EXCLUDE
+)
+install(
+ DIRECTORY ${CCF_DIR}/3rdparty/internal/cose-openssl/
+ DESTINATION share/ccf/3rdparty/internal/cose-openssl
+ PATTERN target EXCLUDE
+)
+install(FILES ${CCF_DIR}/src/rust/app_bridge.cpp DESTINATION share/ccf/rust)
+install(
+ FILES ${CCF_DIR}/samples/apps/main.cpp
+ DESTINATION share/ccf/rust
+ RENAME app_main.cpp
+)
# Copy and install CCF utilities
set(CCF_UTILITIES keygenerator.sh submit_recovery_share.sh)
@@ -575,6 +596,19 @@ if(BUILD_TESTS)
# Unit tests
if(BUILD_UNIT_TESTS)
+ add_test(
+ NAME ccf_app_rust_test
+ COMMAND
+ ${CMAKE_COMMAND} -E env --unset=CARGO_BUILD_TARGET "CARGO_NET_RETRY=10"
+ "CARGO_HTTP_TIMEOUT=60" "CARGO_BUILD_RUSTC=${RUSTC}" ${CARGO} test
+ --manifest-path ${CCF_DIR}/src/rust/ccf-app/Cargo.toml --target-dir
+ ${CMAKE_BINARY_DIR}/cargo/ccf-app-test --locked
+ )
+ set_tests_properties(
+ ccf_app_rust_test
+ PROPERTIES LABELS unit WORKING_DIRECTORY ${CCF_DIR}/src/rust/ccf-app
+ )
+
add_test(
NAME verify_uvm_attestation_and_endorsements
COMMAND
@@ -1310,6 +1344,13 @@ if(BUILD_TESTS)
ADDITIONAL_ARGS --js-app-bundle ${CMAKE_SOURCE_DIR}/samples/apps/logging/js
)
+ add_e2e_test(
+ NAME basic_rust
+ PYTHON_SCRIPT ${CMAKE_SOURCE_DIR}/tests/basic_rust.py
+ BUCKET bucket_c
+ ADDITIONAL_ARGS --package samples/apps/basic_rust/basic_rust
+ )
+
set(
RBAC_CONSTITUTION_ARGS
--constitution
diff --git a/cmake/ccf_app.cmake b/cmake/ccf_app.cmake
index 2f119beae2bf..0f328f3b21a4 100644
--- a/cmake/ccf_app.cmake
+++ b/cmake/ccf_app.cmake
@@ -52,6 +52,89 @@ function(add_ccf_app name)
endif()
endfunction()
+function(add_ccf_rust_app name)
+ cmake_parse_arguments(
+ PARSE_ARGV 1
+ PARSED_ARGS
+ ""
+ "MANIFEST_PATH;PACKAGE;LIB_NAME"
+ ""
+ )
+
+ if(NOT PARSED_ARGS_MANIFEST_PATH)
+ message(FATAL_ERROR "add_ccf_rust_app requires MANIFEST_PATH")
+ endif()
+ if(NOT PARSED_ARGS_PACKAGE)
+ set(PARSED_ARGS_PACKAGE ${name})
+ endif()
+ if(NOT PARSED_ARGS_LIB_NAME)
+ set(PARSED_ARGS_LIB_NAME ${PARSED_ARGS_PACKAGE})
+ endif()
+
+ find_program(CARGO NAMES cargo REQUIRED)
+ find_program(RUSTC NAMES rustc REQUIRED)
+
+ if(CMAKE_CONFIGURATION_TYPES)
+ message(
+ FATAL_ERROR
+ "Multi-config generators are not supported for Rust CCF applications"
+ )
+ endif()
+
+ if(CMAKE_BUILD_TYPE STREQUAL "Debug")
+ set(CARGO_PROFILE_FLAG "")
+ set(CARGO_PROFILE_DIR debug)
+ else()
+ set(CARGO_PROFILE_FLAG --release)
+ set(CARGO_PROFILE_DIR release)
+ endif()
+
+ string(REPLACE "-" "_" RUST_LIB_NAME ${PARSED_ARGS_LIB_NAME})
+ get_filename_component(MANIFEST_PATH ${PARSED_ARGS_MANIFEST_PATH} ABSOLUTE)
+ get_filename_component(MANIFEST_DIR ${MANIFEST_PATH} DIRECTORY)
+ set(CARGO_TARGET_DIR ${CMAKE_CURRENT_BINARY_DIR}/cargo/${name})
+ set(
+ RUST_APP_LIB
+ ${CARGO_TARGET_DIR}/${CARGO_PROFILE_DIR}/lib${RUST_LIB_NAME}.a
+ )
+
+ set(
+ RUSTFLAGS
+ "$ENV{RUSTFLAGS} --remap-path-prefix=${MANIFEST_DIR}=APP --remap-path-prefix=${CCF_DIR}=CCF --remap-path-prefix=$ENV{HOME}/.cargo=CARGO"
+ )
+ add_custom_target(
+ cargo-build_${name}
+ BYPRODUCTS ${RUST_APP_LIB}
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${CARGO_TARGET_DIR}
+ COMMAND
+ ${CMAKE_COMMAND} -E env --unset=CARGO_BUILD_TARGET
+ "RUSTFLAGS=${RUSTFLAGS}" "CARGO_NET_RETRY=10" "CARGO_HTTP_TIMEOUT=60"
+ "CC=${CMAKE_C_COMPILER}" "CXX=${CMAKE_CXX_COMPILER}" "AR=${CMAKE_AR}"
+ "CARGO_BUILD_RUSTC=${RUSTC}" ${CARGO} build --lib --package
+ ${PARSED_ARGS_PACKAGE} --manifest-path ${MANIFEST_PATH} --target-dir
+ ${CARGO_TARGET_DIR} ${CARGO_PROFILE_FLAG} --locked
+ WORKING_DIRECTORY ${MANIFEST_DIR}
+ COMMENT "Building Rust CCF application ${name}"
+ USES_TERMINAL
+ VERBATIM
+ )
+
+ if(EXISTS "${CCF_DIR}/src/rust/app_bridge.cpp")
+ set(RUST_BRIDGE_SOURCE "${CCF_DIR}/src/rust/app_bridge.cpp")
+ set(RUST_APP_MAIN_SOURCE "${CCF_DIR}/samples/apps/main.cpp")
+ else()
+ set(RUST_BRIDGE_SOURCE "${CCF_DIR}/share/ccf/rust/app_bridge.cpp")
+ set(RUST_APP_MAIN_SOURCE "${CCF_DIR}/share/ccf/rust/app_main.cpp")
+ endif()
+
+ add_ccf_app(
+ ${name}
+ SRCS ${RUST_BRIDGE_SOURCE} ${RUST_APP_MAIN_SOURCE}
+ LINK_LIBS ${RUST_APP_LIB}
+ DEPS cargo-build_${name}
+ )
+endfunction()
+
function(add_ccf_static_library name)
cmake_parse_arguments(PARSE_ARGV 1 PARSED_ARGS "" "" "SRCS;LINK_LIBS")
diff --git a/cmake/gersemi_definitions.cmake b/cmake/gersemi_definitions.cmake
index 471cf7395e16..7c8b8494b0a6 100644
--- a/cmake/gersemi_definitions.cmake
+++ b/cmake/gersemi_definitions.cmake
@@ -15,6 +15,16 @@ function(add_ccf_app name)
)
endfunction()
+function(add_ccf_rust_app name)
+ cmake_parse_arguments(
+ PARSE_ARGV 1
+ PARSED_ARGS
+ ""
+ "MANIFEST_PATH;PACKAGE;LIB_NAME"
+ ""
+ )
+endfunction()
+
function(add_ccf_static_library name)
cmake_parse_arguments(PARSE_ARGV 1 PARSED_ARGS "" "" "SRCS;LINK_LIBS")
endfunction()
diff --git a/doc/build_apps/example_rust.rst b/doc/build_apps/example_rust.rst
new file mode 100644
index 000000000000..6681a96b7fba
--- /dev/null
+++ b/doc/build_apps/example_rust.rst
@@ -0,0 +1,74 @@
+Example app (Rust)
+==================
+
+CCF provides an initial Rust interface for native applications. It deliberately
+exposes a small subset of the public application API:
+
+- read-write and read-only HTTP endpoints;
+- user-certificate authentication or no authentication;
+- request bodies, raw queries, decoded path parameters, and named headers;
+- response status, headers, body, and OData errors; and
+- raw-byte KV ``get``, ``has``, ``put``, and ``remove`` operations.
+
+Advanced endpoint configuration, custom authentication, historical queries,
+indexing, and commit callbacks are not currently exposed.
+
+Build
+-----
+
+Rust 1.90 and Cargo are required. A Rust application is a ``staticlib`` crate
+which depends on the source-tree ``src/rust/ccf-app`` crate or the installed
+``share/ccf/src/rust/ccf-app`` crate. Its CMake file registers the crate with
+``add_ccf_rust_app``:
+
+.. code-block:: cmake
+
+ add_ccf_rust_app(
+ my_app
+ MANIFEST_PATH ${CMAKE_CURRENT_LIST_DIR}/Cargo.toml
+ PACKAGE my-app
+ )
+
+The helper maps CMake ``Debug`` builds to Cargo's development profile and all
+other build types to Cargo's release profile. It also links the generic C++ ABI
+bridge, launcher, and CCF libraries. Cargo is invoked on every build and decides
+whether the crate is up to date, so Rust source edits do not require CMake to be
+reconfigured. ``LIB_NAME`` defaults to the package name with dashes replaced by
+underscores; set it explicitly when the crate's ``[lib] name`` differs from its
+package name. The application should commit ``Cargo.lock`` and pin a Rust
+toolchain for reproducible builds.
+
+The complete records example is in :ccf_repo:`samples/apps/basic_rust`. It
+exports a registration function with ``ccf_app::export_app!`` and registers
+handlers through ``Registry::read_write`` and ``Registry::read_only``.
+
+Endpoint execution
+------------------
+
+Handlers may run concurrently and must be ``Send`` and ``Sync``. CCF may also
+retry a read-write handler when a transaction conflicts, so handlers should be
+deterministic and should not perform non-transactional side effects.
+
+Request and response contexts, transactions, and map handles borrow the callback
+context and cannot be retained. Values returned by KV ``get`` are owned copies.
+The SDK requires Rust's ``unwind`` panic strategy so that panics are caught at
+the ABI boundary and become HTTP 500 errors. Builds using ``panic = "abort"``
+are rejected. C++ exceptions are also contained by the bridge.
+
+KV values and keys
+------------------
+
+The initial API treats keys and values as byte strings. Applications may layer
+their own serializers on these operations; the ``Codec`` trait provides a
+common interface without prescribing a wire format.
+
+Map names retain the standard CCF security semantics. Names beginning with
+``public:`` are written to the ledger in plaintext. All other application map
+names, such as the sample's ``records`` map, are private and encrypted. Like
+native C++ applications, native Rust applications are trusted code: raw map
+access does not enforce the namespace restrictions applied to JavaScript
+applications for reserved governance and internal maps.
+
+Read-only handlers receive only ``ReadOnlyMap``, so write operations are
+not available at compile time. Errors returned by a handler use the normal CCF
+transaction semantics: unsuccessful responses discard writes.
diff --git a/doc/build_apps/get_started.rst b/doc/build_apps/get_started.rst
index 2c0ef95df894..b62a831a49e4 100644
--- a/doc/build_apps/get_started.rst
+++ b/doc/build_apps/get_started.rst
@@ -6,7 +6,7 @@ Application Development using CCF Overview
- :ref:`What is Confidential Consortium Framework (CCF) `
- Read the :doc:`CCF overview ` and get familiar with :ref:`overview/what_is_ccf:Core Concepts` and `Azure confidential computing `__
-- :doc:`Build new CCF applications ` in TypeScript/JavaScript or C++
+- :doc:`Build new CCF applications ` in TypeScript/JavaScript, C++, or Rust
- CCF `JavaScript module API reference `__
- CCF application get started repos `CCF application template `__ and `CCF application samples `__
@@ -91,6 +91,13 @@ Packaging your C++ app
To create distributable packages for your CCF application, create a ``cpack.cmake`` file that includes CCF's packaging configuration and add it to your ``CMakeLists.txt``. See :ccf_repo:`tests/ccfapp/CMakeLists.txt` and :ccf_repo:`tests/ccfapp/cpack.cmake` for a complete working example.
+Rust Applications
+-----------------
+
+Rust applications are native CCF executables with the same deployment model as
+C++ applications. See :doc:`example_rust` for the supported API and build
+instructions.
+
Network Governance
------------------
diff --git a/doc/build_apps/index.rst b/doc/build_apps/index.rst
index 47147bcd426c..4d41b11de8f3 100644
--- a/doc/build_apps/index.rst
+++ b/doc/build_apps/index.rst
@@ -5,7 +5,7 @@ This section describes how CCF applications can be developed and deployed to a C
.. tip:: The `ccf-app-template `_ repository can be used to quickly build and run a sample CCF application and provides a minimal template to create new CCF apps.
-Applications can be written in JavaScript/TypeScript or C++. An application consists of a collection of endpoints that can be triggered by :term:`Users`. Each endpoint can define an :ref:`build_apps/example_cpp:API Schema` to validate user requests.
+Applications can be written in JavaScript/TypeScript, C++, or Rust. An application consists of a collection of endpoints that can be triggered by :term:`Users`. Each endpoint can define an :ref:`build_apps/example_cpp:API Schema` to validate user requests.
These endpoints can read or mutate the state of a unique :ref:`build_apps/kv/index:Key-Value Store` that represents the internal state of the application. Applications define a set of ``Maps`` (see :doc:`kv/kv_how_to`), mapping from a key to a value. When an application endpoint is triggered, the effects on the Store are committed atomically.
@@ -37,6 +37,13 @@ These endpoints can read or mutate the state of a unique :ref:`build_apps/kv/ind
---
+ :fa:`gear` :doc:`example_rust`
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+ Minimal native CCF application written in Rust.
+
+ ---
+
.. image:: ../img/ts.svg
:alt: TypeScript
:align: left
@@ -110,6 +117,7 @@ These endpoints can read or mutate the state of a unique :ref:`build_apps/kv/ind
get_started
install_bin
example
+ example_rust
js_app_ts
js_app_bundle
logging
diff --git a/include/ccf/kv/compacted_version_conflict.h b/include/ccf/kv/compacted_version_conflict.h
new file mode 100644
index 000000000000..1fb49ca5cc78
--- /dev/null
+++ b/include/ccf/kv/compacted_version_conflict.h
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the Apache 2.0 License.
+#pragma once
+
+#include
+#include
+
+namespace ccf::kv
+{
+ class CompactedVersionConflict
+ {
+ private:
+ std::string msg;
+
+ public:
+ CompactedVersionConflict(std::string s) : msg(std::move(s)) {}
+
+ [[nodiscard]] char const* what() const
+ {
+ return msg.c_str();
+ }
+ };
+}
diff --git a/include/ccf/rust_ffi.h b/include/ccf/rust_ffi.h
new file mode 100644
index 000000000000..18aa7e9d1a3e
--- /dev/null
+++ b/include/ccf/rust_ffi.h
@@ -0,0 +1,129 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the Apache 2.0 License.
+#pragma once
+
+#include
+#include
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ static const uint32_t CCF_RUST_ABI_VERSION = 1;
+
+ struct ccf_rust_registry;
+ struct ccf_rust_endpoint_context;
+ struct ccf_rust_slice
+ {
+ const uint8_t* data;
+ size_t len;
+ };
+
+#ifdef __cplusplus
+ using ccf_rust_result = int32_t;
+ using ccf_rust_auth = int32_t;
+ using ccf_rust_endpoint_callback =
+ ccf_rust_result (*)(void* user_data, ccf_rust_endpoint_context* ctx);
+ using ccf_rust_drop_callback = void (*)(void* user_data);
+#else
+typedef struct ccf_rust_registry ccf_rust_registry;
+typedef struct ccf_rust_endpoint_context ccf_rust_endpoint_context;
+typedef struct ccf_rust_slice ccf_rust_slice;
+typedef int32_t ccf_rust_result;
+typedef int32_t ccf_rust_auth;
+typedef ccf_rust_result (*ccf_rust_endpoint_callback)(
+ void* user_data, ccf_rust_endpoint_context* ctx);
+typedef void (*ccf_rust_drop_callback)(void* user_data);
+#endif
+
+#ifdef __cplusplus
+ inline constexpr ccf_rust_result CCF_RUST_OK = 0;
+ inline constexpr ccf_rust_result CCF_RUST_NOT_FOUND = 1;
+ inline constexpr ccf_rust_result CCF_RUST_INVALID_ARGUMENT = 2;
+ inline constexpr ccf_rust_result CCF_RUST_READ_ONLY = 3;
+ inline constexpr ccf_rust_result CCF_RUST_INTERNAL_ERROR = 4;
+
+ inline constexpr ccf_rust_auth CCF_RUST_AUTH_NONE = 0;
+ inline constexpr ccf_rust_auth CCF_RUST_AUTH_USER_CERT = 1;
+#else
+enum
+{
+ CCF_RUST_OK = 0,
+ CCF_RUST_NOT_FOUND = 1,
+ CCF_RUST_INVALID_ARGUMENT = 2,
+ CCF_RUST_READ_ONLY = 3,
+ CCF_RUST_INTERNAL_ERROR = 4
+};
+
+enum
+{
+ CCF_RUST_AUTH_NONE = 0,
+ CCF_RUST_AUTH_USER_CERT = 1
+};
+#endif
+
+ uint32_t ccf_rust_get_abi_version(void);
+
+ ccf_rust_result ccf_rust_register_endpoint(
+ ccf_rust_registry* registry,
+ ccf_rust_slice path,
+ ccf_rust_slice method,
+ ccf_rust_auth auth,
+ int32_t read_only,
+ ccf_rust_endpoint_callback callback,
+ ccf_rust_drop_callback drop,
+ void* user_data);
+
+ ccf_rust_result ccf_rust_request_body(
+ ccf_rust_endpoint_context* ctx, ccf_rust_slice* body);
+ ccf_rust_result ccf_rust_request_query(
+ ccf_rust_endpoint_context* ctx, ccf_rust_slice* query);
+ ccf_rust_result ccf_rust_request_path_param(
+ ccf_rust_endpoint_context* ctx, ccf_rust_slice name, ccf_rust_slice* value);
+ ccf_rust_result ccf_rust_request_header(
+ ccf_rust_endpoint_context* ctx, ccf_rust_slice name, ccf_rust_slice* value);
+
+ ccf_rust_result ccf_rust_response_status(
+ ccf_rust_endpoint_context* ctx, uint16_t status);
+ ccf_rust_result ccf_rust_response_header(
+ ccf_rust_endpoint_context* ctx, ccf_rust_slice name, ccf_rust_slice value);
+ ccf_rust_result ccf_rust_response_body(
+ ccf_rust_endpoint_context* ctx, ccf_rust_slice body);
+ ccf_rust_result ccf_rust_response_error(
+ ccf_rust_endpoint_context* ctx,
+ uint16_t status,
+ ccf_rust_slice code,
+ ccf_rust_slice message);
+
+ ccf_rust_result ccf_rust_kv_get(
+ ccf_rust_endpoint_context* ctx,
+ ccf_rust_slice map_name,
+ ccf_rust_slice key,
+ ccf_rust_slice* value);
+ ccf_rust_result ccf_rust_kv_has(
+ ccf_rust_endpoint_context* ctx,
+ ccf_rust_slice map_name,
+ ccf_rust_slice key,
+ int32_t* present);
+ ccf_rust_result ccf_rust_kv_put(
+ ccf_rust_endpoint_context* ctx,
+ ccf_rust_slice map_name,
+ ccf_rust_slice key,
+ ccf_rust_slice value);
+ ccf_rust_result ccf_rust_kv_remove(
+ ccf_rust_endpoint_context* ctx,
+ ccf_rust_slice map_name,
+ ccf_rust_slice key);
+
+ uint32_t ccf_rust_app_abi_version(void);
+ ccf_rust_result ccf_rust_app_register(ccf_rust_registry* registry);
+
+#ifdef __cplusplus
+}
+
+namespace ccf
+{
+ inline constexpr uint32_t rust_abi_version = CCF_RUST_ABI_VERSION;
+}
+#endif
diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt
index 30c6c9f7dbcd..6e016bc8f9e4 100644
--- a/samples/CMakeLists.txt
+++ b/samples/CMakeLists.txt
@@ -9,3 +9,6 @@ add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/apps/nobuiltins)
# Add Programmability app
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/apps/programmability)
+
+# Add Rust basic app
+add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/apps/basic_rust)
diff --git a/samples/apps/basic_rust/CMakeLists.txt b/samples/apps/basic_rust/CMakeLists.txt
new file mode 100644
index 000000000000..5ab68891a035
--- /dev/null
+++ b/samples/apps/basic_rust/CMakeLists.txt
@@ -0,0 +1,19 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the Apache 2.0 License.
+
+cmake_minimum_required(VERSION 3.21)
+
+project(basic_rust LANGUAGES C CXX)
+
+set(CCF_PROJECT "ccf")
+
+if(NOT TARGET "ccf")
+ find_package(${CCF_PROJECT} REQUIRED)
+endif()
+
+add_ccf_rust_app(
+ basic_rust
+ MANIFEST_PATH ${CMAKE_CURRENT_LIST_DIR}/Cargo.toml
+ PACKAGE ccf-basic-rust
+ LIB_NAME ccf_basic_rust_app
+)
diff --git a/samples/apps/basic_rust/Cargo.lock b/samples/apps/basic_rust/Cargo.lock
new file mode 100644
index 000000000000..80b5c8c99f8c
--- /dev/null
+++ b/samples/apps/basic_rust/Cargo.lock
@@ -0,0 +1,14 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "ccf-app"
+version = "0.1.0"
+
+[[package]]
+name = "ccf-basic-rust"
+version = "0.1.0"
+dependencies = [
+ "ccf-app",
+]
diff --git a/samples/apps/basic_rust/Cargo.toml b/samples/apps/basic_rust/Cargo.toml
new file mode 100644
index 000000000000..f000f62b8511
--- /dev/null
+++ b/samples/apps/basic_rust/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "ccf-basic-rust"
+version = "0.1.0"
+edition = "2024"
+
+[lib]
+name = "ccf_basic_rust_app"
+crate-type = ["staticlib"]
+
+[dependencies]
+ccf-app = { path = "../../../src/rust/ccf-app" }
+
+[profile.release]
+lto = true
+codegen-units = 1
diff --git a/samples/apps/basic_rust/rust-toolchain.toml b/samples/apps/basic_rust/rust-toolchain.toml
new file mode 100644
index 000000000000..ff100edcbbe7
--- /dev/null
+++ b/samples/apps/basic_rust/rust-toolchain.toml
@@ -0,0 +1,2 @@
+[toolchain]
+channel = "1.90.0"
diff --git a/samples/apps/basic_rust/src/lib.rs b/samples/apps/basic_rust/src/lib.rs
new file mode 100644
index 000000000000..de083c8e6900
--- /dev/null
+++ b/samples/apps/basic_rust/src/lib.rs
@@ -0,0 +1,75 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the Apache 2.0 License.
+
+use ccf_app::{Auth, BridgeError, EndpointError, EndpointResult, Registry};
+
+const RECORDS: &str = "records";
+
+fn required_key(value: Result