IronWeave.Crypto 0.3.0

dotnet add package IronWeave.Crypto --version 0.3.0
                    
NuGet\Install-Package IronWeave.Crypto -Version 0.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="IronWeave.Crypto" Version="0.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="IronWeave.Crypto" Version="0.3.0" />
                    
Directory.Packages.props
<PackageReference Include="IronWeave.Crypto" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add IronWeave.Crypto --version 0.3.0
                    
#r "nuget: IronWeave.Crypto, 0.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package IronWeave.Crypto@0.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=IronWeave.Crypto&version=0.3.0
                    
Install as a Cake Addin
#tool nuget:?package=IronWeave.Crypto&version=0.3.0
                    
Install as a Cake Tool

IronWeave.Crypto — C# Binding

A .NET wrapper for the iwcrypto native library, providing zero-knowledge cryptographic operations for confidential token transfers. All operations are exposed as static methods on a single class: IronWeave.Crypto.IwCrypto.

Installation

Install the NuGet package (includes native binaries for all 11 platforms):

dotnet add package IronWeave.Crypto

Requirements

  • .NET 10
  • Native iwcrypto library (built from the Rust crate with cargo build --release)
  • Protocol Buffers compiler (protoc) — only needed for test projects using proto types

Project Structure

bindings/csharp/
  IronWeave.Crypto.csproj    Class library
  IwCrypto.cs                All public API methods
  tests/
    IronWeave.Crypto.Tests.csproj    xUnit test project
    IwCryptoTests.cs                 FFI wrapper round-trip tests
    CryptoInteropTests.cs            Cross-language encryption interop (BouncyCastle)
    SigningInteropTests.cs            Cross-language signing interop (BouncyCastle)
    ZkChainTests.cs                  Full blockchain scenario integration test

Adding to Your Project

Reference the class library project:

<ProjectReference Include="path/to/bindings/csharp/IronWeave.Crypto.csproj" />

Your project must enable unsafe blocks since the wrapper uses pointer-based P/Invoke internally:

<AllowUnsafeBlocks>true</AllowUnsafeBlocks>

The native library must be resolvable at runtime. For development, register a NativeLibrary.SetDllImportResolver to point at the Cargo build output:

using System.Runtime.InteropServices;

NativeLibrary.SetDllImportResolver(
    typeof(IwCrypto).Assembly,
    (libraryName, assembly, searchPath) =>
    {
        if (libraryName == "iwcrypto")
        {
            // Adjust path to your cargo build output
            return NativeLibrary.Load("/path/to/target/release/libiwcrypto.dylib");
        }
        return IntPtr.Zero;
    });

For production, place the native library where .NET can find it (application directory, system library path, or bundled in a NuGet runtime package).

.NET MAUI Deployment

The same iwcrypto native binaries work across all MAUI target platforms — no special MAUI-specific builds are needed. The differences are purely in packaging and linking.

iOS

MAUI uses static linking on iOS. Reference the .a file and use [LibraryImport("__Internal")] instead of [LibraryImport("iwcrypto")]:


<ItemGroup Condition="$(TargetFramework.Contains('ios'))">
  <NativeReference Include="path/to/libiwcrypto.a">
    <Kind>Static</Kind>
    <ForceLoad>true</ForceLoad>
  </NativeReference>
</ItemGroup>

The IwCrypto wrapper class uses [LibraryImport("iwcrypto")] by default. For iOS static linking, you'll need a conditional DllImportResolver or a build-time substitution to route to __Internal.

Android

MAUI uses the same .so files produced by the Android NDK (via cargo-ndk). Place them under the NuGet RID directories:

NDK ABI NuGet RID
arm64-v8a/libiwcrypto.so runtimes/android-arm64/native/
armeabi-v7a/libiwcrypto.so runtimes/android-arm/native/
x86_64/libiwcrypto.so runtimes/android-x64/native/

Alternatively, add the .so files as AndroidNativeLibrary items:

