This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
CAS .NET SDK is a managed cryptographic toolkit published as the cas-dotnet-sdk NuGet package. It is a thin, high-level .NET API over a native Rust FFI layer (cas-core-lib), which itself wraps the RustCrypto and Dalek-Cryptography suites. The .NET code does almost no cryptography itself — it marshals data to/from the native library.
cas-dotnet-sdk/— the SDK class library (NuGet package source). Multi-targetsnet6.0–net10.0.cas-dotnet-sdk-tests/— xUnit test project, references the SDK directly.cas-core-lib/— git submodule containing the Rust source compiled tocas_core_lib.dll(Windows) /libcas_core_lib.so(Linux) /libcas_core_lib.dylib(macOS). Clone with--recursiveor rungit submodule update --init --recursive.docs/EXAMPLES.md— usage samples.
The Rust native library must be built and present alongside the managed assemblies, or all P/Invoke calls fail at runtime. The SDK .csproj automates this via MSBuild targets (BuildRust / StageRustArtifacts) that run cargo build --release before each build — so a plain dotnet build builds Rust too. This requires a working Rust toolchain (cargo) on PATH.
# Full build (also compiles the Rust submodule via the BuildRust target)
dotnet build cas-dotnet-sdk.sln
# Skip the Rust build when the native lib is already staged (much faster)
dotnet build cas-dotnet-sdk.sln -p:BuildNativeRust=false
# Build the Rust lib manually
cd cas-core-lib && cargo build --release
# Run all tests for one framework (must specify -f; the project multi-targets)
dotnet test ./cas-dotnet-sdk-tests/cas-dotnet-sdk-tests.csproj -c Release -f net8.0
# Run a single test class or method
dotnet test ./cas-dotnet-sdk-tests/cas-dotnet-sdk-tests.csproj -f net8.0 --filter "FullyQualifiedName~SHAWrapperTests"
dotnet test ./cas-dotnet-sdk-tests/cas-dotnet-sdk-tests.csproj -f net8.0 --filter "DisplayName~Hash512"The same commands work for local development on macOS (Apple Silicon): a Rust toolchain on PATH builds libcas_core_lib.dylib, and the StageRustArtifacts MSBuild target stages it into artifacts/native/osx-arm64/ automatically — no extra flags needed.
Note: CI (.github/workflows/pr-tests-*.yml) builds cargo build --release separately, then copies the native lib into the test bin/Release/<tfm> directory before running dotnet test. If you build the Rust lib by hand and the MSBuild target is skipped, the native artifact must end up next to the test assembly the same way.
Every cryptographic capability is grouped into a top-level folder by category (Hashers, Symmetric, Asymmetric, Signatures, PasswordHashers, KeyExchange, Hybrid, Compression, Sponges, PQC). Within each category folder, the same three-part structure repeats — understand it once and it applies everywhere:
-
Public wrapper (e.g.
Hashers/SHAWrapper.cs) — the user-facing class. InheritsBaseWrapperand implements a category interface (e.g.IHasherBase). It contains the real logic: choose the platform DLL import, call it, copy the returned bytes out of the native pointer withMarshal.Copy, then free the native memory. -
Platform P/Invoke wrappers (
<Category>/Windows/*.csand<Category>/Linux/*.cs) —internal staticclasses containing only[DllImport]declarations. Windows imports targetcas_core_lib.dll, Linux imports targetlibcas_core_lib.so. The two files are otherwise identical signatures. The public wrapper dispatches between them withthis._platform == OSPlatform.Linux ? LinuxWrapper.fn(...) : WindowsWrapper.fn(...). -
Types (
<Category>/Types/*.cs) —internal structs mirroring the C ABI structs returned by the Rust FFI (e.g.SHAHashByteResult { IntPtr result_bytes_ptr; int length; }), plus category interfaces and enums.
BaseWrapper— base class for all public wrappers; resolves the current OS into_platform(OSPlatform) at construction viaOperatingSystemDeterminator. Throws on any OS that is not Windows or Linux.FreeMemoryHelper— frees native heap memory returned by the Rust layer. Any FFI call that returns a pointer-bearing struct must have its pointer freed viaFreeMemoryHelperafter the bytes are marshaled out, or memory leaks. This is the single most important invariant when adding new wrappers.
Categories with interchangeable algorithms expose a *Factory (e.g. HasherFactory.Get(IHasherType.SHA)) returning the category interface. When adding a new algorithm to such a category, wire it into both the enum and the factory switch.
- Add the Rust FFI function in
cas-core-lib(separate submodule/repo) and rebuild it. - Add matching
[DllImport]declarations in both theWindowsandLinuxP/Invoke files of the relevant category. - Add any new return struct to
Types/. - Implement the public method on the wrapper: platform-dispatch the call, marshal out the result, free native memory.
- If the category has a factory/enum, register the new type there.
- Add an xUnit test. NIST test vectors live as
.rspfiles under the test project'sAES/DataandSHA/Datafolders (copied to output viaContentitems in the test.csproj).
Supported targets: Windows x64, Linux x64, and macOS Apple Silicon (osx-arm64). Intel macs (osx-x64) are intentionally not supported — the Intel runners were too slow for this project's CI. Since the csbindgen migration (#191), P/Invoke goes through the generated generated/NativeMethods.g.cs using the bare library name cas_core_lib; the .NET runtime resolves it per-OS to cas_core_lib.dll / libcas_core_lib.so / libcas_core_lib.dylib, so there are no per-platform [DllImport] files to keep in sync. The native lib is built (cargo build --release), staged into artifacts/native/<rid>/, and packed into runtimes/<rid>/native for each RID. Tests run against .NET 6–10 on all three OSes in CI on every PR: pr-tests-windows and pr-tests-linux fan out one job per TFM, while pr-tests-macos-arm64 (macos-latest, Apple Silicon) runs all five target frameworks in a single dotnet test invocation (no -f). The OperatingSystemDeterminator resolves Windows, Linux, and OSX and throws on anything else.
Note: this is desktop macOS / Darwin, not iOS. Real iOS (aarch64-apple-ios) would need a cross-compiled native lib and a simulator/device to run tests, neither of which the current CI provides.