<ItemGroup Condition="$(TargetFramework.Contains('android'))">
  <AndroidNativeLibrary Include="libs/arm64-v8a/libiwcrypto.so" Abi="arm64-v8a" />
  <AndroidNativeLibrary Include="libs/armeabi-v7a/libiwcrypto.so" Abi="armeabi-v7a" />
  <AndroidNativeLibrary Include="libs/x86_64/libiwcrypto.so" Abi="x86_64" />
</ItemGroup>

Desktop (macOS / Windows / Linux)

Standard dynamic linking via [LibraryImport("iwcrypto")]. Place the native library (.dylib / .dll / .so) in a NuGet runtimes/{rid}/native/ directory or alongside the application binary.

CI-Built Binaries

The build-release.yml workflow produces a iwcrypto-nuget-runtimes.zip artifact containing the runtimes/ directory pre-organized by RID for all 11 targets. Extract it into your NuGet package or project to get all platforms at once.

API Reference

All methods are static on IronWeave.Crypto.IwCrypto. Methods that can fail on invalid input throw InvalidOperationException. Verification methods (VerifySignature, ValidateOperationProofs, ValidateSignedOperation) return bool instead.

Key Generation

using IronWeave.Crypto;

// Ed25519 keypair — public key (32 bytes) doubles as account address
var (privateKey, publicKey) = IwCrypto.GenerateEd25519Keypair();

// Convert Ed25519 keys to X25519 for direct ECDH use
var xPub = IwCrypto.Ed25519ToX25519Public(publicKey);
var (xPriv, xPub2) = IwCrypto.Ed25519ToX25519Keypair(privateKey);

Commitments

// Generate a random 32-byte blinding factor (canonical Ristretto scalar)
byte[] blinding = IwCrypto.GenerateRandomBlinding();

// Pedersen commitment: C = value * B + blinding * B_blinding
// Returns 32-byte compressed Ristretto point
byte[] commitment = IwCrypto.Commit(1000, blinding);

Signing & Verification

var (privateKey, publicKey) = IwCrypto.GenerateEd25519Keypair();
byte[] message = "operation data"u8.ToArray();

// Sign — returns 64-byte Ed25519 signature
byte[] signature = IwCrypto.SignMessage(privateKey, message);

// Verify — returns true/false (does not throw)
bool valid = IwCrypto.VerifySignature(publicKey, message, signature);

Building Operations

Each Build* method returns serialized protobuf bytes representing the ZK proof payload. These bytes are embedded in a CryptoOperation proto message along with addresses, UUIDs, and encrypted private data before signing.

byte[] opUuid = IwCrypto.GenerateUuidV7();

// Mint — token issuer mints to own account (public amount for supply auditing)
byte[] mintPayload = IwCrypto.BuildZkMint(
    opUuid, tokenAddr, senderPrevOpUuid,
    currentBalance, currentBlinding,
    500, newBlinding);

// Burn — any account burns tokens (public amount for supply auditing)
byte[] burnPayload = IwCrypto.BuildZkBurn(
    opUuid, tokenAddr, senderAddr, senderPrevOpUuid,
    currentBalance, currentBlinding,
    200, newBlinding);

// Zero-account transfer — send to an identity-commitment address (new or reset account)
byte[] firstPayload = IwCrypto.BuildZkTransfer(
    opUuid, tokenAddr, senderAddr, senderPrevOpUuid,
    senderStartingBalance, senderStartingBlinding, senderFinalBlinding,
    receiverAddr, IwCrypto.NoPrevOpUuid,
    IwCrypto.ZeroAccountBalance, IwCrypto.ZeroAccountBlinding, receiverFinalBlinding,
    transferAmount);

// Transfer — between two accounts with known openings (same actor controls both)
byte[] transferPayload = IwCrypto.BuildZkTransfer(
    opUuid, tokenAddr, senderAddr, senderPrevOpUuid,
    senderStartingBalance, senderStartingBlinding, senderFinalBlinding,
    receiverAddr, receiverPrevOpUuid,
    receiverStartingBalance, receiverStartingBlinding, receiverFinalBlinding,
    transferAmount);

// Zero-balance reset — prove balance is zero, reset commitment to identity
byte[] resetPayload = IwCrypto.BuildZeroBalanceReset(currentBlinding);

// Rerandomize — switch blinding factor without changing balance
byte[] rerandPayload = IwCrypto.BuildRerandomize(
    currentBalance, currentBlinding, newBlinding);

BuildZkTransfer has a convenience overload that accepts Guid for UUID parameters instead of ReadOnlySpan<byte>.

Validation

// Pre-sign check (operation author) — validates ZK proofs only
bool proofsOk = IwCrypto.ValidateOperationProofs(
    cryptoOpBytes, senderOnChainCommitment, receiverOnChainCommitment);

// Full validation (validator) — verifies Ed25519 signature + ZK proofs
bool signedOk = IwCrypto.ValidateSignedOperation(
    signedCryptoOpBytes, senderOnChainCommitment, receiverOnChainCommitment);

Encryption & Decryption

Private transfer data (balance + blinding) is encrypted using ECDH (X25519) + AES-256-GCM. Ed25519 keys are converted internally when keyType is 0 (default).

The aad parameter (Associated Authenticated Data) binds additional context into the GCM authentication tag without encrypting it. For ZK operations, pass the party's final commitment (32 bytes) as AAD — this cryptographically ties the encrypted private data to the on-chain commitment. For user_data entries, omit aad (defaults to empty).

// Parse ZkTransfer to access final commitments for AAD
var zkTransfer = ZkTransfer.Parser.ParseFrom(payloadBytes);

// sender_private_data — self-encrypt, AAD = sender_final_commitment
var encSender = IwCrypto.EncryptPrivateData(
    senderPtdBytes, opUuid, senderPriv, senderPub,
    aad: zkTransfer.SenderFinalCommitment.ToByteArray());

// receiver_private_data — ECDH with receiver, AAD = receiver_final_commitment
var encReceiver = IwCrypto.EncryptPrivateData(
    receiverPtdBytes, opUuid, senderPriv, receiverPub,
    aad: zkTransfer.ReceiverFinalCommitment.ToByteArray());

// user_data entries — ECDH with receiver, no AAD
var encUserData = IwCrypto.EncryptPrivateData(
    userDataBytes, opUuid, senderPriv, receiverPub);

// Receiver decrypts with matching AAD
var decrypted = IwCrypto.DecryptPrivateData(
    encReceiver, opUuid, receiverPriv, senderPub,
    aad: zkTransfer.ReceiverFinalCommitment.ToByteArray());

Key type parameter: 0 = Ed25519 (default, keys converted to X25519 internally), 1 = X25519 (already converted).

SECURITY: sender_private_data must use self-encryption. Because ECDH is symmetric (ECDH(A_priv, B_pub) == ECDH(B_priv, A_pub)), encrypting sender_private_data with (senderPriv, receiverPub) would let the receiver decrypt it and learn the sender's remaining balance. Always use (senderPriv, senderPub) for sender_private_data so only the sender can decrypt.

Field Encrypt with Who can decrypt
SenderPrivateData (senderPriv, senderPub) — self-encryption Sender only
ReceiverPrivateData (senderPriv, receiverPub) — ECDH with receiver Sender and receiver
UserData entries (senderPriv, receiverPub) — ECDH with receiver Sender and receiver

Deterministic Blinding Derivation

Both sender and receiver can independently compute the same blinding factor from their ECDH shared secret, preventing fund-locking griefing:

// Sender derives
byte[] senderBlinding = IwCrypto.DeriveBlinding(
    opUuid, senderPrivateKey, receiverPublicKey);

// Receiver derives the same value independently
byte[] receiverBlinding = IwCrypto.DeriveBlinding(
    opUuid, receiverPrivateKey, senderPublicKey);

// senderBlinding == receiverBlinding

UUID Generation

// 16-byte UUIDv7 (big-endian, time-ordered)
byte[] opUuid = IwCrypto.GenerateUuidV7();

Working with Proto Types

Operation payloads must be wrapped in CryptoOperation and SignedCryptoOperation proto messages for signing and validation. Add proto codegen to your test or application project:

<PackageReference Include="Google.Protobuf" Version="3.*" />
<PackageReference Include="Grpc.Tools" Version="2.*" PrivateAssets="all" />

<Protobuf Include="path/to/proto/zk_transfer.proto" GrpcServices="None" />

Then build CryptoOperation messages using the generated types:

using Google.Protobuf;
using ZkTransfer;

// Build the ZK proof payload (mint to own account)
var payload = IwCrypto.BuildZkMint(
    opUuid, tokenAddr, senderPrevOpUuid,
    currentBalance, currentBlinding,
    500, newBlinding);

// Parse Mint to access new_balance_commitment for AAD
var mint = ZkTransfer.Mint.Parser.ParseFrom(payload);

// Wrap in a CryptoOperation (single-party — no receiver)
var tx = new CryptoOperation
{
    Uuid = new UUID { Value = ByteString.CopyFrom(opUuid) },
    SenderAddr = new Address { Value = ByteString.CopyFrom(senderPublicKey) },
    SenderPrevOpUuid = new UUID { Value = ByteString.CopyFrom(senderPrevOpUuid) },
    TokenAddr = new Address { Value = ByteString.CopyFrom(tokenAddr) },
    Mint = mint,
    SenderPrivateData = ByteString.CopyFrom(
        IwCrypto.EncryptPrivateData(senderPtdBytes, opUuid, senderPriv, senderPub,
            aad: mint.NewBalanceCommitment.ToByteArray())),
};

// Validate proofs before signing
var opBytes = tx.ToByteArray();
bool valid = IwCrypto.ValidateOperationProofs(opBytes, senderCommitment, receiverCommitment);

// Sign and wrap in SignedCryptoOperation
var signature = IwCrypto.SignMessage(senderPrivateKey, opBytes);
var signed = new SignedCryptoOperation
{
    Data = ByteString.CopyFrom(opBytes),
    Signature = ByteString.CopyFrom(signature),
    SignatureType = SignatureType.Ed25519,
};
byte[] signedBytes = signed.ToByteArray();

Error Handling

Method category Failure behavior
Build*, Commit, Generate*, Sign*, Encrypt*, Decrypt*, DeriveBlinding, Ed25519ToX25519* Throws IwCryptoException (extends InvalidOperationException)
VerifySignature Returns false
ValidateOperationProofs, ValidateSignedOperation Returns false

All methods map FFI return codes internally. IwCryptoException exposes structured fields for programmatic error handling:

Property Type Description
Function string The FFI function that failed (e.g. "iwzk_build_transfer")
Code int Return code: 1=input error, 3=proof failed, 4=insufficient balance, 5=overflow, 6=bad signature, 7=encryption error
Detail string? Human-readable detail from iwzk_last_error()

Running Tests

Build the native library first, then run the C# tests:

# Build the native library
cargo build --release

# Run all C# tests (47 tests)
cd bindings/csharp/tests && dotnet test

The test project includes:

Suite Tests Description
IwCryptoTests 18 FFI wrapper round-trips for all operation types
CryptoInteropTests 20 Rust encrypt / .NET decrypt and vice versa (BouncyCastle X25519 + AES-GCM)
SigningInteropTests 7 Rust sign / .NET verify and vice versa (BouncyCastle Ed25519)
ZkChainTests 2 Full blockchain scenario + blinding recovery

Cross-language interop tests use BouncyCastle to independently implement the same X25519 ECDH + HKDF-SHA256 + AES-256-GCM and Ed25519 pipelines, verifying that the Rust native library and pure .NET produce byte-identical results.

License

Proprietary. All rights reserved.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.3.0 95 5/28/2026
0.2.0 134 5/18/2026