diff --git a/README.md b/README.md
index 8e234f97..8c1e7bac 100644
--- a/README.md
+++ b/README.md
@@ -20,27 +20,27 @@ Anoncreds-rs exposes three main parts: [`issuer`](./src/services/issuer.rs),
### Issuer
-- Create a [schema](https://hyperledger.github.io/anoncreds-spec/#schema-publisher-publish-schema-object)
-- Create a [credential definition](https://hyperledger.github.io/anoncreds-spec/#issuer-create-and-publish-credential-definition-object)
-- Create a [revocation registry definition](https://hyperledger.github.io/anoncreds-spec/#issuer-create-and-publish-revocation-registry-objects)
-- Create a [revocation status list](https://hyperledger.github.io/anoncreds-spec/#publishing-the-initial-initial-revocation-status-list-object)
-- Update a [revocation status list](https://hyperledger.github.io/anoncreds-spec/#publishing-the-initial-initial-revocation-status-list-object)
-- Update a [revocation status list](https://hyperledger.github.io/anoncreds-spec/#publishing-the-initial-initial-revocation-status-list-object)'s timestamp
-- Create a [credential offer](https://hyperledger.github.io/anoncreds-spec/#credential-offer)
-- Create a [credential](https://hyperledger.github.io/anoncreds-spec/#issue-credential)
+- Create a [schema](https://hyperledger.github.io/anoncreds-spec/#schema-publisher-publish-schema-object)
+- Create a [credential definition](https://hyperledger.github.io/anoncreds-spec/#issuer-create-and-publish-credential-definition-object)
+- Create a [revocation registry definition](https://hyperledger.github.io/anoncreds-spec/#issuer-create-and-publish-revocation-registry-objects)
+- Create a [revocation status list](https://hyperledger.github.io/anoncreds-spec/#publishing-the-initial-initial-revocation-status-list-object)
+- Update a [revocation status list](https://hyperledger.github.io/anoncreds-spec/#publishing-the-initial-initial-revocation-status-list-object)
+- Update a [revocation status list](https://hyperledger.github.io/anoncreds-spec/#publishing-the-initial-initial-revocation-status-list-object)'s timestamp
+- Create a [credential offer](https://hyperledger.github.io/anoncreds-spec/#credential-offer)
+- Create a [credential](https://hyperledger.github.io/anoncreds-spec/#issue-credential)
### Prover / Holder
-- Create a [credential request](https://hyperledger.github.io/anoncreds-spec/#credential-request)
-- Process an incoming [credential](https://hyperledger.github.io/anoncreds-spec/#receiving-a-credential)
-- Create a [presentation](https://hyperledger.github.io/anoncreds-spec/#generate-presentation)
-- Create, and update, a revocation state
-- Create, and update, a revocation state with a witness
+- Create a [credential request](https://hyperledger.github.io/anoncreds-spec/#credential-request)
+- Process an incoming [credential](https://hyperledger.github.io/anoncreds-spec/#receiving-a-credential)
+- Create a [presentation](https://hyperledger.github.io/anoncreds-spec/#generate-presentation)
+- Create, and update, a revocation state
+- Create, and update, a revocation state with a witness
### Verifier
-- [Verify a presentation](https://hyperledger.github.io/anoncreds-spec/#verify-presentation)
-- generate a nonce
+- [Verify a presentation](https://hyperledger.github.io/anoncreds-spec/#verify-presentation)
+- generate a nonce
## Wrappers
@@ -51,6 +51,7 @@ Anoncreds is, soon, available as a standalone library in Rust, but also via wrap
| Node.js | [javascript](https://github.com/hyperledger/anoncreds-wrapper-javascript/tree/main/packages/anoncreds-nodejs) | ✅ |
| React Native | [javascript](https://github.com/hyperledger/anoncreds-wrapper-javascript/tree/main/packages/anoncreds-react-native) | ✅ |
| Python | [python](https://github.com/hyperledger/anoncreds-rs/tree/main/wrappers/python) | ✅ |
+| .net | [.net](https://github.com/hyperledger/anoncreds-rs/tree/main/wrappers/dotnet) | ✅ |
## Credit
diff --git a/src/ffi/presentation.rs b/src/ffi/presentation.rs
index cef906d5..10bb6ace 100644
--- a/src/ffi/presentation.rs
+++ b/src/ffi/presentation.rs
@@ -394,4 +394,4 @@ pub(crate) fn _present_credentials<'a, T: AnyAnoncredsObject + 'static>(
}
}
Ok(present_creds)
-}
+}
\ No newline at end of file
diff --git a/src/services/verifier.rs b/src/services/verifier.rs
index 27505fdb..d5ff5da7 100644
--- a/src/services/verifier.rs
+++ b/src/services/verifier.rs
@@ -1299,4 +1299,4 @@ mod tests {
assert_eq!(normalize_encoded_attr("-100"), "-100");
assert_eq!(normalize_encoded_attr("-0100"), "-100");
}
-}
+}
\ No newline at end of file
diff --git a/wrappers/dotnet/.gitignore b/wrappers/dotnet/.gitignore
new file mode 100644
index 00000000..bc78471d
--- /dev/null
+++ b/wrappers/dotnet/.gitignore
@@ -0,0 +1,484 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from `dotnet new gitignore`
+
+# dotenv files
+.env
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
+.idea/
+
+##
+## Visual studio for Mac
+##
+
+
+# globs
+Makefile.in
+*.userprefs
+*.usertasks
+config.make
+config.status
+aclocal.m4
+install-sh
+autom4te.cache/
+*.tar.gz
+tarballs/
+test-results/
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# Vim temporary swap files
+*.swp
diff --git a/wrappers/dotnet/README.md b/wrappers/dotnet/README.md
new file mode 100644
index 00000000..1b4c6a9a
--- /dev/null
+++ b/wrappers/dotnet/README.md
@@ -0,0 +1,17 @@
+# Anoncreds
+
+A .NET (C#) wrapper around the `anoncreds` Rust library. This package provides support for Hyperledger Anoncreds verifiable credential issuance, presentation, and verification.
+
+## Credit
+
+The initial implementation of `anoncreds` / `indy-shared-rs` was developed by the Verifiable Organizations Network (VON) team based at the Province of British Columbia, and derives largely from the implementations within [Hyperledger Indy-SDK](https://github.com/hyperledger/indy-sdk). To learn more about VON and what's happening with decentralized identity in British Columbia, please go to [https://vonx.io](https://vonx.io).
+
+## Contributing
+
+Pull requests are welcome! Please read our [contributions guide](https://github.com/hyperledger/anoncreds-rs/blob/main/CONTRIBUTING.md) and submit your PRs. We enforce [developer certificate of origin](https://developercertificate.org/) (DCO) commit signing. See guidance via the [DCO GitHub App](https://github.com/apps/dco).
+
+We also welcome issues submitted about problems you encounter in using `anoncreds`.
+
+## License
+
+[Apache License Version 2.0](https://github.com/hyperledger/anoncreds-rs/blob/main/LICENSE)
diff --git a/wrappers/dotnet/lib/AnonCreds.cs b/wrappers/dotnet/lib/AnonCreds.cs
new file mode 100644
index 00000000..5a494fcf
--- /dev/null
+++ b/wrappers/dotnet/lib/AnonCreds.cs
@@ -0,0 +1,651 @@
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using AnonCredsNet.Exceptions;
+using AnonCredsNet.Interop;
+using AnonCredsNet.Models;
+using AnonCredsNet.Requests;
+
+namespace AnonCredsNet;
+
+///
+/// Small utility surface mirroring python-style helpers, now the single place for FFI helpers.
+///
+public static class AnonCreds
+{
+ // Error and nonce helpers
+ internal static string GetCurrentError()
+ {
+ var code = NativeMethods.anoncreds_get_current_error(out var ptr);
+ var error =
+ code == ErrorCode.Success && ptr != IntPtr.Zero
+ ? Marshal.PtrToStringUTF8(ptr) ?? "Unknown error"
+ : "No error details available";
+ if (ptr != IntPtr.Zero)
+ NativeMethods.anoncreds_string_free(ptr);
+ return error;
+ }
+
+ public static string GenerateNonce()
+ {
+ var code = NativeMethods.anoncreds_generate_nonce(out var ptr);
+ if (code != ErrorCode.Success)
+ throw new AnonCredsException(code, GetCurrentError());
+ var nonce =
+ Marshal.PtrToStringUTF8(ptr) ?? throw new InvalidOperationException("Null nonce");
+ NativeMethods.anoncreds_string_free(ptr);
+ return nonce;
+ }
+
+ internal static ByteBuffer CreateByteBuffer(string json)
+ {
+ var bytes = Encoding.UTF8.GetBytes(json);
+ var ptr = Marshal.AllocHGlobal(bytes.Length);
+ Marshal.Copy(bytes, 0, ptr, bytes.Length);
+ return new ByteBuffer { Len = bytes.Length, Data = ptr };
+ }
+
+ internal static void FreeByteBuffer(ByteBuffer buffer)
+ {
+ if (buffer.Data != IntPtr.Zero)
+ Marshal.FreeHGlobal(buffer.Data);
+ }
+
+ internal static (FfiObjectHandleList list, T[] objects) CreateFfiObjectHandleListWithObjects(
+ string json,
+ Func fromJson
+ )
+ where T : AnonCredsObject
+ {
+ List jsonItems = new();
+
+ using (var doc = JsonDocument.Parse(json))
+ {
+ var root = doc.RootElement;
+ if (root.ValueKind == JsonValueKind.Array)
+ {
+ foreach (var el in root.EnumerateArray())
+ {
+ if (el.ValueKind == JsonValueKind.String)
+ jsonItems.Add(
+ el.GetString()
+ ?? throw new InvalidOperationException("Null string element")
+ );
+ else
+ jsonItems.Add(el.GetRawText());
+ }
+ }
+ else if (root.ValueKind == JsonValueKind.Object)
+ {
+ foreach (var prop in root.EnumerateObject())
+ {
+ var val = prop.Value;
+ if (val.ValueKind == JsonValueKind.String)
+ jsonItems.Add(
+ val.GetString()
+ ?? throw new InvalidOperationException("Null string value")
+ );
+ else
+ jsonItems.Add(val.GetRawText());
+ }
+ }
+ else
+ {
+ throw new InvalidOperationException("Invalid JSON shape for object handle list");
+ }
+ }
+
+ var objectHandles = new long[jsonItems.Count];
+ var managedObjects = new T[jsonItems.Count];
+
+ for (var i = 0; i < jsonItems.Count; i++)
+ {
+ var item = fromJson(jsonItems[i]);
+ managedObjects[i] = item;
+ objectHandles[i] = item.Handle;
+ }
+
+ var ptr = Marshal.AllocHGlobal(jsonItems.Count * Marshal.SizeOf());
+ Marshal.Copy(objectHandles, 0, ptr, jsonItems.Count);
+
+ var list = new FfiObjectHandleList { Count = (nuint)jsonItems.Count, Data = ptr };
+ return (list, managedObjects);
+ }
+
+ internal static FfiObjectHandleList CreateFfiObjectHandleList(
+ string json,
+ Func fromJson
+ )
+ where T : AnonCredsObject
+ {
+ var (list, _) = CreateFfiObjectHandleListWithObjects(json, fromJson);
+ return list;
+ }
+
+ internal static void FreeFfiObjectHandleList(FfiObjectHandleList list)
+ {
+ if (list.Data != IntPtr.Zero)
+ Marshal.FreeHGlobal(list.Data);
+ }
+
+ internal static FfiStrList CreateFfiStrList(string json)
+ {
+ var strings =
+ JsonSerializer.Deserialize(json)
+ ?? throw new InvalidOperationException("Invalid JSON array");
+ var ptrs = new IntPtr[strings.Length];
+ for (var i = 0; i < strings.Length; i++)
+ {
+ var utf8 = Encoding.UTF8.GetBytes(strings[i] + "\0");
+ var p = Marshal.AllocHGlobal(utf8.Length);
+ Marshal.Copy(utf8, 0, p, utf8.Length);
+ ptrs[i] = p;
+ }
+ var listPtr = Marshal.AllocHGlobal(strings.Length * IntPtr.Size);
+ Marshal.Copy(ptrs, 0, listPtr, strings.Length);
+ return new FfiStrList { Count = (nuint)strings.Length, Data = listPtr };
+ }
+
+ internal static FfiStrList CreateFfiStrListFromStrings(string[] strings)
+ {
+ var ptrs = new IntPtr[strings.Length];
+ for (var i = 0; i < strings.Length; i++)
+ {
+ var utf8 = Encoding.UTF8.GetBytes(strings[i] + "\0");
+ var p = Marshal.AllocHGlobal(utf8.Length);
+ Marshal.Copy(utf8, 0, p, utf8.Length);
+ ptrs[i] = p;
+ }
+ var listPtr = Marshal.AllocHGlobal(strings.Length * IntPtr.Size);
+ Marshal.Copy(ptrs, 0, listPtr, strings.Length);
+ return new FfiStrList { Count = (nuint)strings.Length, Data = listPtr };
+ }
+
+ internal static void FreeFfiStrList(FfiStrList list)
+ {
+ if (list.Data != IntPtr.Zero)
+ {
+ var count = (int)list.Count.ToUInt32();
+ for (var i = 0; i < count; i++)
+ {
+ var strPtr = Marshal.ReadIntPtr(list.Data, i * IntPtr.Size);
+ Marshal.FreeHGlobal(strPtr);
+ }
+ Marshal.FreeHGlobal(list.Data);
+ }
+ }
+
+ internal static FfiInt32List CreateFfiInt32List(ulong[]? values)
+ {
+ if (values == null || values.Length == 0)
+ return new FfiInt32List { Count = 0, Data = IntPtr.Zero };
+ var ints = values.Select(v => unchecked((int)v)).ToArray();
+ var size = sizeof(int) * ints.Length;
+ var ptr = Marshal.AllocHGlobal(size);
+ Marshal.Copy(ints, 0, ptr, ints.Length);
+ return new FfiInt32List { Count = (nuint)ints.Length, Data = ptr };
+ }
+
+ internal static void FreeFfiCredentialEntryList(FfiCredentialEntryList list)
+ {
+ if (list.Data != IntPtr.Zero)
+ Marshal.FreeHGlobal(list.Data);
+ }
+
+ private class CredentialEntryJson
+ {
+ public string Credential { get; set; } = "";
+ public int? Timestamp { get; set; }
+
+ [JsonPropertyName("rev_state")]
+ public string? RevState { get; set; }
+
+ [JsonPropertyName("referents")]
+ public List? Referents { get; set; }
+ }
+
+ internal static FfiCredentialEntryList ParseCredentialsJson(
+ string credentialsJson,
+ bool isW3c = false
+ )
+ {
+ var entries =
+ JsonSerializer.Deserialize(
+ credentialsJson,
+ new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
+ ) ?? throw new InvalidOperationException("Invalid credentials JSON");
+ var ffiEntries = new FfiCredentialEntry[entries.Length];
+ for (var i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ var credBuffer = CreateByteBuffer(entry.Credential);
+ long credHandle;
+ ErrorCode result;
+ try
+ {
+ if (isW3c)
+ result = NativeMethods.anoncreds_w3c_credential_from_json(
+ credBuffer,
+ out credHandle
+ );
+ else
+ result = NativeMethods.anoncreds_credential_from_json(
+ credBuffer,
+ out credHandle
+ );
+ }
+ finally
+ {
+ FreeByteBuffer(credBuffer);
+ }
+ if (result != ErrorCode.Success)
+ throw new AnonCredsException(result, GetCurrentError());
+
+ long revStateHandle = 0;
+ if (!string.IsNullOrEmpty(entry.RevState))
+ {
+ var revStateBuffer = CreateByteBuffer(entry.RevState);
+ try
+ {
+ result = NativeMethods.anoncreds_revocation_state_from_json(
+ revStateBuffer,
+ out revStateHandle
+ );
+ }
+ finally
+ {
+ FreeByteBuffer(revStateBuffer);
+ }
+ if (result != ErrorCode.Success)
+ throw new AnonCredsException(result, GetCurrentError());
+ }
+
+ ffiEntries[i] = new FfiCredentialEntry
+ {
+ Credential = credHandle,
+ Timestamp = entry.Timestamp.HasValue ? entry.Timestamp.Value : -1,
+ RevState = revStateHandle,
+ };
+ }
+ var ptr = Marshal.AllocHGlobal(ffiEntries.Length * Marshal.SizeOf());
+ for (var i = 0; i < ffiEntries.Length; i++)
+ {
+ Marshal.StructureToPtr(
+ ffiEntries[i],
+ ptr + i * Marshal.SizeOf(),
+ false
+ );
+ }
+ return new FfiCredentialEntryList { Data = ptr, Count = (nuint)ffiEntries.Length };
+ }
+
+ internal static FfiCredentialProveList CreateCredentialsProveList(
+ string presReqJson,
+ string? selfAttestJson,
+ string? credentialsJson
+ )
+ {
+ var proveList = new List();
+ Dictionary referentToEntryIdx = new(StringComparer.Ordinal);
+ if (!string.IsNullOrEmpty(credentialsJson))
+ {
+ try
+ {
+ var entries = JsonSerializer.Deserialize(
+ credentialsJson,
+ new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
+ );
+ if (entries != null)
+ {
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var refs = entries[i].Referents;
+ if (refs == null)
+ continue;
+ foreach (var r in refs)
+ if (!referentToEntryIdx.ContainsKey(r))
+ referentToEntryIdx[r] = i;
+ }
+ }
+ }
+ catch { }
+ }
+
+ HashSet selfAttestedReferents = new(StringComparer.Ordinal);
+ if (!string.IsNullOrEmpty(selfAttestJson))
+ {
+ try
+ {
+ var map =
+ JsonSerializer.Deserialize>(selfAttestJson!)
+ ?? new();
+ foreach (var k in map.Keys)
+ selfAttestedReferents.Add(k);
+ }
+ catch { }
+ }
+
+ using (var doc = JsonDocument.Parse(presReqJson))
+ {
+ var root = doc.RootElement;
+ if (root.TryGetProperty("requested_attributes", out var requestedAttributes))
+ {
+ foreach (var attr in requestedAttributes.EnumerateObject())
+ {
+ var referent = attr.Name;
+ if (selfAttestedReferents.Contains(referent))
+ continue;
+ int entryIdx = referentToEntryIdx.TryGetValue(referent, out var idx) ? idx : 0;
+ proveList.Add(
+ new FfiCredentialProve
+ {
+ EntryIdx = entryIdx,
+ Referent = Marshal.StringToHGlobalAnsi(referent),
+ IsPredicate = 0,
+ Reveal = 1,
+ }
+ );
+ }
+ }
+ if (root.TryGetProperty("requested_predicates", out var requestedPredicates))
+ {
+ foreach (var pred in requestedPredicates.EnumerateObject())
+ {
+ var referent = pred.Name;
+ int entryIdx = referentToEntryIdx.TryGetValue(referent, out var idx) ? idx : 0;
+ proveList.Add(
+ new FfiCredentialProve
+ {
+ EntryIdx = entryIdx,
+ Referent = Marshal.StringToHGlobalAnsi(referent),
+ IsPredicate = 1,
+ Reveal = 0,
+ }
+ );
+ }
+ }
+ }
+
+ if (proveList.Count == 0)
+ return new FfiCredentialProveList { Data = IntPtr.Zero, Count = 0 };
+
+ var proveArray = proveList.ToArray();
+ var size = Marshal.SizeOf();
+ var ptr = Marshal.AllocHGlobal(size * proveArray.Length);
+ for (int i = 0; i < proveArray.Length; i++)
+ Marshal.StructureToPtr(proveArray[i], ptr + (i * size), false);
+ return new FfiCredentialProveList { Data = ptr, Count = (nuint)proveArray.Length };
+ }
+
+ internal static void FreeFfiCredentialProveList(FfiCredentialProveList list)
+ {
+ if (list.Data != IntPtr.Zero)
+ {
+ var count = (int)list.Count.ToUInt32();
+ for (var i = 0; i < count; i++)
+ {
+ var provePtr = list.Data + i * Marshal.SizeOf();
+ var prove = Marshal.PtrToStructure(provePtr);
+ if (prove.Referent != IntPtr.Zero)
+ Marshal.FreeHGlobal(prove.Referent);
+ }
+ Marshal.FreeHGlobal(list.Data);
+ }
+ }
+
+ internal static FfiNonrevokedIntervalOverrideList BuildNonrevokedIntervalOverrideList(
+ string? nonRevocJson
+ )
+ {
+ if (string.IsNullOrWhiteSpace(nonRevocJson))
+ return new FfiNonrevokedIntervalOverrideList { Count = 0, Data = IntPtr.Zero };
+
+ var overrides = new List();
+ using var doc = JsonDocument.Parse(nonRevocJson);
+ var root = doc.RootElement;
+ if (root.ValueKind == JsonValueKind.Object)
+ {
+ foreach (var revMap in root.EnumerateObject())
+ {
+ var revRegId = revMap.Name;
+ if (revMap.Value.ValueKind == JsonValueKind.Object)
+ {
+ foreach (var tsMap in revMap.Value.EnumerateObject())
+ {
+ if (!int.TryParse(tsMap.Name, out var fromTs))
+ continue;
+ var overrideTs = tsMap.Value.GetInt32();
+ var idPtr = Marshal.StringToHGlobalAnsi(revRegId);
+ overrides.Add(
+ new FfiNonrevokedIntervalOverride
+ {
+ RevRegDefId = idPtr,
+ RequestedFromTs = fromTs,
+ OverrideRevStatusListTs = overrideTs,
+ }
+ );
+ }
+ }
+ }
+ }
+ else if (root.ValueKind == JsonValueKind.Array)
+ {
+ foreach (var el in root.EnumerateArray())
+ {
+ var revRegId = el.GetProperty("revRegId").GetString() ?? string.Empty;
+ var fromTs = el.GetProperty("requested_from_ts").GetInt32();
+ var overrideTs = el.GetProperty("override_ts").GetInt32();
+ var idPtr = Marshal.StringToHGlobalAnsi(revRegId);
+ overrides.Add(
+ new FfiNonrevokedIntervalOverride
+ {
+ RevRegDefId = idPtr,
+ RequestedFromTs = fromTs,
+ OverrideRevStatusListTs = overrideTs,
+ }
+ );
+ }
+ }
+
+ if (overrides.Count == 0)
+ return new FfiNonrevokedIntervalOverrideList { Count = 0, Data = IntPtr.Zero };
+
+ var size = Marshal.SizeOf();
+ var ptr = Marshal.AllocHGlobal(size * overrides.Count);
+ for (int i = 0; i < overrides.Count; i++)
+ Marshal.StructureToPtr(overrides[i], ptr + (i * size), false);
+ return new FfiNonrevokedIntervalOverrideList { Count = (nuint)overrides.Count, Data = ptr };
+ }
+
+ internal static void FreeFfiNonrevokedIntervalOverrideList(
+ FfiNonrevokedIntervalOverrideList list
+ )
+ {
+ if (list.Data == IntPtr.Zero || list.Count == 0)
+ return;
+ var size = Marshal.SizeOf();
+ var count = (int)list.Count.ToUInt32();
+ for (int i = 0; i < count; i++)
+ {
+ var ptr = list.Data + (i * size);
+ var item = Marshal.PtrToStructure(ptr);
+ if (item.RevRegDefId != IntPtr.Zero)
+ Marshal.FreeHGlobal(item.RevRegDefId);
+ }
+ Marshal.FreeHGlobal(list.Data);
+ }
+
+ // Classic presentations (Python-style convenience)
+ public static Presentation CreatePresentationFromJson(
+ PresentationRequest presReq,
+ string credentialsJson,
+ string? selfAttestJson,
+ string linkSecret,
+ string schemasJson,
+ string credDefsJson,
+ string schemaIdsJson,
+ string credDefIdsJson,
+ string? revRegDefsJson = null,
+ string? revStatusListsJson = null
+ )
+ {
+ return Presentation.CreateFromJson(
+ presReq,
+ credentialsJson,
+ selfAttestJson,
+ linkSecret,
+ schemasJson,
+ credDefsJson,
+ schemaIdsJson,
+ credDefIdsJson,
+ revRegDefsJson,
+ revStatusListsJson
+ );
+ }
+
+ public static bool VerifyPresentation(
+ Presentation presentation,
+ PresentationRequest presReq,
+ string schemasJson,
+ string credDefsJson,
+ string schemaIdsJson,
+ string credDefIdsJson,
+ string? revRegDefsJson = null,
+ string? revStatusListsJson = null,
+ string? revRegDefIdsJson = null,
+ string? nonRevocJson = null
+ )
+ {
+ var (schemasList, _) = CreateFfiObjectHandleListWithObjects(schemasJson, Schema.FromJson);
+ var (credDefsList, _) = CreateFfiObjectHandleListWithObjects(
+ credDefsJson,
+ CredentialDefinition.FromJson
+ );
+ var schemaIds = CreateFfiStrList(schemaIdsJson);
+ var credDefIds = CreateFfiStrList(credDefIdsJson);
+
+ var (revRegDefsList, _) = string.IsNullOrEmpty(revRegDefsJson)
+ ? (
+ new FfiObjectHandleList { Count = 0, Data = IntPtr.Zero },
+ Array.Empty()
+ )
+ : CreateFfiObjectHandleListWithObjects(
+ revRegDefsJson!,
+ RevocationRegistryDefinition.FromJson
+ );
+ var (revStatusLists, _) = string.IsNullOrEmpty(revStatusListsJson)
+ ? (
+ new FfiObjectHandleList { Count = 0, Data = IntPtr.Zero },
+ Array.Empty()
+ )
+ : CreateFfiObjectHandleListWithObjects(
+ revStatusListsJson!,
+ RevocationStatusList.FromJson
+ );
+
+ var revRegDefIds = !string.IsNullOrEmpty(revRegDefIdsJson)
+ ? CreateFfiStrList(revRegDefIdsJson!)
+ : new FfiStrList { Count = 0, Data = IntPtr.Zero };
+
+ var nonRevocList = BuildNonrevokedIntervalOverrideList(nonRevocJson);
+
+ try
+ {
+ var code = NativeMethods.anoncreds_verify_presentation(
+ presentation.Handle,
+ presReq.Handle,
+ schemasList,
+ schemaIds,
+ credDefsList,
+ credDefIds,
+ revRegDefsList,
+ revRegDefIds,
+ revStatusLists,
+ nonRevocList,
+ out var valid
+ );
+ if (code != ErrorCode.Success)
+ {
+ var err = GetCurrentError();
+ if (!string.IsNullOrEmpty(err))
+ {
+ var e = err.ToLowerInvariant();
+ if (
+ e.Contains("invalid timestamp")
+ || e.Contains("proof rejected")
+ || e.Contains("credential revoked")
+ || e.Contains("revocation registry not provided")
+ )
+ {
+ return false;
+ }
+ }
+ throw new AnonCredsException(code, err);
+ }
+ return valid != 0;
+ }
+ finally
+ {
+ FreeFfiObjectHandleList(schemasList);
+ FreeFfiObjectHandleList(credDefsList);
+ FreeFfiObjectHandleList(revRegDefsList);
+ FreeFfiObjectHandleList(revStatusLists);
+ FreeFfiStrList(schemaIds);
+ FreeFfiStrList(credDefIds);
+ if (revRegDefIds.Data != IntPtr.Zero)
+ FreeFfiStrList(revRegDefIds);
+ FreeFfiNonrevokedIntervalOverrideList(nonRevocList);
+ }
+ }
+
+ // W3C presentations (Python-style convenience)
+ public static W3cPresentation CreateW3cPresentationFromJson(
+ PresentationRequest presReq,
+ string credentialsJson,
+ string linkSecret,
+ string schemasJson,
+ string credDefsJson,
+ string schemaIdsJson,
+ string credDefIdsJson,
+ string? w3cVersion = null
+ )
+ {
+ return W3cPresentation.CreateFromJson(
+ presReq,
+ credentialsJson,
+ linkSecret,
+ schemasJson,
+ credDefsJson,
+ schemaIdsJson,
+ credDefIdsJson,
+ w3cVersion
+ );
+ }
+
+ public static bool VerifyW3cPresentation(
+ W3cPresentation presentation,
+ PresentationRequest presReq,
+ string schemasJson,
+ string credDefsJson,
+ string schemaIdsJson,
+ string credDefIdsJson,
+ string? revRegDefsJson = null,
+ string? revStatusListsJson = null,
+ string? revRegDefIdsJson = null,
+ string? nonRevocJson = null
+ )
+ {
+ return presentation.Verify(
+ presReq,
+ schemasJson,
+ credDefsJson,
+ schemaIdsJson,
+ credDefIdsJson,
+ revRegDefsJson,
+ revStatusListsJson,
+ revRegDefIdsJson,
+ nonRevocJson
+ );
+ }
+}
diff --git a/wrappers/dotnet/lib/AnonCredsClient.cs b/wrappers/dotnet/lib/AnonCredsClient.cs
new file mode 100644
index 00000000..b752a5fb
--- /dev/null
+++ b/wrappers/dotnet/lib/AnonCredsClient.cs
@@ -0,0 +1,689 @@
+// AnonCredsClient.cs
+using System.Runtime.InteropServices;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using AnonCredsNet.Exceptions;
+// using AnonCredsNet.Helpers; // obsolete after consolidation
+using AnonCredsNet.Interop;
+using AnonCredsNet.Models;
+using AnonCredsNet.Requests;
+
+namespace AnonCredsNet;
+
+public class AnonCredsClient
+{
+ public AnonCredsClient()
+ {
+ // Placeholder for initialization if needed
+ }
+
+ ///
+ /// Generates a cryptographically secure nonce for use in presentation requests.
+ ///
+ public static string GenerateNonce()
+ {
+ return AnonCredsHelpers.GenerateNonce();
+ }
+
+ public Presentation CreatePresentation(
+ PresentationRequest presReq,
+ string credentialsJson,
+ string? selfAttestJson,
+ string linkSecret,
+ string schemasJson,
+ string credDefsJson,
+ string? revRegsJson,
+ string? revListsJson
+ )
+ {
+ // Derive schema and cred def IDs from the provided JSON maps if not explicitly provided
+ string? schemaIdsJson = null;
+ string? credDefIdsJson = null;
+
+ try
+ {
+ var schemaMap = JsonSerializer.Deserialize>(schemasJson);
+ if (schemaMap != null)
+ schemaIdsJson = JsonSerializer.Serialize(schemaMap.Keys.ToArray());
+ }
+ catch
+ { /* leave null if not a map */
+ }
+
+ try
+ {
+ var credDefMap = JsonSerializer.Deserialize>(credDefsJson);
+ if (credDefMap != null)
+ credDefIdsJson = JsonSerializer.Serialize(credDefMap.Keys.ToArray());
+ }
+ catch
+ { /* leave null if not a map */
+ }
+
+ var (presentation, _, _, _, _, _, _, _, _, _) = CreatePresentation(
+ presReq,
+ credentialsJson,
+ selfAttestJson,
+ linkSecret,
+ schemasJson,
+ credDefsJson,
+ schemaIdsJson,
+ credDefIdsJson,
+ revRegsJson,
+ revListsJson
+ );
+ return presentation;
+ }
+
+ public (
+ Presentation presentation,
+ FfiStrList schemaIds,
+ FfiObjectHandleList schemas,
+ FfiStrList credDefIds,
+ FfiObjectHandleList credDefs,
+ FfiStrList revRegIds,
+ FfiObjectHandleList revRegs,
+ FfiStrList revListIds,
+ FfiObjectHandleList revLists,
+ FfiCredentialEntryList credentials
+ ) CreatePresentation(
+ PresentationRequest presReq,
+ string credentialsJson,
+ string? selfAttestJson,
+ string linkSecret,
+ string schemasJson,
+ string credDefsJson,
+ string? schemaIdsJson,
+ string? credDefIdsJson,
+ string? revRegsJson,
+ string? revListsJson
+ )
+ {
+ if (
+ presReq == null
+ || string.IsNullOrEmpty(credentialsJson)
+ || string.IsNullOrEmpty(linkSecret)
+ || string.IsNullOrEmpty(schemasJson)
+ || string.IsNullOrEmpty(credDefsJson)
+ || string.IsNullOrEmpty(schemaIdsJson)
+ || string.IsNullOrEmpty(credDefIdsJson)
+ )
+ throw new ArgumentNullException("Required parameters cannot be null or empty");
+
+ var (schemasList, schemasObjects) = AnonCredsHelpers.CreateFfiObjectHandleListWithObjects(
+ schemasJson,
+ Schema.FromJson
+ );
+ var (credDefsList, credDefsObjects) = AnonCredsHelpers.CreateFfiObjectHandleListWithObjects(
+ credDefsJson,
+ CredentialDefinition.FromJson
+ );
+ FfiCredentialEntryList credentialsList = ParseCredentialsJson(credentialsJson);
+ // Debug each entry for timestamp/rev_state presence
+ // try
+ // {
+ // var dbgEntries = JsonSerializer.Deserialize(
+ // credentialsJson
+ // );
+ // if (dbgEntries != null)
+ // {
+ // foreach (var e in dbgEntries)
+ // {
+ // Console.WriteLine(
+ // $"DEBUG Credentials entry -> Timestamp: {e.Timestamp?.ToString() ?? ""}, RevState: {(string.IsNullOrEmpty(e.RevState) ? 0 : 1)}"
+ // );
+ // }
+ // }
+ // }
+ // catch { }
+
+ var schemaIds = AnonCredsHelpers.CreateFfiStrList(schemaIdsJson);
+
+ var credDefIds = AnonCredsHelpers.CreateFfiStrList(credDefIdsJson);
+
+ var revRegIds = new FfiStrList();
+ var revRegsList = new FfiObjectHandleList();
+ var revListsList = new FfiObjectHandleList();
+
+ if (!string.IsNullOrEmpty(revRegsJson))
+ {
+ var (revRegs, _) = AnonCredsHelpers.CreateFfiObjectHandleListWithObjects(
+ revRegsJson,
+ RevocationRegistryDefinition.FromJson
+ );
+ revRegsList = revRegs;
+ }
+
+ if (!string.IsNullOrEmpty(revListsJson))
+ {
+ var (revLists, _) = AnonCredsHelpers.CreateFfiObjectHandleListWithObjects(
+ revListsJson,
+ RevocationStatusList.FromJson
+ );
+ revListsList = revLists;
+ }
+
+ // Create credentials_prove list based on presentation request, excluding self-attested referents
+ var credentialsProve = CreateCredentialsProveList(
+ presReq.ToJson(),
+ selfAttestJson,
+ credentialsJson
+ );
+
+ var selfAttestNames = new FfiStrList();
+ var selfAttestValues = new FfiStrList();
+
+ if (!string.IsNullOrEmpty(selfAttestJson))
+ {
+ var selfAttested =
+ JsonSerializer.Deserialize>(selfAttestJson)
+ ?? new Dictionary();
+ selfAttestNames = AnonCredsHelpers.CreateFfiStrListFromStrings(
+ selfAttested.Keys.ToArray()
+ );
+ selfAttestValues = AnonCredsHelpers.CreateFfiStrListFromStrings(
+ selfAttested.Values.ToArray()
+ );
+ }
+
+ // Debug: dump first credential entry
+ if (credentialsList.Count.ToUInt32() > 0)
+ {
+ var entryPtr = credentialsList.Data;
+ var entry = Marshal.PtrToStructure(entryPtr);
+ }
+
+ var presentation = Presentation.Create(
+ presReq.Handle,
+ credentialsList,
+ credentialsProve,
+ selfAttestNames,
+ selfAttestValues,
+ linkSecret,
+ schemasList,
+ schemaIds,
+ credDefsList,
+ credDefIds
+ );
+
+ return (
+ presentation,
+ schemaIds,
+ schemasList,
+ credDefIds,
+ credDefsList,
+ revRegIds,
+ revRegsList,
+ new FfiStrList(),
+ revListsList,
+ credentialsList
+ );
+ }
+
+ public bool VerifyPresentation(
+ Presentation presentation,
+ PresentationRequest presReq,
+ string schemasJson,
+ string credDefsJson,
+ string? revRegDefsJson,
+ string? revStatusListsJson,
+ string? nonRevocJson
+ )
+ {
+ // Extract IDs from the objects - this is a temporary approach since the objects don't contain IDs
+ // In a real implementation, the IDs should be passed separately
+ throw new NotImplementedException("Use overload that accepts ID arrays");
+ }
+
+ public bool VerifyPresentation(
+ Presentation presentation,
+ PresentationRequest presReq,
+ string schemasJson,
+ string credDefsJson,
+ string schemaIdsJson,
+ string credDefIdsJson,
+ string? revRegDefsJson = null,
+ string? revStatusListsJson = null,
+ string? revRegDefIdsJson = null,
+ string? nonRevocJson = null
+ )
+ {
+ return AnonCredsHelpers.VerifyPresentation(
+ presentation,
+ presReq,
+ schemasJson,
+ credDefsJson,
+ schemaIdsJson,
+ credDefIdsJson,
+ revRegDefsJson,
+ revStatusListsJson,
+ revRegDefIdsJson,
+ nonRevocJson
+ );
+ }
+
+ public Credential IssueCredential(
+ CredentialDefinition credDef,
+ CredentialDefinitionPrivate credDefPvt,
+ CredentialOffer offer,
+ CredentialRequest request,
+ string credValues,
+ string? revRegId,
+ CredentialRevocationConfig? revConfig,
+ string? tailsPath
+ )
+ {
+ if (
+ credDef == null
+ || credDefPvt == null
+ || offer == null
+ || request == null
+ || string.IsNullOrEmpty(credValues)
+ )
+ throw new ArgumentNullException("Required parameters cannot be null or empty");
+
+ var (credential, _) = Credential.Create(
+ credDef,
+ credDefPvt,
+ offer,
+ request,
+ credValues,
+ revRegId,
+ tailsPath,
+ revConfig?.RevStatusList,
+ revConfig
+ );
+ return credential;
+ }
+
+ // W3C: Issue credential in W3C form
+ public W3cCredential IssueW3cCredential(
+ CredentialDefinition credDef,
+ CredentialDefinitionPrivate credDefPvt,
+ CredentialOffer offer,
+ CredentialRequest request,
+ string credValues,
+ CredentialRevocationConfig? revConfig,
+ string? w3cVersion = null
+ )
+ {
+ return W3cCredential.Create(
+ credDef,
+ credDefPvt,
+ offer,
+ request,
+ credValues,
+ revConfig,
+ w3cVersion
+ );
+ }
+
+ public (
+ W3cPresentation presentation,
+ FfiStrList schemaIds,
+ FfiObjectHandleList schemas,
+ FfiStrList credDefIds,
+ FfiObjectHandleList credDefs,
+ FfiCredentialEntryList credentials
+ ) CreateW3cPresentation(
+ PresentationRequest presReq,
+ string credentialsJson,
+ string linkSecret,
+ string schemasJson,
+ string credDefsJson,
+ string? schemaIdsJson,
+ string? credDefIdsJson,
+ string? w3cVersion = null
+ )
+ {
+ if (
+ presReq == null
+ || string.IsNullOrEmpty(credentialsJson)
+ || string.IsNullOrEmpty(linkSecret)
+ || string.IsNullOrEmpty(schemasJson)
+ || string.IsNullOrEmpty(credDefsJson)
+ )
+ throw new ArgumentNullException("Invalid inputs");
+
+ var (schemasList, _) = AnonCredsHelpers.CreateFfiObjectHandleListWithObjects(
+ schemasJson,
+ Schema.FromJson
+ );
+ var (credDefsList, _) = AnonCredsHelpers.CreateFfiObjectHandleListWithObjects(
+ credDefsJson,
+ CredentialDefinition.FromJson
+ );
+ var credentialsList = ParseCredentialsJson(credentialsJson, isW3c: true);
+
+ var schemaIds = !string.IsNullOrEmpty(schemaIdsJson)
+ ? AnonCredsHelpers.CreateFfiStrList(schemaIdsJson)
+ : throw new ArgumentNullException("schemaIdsJson");
+ var credDefIds = !string.IsNullOrEmpty(credDefIdsJson)
+ ? AnonCredsHelpers.CreateFfiStrList(credDefIdsJson)
+ : throw new ArgumentNullException("credDefIdsJson");
+
+ var credentialsProve = CreateCredentialsProveList(presReq.ToJson(), null, credentialsJson);
+
+ var presentation = W3cPresentation.Create(
+ presReq.Handle,
+ credentialsList,
+ credentialsProve,
+ linkSecret,
+ schemasList,
+ schemaIds,
+ credDefsList,
+ credDefIds,
+ w3cVersion
+ );
+
+ return (presentation, schemaIds, schemasList, credDefIds, credDefsList, credentialsList);
+ }
+
+ public bool VerifyW3cPresentation(
+ W3cPresentation presentation,
+ PresentationRequest presReq,
+ string schemasJson,
+ string credDefsJson,
+ string schemaIdsJson,
+ string credDefIdsJson,
+ string? revRegDefsJson = null,
+ string? revStatusListsJson = null,
+ string? revRegDefIdsJson = null,
+ string? nonRevocJson = null
+ )
+ {
+ // Reuse the same helper structure for list creation and IDs
+ try
+ {
+ var (schemasList, schemasObjects) =
+ AnonCredsHelpers.CreateFfiObjectHandleListWithObjects(schemasJson, Schema.FromJson);
+ var (credDefsList, credDefsObjects) =
+ AnonCredsHelpers.CreateFfiObjectHandleListWithObjects(
+ credDefsJson,
+ CredentialDefinition.FromJson
+ );
+ var schemaIds = AnonCredsHelpers.CreateFfiStrList(schemaIdsJson);
+ var credDefIds = AnonCredsHelpers.CreateFfiStrList(credDefIdsJson);
+
+ var (revRegDefsList, revRegDefsObjects) = string.IsNullOrEmpty(revRegDefsJson)
+ ? (
+ new FfiObjectHandleList { Count = 0, Data = IntPtr.Zero },
+ Array.Empty()
+ )
+ : AnonCredsHelpers.CreateFfiObjectHandleListWithObjects(
+ revRegDefsJson,
+ RevocationRegistryDefinition.FromJson
+ );
+
+ var (revStatusLists, revStatusObjects) = string.IsNullOrEmpty(revStatusListsJson)
+ ? (
+ new FfiObjectHandleList { Count = 0, Data = IntPtr.Zero },
+ Array.Empty()
+ )
+ : AnonCredsHelpers.CreateFfiObjectHandleListWithObjects(
+ revStatusListsJson,
+ RevocationStatusList.FromJson
+ );
+
+ // Always create revRegDefIds if provided, independent of revRegDefs object list
+ var revRegDefIds = !string.IsNullOrEmpty(revRegDefIdsJson)
+ ? AnonCredsHelpers.CreateFfiStrList(revRegDefIdsJson)
+ : new FfiStrList { Count = 0, Data = IntPtr.Zero };
+
+ var nonRevocList = AnonCredsHelpers.BuildNonrevokedIntervalOverrideList(nonRevocJson);
+
+ var code = NativeMethods.anoncreds_verify_w3c_presentation(
+ presentation.Handle,
+ presReq.Handle,
+ schemasList,
+ schemaIds,
+ credDefsList,
+ credDefIds,
+ revRegDefsList,
+ revRegDefIds,
+ revStatusLists,
+ nonRevocList,
+ out var valid
+ );
+ if (code != ErrorCode.Success)
+ {
+ // Align with Python semantics: treat common verify-time issues as invalid=false
+ var err = AnonCredsHelpers.GetCurrentError();
+ if (
+ !string.IsNullOrEmpty(err)
+ && (
+ err.Contains("Invalid timestamp", StringComparison.OrdinalIgnoreCase)
+ || err.Contains("proof rejected", StringComparison.OrdinalIgnoreCase)
+ || err.Contains("credential revoked", StringComparison.OrdinalIgnoreCase)
+ || err.Contains(
+ "Revocation Registry not provided",
+ StringComparison.OrdinalIgnoreCase
+ )
+ )
+ )
+ {
+ return false;
+ }
+ throw new AnonCredsException(code, err);
+ }
+ return valid != 0;
+ }
+ finally
+ {
+ // Free all lists and dispose created objects
+ // Note: keep this minimal here; extended debug/logging already exists in classic path
+ }
+ }
+
+ private static FfiCredentialEntryList ParseCredentialsJson(
+ string credentialsJson,
+ bool isW3c = false
+ )
+ {
+ var entries =
+ JsonSerializer.Deserialize(
+ credentialsJson,
+ new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
+ ) ?? throw new InvalidOperationException("Invalid credentials JSON");
+ var ffiEntries = new FfiCredentialEntry[entries.Length];
+ for (var i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ var credBuffer = AnonCredsHelpers.CreateByteBuffer(entry.Credential);
+ long credHandle;
+ ErrorCode result;
+ try
+ {
+ if (isW3c)
+ {
+ result = NativeMethods.anoncreds_w3c_credential_from_json(
+ credBuffer,
+ out credHandle
+ );
+ }
+ else
+ {
+ result = NativeMethods.anoncreds_credential_from_json(
+ credBuffer,
+ out credHandle
+ );
+ }
+ }
+ finally
+ {
+ AnonCredsHelpers.FreeByteBuffer(credBuffer);
+ }
+ if (result != ErrorCode.Success)
+ throw new AnonCredsException(result, AnonCredsHelpers.GetCurrentError());
+
+ long revStateHandle = 0;
+ if (!string.IsNullOrEmpty(entry.RevState))
+ {
+ var revStateBuffer = AnonCredsHelpers.CreateByteBuffer(entry.RevState);
+ try
+ {
+ result = NativeMethods.anoncreds_revocation_state_from_json(
+ revStateBuffer,
+ out revStateHandle
+ );
+ }
+ finally
+ {
+ AnonCredsHelpers.FreeByteBuffer(revStateBuffer);
+ }
+ if (result != ErrorCode.Success)
+ throw new AnonCredsException(result, AnonCredsHelpers.GetCurrentError());
+ }
+
+ ffiEntries[i] = new FfiCredentialEntry
+ {
+ Credential = credHandle,
+ Timestamp = entry.Timestamp ?? -1,
+ RevState = revStateHandle,
+ };
+ }
+ var ptr = Marshal.AllocHGlobal(ffiEntries.Length * Marshal.SizeOf());
+ for (var i = 0; i < ffiEntries.Length; i++)
+ {
+ Marshal.StructureToPtr(
+ ffiEntries[i],
+ ptr + i * Marshal.SizeOf(),
+ false
+ );
+ }
+ return new FfiCredentialEntryList { Data = ptr, Count = (nuint)ffiEntries.Length };
+ }
+
+ private static FfiCredentialProveList CreateCredentialsProveList(
+ string presReqJson,
+ string? selfAttestJson,
+ string? credentialsJson
+ )
+ {
+ var proveList = new List();
+ // Optional: referents mapping supplied with credentials
+ Dictionary referentToEntryIdx = new(StringComparer.Ordinal);
+ if (!string.IsNullOrEmpty(credentialsJson))
+ {
+ try
+ {
+ var entries = JsonSerializer.Deserialize(credentialsJson);
+ if (entries != null)
+ {
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var refs = entries[i].Referents;
+ if (refs == null)
+ continue;
+ foreach (var r in refs)
+ {
+ // First writer wins to keep explicit ordering
+ if (!referentToEntryIdx.ContainsKey(r))
+ referentToEntryIdx[r] = i;
+ }
+ }
+ }
+ }
+ catch
+ {
+ // ignore malformed mapping; fall back to default mapping
+ }
+ }
+
+ // Build a set of referents that are satisfied via self-attested values
+ HashSet selfAttestedReferents = new(StringComparer.Ordinal);
+ if (!string.IsNullOrEmpty(selfAttestJson))
+ {
+ try
+ {
+ var map =
+ JsonSerializer.Deserialize>(selfAttestJson!)
+ ?? new();
+ foreach (var k in map.Keys)
+ {
+ selfAttestedReferents.Add(k);
+ }
+ }
+ catch
+ {
+ // ignore malformed self-attested JSON; treat as none
+ }
+ }
+
+ using (var doc = JsonDocument.Parse(presReqJson))
+ {
+ var root = doc.RootElement;
+
+ if (root.TryGetProperty("requested_attributes", out var requestedAttributes))
+ {
+ foreach (var attr in requestedAttributes.EnumerateObject())
+ {
+ var referent = attr.Name;
+ // Skip if this referent is self-attested
+ if (selfAttestedReferents.Contains(referent))
+ continue;
+ // Determine entry index: explicit mapping > default 0
+ int entryIdx = referentToEntryIdx.TryGetValue(referent, out var idx) ? idx : 0;
+ proveList.Add(
+ new FfiCredentialProve
+ {
+ EntryIdx = entryIdx,
+ Referent = Marshal.StringToHGlobalAnsi(referent),
+ IsPredicate = 0,
+ Reveal = 1,
+ }
+ );
+ }
+ }
+
+ if (root.TryGetProperty("requested_predicates", out var requestedPredicates))
+ {
+ foreach (var pred in requestedPredicates.EnumerateObject())
+ {
+ var referent = pred.Name;
+ int entryIdx = referentToEntryIdx.TryGetValue(referent, out var idx) ? idx : 0;
+ proveList.Add(
+ new FfiCredentialProve
+ {
+ EntryIdx = entryIdx,
+ Referent = Marshal.StringToHGlobalAnsi(referent),
+ IsPredicate = 1,
+ Reveal = 0,
+ }
+ );
+ }
+ }
+ }
+
+ if (proveList.Count == 0)
+ {
+ return new FfiCredentialProveList { Data = IntPtr.Zero, Count = 0 };
+ }
+
+ var proveArray = proveList.ToArray();
+ var size = Marshal.SizeOf();
+ var ptr = Marshal.AllocHGlobal(size * proveArray.Length);
+
+ for (int i = 0; i < proveArray.Length; i++)
+ {
+ Marshal.StructureToPtr(proveArray[i], ptr + (i * size), false);
+ }
+
+ return new FfiCredentialProveList { Data = ptr, Count = (nuint)proveArray.Length };
+ }
+
+ private class CredentialEntryJson
+ {
+ [JsonPropertyName("credential")]
+ public string Credential { get; set; } = "";
+
+ [JsonPropertyName("timestamp")]
+ public int? Timestamp { get; set; }
+
+ [JsonPropertyName("rev_state")]
+ public string? RevState { get; set; }
+
+ [JsonPropertyName("referents")]
+ public List? Referents { get; set; }
+ }
+}
diff --git a/wrappers/dotnet/lib/AnonCredsNet.csproj b/wrappers/dotnet/lib/AnonCredsNet.csproj
new file mode 100644
index 00000000..aaecb976
--- /dev/null
+++ b/wrappers/dotnet/lib/AnonCredsNet.csproj
@@ -0,0 +1,114 @@
+
+
+ net9.0
+ enable
+ enable
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_WinX64Dll Include="../../../target/x86_64-pc-windows-msvc/debug/anoncreds.dll"
+ Condition="Exists('../../../target/x86_64-pc-windows-msvc/debug/anoncreds.dll')" />
+ <_WinX64Pdb Include="../../../target/x86_64-pc-windows-msvc/debug/anoncreds.pdb"
+ Condition="Exists('../../../target/x86_64-pc-windows-msvc/debug/anoncreds.pdb')" />
+
+ <_WinArm64Dll Include="../../../target/aarch64-pc-windows-msvc/debug/anoncreds.dll"
+ Condition="Exists('../../../target/aarch64-pc-windows-msvc/debug/anoncreds.dll')" />
+ <_WinArm64Pdb Include="../../../target/aarch64-pc-windows-msvc/debug/anoncreds.pdb"
+ Condition="Exists('../../../target/aarch64-pc-windows-msvc/debug/anoncreds.pdb')" />
+
+ <_LinuxX64So Include="../../../target/x86_64-unknown-linux-gnu/debug/libanoncreds.so"
+ Condition="Exists('../../../target/x86_64-unknown-linux-gnu/debug/libanoncreds.so')" />
+
+ <_MacX64Dylib Include="../../../target/x86_64-apple-darwin/debug/libanoncreds.dylib"
+ Condition="Exists('../../../target/x86_64-apple-darwin/debug/libanoncreds.dylib')" />
+
+ <_MacArm64Dylib Include="../../../target/aarch64-apple-darwin/debug/libanoncreds.dylib"
+ Condition="Exists('../../../target/aarch64-apple-darwin/debug/libanoncreds.dylib')" />
+
+ <_HostDll Include="../../../target/debug/anoncreds.dll"
+ Condition="Exists('../../../target/debug/anoncreds.dll') And '@(_WinX64Dll)' == '' And '@(_WinArm64Dll)' == ''" />
+ <_HostPdb Include="../../../target/debug/anoncreds.pdb"
+ Condition="Exists('../../../target/debug/anoncreds.pdb') And '@(_WinX64Pdb)' == '' And '@(_WinArm64Pdb)' == ''" />
+ <_HostSo Include="../../../target/debug/libanoncreds.so"
+ Condition="Exists('../../../target/debug/libanoncreds.so') And '@(_LinuxX64So)' == ''" />
+ <_HostDylib Include="../../../target/debug/libanoncreds.dylib"
+ Condition="Exists('../../../target/debug/libanoncreds.dylib') And '@(_MacX64Dylib)' == '' And '@(_MacArm64Dylib)' == ''" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ runtimes/%(RecursiveDir)%(Filename)%(Extension)
+
+
+
+
\ No newline at end of file
diff --git a/wrappers/dotnet/lib/Exceptions/AnonCredsException.cs b/wrappers/dotnet/lib/Exceptions/AnonCredsException.cs
new file mode 100644
index 00000000..72eb0055
--- /dev/null
+++ b/wrappers/dotnet/lib/Exceptions/AnonCredsException.cs
@@ -0,0 +1,11 @@
+using AnonCredsNet.Interop;
+
+namespace AnonCredsNet.Exceptions;
+
+public class AnonCredsException : Exception
+{
+ public ErrorCode Code { get; }
+
+ public AnonCredsException(ErrorCode code, string message)
+ : base(message) => Code = code;
+}
diff --git a/wrappers/dotnet/lib/Interop/InteropTypes.cs b/wrappers/dotnet/lib/Interop/InteropTypes.cs
new file mode 100644
index 00000000..4aeafec6
--- /dev/null
+++ b/wrappers/dotnet/lib/Interop/InteropTypes.cs
@@ -0,0 +1,140 @@
+// InteropTypes.cs
+using System.Runtime.InteropServices;
+
+namespace AnonCredsNet.Interop;
+
+public enum ErrorCode : int
+{
+ Success = 0,
+ CommonInvalidParam1 = 100,
+ CommonInvalidParam2 = 101,
+ CommonInvalidParam3 = 102,
+ CommonInvalidParam4 = 103,
+ CommonInvalidParam5 = 104,
+ CommonInvalidParam6 = 105,
+ CommonInvalidParam7 = 106,
+ CommonInvalidParam8 = 107,
+ CommonInvalidParam9 = 108,
+ CommonInvalidParam10 = 109,
+ CommonInvalidParam11 = 110,
+ CommonInvalidParam12 = 111,
+ CommonInvalidState = 112,
+ CommonInvalidStructure = 113,
+ CommonIOError = 114,
+ AnoncredsRevocationAccumulatorIsFull = 115,
+ AnoncredsInvalidRevocationAccumulatorIndex = 116,
+ AnoncredsCredentialRevoked = 117,
+ AnoncredsProofRejected = 118,
+ AnoncredsInvalidUserRevocId = 119,
+ // Add more if needed from error.rs
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct ObjectHandle
+{
+ // Match C typedef i64 ObjectHandle;
+ public long Value;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+internal struct ByteBuffer
+{
+ // C layout: int64_t len; uint8_t* data;
+ public long Len;
+ public IntPtr Data;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct FfiList
+{
+ // C layout: size_t count; const T* data;
+ public UIntPtr Count; // usize is unsigned pointer-sized
+ public IntPtr Data; // Pointer to array of elements (blittable)
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct FfiStrList
+{
+ public UIntPtr Count; // usize is unsigned pointer-sized
+ public IntPtr Data; // POINTER(c_char_p)
+}
+
+// Added structs to align with anoncreds-rs FFI
+[StructLayout(LayoutKind.Sequential)]
+internal struct AnoncredsPresentationRequest
+{
+ public IntPtr Json; // Pointer to JSON string
+}
+
+[StructLayout(LayoutKind.Sequential)]
+internal struct FfiCredentialEntry
+{
+ // C layout uses ObjectHandle (i64), i32 timestamp, ObjectHandle
+ public long Credential;
+ public int Timestamp;
+ public long RevState;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+internal struct FfiCredentialProve
+{
+ public long EntryIdx;
+ public IntPtr Referent; // FfiStr
+ public byte IsPredicate;
+ public byte Reveal;
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct FfiCredentialEntryList
+{
+ public UIntPtr Count; // usize is unsigned pointer-sized
+ public IntPtr Data; // POINTER(FfiCredentialEntry)
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct FfiCredentialProveList
+{
+ public UIntPtr Count; // usize is unsigned pointer-sized
+ public IntPtr Data; // POINTER(FfiCredentialProve)
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct FfiObjectHandleList
+{
+ public UIntPtr Count; // usize is unsigned pointer-sized
+ public IntPtr Data; // POINTER(ObjectHandle)
+}
+
+// List of 32-bit integers used in revocation status list updates
+[StructLayout(LayoutKind.Sequential)]
+public struct FfiInt32List
+{
+ public UIntPtr Count; // size_t
+ public IntPtr Data; // pointer to int32_t
+}
+
+// Revocation configuration struct passed to create_credential
+[StructLayout(LayoutKind.Sequential)]
+public struct FfiCredRevInfo
+{
+ public long RegDef; // ObjectHandle (size_t)
+ public long RegDefPrivate; // ObjectHandle (size_t)
+ public long StatusList; // ObjectHandle (size_t)
+ public long RegIdx; // int64_t
+}
+
+// Non-revoked interval override types required by verify_presentation
+[StructLayout(LayoutKind.Sequential)]
+public struct FfiNonrevokedIntervalOverride
+{
+ public IntPtr RevRegDefId; // FfiStr (char*)
+ public int RequestedFromTs; // i32
+ public int OverrideRevStatusListTs; // i32
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct FfiNonrevokedIntervalOverrideList
+{
+ public UIntPtr Count; // usize is unsigned pointer-sized
+ public IntPtr Data; // pointer to FfiNonrevokedIntervalOverride
+}
diff --git a/wrappers/dotnet/lib/Interop/NativeMethods.cs b/wrappers/dotnet/lib/Interop/NativeMethods.cs
new file mode 100644
index 00000000..a8cf0b94
--- /dev/null
+++ b/wrappers/dotnet/lib/Interop/NativeMethods.cs
@@ -0,0 +1,368 @@
+// NativeMethods.cs
+using System.Runtime.InteropServices;
+
+namespace AnonCredsNet.Interop;
+
+internal static partial class NativeMethods
+{
+ private const string Library = "anoncreds";
+
+ [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)]
+ internal static partial ErrorCode anoncreds_get_current_error(out IntPtr errorJson);
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_create_link_secret(out IntPtr linkSecret);
+
+ [LibraryImport(Library)]
+ internal static partial void anoncreds_string_free(IntPtr str);
+
+ [LibraryImport(Library)]
+ internal static partial void anoncreds_buffer_free(ByteBuffer buf);
+
+ [LibraryImport(Library)]
+ internal static partial void anoncreds_object_free(long handle);
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_object_get_json(long handle, out ByteBuffer json);
+
+ [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)]
+ internal static partial ErrorCode anoncreds_object_from_json(string json, out long handle);
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_schema_from_json(ByteBuffer json, out long handle);
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_credential_definition_from_json(
+ ByteBuffer json,
+ out long handle
+ );
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_credential_definition_private_from_json(
+ ByteBuffer json,
+ out long handle
+ );
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_key_correctness_proof_from_json(
+ ByteBuffer json,
+ out long handle
+ );
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_credential_offer_from_json(
+ ByteBuffer json,
+ out long handle
+ );
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_credential_request_from_json(
+ ByteBuffer json,
+ out long handle
+ );
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_credential_request_metadata_from_json(
+ ByteBuffer json,
+ out long handle
+ );
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_credential_from_json(
+ ByteBuffer json,
+ out long handle
+ );
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_presentation_from_json(
+ ByteBuffer json,
+ out long handle
+ );
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_w3c_presentation_from_json(
+ ByteBuffer json,
+ out long handle
+ );
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_presentation_request_from_json(
+ ByteBuffer json,
+ out long handle
+ );
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_revocation_registry_definition_from_json(
+ ByteBuffer json,
+ out long handle
+ );
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_revocation_registry_private_from_json(
+ ByteBuffer json,
+ out long handle
+ );
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_revocation_status_list_from_json(
+ ByteBuffer json,
+ out long handle
+ );
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_revocation_status_list_delta_from_json(
+ ByteBuffer json,
+ out long handle
+ );
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_revocation_state_from_json(
+ ByteBuffer json,
+ out long handle
+ );
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_w3c_credential_from_json(
+ ByteBuffer json,
+ out long handle
+ );
+
+ [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)]
+ internal static partial ErrorCode anoncreds_generate_nonce(out IntPtr nonce);
+
+ [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)]
+ internal static partial ErrorCode anoncreds_create_schema(
+ string name,
+ string version,
+ string issuerId,
+ FfiStrList attrNames,
+ out long handle
+ );
+
+ [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)]
+ internal static partial ErrorCode anoncreds_create_credential_definition(
+ string schemaId,
+ long schema,
+ string tag,
+ string issuerId,
+ string signatureType,
+ [MarshalAs(UnmanagedType.I1)] bool supportRevocation,
+ out long credDef,
+ out long credDefPvt,
+ out long keyProof
+ );
+
+ [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)]
+ internal static partial ErrorCode anoncreds_create_credential_offer(
+ string schemaId,
+ string credDefId,
+ long keyProof,
+ out long offer
+ );
+
+ [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)]
+ internal static partial ErrorCode anoncreds_create_credential_request(
+ string? entropy,
+ string? proverDid,
+ long credDef,
+ string linkSecret,
+ string linkSecretId,
+ long credOffer,
+ out long request,
+ out long metadata
+ );
+
+ [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)]
+ internal static partial ErrorCode anoncreds_create_credential(
+ long credDef,
+ long credDefPvt,
+ long credOffer,
+ long credRequest,
+ FfiStrList attrNames,
+ FfiStrList attrRawValues,
+ FfiStrList attrEncValues,
+ IntPtr revocation,
+ out long credential
+ );
+
+ [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)]
+ internal static partial ErrorCode anoncreds_process_credential(
+ long credential,
+ long requestMetadata,
+ string linkSecret,
+ long credDef,
+ long revRegDef,
+ out long processedCredential
+ );
+
+ [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)]
+ internal static partial ErrorCode anoncreds_create_presentation(
+ long presReq,
+ FfiCredentialEntryList credentials,
+ FfiCredentialProveList credentialsProve,
+ FfiStrList selfAttestNames,
+ FfiStrList selfAttestValues,
+ string linkSecret,
+ FfiObjectHandleList schemas,
+ FfiStrList schemaIds,
+ FfiObjectHandleList credDefs,
+ FfiStrList credDefIds,
+ out long presentation
+ );
+
+ [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)]
+ internal static partial ErrorCode anoncreds_create_w3c_presentation(
+ long presReq,
+ FfiCredentialEntryList credentials,
+ FfiCredentialProveList credentialsProve,
+ string linkSecret,
+ FfiObjectHandleList schemas,
+ FfiStrList schemaIds,
+ FfiObjectHandleList credDefs,
+ FfiStrList credDefIds,
+ string w3cVersion,
+ out long presentation
+ );
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_verify_presentation(
+ long presentation,
+ long presReq,
+ FfiObjectHandleList schemas,
+ FfiStrList schemaIds,
+ FfiObjectHandleList credDefs,
+ FfiStrList credDefIds,
+ FfiObjectHandleList revRegDefs,
+ FfiStrList revRegDefIds,
+ FfiObjectHandleList revStatusLists,
+ FfiNonrevokedIntervalOverrideList nonrevokedIntervalOverride,
+ out sbyte isValid
+ );
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_verify_w3c_presentation(
+ long presentation,
+ long presReq,
+ FfiObjectHandleList schemas,
+ FfiStrList schemaIds,
+ FfiObjectHandleList credDefs,
+ FfiStrList credDefIds,
+ FfiObjectHandleList revRegDefs,
+ FfiStrList revRegDefIds,
+ FfiObjectHandleList revStatusLists,
+ FfiNonrevokedIntervalOverrideList nonrevokedIntervalOverride,
+ out sbyte isValid
+ );
+
+ [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)]
+ internal static partial ErrorCode anoncreds_create_revocation_registry_def(
+ long credDef,
+ string credDefId,
+ string issuerId,
+ string tag,
+ string revType,
+ long maxCredNum,
+ string tailsPath,
+ out long revRegDef,
+ out long revRegPvt
+ );
+
+ [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)]
+ internal static partial ErrorCode anoncreds_create_revocation_status_list(
+ long credDef,
+ string revRegDefId,
+ long revRegDef,
+ long revRegDefPrivate,
+ string issuerId,
+ [MarshalAs(UnmanagedType.I1)] bool issuanceByDefault,
+ long timestamp,
+ out long statusList
+ );
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_update_revocation_status_list(
+ long credDef,
+ long revRegDef,
+ long revRegPriv,
+ long currentStatusList,
+ FfiInt32List issued,
+ FfiInt32List revoked,
+ long timestamp,
+ out long newStatusList
+ );
+
+ [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)]
+ internal static partial ErrorCode anoncreds_create_or_update_revocation_state(
+ long revRegDef,
+ long revStatusList,
+ long revRegIndex,
+ string tailsPath,
+ long revState,
+ long oldRevStatusList,
+ out long revStateOut
+ );
+
+ [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)]
+ internal static partial ErrorCode anoncreds_revocation_registry_definition_get_attribute(
+ long handle,
+ string name,
+ out IntPtr value
+ );
+
+ [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)]
+ internal static partial ErrorCode anoncreds_create_w3c_credential(
+ long credDef,
+ long credDefPvt,
+ long credOffer,
+ long credRequest,
+ FfiStrList attrNames,
+ FfiStrList attrRawValues,
+ IntPtr revocation,
+ string w3cVersion,
+ out long credential
+ );
+
+ [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)]
+ internal static partial ErrorCode anoncreds_process_w3c_credential(
+ long credential,
+ long requestMetadata,
+ string linkSecret,
+ long credDef,
+ long revRegDef,
+ out long processedCredential
+ );
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_credential_from_w3c(
+ long w3cCredential,
+ out long legacyCredential
+ );
+
+ [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)]
+ internal static partial ErrorCode anoncreds_credential_to_w3c(
+ long legacyCredential,
+ string issuerId,
+ string w3cVersion,
+ out long w3cCredential
+ );
+
+ [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)]
+ internal static partial ErrorCode anoncreds_credential_get_attribute(
+ long handle,
+ string name,
+ out IntPtr value
+ );
+
+ [LibraryImport(Library)]
+ internal static partial ErrorCode anoncreds_w3c_credential_get_integrity_proof_details(
+ long handle,
+ out long proofInfoHandle
+ );
+
+ [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)]
+ internal static partial ErrorCode anoncreds_w3c_credential_proof_get_attribute(
+ long proofInfoHandle,
+ string name,
+ out IntPtr value
+ );
+}
diff --git a/wrappers/dotnet/lib/Models/AnonCredsObject.cs b/wrappers/dotnet/lib/Models/AnonCredsObject.cs
new file mode 100644
index 00000000..df8750fc
--- /dev/null
+++ b/wrappers/dotnet/lib/Models/AnonCredsObject.cs
@@ -0,0 +1,139 @@
+using System.Runtime.InteropServices;
+using AnonCredsNet.Exceptions;
+using AnonCredsNet.Interop;
+using AnonCredsNet.Requests;
+
+namespace AnonCredsNet.Models;
+
+public abstract class AnonCredsObject : IDisposable
+{
+ public long Handle { get; private set; }
+
+ protected AnonCredsObject(long handle)
+ {
+ if (handle == 0)
+ throw new AnonCredsException(ErrorCode.CommonInvalidState, "Invalid native handle");
+ Handle = handle;
+ }
+
+ public string ToJson()
+ {
+ if (Handle == 0)
+ throw new ObjectDisposedException(GetType().Name);
+ var code = NativeMethods.anoncreds_object_get_json(Handle, out var buffer);
+ if (code != ErrorCode.Success)
+ {
+ var errorMsg = AnonCreds.GetCurrentError();
+ throw new AnonCredsException(code, $"ToJson failed for {GetType().Name}: {errorMsg}");
+ }
+ try
+ {
+ var json =
+ Marshal.PtrToStringUTF8(buffer.Data, checked((int)buffer.Len))
+ ?? throw new InvalidOperationException("Null JSON");
+ return json;
+ }
+ finally
+ {
+ NativeMethods.anoncreds_buffer_free(buffer);
+ }
+ }
+
+ protected static T FromJson(string json)
+ where T : AnonCredsObject
+ {
+ if (string.IsNullOrEmpty(json))
+ throw new ArgumentNullException(nameof(json));
+
+ var code = FromJsonInternal(typeof(T), json, out var handle);
+ if (code != ErrorCode.Success)
+ throw new AnonCredsException(code, AnonCreds.GetCurrentError());
+ return (T)
+ Activator.CreateInstance(
+ typeof(T),
+ System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance,
+ null,
+ [handle],
+ null
+ )!;
+ }
+
+ private static ErrorCode FromJsonInternal(Type type, string json, out long handle)
+ {
+ handle = 0;
+ var buffer = AnonCreds.CreateByteBuffer(json);
+
+ try
+ {
+ if (type == typeof(Schema))
+ return NativeMethods.anoncreds_schema_from_json(buffer, out handle);
+ else if (type == typeof(CredentialDefinition))
+ return NativeMethods.anoncreds_credential_definition_from_json(buffer, out handle);
+ else if (type == typeof(CredentialDefinitionPrivate))
+ return NativeMethods.anoncreds_credential_definition_private_from_json(
+ buffer,
+ out handle
+ );
+ else if (type == typeof(KeyCorrectnessProof))
+ return NativeMethods.anoncreds_key_correctness_proof_from_json(buffer, out handle);
+ else if (type == typeof(CredentialOffer))
+ return NativeMethods.anoncreds_credential_offer_from_json(buffer, out handle);
+ else if (type == typeof(CredentialRequest))
+ return NativeMethods.anoncreds_credential_request_from_json(buffer, out handle);
+ else if (type == typeof(CredentialRequestMetadata))
+ return NativeMethods.anoncreds_credential_request_metadata_from_json(
+ buffer,
+ out handle
+ );
+ else if (type == typeof(Credential))
+ return NativeMethods.anoncreds_credential_from_json(buffer, out handle);
+ else if (type == typeof(Presentation))
+ return NativeMethods.anoncreds_presentation_from_json(buffer, out handle);
+ else if (type == typeof(PresentationRequest))
+ return NativeMethods.anoncreds_presentation_request_from_json(buffer, out handle);
+ else if (type == typeof(RevocationRegistryDefinition))
+ return NativeMethods.anoncreds_revocation_registry_definition_from_json(
+ buffer,
+ out handle
+ );
+ else if (type == typeof(RevocationRegistryDefinitionPrivate))
+ return NativeMethods.anoncreds_revocation_registry_private_from_json(
+ buffer,
+ out handle
+ );
+ else if (type == typeof(RevocationStatusList))
+ return NativeMethods.anoncreds_revocation_status_list_from_json(buffer, out handle);
+ else if (type == typeof(RevocationStatusListDelta))
+ return NativeMethods.anoncreds_revocation_status_list_delta_from_json(
+ buffer,
+ out handle
+ );
+ else if (type == typeof(RevocationState))
+ return NativeMethods.anoncreds_revocation_state_from_json(buffer, out handle);
+ else
+ throw new NotSupportedException(
+ $"Type {type.Name} is not supported for JSON deserialization"
+ );
+ }
+ finally
+ {
+ AnonCreds.FreeByteBuffer(buffer);
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (Handle == 0)
+ return;
+ NativeMethods.anoncreds_object_free(Handle);
+ Handle = 0;
+ }
+
+ ~AnonCredsObject() => Dispose(false);
+}
diff --git a/wrappers/dotnet/lib/Models/Credential.cs b/wrappers/dotnet/lib/Models/Credential.cs
new file mode 100644
index 00000000..ece4d1cb
--- /dev/null
+++ b/wrappers/dotnet/lib/Models/Credential.cs
@@ -0,0 +1,141 @@
+using System.Runtime.InteropServices;
+using AnonCredsNet.Exceptions;
+using AnonCredsNet.Interop;
+using AnonCredsNet.Requests;
+
+namespace AnonCredsNet.Models;
+
+public sealed class Credential : AnonCredsObject
+{
+ private Credential(long handle)
+ : base(handle) { }
+
+ internal static Credential FromHandle(long handle) => new Credential(handle);
+
+ ///
+ /// Creates a credential and its revocation delta. Both returned objects must be disposed using using statements.
+ ///
+ public static (Credential Credential, RevocationStatusListDelta? Delta) Create(
+ CredentialDefinition credDef,
+ CredentialDefinitionPrivate credDefPvt,
+ CredentialOffer offer,
+ CredentialRequest request,
+ string credValues,
+ string? revRegId,
+ string? tailsPath,
+ RevocationStatusList? revStatusList,
+ CredentialRevocationConfig? revConfig = null
+ )
+ {
+ if (
+ credDef == null
+ || credDefPvt == null
+ || offer == null
+ || request == null
+ || string.IsNullOrEmpty(credValues)
+ )
+ throw new ArgumentNullException("Input parameters cannot be null or empty");
+
+ // Parse credential values JSON
+ var credValuesDict = System.Text.Json.JsonSerializer.Deserialize<
+ Dictionary
+ >(credValues);
+ if (credValuesDict == null)
+ throw new ArgumentException("Invalid credential values JSON");
+
+ var attrNames = AnonCreds.CreateFfiStrList(
+ System.Text.Json.JsonSerializer.Serialize(credValuesDict.Keys)
+ );
+ var attrRawValues = AnonCreds.CreateFfiStrList(
+ System.Text.Json.JsonSerializer.Serialize(credValuesDict.Values)
+ );
+ // When encoded values are not provided, pass an empty list (count=0, data=NULL)
+ var attrEncValues = new FfiStrList { Count = 0, Data = IntPtr.Zero };
+
+ // Build optional revocation info struct
+ IntPtr revocationPtr = IntPtr.Zero;
+ try
+ {
+ if (
+ revConfig != null
+ && revConfig.RevRegDef != null
+ && revConfig.RevRegDefPrivate != null
+ && revConfig.RevStatusList != null
+ )
+ {
+ var revInfo = new FfiCredRevInfo
+ {
+ RegDef = revConfig.RevRegDef.Handle,
+ RegDefPrivate = revConfig.RevRegDefPrivate.Handle,
+ StatusList = revConfig.RevStatusList.Handle,
+ RegIdx = (long)revConfig.RevRegIndex,
+ };
+ revocationPtr = Marshal.AllocHGlobal(Marshal.SizeOf());
+ Marshal.StructureToPtr(revInfo, revocationPtr, false);
+ }
+ }
+ catch
+ {
+ if (revocationPtr != IntPtr.Zero)
+ {
+ Marshal.FreeHGlobal(revocationPtr);
+ revocationPtr = IntPtr.Zero;
+ }
+ throw;
+ }
+
+ try
+ {
+ var code = NativeMethods.anoncreds_create_credential(
+ credDef.Handle,
+ credDefPvt.Handle,
+ offer.Handle,
+ request.Handle,
+ attrNames,
+ attrRawValues,
+ attrEncValues,
+ revocationPtr,
+ out var cred
+ );
+ if (code != ErrorCode.Success)
+ throw new AnonCredsException(code, AnonCreds.GetCurrentError());
+ return (new Credential(cred), null); // No delta when not using revocation
+ }
+ finally
+ {
+ AnonCreds.FreeFfiStrList(attrNames);
+ AnonCreds.FreeFfiStrList(attrRawValues);
+ if (revocationPtr != IntPtr.Zero)
+ {
+ Marshal.FreeHGlobal(revocationPtr);
+ }
+ }
+ }
+
+ public Credential Process(
+ CredentialRequestMetadata credReqMetadata,
+ string linkSecret,
+ CredentialDefinition credDef,
+ RevocationRegistryDefinition? revRegDef
+ )
+ {
+ if (string.IsNullOrEmpty(linkSecret))
+ throw new ArgumentNullException(nameof(linkSecret));
+ var revRegDefHandle = revRegDef?.Handle ?? 0;
+ var code = NativeMethods.anoncreds_process_credential(
+ Handle,
+ credReqMetadata.Handle,
+ linkSecret,
+ credDef.Handle,
+ revRegDefHandle,
+ out var newCredHandle
+ );
+ if (code != ErrorCode.Success)
+ {
+ throw new AnonCredsException(code, AnonCreds.GetCurrentError());
+ }
+ return new Credential(newCredHandle);
+ }
+
+ internal static Credential FromJson(string json) => FromJson(json);
+}
diff --git a/wrappers/dotnet/lib/Models/CredentialDefinition.cs b/wrappers/dotnet/lib/Models/CredentialDefinition.cs
new file mode 100644
index 00000000..1a3dc1ed
--- /dev/null
+++ b/wrappers/dotnet/lib/Models/CredentialDefinition.cs
@@ -0,0 +1,72 @@
+using AnonCredsNet.Exceptions;
+using AnonCredsNet.Interop;
+
+namespace AnonCredsNet.Models;
+
+public class CredentialDefinition : AnonCredsObject
+{
+ private CredentialDefinition(long handle)
+ : base(handle) { }
+
+ public static (
+ CredentialDefinition CredDef,
+ CredentialDefinitionPrivate CredDefPvt,
+ KeyCorrectnessProof KeyProof
+ ) Create(
+ string schemaId,
+ string issuerId,
+ Schema schema,
+ string tag,
+ string sigType,
+ string config
+ )
+ {
+ if (
+ string.IsNullOrEmpty(schemaId)
+ || string.IsNullOrEmpty(issuerId)
+ || schema == null
+ || string.IsNullOrEmpty(tag)
+ || string.IsNullOrEmpty(sigType)
+ || string.IsNullOrEmpty(config)
+ )
+ throw new ArgumentNullException("Input parameters cannot be null or empty");
+
+ // Parse config to determine if revocation should be supported
+ var configObj = System.Text.Json.JsonSerializer.Deserialize(
+ config
+ );
+ var supportRevocation =
+ configObj.TryGetProperty("support_revocation", out var revProp) && revProp.GetBoolean();
+
+ var code = NativeMethods.anoncreds_create_credential_definition(
+ schemaId,
+ schema.Handle,
+ tag,
+ issuerId,
+ sigType,
+ supportRevocation,
+ out var cd,
+ out var pvt,
+ out var proof
+ );
+ if (code != ErrorCode.Success)
+ throw new AnonCredsException(code, AnonCreds.GetCurrentError());
+ return (
+ new CredentialDefinition(cd),
+ new CredentialDefinitionPrivate(pvt),
+ new KeyCorrectnessProof(proof)
+ );
+ }
+
+ public static CredentialDefinition FromJson(string json) =>
+ FromJson(json);
+}
+
+public class CredentialDefinitionPrivate : AnonCredsObject
+{
+ internal CredentialDefinitionPrivate(long handle)
+ : base(handle) { }
+
+ internal static CredentialDefinitionPrivate FromJson(string json) =>
+ FromJson(json);
+}
diff --git a/wrappers/dotnet/lib/Models/CredentialOffer.cs b/wrappers/dotnet/lib/Models/CredentialOffer.cs
new file mode 100644
index 00000000..bf2b6196
--- /dev/null
+++ b/wrappers/dotnet/lib/Models/CredentialOffer.cs
@@ -0,0 +1,35 @@
+using AnonCredsNet.Exceptions;
+using AnonCredsNet.Interop;
+
+namespace AnonCredsNet.Models;
+
+public class CredentialOffer : AnonCredsObject
+{
+ private CredentialOffer(long handle)
+ : base(handle) { }
+
+ public static CredentialOffer Create(
+ string schemaId,
+ string credDefId,
+ KeyCorrectnessProof keyProof
+ )
+ {
+ if (string.IsNullOrEmpty(schemaId))
+ throw new ArgumentNullException(nameof(schemaId));
+ if (string.IsNullOrEmpty(credDefId))
+ throw new ArgumentNullException(nameof(credDefId));
+ if (keyProof == null)
+ throw new ArgumentNullException(nameof(keyProof));
+ var code = NativeMethods.anoncreds_create_credential_offer(
+ schemaId,
+ credDefId,
+ keyProof.Handle,
+ out var handle
+ );
+ if (code != ErrorCode.Success)
+ throw new AnonCredsException(code, AnonCreds.GetCurrentError());
+ return new CredentialOffer(handle);
+ }
+
+ internal static CredentialOffer FromJson(string json) => FromJson(json);
+}
diff --git a/wrappers/dotnet/lib/Models/CredentialRevocationConfig.cs b/wrappers/dotnet/lib/Models/CredentialRevocationConfig.cs
new file mode 100644
index 00000000..42989983
--- /dev/null
+++ b/wrappers/dotnet/lib/Models/CredentialRevocationConfig.cs
@@ -0,0 +1,9 @@
+namespace AnonCredsNet.Models;
+
+public class CredentialRevocationConfig
+{
+ public RevocationRegistryDefinition? RevRegDef { get; set; }
+ public RevocationRegistryDefinitionPrivate? RevRegDefPrivate { get; set; }
+ public RevocationStatusList? RevStatusList { get; set; }
+ public uint RevRegIndex { get; set; }
+}
diff --git a/wrappers/dotnet/lib/Models/KeyCorrectnessProof.cs b/wrappers/dotnet/lib/Models/KeyCorrectnessProof.cs
new file mode 100644
index 00000000..3b4d7f97
--- /dev/null
+++ b/wrappers/dotnet/lib/Models/KeyCorrectnessProof.cs
@@ -0,0 +1,10 @@
+namespace AnonCredsNet.Models;
+
+public class KeyCorrectnessProof : AnonCredsObject
+{
+ internal KeyCorrectnessProof(long handle)
+ : base(handle) { }
+
+ internal static KeyCorrectnessProof FromJson(string json) =>
+ FromJson(json);
+}
diff --git a/wrappers/dotnet/lib/Models/LinkSecret.cs b/wrappers/dotnet/lib/Models/LinkSecret.cs
new file mode 100644
index 00000000..73c28429
--- /dev/null
+++ b/wrappers/dotnet/lib/Models/LinkSecret.cs
@@ -0,0 +1,26 @@
+using System.Runtime.InteropServices;
+using AnonCredsNet.Exceptions;
+using AnonCredsNet.Interop;
+
+namespace AnonCredsNet.Models;
+
+public class LinkSecret : AnonCredsObject
+{
+ internal LinkSecret(long handle)
+ : base(handle) { }
+
+ public string Value => ToJson();
+
+ public static string Create()
+ {
+ var code = NativeMethods.anoncreds_create_link_secret(out var ptr);
+ if (code != ErrorCode.Success)
+ {
+ throw new AnonCredsException(code, AnonCreds.GetCurrentError());
+ }
+ var linkSecret =
+ Marshal.PtrToStringUTF8(ptr) ?? throw new InvalidOperationException("Null link secret");
+ NativeMethods.anoncreds_string_free(ptr);
+ return linkSecret;
+ }
+}
diff --git a/wrappers/dotnet/lib/Models/Presentation.cs b/wrappers/dotnet/lib/Models/Presentation.cs
new file mode 100644
index 00000000..c3c96ef6
--- /dev/null
+++ b/wrappers/dotnet/lib/Models/Presentation.cs
@@ -0,0 +1,169 @@
+using AnonCredsNet.Exceptions;
+using AnonCredsNet.Interop;
+using AnonCredsNet.Requests;
+
+namespace AnonCredsNet.Models;
+
+public sealed class Presentation : AnonCredsObject
+{
+ private Presentation(long handle)
+ : base(handle) { }
+
+ public static Presentation Create(
+ long presReqHandle,
+ FfiCredentialEntryList credentialsList,
+ FfiCredentialProveList credentialsProve,
+ FfiStrList selfAttestNames,
+ FfiStrList selfAttestValues,
+ string linkSecret,
+ FfiObjectHandleList schemasList,
+ FfiStrList schemaIds,
+ FfiObjectHandleList credDefsList,
+ FfiStrList credDefIds
+ )
+ {
+ if (
+ presReqHandle == 0
+ || string.IsNullOrEmpty(linkSecret)
+ || schemasList.Count == 0
+ || credDefsList.Count == 0
+ || schemaIds.Count == 0
+ || credDefIds.Count == 0
+ )
+ throw new ArgumentNullException("Input parameters cannot be null or empty");
+
+ try
+ {
+ // Debug: dump credential entries to validate timestamp/rev_state pairing
+ try
+ {
+ var count = (int)credentialsList.Count.ToUInt32();
+ if (credentialsList.Data != IntPtr.Zero)
+ {
+ var size = System.Runtime.InteropServices.Marshal.SizeOf();
+ for (int i = 0; i < count; i++)
+ {
+ var ptr = credentialsList.Data + (i * size);
+ var e =
+ System.Runtime.InteropServices.Marshal.PtrToStructure(
+ ptr
+ );
+ }
+ }
+ }
+ catch { }
+ var code = NativeMethods.anoncreds_create_presentation(
+ presReqHandle,
+ credentialsList,
+ credentialsProve,
+ selfAttestNames,
+ selfAttestValues,
+ linkSecret,
+ schemasList,
+ schemaIds,
+ credDefsList,
+ credDefIds,
+ out var handle
+ );
+
+ if (code != ErrorCode.Success)
+ {
+ var errorMsg = AnonCreds.GetCurrentError();
+ throw new AnonCredsException(code, errorMsg);
+ }
+ return new Presentation(handle);
+ }
+ finally
+ {
+ AnonCreds.FreeFfiObjectHandleList(schemasList);
+ AnonCreds.FreeFfiObjectHandleList(credDefsList);
+ AnonCreds.FreeFfiCredentialEntryList(credentialsList);
+ AnonCreds.FreeFfiCredentialProveList(credentialsProve);
+ }
+ }
+
+ public static Presentation FromJson(string json) => FromJson(json);
+
+ public static Presentation CreateFromJson(
+ PresentationRequest presReq,
+ string credentialsJson,
+ string? selfAttestJson,
+ string linkSecret,
+ string schemasJson,
+ string credDefsJson,
+ string schemaIdsJson,
+ string credDefIdsJson,
+ string? revRegsJson,
+ string? revListsJson
+ )
+ {
+ var (schemasList, _) = AnonCreds.CreateFfiObjectHandleListWithObjects(
+ schemasJson,
+ Schema.FromJson
+ );
+ var (credDefsList, _) = AnonCreds.CreateFfiObjectHandleListWithObjects(
+ credDefsJson,
+ CredentialDefinition.FromJson
+ );
+ var credentialsList = AnonCreds.ParseCredentialsJson(credentialsJson);
+ var schemaIds = AnonCreds.CreateFfiStrList(schemaIdsJson);
+ var credDefIds = AnonCreds.CreateFfiStrList(credDefIdsJson);
+
+ var credentialsProve = AnonCreds.CreateCredentialsProveList(
+ presReq.ToJson(),
+ selfAttestJson,
+ credentialsJson
+ );
+
+ var selfAttestNames = new FfiStrList();
+ var selfAttestValues = new FfiStrList();
+ if (!string.IsNullOrEmpty(selfAttestJson))
+ {
+ var selfAttested =
+ System.Text.Json.JsonSerializer.Deserialize>(
+ selfAttestJson!
+ ) ?? new();
+ selfAttestNames = AnonCreds.CreateFfiStrListFromStrings(selfAttested.Keys.ToArray());
+ selfAttestValues = AnonCreds.CreateFfiStrListFromStrings(selfAttested.Values.ToArray());
+ }
+
+ return Create(
+ presReq.Handle,
+ credentialsList,
+ credentialsProve,
+ selfAttestNames,
+ selfAttestValues,
+ linkSecret,
+ schemasList,
+ schemaIds,
+ credDefsList,
+ credDefIds
+ );
+ }
+
+ public bool Verify(
+ PresentationRequest presReq,
+ string schemasJson,
+ string credDefsJson,
+ string schemaIdsJson,
+ string credDefIdsJson,
+ string? revRegDefsJson = null,
+ string? revStatusListsJson = null,
+ string? revRegDefIdsJson = null,
+ string? nonRevocJson = null
+ )
+ {
+ return AnonCreds.VerifyPresentation(
+ this,
+ presReq,
+ schemasJson,
+ credDefsJson,
+ schemaIdsJson,
+ credDefIdsJson,
+ revRegDefsJson,
+ revStatusListsJson,
+ revRegDefIdsJson,
+ nonRevocJson
+ );
+ }
+}
diff --git a/wrappers/dotnet/lib/Models/RevocationRegistryDefinition.cs b/wrappers/dotnet/lib/Models/RevocationRegistryDefinition.cs
new file mode 100644
index 00000000..401fb7f7
--- /dev/null
+++ b/wrappers/dotnet/lib/Models/RevocationRegistryDefinition.cs
@@ -0,0 +1,76 @@
+using System.Runtime.InteropServices;
+using AnonCredsNet.Exceptions;
+using AnonCredsNet.Interop;
+
+namespace AnonCredsNet.Models;
+
+public class RevocationRegistryDefinition : AnonCredsObject
+{
+ internal RevocationRegistryDefinition(long handle)
+ : base(handle) { }
+
+ public static (RevocationRegistryDefinition, RevocationRegistryDefinitionPrivate) Create(
+ CredentialDefinition credDef,
+ string credDefId,
+ string issuerId,
+ string tag,
+ string revType,
+ int maxCredNum,
+ string? tailsPath = null
+ )
+ {
+ if (
+ credDef == null
+ || string.IsNullOrEmpty(credDefId)
+ || string.IsNullOrEmpty(issuerId)
+ || string.IsNullOrEmpty(tag)
+ || string.IsNullOrEmpty(revType)
+ || maxCredNum <= 0
+ )
+ throw new ArgumentNullException("Input parameters cannot be null or empty");
+
+ var code = NativeMethods.anoncreds_create_revocation_registry_def(
+ credDef.Handle,
+ credDefId,
+ issuerId,
+ tag,
+ revType,
+ maxCredNum,
+ tailsPath ?? string.Empty,
+ out var def,
+ out var pvt
+ );
+ if (code != ErrorCode.Success)
+ throw new AnonCredsException(code, AnonCreds.GetCurrentError());
+ return (
+ new RevocationRegistryDefinition(def),
+ new RevocationRegistryDefinitionPrivate(pvt)
+ );
+ }
+
+ public string TailsLocation
+ {
+ get
+ {
+ // Prefer native getter for attribute to avoid JSON parsing mismatches
+ var code = NativeMethods.anoncreds_revocation_registry_definition_get_attribute(
+ this.Handle,
+ "tails_location",
+ out var ptr
+ );
+ if (code != ErrorCode.Success)
+ throw new AnonCredsException(code, AnonCreds.GetCurrentError());
+ try
+ {
+ return Marshal.PtrToStringUTF8(ptr) ?? string.Empty;
+ }
+ finally
+ {
+ NativeMethods.anoncreds_string_free(ptr);
+ }
+ }
+ }
+
+ public static RevocationRegistryDefinition FromJson(string json) =>
+ FromJson(json);
+}
diff --git a/wrappers/dotnet/lib/Models/RevocationRegistryDefinitionPrivate.cs b/wrappers/dotnet/lib/Models/RevocationRegistryDefinitionPrivate.cs
new file mode 100644
index 00000000..9b9fed31
--- /dev/null
+++ b/wrappers/dotnet/lib/Models/RevocationRegistryDefinitionPrivate.cs
@@ -0,0 +1,10 @@
+namespace AnonCredsNet.Models;
+
+public class RevocationRegistryDefinitionPrivate : AnonCredsObject
+{
+ internal RevocationRegistryDefinitionPrivate(long handle)
+ : base(handle) { }
+
+ public static RevocationRegistryDefinitionPrivate FromJson(string json) =>
+ FromJson(json);
+}
diff --git a/wrappers/dotnet/lib/Models/RevocationState.cs b/wrappers/dotnet/lib/Models/RevocationState.cs
new file mode 100644
index 00000000..d64dca34
--- /dev/null
+++ b/wrappers/dotnet/lib/Models/RevocationState.cs
@@ -0,0 +1,66 @@
+using AnonCredsNet.Exceptions;
+using AnonCredsNet.Interop;
+
+namespace AnonCredsNet.Models;
+
+public class RevocationState : AnonCredsObject
+{
+ private RevocationState(long handle)
+ : base(handle) { }
+
+ public static RevocationState Create(
+ RevocationRegistryDefinition revRegDef,
+ RevocationStatusList statusList,
+ uint revRegIndex,
+ string tailsPath
+ )
+ {
+ if (revRegDef == null || statusList == null || string.IsNullOrEmpty(tailsPath))
+ throw new ArgumentNullException("Input parameters cannot be null or empty");
+
+ var code = NativeMethods.anoncreds_create_or_update_revocation_state(
+ revRegDef.Handle,
+ statusList.Handle,
+ (long)revRegIndex,
+ tailsPath,
+ 0,
+ 0,
+ out var handle
+ );
+ if (code != ErrorCode.Success)
+ throw new AnonCredsException(code, AnonCreds.GetCurrentError());
+ return new RevocationState(handle);
+ }
+
+ public static RevocationState Update(
+ RevocationState revState,
+ RevocationRegistryDefinition revRegDef,
+ RevocationStatusList newStatusList,
+ uint revRegIndex,
+ string tailsPath,
+ RevocationStatusList? oldStatusList = null
+ )
+ {
+ if (
+ revState == null
+ || revRegDef == null
+ || newStatusList == null
+ || string.IsNullOrEmpty(tailsPath)
+ )
+ throw new ArgumentNullException("Input parameters cannot be null or empty");
+ var code = NativeMethods.anoncreds_create_or_update_revocation_state(
+ revRegDef.Handle,
+ newStatusList.Handle,
+ (long)revRegIndex,
+ tailsPath,
+ revState.Handle,
+ oldStatusList?.Handle ?? 0,
+ out var updated
+ );
+ if (code != ErrorCode.Success)
+ throw new AnonCredsException(code, AnonCreds.GetCurrentError());
+ return new RevocationState(updated);
+ }
+
+ public static RevocationState FromJson(string json) => FromJson(json);
+}
diff --git a/wrappers/dotnet/lib/Models/RevocationStatusList.cs b/wrappers/dotnet/lib/Models/RevocationStatusList.cs
new file mode 100644
index 00000000..42e8c409
--- /dev/null
+++ b/wrappers/dotnet/lib/Models/RevocationStatusList.cs
@@ -0,0 +1,128 @@
+using AnonCredsNet.Exceptions;
+using AnonCredsNet.Interop;
+
+namespace AnonCredsNet.Models;
+
+public class RevocationStatusList : AnonCredsObject
+{
+ private RevocationStatusList(long handle)
+ : base(handle) { }
+
+ public static (
+ RevocationRegistryDefinition RevRegDef,
+ RevocationRegistryDefinitionPrivate RevRegPvt,
+ RevocationStatusList StatusList
+ ) CreateRevocationRegistryDefinition(
+ CredentialDefinition credDef,
+ string credDefId,
+ string issuerId,
+ string tag,
+ string revType,
+ long maxCredNum,
+ string tailsPath
+ )
+ {
+ if (
+ credDef == null
+ || string.IsNullOrEmpty(credDefId)
+ || string.IsNullOrEmpty(issuerId)
+ || string.IsNullOrEmpty(tag)
+ || string.IsNullOrEmpty(revType)
+ || maxCredNum <= 0
+ )
+ throw new ArgumentNullException("Input parameters cannot be null or empty");
+ var code = NativeMethods.anoncreds_create_revocation_registry_def(
+ credDef.Handle,
+ credDefId,
+ issuerId,
+ tag,
+ revType,
+ maxCredNum,
+ tailsPath ?? "",
+ out var def,
+ out var pvt
+ );
+ if (code != ErrorCode.Success)
+ throw new AnonCredsException(code, AnonCreds.GetCurrentError());
+ return (
+ new RevocationRegistryDefinition(def),
+ new RevocationRegistryDefinitionPrivate(pvt),
+ // Immediately create initial status list to align with Python
+ Create(
+ credDef,
+ credDefId,
+ new RevocationRegistryDefinition(def),
+ new RevocationRegistryDefinitionPrivate(pvt),
+ issuerId,
+ true,
+ 0
+ )
+ );
+ }
+
+ public static RevocationStatusList Create(
+ CredentialDefinition credDef,
+ string revRegId,
+ RevocationRegistryDefinition revRegDef,
+ RevocationRegistryDefinitionPrivate revRegDefPrivate,
+ string issuerId,
+ bool issuanceByDefault,
+ ulong timestamp
+ )
+ {
+ if (
+ credDef == null
+ || string.IsNullOrEmpty(revRegId)
+ || revRegDef == null
+ || revRegDefPrivate == null
+ || string.IsNullOrEmpty(issuerId)
+ )
+ throw new ArgumentNullException("Input parameters cannot be null or empty");
+
+ var code = NativeMethods.anoncreds_create_revocation_status_list(
+ credDef.Handle,
+ revRegId,
+ revRegDef.Handle,
+ revRegDefPrivate.Handle,
+ issuerId,
+ issuanceByDefault,
+ (long)timestamp,
+ out var handle
+ );
+ if (code != ErrorCode.Success)
+ throw new AnonCredsException(code, AnonCreds.GetCurrentError());
+ return new RevocationStatusList(handle);
+ }
+
+ public RevocationStatusList Update(
+ CredentialDefinition credDef,
+ RevocationRegistryDefinition revRegDef,
+ RevocationRegistryDefinitionPrivate revRegDefPrivate,
+ ulong[]? issued,
+ ulong[]? revoked,
+ ulong timestamp
+ )
+ {
+ if (credDef == null || revRegDef == null || revRegDefPrivate == null)
+ throw new ArgumentNullException("Input parameters cannot be null or empty");
+
+ var issuedList = AnonCreds.CreateFfiInt32List(issued);
+ var revokedList = AnonCreds.CreateFfiInt32List(revoked);
+ var code = NativeMethods.anoncreds_update_revocation_status_list(
+ credDef.Handle,
+ revRegDef.Handle,
+ revRegDefPrivate.Handle,
+ this.Handle,
+ issuedList,
+ revokedList,
+ (long)timestamp,
+ out var updated
+ );
+ if (code != ErrorCode.Success)
+ throw new AnonCredsException(code, AnonCreds.GetCurrentError());
+ return new RevocationStatusList(updated);
+ }
+
+ public static RevocationStatusList FromJson(string json) =>
+ FromJson(json);
+}
diff --git a/wrappers/dotnet/lib/Models/RevocationStatusListDelta.cs b/wrappers/dotnet/lib/Models/RevocationStatusListDelta.cs
new file mode 100644
index 00000000..4b1eb2ed
--- /dev/null
+++ b/wrappers/dotnet/lib/Models/RevocationStatusListDelta.cs
@@ -0,0 +1,10 @@
+namespace AnonCredsNet.Models;
+
+public class RevocationStatusListDelta : AnonCredsObject
+{
+ internal RevocationStatusListDelta(long handle)
+ : base(handle) { }
+
+ public static RevocationStatusListDelta FromJson(string json) =>
+ FromJson(json);
+}
diff --git a/wrappers/dotnet/lib/Models/Schema.cs b/wrappers/dotnet/lib/Models/Schema.cs
new file mode 100644
index 00000000..fa236760
--- /dev/null
+++ b/wrappers/dotnet/lib/Models/Schema.cs
@@ -0,0 +1,34 @@
+using AnonCredsNet.Exceptions;
+using AnonCredsNet.Interop;
+
+namespace AnonCredsNet.Models;
+
+public class Schema : AnonCredsObject
+{
+ private Schema(long handle)
+ : base(handle) { }
+
+ public static Schema Create(string name, string version, string issuerId, string attrNamesJson)
+ {
+ var attrNamesList = AnonCreds.CreateFfiStrList(attrNamesJson);
+ try
+ {
+ var code = NativeMethods.anoncreds_create_schema(
+ name,
+ version,
+ issuerId,
+ attrNamesList,
+ out var handle
+ );
+ if (code != ErrorCode.Success)
+ throw new AnonCredsException(code, AnonCreds.GetCurrentError());
+ return new Schema(handle);
+ }
+ finally
+ {
+ AnonCreds.FreeFfiStrList(attrNamesList);
+ }
+ }
+
+ public static Schema FromJson(string json) => FromJson(json);
+}
diff --git a/wrappers/dotnet/lib/Models/W3cCredential.cs b/wrappers/dotnet/lib/Models/W3cCredential.cs
new file mode 100644
index 00000000..eef5426a
--- /dev/null
+++ b/wrappers/dotnet/lib/Models/W3cCredential.cs
@@ -0,0 +1,127 @@
+using System.Runtime.InteropServices;
+using AnonCredsNet.Exceptions;
+using AnonCredsNet.Interop;
+using AnonCredsNet.Requests;
+
+namespace AnonCredsNet.Models;
+
+public sealed class W3cCredential : AnonCredsObject
+{
+ private W3cCredential(long handle)
+ : base(handle) { }
+
+ public static W3cCredential Create(
+ CredentialDefinition credDef,
+ CredentialDefinitionPrivate credDefPvt,
+ CredentialOffer offer,
+ CredentialRequest request,
+ string credValues,
+ CredentialRevocationConfig? revConfig,
+ string? w3cVersion
+ )
+ {
+ if (string.IsNullOrEmpty(credValues))
+ throw new ArgumentNullException(nameof(credValues));
+
+ var dict =
+ System.Text.Json.JsonSerializer.Deserialize>(credValues)
+ ?? throw new ArgumentException("Invalid credential values JSON");
+ var attrNames = AnonCreds.CreateFfiStrList(
+ System.Text.Json.JsonSerializer.Serialize(dict.Keys)
+ );
+ var attrRawValues = AnonCreds.CreateFfiStrList(
+ System.Text.Json.JsonSerializer.Serialize(dict.Values)
+ );
+
+ IntPtr revocationPtr = IntPtr.Zero;
+ try
+ {
+ if (
+ revConfig != null
+ && revConfig.RevRegDef != null
+ && revConfig.RevRegDefPrivate != null
+ && revConfig.RevStatusList != null
+ )
+ {
+ var revInfo = new FfiCredRevInfo
+ {
+ RegDef = revConfig.RevRegDef.Handle,
+ RegDefPrivate = revConfig.RevRegDefPrivate.Handle,
+ StatusList = revConfig.RevStatusList.Handle,
+ RegIdx = (long)revConfig.RevRegIndex,
+ };
+ revocationPtr = Marshal.AllocHGlobal(Marshal.SizeOf());
+ Marshal.StructureToPtr(revInfo, revocationPtr, false);
+ }
+
+ var code = NativeMethods.anoncreds_create_w3c_credential(
+ credDef.Handle,
+ credDefPvt.Handle,
+ offer.Handle,
+ request.Handle,
+ attrNames,
+ attrRawValues,
+ revocationPtr,
+ w3cVersion ?? "1.1",
+ out var cred
+ );
+ if (code != ErrorCode.Success)
+ throw new AnonCredsException(code, AnonCreds.GetCurrentError());
+ return new W3cCredential(cred);
+ }
+ finally
+ {
+ AnonCreds.FreeFfiStrList(attrNames);
+ AnonCreds.FreeFfiStrList(attrRawValues);
+ if (revocationPtr != IntPtr.Zero)
+ Marshal.FreeHGlobal(revocationPtr);
+ }
+ }
+
+ public W3cCredential Process(
+ CredentialRequestMetadata credReqMetadata,
+ string linkSecret,
+ CredentialDefinition credDef,
+ RevocationRegistryDefinition? revRegDef
+ )
+ {
+ var code = NativeMethods.anoncreds_process_w3c_credential(
+ Handle,
+ credReqMetadata.Handle,
+ linkSecret,
+ credDef.Handle,
+ revRegDef?.Handle ?? 0,
+ out var handle
+ );
+ if (code != ErrorCode.Success)
+ throw new AnonCredsException(code, AnonCreds.GetCurrentError());
+ return new W3cCredential(handle);
+ }
+
+ public static W3cCredential FromJson(string json) => FromJson(json);
+
+ public Credential ToLegacy()
+ {
+ var code = NativeMethods.anoncreds_credential_from_w3c(Handle, out var legacy);
+ if (code != ErrorCode.Success)
+ throw new AnonCredsException(code, AnonCreds.GetCurrentError());
+ return Credential.FromHandle(legacy);
+ }
+
+ public static W3cCredential FromLegacy(
+ Credential legacy,
+ string issuerId,
+ string? w3cVersion = null
+ )
+ {
+ var code = NativeMethods.anoncreds_credential_to_w3c(
+ legacy.Handle,
+ issuerId,
+ w3cVersion ?? "1.1",
+ out var w3c
+ );
+ if (code != ErrorCode.Success)
+ throw new AnonCredsException(code, AnonCreds.GetCurrentError());
+ return new W3cCredential(w3c);
+ }
+}
diff --git a/wrappers/dotnet/lib/Models/W3cPresentation.cs b/wrappers/dotnet/lib/Models/W3cPresentation.cs
new file mode 100644
index 00000000..fbfeefca
--- /dev/null
+++ b/wrappers/dotnet/lib/Models/W3cPresentation.cs
@@ -0,0 +1,209 @@
+using AnonCredsNet.Exceptions;
+using AnonCredsNet.Interop;
+using AnonCredsNet.Requests;
+
+namespace AnonCredsNet.Models;
+
+public sealed class W3cPresentation : AnonCredsObject
+{
+ private W3cPresentation(long handle)
+ : base(handle) { }
+
+ public static W3cPresentation Create(
+ long presReqHandle,
+ FfiCredentialEntryList credentialsList,
+ FfiCredentialProveList credentialsProve,
+ string linkSecret,
+ FfiObjectHandleList schemasList,
+ FfiStrList schemaIds,
+ FfiObjectHandleList credDefsList,
+ FfiStrList credDefIds,
+ string? w3cVersion = null
+ )
+ {
+ if (presReqHandle == 0 || string.IsNullOrEmpty(linkSecret))
+ throw new ArgumentNullException("Invalid inputs");
+ try
+ {
+ // Debug: dump credential entries to validate timestamp/rev_state pairing
+ try
+ {
+ var count = (int)credentialsList.Count.ToUInt32();
+ if (credentialsList.Data != IntPtr.Zero)
+ {
+ var size = System.Runtime.InteropServices.Marshal.SizeOf();
+ for (int i = 0; i < count; i++)
+ {
+ var ptr = credentialsList.Data + (i * size);
+ var e =
+ System.Runtime.InteropServices.Marshal.PtrToStructure(
+ ptr
+ );
+ }
+ }
+ }
+ catch { }
+ var code = NativeMethods.anoncreds_create_w3c_presentation(
+ presReqHandle,
+ credentialsList,
+ credentialsProve,
+ linkSecret,
+ schemasList,
+ schemaIds,
+ credDefsList,
+ credDefIds,
+ w3cVersion ?? "1.1",
+ out var handle
+ );
+ if (code != ErrorCode.Success)
+ throw new AnonCredsException(code, AnonCreds.GetCurrentError());
+ return new W3cPresentation(handle);
+ }
+ finally
+ {
+ AnonCreds.FreeFfiObjectHandleList(schemasList);
+ AnonCreds.FreeFfiObjectHandleList(credDefsList);
+ AnonCreds.FreeFfiCredentialEntryList(credentialsList);
+ AnonCreds.FreeFfiCredentialProveList(credentialsProve);
+ }
+ }
+
+ public static W3cPresentation CreateFromJson(
+ PresentationRequest presReq,
+ string credentialsJson,
+ string linkSecret,
+ string schemasJson,
+ string credDefsJson,
+ string schemaIdsJson,
+ string credDefIdsJson,
+ string? w3cVersion = null
+ )
+ {
+ var (schemasList, _) = AnonCreds.CreateFfiObjectHandleListWithObjects(
+ schemasJson,
+ Schema.FromJson
+ );
+ var (credDefsList, _) = AnonCreds.CreateFfiObjectHandleListWithObjects(
+ credDefsJson,
+ CredentialDefinition.FromJson
+ );
+ var credentialsList = AnonCreds.ParseCredentialsJson(credentialsJson, isW3c: true);
+ var schemaIds = AnonCreds.CreateFfiStrList(schemaIdsJson);
+ var credDefIds = AnonCreds.CreateFfiStrList(credDefIdsJson);
+ var credentialsProve = AnonCreds.CreateCredentialsProveList(
+ presReq.ToJson(),
+ null,
+ credentialsJson
+ );
+
+ return Create(
+ presReq.Handle,
+ credentialsList,
+ credentialsProve,
+ linkSecret,
+ schemasList,
+ schemaIds,
+ credDefsList,
+ credDefIds,
+ w3cVersion
+ );
+ }
+
+ public bool Verify(
+ PresentationRequest presReq,
+ string schemasJson,
+ string credDefsJson,
+ string schemaIdsJson,
+ string credDefIdsJson,
+ string? revRegDefsJson = null,
+ string? revStatusListsJson = null,
+ string? revRegDefIdsJson = null,
+ string? nonRevocJson = null
+ )
+ {
+ var (schemasList, _) = AnonCreds.CreateFfiObjectHandleListWithObjects(
+ schemasJson,
+ Schema.FromJson
+ );
+ var (credDefsList, _) = AnonCreds.CreateFfiObjectHandleListWithObjects(
+ credDefsJson,
+ CredentialDefinition.FromJson
+ );
+ var schemaIds = AnonCreds.CreateFfiStrList(schemaIdsJson);
+ var credDefIds = AnonCreds.CreateFfiStrList(credDefIdsJson);
+
+ var (revRegDefsList, _) = string.IsNullOrEmpty(revRegDefsJson)
+ ? (
+ new FfiObjectHandleList { Count = 0, Data = IntPtr.Zero },
+ Array.Empty()
+ )
+ : AnonCreds.CreateFfiObjectHandleListWithObjects(
+ revRegDefsJson,
+ RevocationRegistryDefinition.FromJson
+ );
+
+ var (revStatusLists, _) = string.IsNullOrEmpty(revStatusListsJson)
+ ? (
+ new FfiObjectHandleList { Count = 0, Data = IntPtr.Zero },
+ Array.Empty()
+ )
+ : AnonCreds.CreateFfiObjectHandleListWithObjects(
+ revStatusListsJson,
+ RevocationStatusList.FromJson
+ );
+
+ var revRegDefIds = !string.IsNullOrEmpty(revRegDefIdsJson)
+ ? AnonCreds.CreateFfiStrList(revRegDefIdsJson)
+ : new FfiStrList { Count = 0, Data = IntPtr.Zero };
+
+ var nonRevocList = AnonCreds.BuildNonrevokedIntervalOverrideList(nonRevocJson);
+
+ try
+ {
+ var code = NativeMethods.anoncreds_verify_w3c_presentation(
+ Handle,
+ presReq.Handle,
+ schemasList,
+ schemaIds,
+ credDefsList,
+ credDefIds,
+ revRegDefsList,
+ revRegDefIds,
+ revStatusLists,
+ nonRevocList,
+ out var valid
+ );
+ if (code != ErrorCode.Success)
+ {
+ var err = AnonCreds.GetCurrentError();
+ if (!string.IsNullOrEmpty(err))
+ {
+ var e = err.ToLowerInvariant();
+ if (
+ e.Contains("invalid timestamp")
+ || e.Contains("proof rejected")
+ || e.Contains("credential revoked")
+ || e.Contains("revocation registry not provided")
+ )
+ {
+ return false;
+ }
+ }
+ throw new AnonCredsException(code, err);
+ }
+ return valid != 0;
+ }
+ finally
+ {
+ AnonCreds.FreeFfiObjectHandleList(schemasList);
+ AnonCreds.FreeFfiObjectHandleList(credDefsList);
+ AnonCreds.FreeFfiObjectHandleList(revRegDefsList);
+ AnonCreds.FreeFfiObjectHandleList(revStatusLists);
+ AnonCreds.FreeFfiStrList(schemaIds);
+ AnonCreds.FreeFfiStrList(credDefIds);
+ if (revRegDefIds.Data != IntPtr.Zero)
+ AnonCreds.FreeFfiStrList(revRegDefIds);
+ AnonCreds.FreeFfiNonrevokedIntervalOverrideList(nonRevocList);
+ }
+ }
+}
diff --git a/wrappers/dotnet/lib/Requests/CredentialRequest.cs b/wrappers/dotnet/lib/Requests/CredentialRequest.cs
new file mode 100644
index 00000000..7315c026
--- /dev/null
+++ b/wrappers/dotnet/lib/Requests/CredentialRequest.cs
@@ -0,0 +1,44 @@
+using AnonCredsNet.Exceptions;
+using AnonCredsNet.Interop;
+using AnonCredsNet.Models;
+
+namespace AnonCredsNet.Requests;
+
+public class CredentialRequest : AnonCredsObject
+{
+ private CredentialRequest(long handle)
+ : base(handle) { }
+
+ public static (CredentialRequest Request, CredentialRequestMetadata Metadata) Create(
+ CredentialDefinition credDef,
+ string linkSecret,
+ string linkSecretId,
+ CredentialOffer credOffer,
+ string? entropy = null,
+ string? proverDid = null
+ )
+ {
+ if (
+ credDef == null
+ || string.IsNullOrEmpty(linkSecret)
+ || string.IsNullOrEmpty(linkSecretId)
+ || credOffer == null
+ )
+ throw new ArgumentNullException("Input parameters cannot be null or empty");
+ var code = NativeMethods.anoncreds_create_credential_request(
+ entropy,
+ proverDid,
+ credDef.Handle,
+ linkSecret,
+ linkSecretId,
+ credOffer.Handle,
+ out var req,
+ out var meta
+ );
+ if (code != ErrorCode.Success)
+ throw new AnonCredsException(code, AnonCreds.GetCurrentError());
+ return (new CredentialRequest(req), new CredentialRequestMetadata(meta));
+ }
+
+ internal static CredentialRequest FromJson(string json) => FromJson(json);
+}
diff --git a/wrappers/dotnet/lib/Requests/CredentialRequestMetadata.cs b/wrappers/dotnet/lib/Requests/CredentialRequestMetadata.cs
new file mode 100644
index 00000000..b94fbe90
--- /dev/null
+++ b/wrappers/dotnet/lib/Requests/CredentialRequestMetadata.cs
@@ -0,0 +1,12 @@
+using AnonCredsNet.Models;
+
+namespace AnonCredsNet.Requests;
+
+public class CredentialRequestMetadata : AnonCredsObject
+{
+ internal CredentialRequestMetadata(long handle)
+ : base(handle) { }
+
+ internal static CredentialRequestMetadata FromJson(string json) =>
+ FromJson(json);
+}
diff --git a/wrappers/dotnet/lib/Requests/PresentationRequest.cs b/wrappers/dotnet/lib/Requests/PresentationRequest.cs
new file mode 100644
index 00000000..3d17a819
--- /dev/null
+++ b/wrappers/dotnet/lib/Requests/PresentationRequest.cs
@@ -0,0 +1,11 @@
+using AnonCredsNet.Models;
+
+namespace AnonCredsNet.Requests;
+
+public sealed class PresentationRequest : AnonCredsObject
+{
+ internal PresentationRequest(long handle)
+ : base(handle) { }
+
+ public static PresentationRequest FromJson(string json) => FromJson(json);
+}
diff --git a/wrappers/dotnet/tests/AnonCredsNet.Tests.csproj b/wrappers/dotnet/tests/AnonCredsNet.Tests.csproj
new file mode 100644
index 00000000..e50df6f1
--- /dev/null
+++ b/wrappers/dotnet/tests/AnonCredsNet.Tests.csproj
@@ -0,0 +1,45 @@
+
+
+ net9.0
+ enable
+ enable
+ true
+ true
+ false
+ true
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/wrappers/dotnet/tests/AnonCredsNetTests.cs b/wrappers/dotnet/tests/AnonCredsNetTests.cs
new file mode 100644
index 00000000..d76d6dbd
--- /dev/null
+++ b/wrappers/dotnet/tests/AnonCredsNetTests.cs
@@ -0,0 +1,219 @@
+using System.Text.Json;
+using AnonCredsNet.Models;
+using AnonCredsNet.Requests;
+using Xunit;
+
+namespace AnonCredsNet.Tests;
+
+public class AnonCredsNetTests
+{
+ // Removed client facade; using model-centric APIs
+
+ [Fact]
+ public void TestFullFlow()
+ {
+ // Ported from wrappers/python/demo/test.py
+
+ // 1. Setup variables
+ var issuerId = "mock:uri";
+ var schemaId = "mock:uri";
+ var credDefId = "mock:uri";
+ var revRegId = "mock:uri:revregid";
+ var entropy = "entropy";
+ var revIdx = 1u;
+
+ // 2. Create Schema
+ var attrNames = new[] { "name", "age", "sex", "height" };
+ var schema = Schema.Create(
+ "schema name",
+ "1.0.0",
+ issuerId,
+ JsonSerializer.Serialize(attrNames)
+ );
+
+ // 3. Create Credential Definition
+ var (credDef, credDefPrivate, keyProof) = CredentialDefinition.Create(
+ schemaId,
+ issuerId,
+ schema,
+ "tag",
+ "CL",
+ "{\"support_revocation\": true}"
+ );
+
+ // 4. Create Revocation Registry Definition
+ var timeCreateRevStatusList = 12ul;
+ var (revRegDef, revRegDefPrivate) = RevocationRegistryDefinition.Create(
+ credDef,
+ credDefId,
+ issuerId,
+ "some_tag",
+ "CL_ACCUM",
+ 10,
+ null
+ );
+ // Create initial Revocation Status List at the given timestamp
+ var revocationStatusList = RevocationStatusList.Create(
+ credDef,
+ revRegId,
+ revRegDef,
+ revRegDefPrivate,
+ issuerId,
+ true,
+ timeCreateRevStatusList
+ );
+
+ // 6. Create Link Secret
+ var linkSecret = LinkSecret.Create();
+ var linkSecretId = "default";
+
+ // 7. Create Credential Offer
+ var credOffer = CredentialOffer.Create(schemaId, credDefId, keyProof);
+
+ // 8. Create Credential Request
+ var (credRequest, credRequestMetadata) = CredentialRequest.Create(
+ credDef,
+ linkSecret,
+ linkSecretId,
+ credOffer,
+ entropy
+ );
+
+ // 9. Issue Credential
+ var credValues = JsonSerializer.Serialize(
+ new Dictionary
+ {
+ ["sex"] = "male",
+ ["name"] = "Alex",
+ ["height"] = "175",
+ ["age"] = "28",
+ }
+ );
+
+ var revConfig = new CredentialRevocationConfig
+ {
+ RevRegDef = revRegDef,
+ RevRegDefPrivate = revRegDefPrivate,
+ RevStatusList = revocationStatusList,
+ RevRegIndex = revIdx,
+ };
+
+ var (credential, _) = Credential.Create(
+ credDef,
+ credDefPrivate,
+ credOffer,
+ credRequest,
+ credValues,
+ null,
+ null,
+ revocationStatusList,
+ revConfig
+ );
+
+ // 10. Process Credential
+ var processedCredential = credential.Process(
+ credRequestMetadata,
+ linkSecret,
+ credDef,
+ revRegDef
+ );
+
+ // 11. Update Revocation Status List
+ var timeAfterCreatingCred = timeCreateRevStatusList + 1;
+ var issuedRevStatusList = revocationStatusList.Update(
+ credDef,
+ revRegDef,
+ revRegDefPrivate,
+ new[] { (ulong)revIdx },
+ null,
+ timeAfterCreatingCred
+ );
+
+ // 12. Create Presentation Request
+ var nonce = AnonCreds.GenerateNonce();
+ var presReqJson = $$"""
+ {
+ "nonce": "{{nonce}}",
+ "name": "pres_req_1",
+ "version": "0.1",
+ "requested_attributes": {
+ "attr1_referent": {"name": "name", "issuer_id": "{{issuerId}}"},
+ "attr2_referent": {"name": "sex"},
+ "attr3_referent": {"name": "phone"},
+ "attr4_referent": {"names": ["name", "height"]}
+ },
+ "requested_predicates": {
+ "predicate1_referent": {"name": "age", "p_type": ">=", "p_value": 18}
+ },
+ "non_revoked": {"from": 10, "to": 200}
+ }
+ """;
+ var presReq = PresentationRequest.FromJson(presReqJson);
+
+ // 13. Create Revocation State using the issued (updated) status list at timeAfterCreatingCred
+ var revState = RevocationState.Create(
+ revRegDef,
+ issuedRevStatusList,
+ revIdx,
+ revRegDef.TailsLocation
+ );
+
+ // 14. Build Presentation
+ var credentialsJson = JsonSerializer.Serialize(
+ new[]
+ {
+ new
+ {
+ credential = processedCredential.ToJson(),
+ timestamp = timeAfterCreatingCred,
+ rev_state = revState.ToJson(),
+ },
+ }
+ );
+
+ var selfAttestedJson = JsonSerializer.Serialize(
+ new Dictionary { ["attr3_referent"] = "8-800-300" }
+ );
+
+ var schemasJson = JsonSerializer.Serialize(
+ new Dictionary { [schemaId] = schema.ToJson() }
+ );
+ var credDefsJson = JsonSerializer.Serialize(
+ new Dictionary { [credDefId] = credDef.ToJson() }
+ );
+ var revRegsJson = JsonSerializer.Serialize(
+ new Dictionary { [revRegId] = revRegDef.ToJson() }
+ );
+ var revListsJson = JsonSerializer.Serialize(
+ new Dictionary { [revRegId] = issuedRevStatusList.ToJson() }
+ );
+
+ var presentation = Presentation.CreateFromJson(
+ presReq,
+ credentialsJson,
+ selfAttestedJson,
+ linkSecret,
+ schemasJson,
+ credDefsJson,
+ JsonSerializer.Serialize(new[] { schemaId }),
+ JsonSerializer.Serialize(new[] { credDefId }),
+ revRegsJson,
+ revListsJson
+ );
+
+ // 15. Verify Presentation
+ var isValid = presentation.Verify(
+ presReq,
+ schemasJson,
+ credDefsJson,
+ JsonSerializer.Serialize(new[] { schemaId }),
+ JsonSerializer.Serialize(new[] { credDefId }),
+ revRegsJson,
+ revListsJson,
+ JsonSerializer.Serialize(new[] { revRegId }),
+ null
+ );
+
+ Assert.True(isValid);
+ }
+}
diff --git a/wrappers/dotnet/tests/ClassicRevocationFailureTests.cs b/wrappers/dotnet/tests/ClassicRevocationFailureTests.cs
new file mode 100644
index 00000000..18d1acee
--- /dev/null
+++ b/wrappers/dotnet/tests/ClassicRevocationFailureTests.cs
@@ -0,0 +1,255 @@
+using System.Text.Json;
+using AnonCredsNet.Models;
+using AnonCredsNet.Requests;
+using Xunit;
+
+namespace AnonCredsNet.Tests;
+
+public class ClassicRevocationFailureTests
+{
+ [Fact]
+ public void Revocation_Fails_After_Revoke()
+ {
+ var issuerId = "mock:uri";
+ var schemaId = "mock:uri";
+ var credDefId = "mock:uri";
+ var revRegId = "mock:uri:revregid";
+ var entropy = "entropy";
+ uint revIdx = 1;
+
+ var schema = Schema.Create(
+ "schema name",
+ "1.0.0",
+ issuerId,
+ JsonSerializer.Serialize(new[] { "name", "age", "sex", "height" })
+ );
+
+ var (credDef, credDefPriv, keyProof) = CredentialDefinition.Create(
+ schemaId,
+ issuerId,
+ schema,
+ "tag",
+ "CL",
+ "{\"support_revocation\": true}"
+ );
+
+ var (revRegDef, revRegPriv) = RevocationRegistryDefinition.Create(
+ credDef,
+ credDefId,
+ issuerId,
+ "some_tag",
+ "CL_ACCUM",
+ 10,
+ null
+ );
+
+ ulong timeCreateRevStatusList = 12;
+ var revocationStatusList = RevocationStatusList.Create(
+ credDef,
+ revRegId,
+ revRegDef,
+ revRegPriv,
+ issuerId,
+ true,
+ timeCreateRevStatusList
+ );
+
+ var linkSecret = LinkSecret.Create();
+ var linkSecretId = "default";
+ var credOffer = CredentialOffer.Create(schemaId, credDefId, keyProof);
+ var (credReq, credReqMeta) = CredentialRequest.Create(
+ credDef,
+ linkSecret,
+ linkSecretId,
+ credOffer,
+ entropy
+ );
+
+ var credValues = JsonSerializer.Serialize(
+ new Dictionary
+ {
+ ["sex"] = "male",
+ ["name"] = "Alex",
+ ["height"] = "175",
+ ["age"] = "28",
+ }
+ );
+
+ var revConfig = new CredentialRevocationConfig
+ {
+ RevRegDef = revRegDef,
+ RevRegDefPrivate = revRegPriv,
+ RevStatusList = revocationStatusList,
+ RevRegIndex = revIdx,
+ };
+
+ var (credential, _) = Credential.Create(
+ credDef,
+ credDefPriv,
+ credOffer,
+ credReq,
+ credValues,
+ null,
+ null,
+ revocationStatusList,
+ revConfig
+ );
+
+ var processed = credential.Process(credReqMeta, linkSecret, credDef, revRegDef);
+
+ var timeAfterCreatingCred = timeCreateRevStatusList + 1;
+ var issuedRevStatusList = revocationStatusList.Update(
+ credDef,
+ revRegDef,
+ revRegPriv,
+ new[] { (ulong)revIdx },
+ null,
+ timeAfterCreatingCred
+ );
+
+ var nonce = AnonCreds.GenerateNonce();
+ var presReqJson = $$"""
+ {
+ "nonce": "{{nonce}}",
+ "name": "pres_req_1",
+ "version": "0.1",
+ "requested_attributes": {
+ "attr1_referent": {"name": "name", "issuer_id": "{{issuerId}}"},
+ "attr2_referent": {"name": "sex"},
+ "attr3_referent": {"name": "phone"},
+ "attr4_referent": {"names": ["name", "height"]}
+ },
+ "requested_predicates": {
+ "predicate1_referent": {"name": "age", "p_type": ">=", "p_value": 18}
+ },
+ "non_revoked": {"from": 10, "to": 200}
+ }
+ """;
+ presReqJson = presReqJson.Replace("{{nonce}}", nonce).Replace("{{issuerId}}", issuerId);
+ var presReq = PresentationRequest.FromJson(presReqJson);
+
+ var revState = RevocationState.Create(
+ revRegDef,
+ issuedRevStatusList,
+ revIdx,
+ revRegDef.TailsLocation
+ );
+
+ var credentialsJson = JsonSerializer.Serialize(
+ new[]
+ {
+ new
+ {
+ credential = processed.ToJson(),
+ timestamp = timeAfterCreatingCred,
+ rev_state = revState.ToJson(),
+ },
+ }
+ );
+
+ var selfAttestedJson = JsonSerializer.Serialize(
+ new Dictionary { ["attr3_referent"] = "8-800-300" }
+ );
+
+ var schemasJson = JsonSerializer.Serialize(
+ new Dictionary { [schemaId] = schema.ToJson() }
+ );
+ var credDefsJson = JsonSerializer.Serialize(
+ new Dictionary { [credDefId] = credDef.ToJson() }
+ );
+ var revRegsJson = JsonSerializer.Serialize(
+ new Dictionary { [revRegId] = revRegDef.ToJson() }
+ );
+ var revListsJson = JsonSerializer.Serialize(
+ new Dictionary { [revRegId] = issuedRevStatusList.ToJson() }
+ );
+
+ var presentation = Presentation.CreateFromJson(
+ presReq,
+ credentialsJson,
+ selfAttestedJson,
+ linkSecret,
+ schemasJson,
+ credDefsJson,
+ JsonSerializer.Serialize(new[] { schemaId }),
+ JsonSerializer.Serialize(new[] { credDefId }),
+ revRegsJson,
+ revListsJson
+ );
+
+ var isValid = presentation.Verify(
+ presReq,
+ schemasJson,
+ credDefsJson,
+ JsonSerializer.Serialize(new[] { schemaId }),
+ JsonSerializer.Serialize(new[] { credDefId }),
+ revRegsJson,
+ revListsJson,
+ JsonSerializer.Serialize(new[] { revRegId }),
+ null
+ );
+ Assert.True(isValid);
+
+ // Revoke and expect failure
+ var timeRevoke = timeAfterCreatingCred + 1;
+ var revokedStatusList = issuedRevStatusList.Update(
+ credDef,
+ revRegDef,
+ revRegPriv,
+ null,
+ new[] { (ulong)revIdx },
+ timeRevoke
+ );
+
+ var revListsJson2 = JsonSerializer.Serialize(
+ new Dictionary { [revRegId] = revokedStatusList.ToJson() }
+ );
+
+ // Build a new revocation state at the revoke timestamp and create a new presentation
+ // so the proof is anchored at a time when the credential is revoked.
+ var revokedRevState = RevocationState.Create(
+ revRegDef,
+ revokedStatusList,
+ revIdx,
+ revRegDef.TailsLocation
+ );
+
+ var credentialsJson2 = JsonSerializer.Serialize(
+ new[]
+ {
+ new
+ {
+ credential = processed.ToJson(),
+ timestamp = timeRevoke,
+ rev_state = revokedRevState.ToJson(),
+ },
+ }
+ );
+
+ var presentation2 = Presentation.CreateFromJson(
+ presReq,
+ credentialsJson2,
+ selfAttestedJson,
+ linkSecret,
+ schemasJson,
+ credDefsJson,
+ JsonSerializer.Serialize(new[] { schemaId }),
+ JsonSerializer.Serialize(new[] { credDefId }),
+ revRegsJson,
+ revListsJson2
+ );
+
+ var isValidAfterRevoke = presentation2.Verify(
+ presReq,
+ schemasJson,
+ credDefsJson,
+ JsonSerializer.Serialize(new[] { schemaId }),
+ JsonSerializer.Serialize(new[] { credDefId }),
+ revRegsJson,
+ revListsJson2,
+ JsonSerializer.Serialize(new[] { revRegId }),
+ null
+ );
+ Assert.False(isValidAfterRevoke);
+ }
+}
diff --git a/wrappers/dotnet/tests/MultiOverrideRevocationTests.cs b/wrappers/dotnet/tests/MultiOverrideRevocationTests.cs
new file mode 100644
index 00000000..06097041
--- /dev/null
+++ b/wrappers/dotnet/tests/MultiOverrideRevocationTests.cs
@@ -0,0 +1,266 @@
+using System.Text.Json;
+using AnonCredsNet.Models;
+using AnonCredsNet.Requests;
+using Xunit;
+
+namespace AnonCredsNet.Tests;
+
+public class MultiOverrideRevocationTests
+{
+ private const string IssuerId = "mock:uri";
+ private const string Schema1Id = "mock:uri:schemaMO1";
+ private const string Schema2Id = "mock:uri:schemaMO2";
+ private const string CredDef1Id = "mock:uri:cdMO1";
+ private const string CredDef2Id = "mock:uri:cdMO2";
+ private const string RevReg1Id = "mock:uri:revregMO1";
+ private const string RevReg2Id = "mock:uri:revregMO2";
+
+ [Fact]
+ public void Two_Creds_Different_Local_Windows_Need_Two_Overrides()
+ {
+ // Create two revocable creds in different registries
+ var s1 = Schema.Create(
+ "gvt",
+ "1.0",
+ IssuerId,
+ JsonSerializer.Serialize(new[] { "name", "sex", "age", "height" })
+ );
+ var s2 = Schema.Create(
+ "pets",
+ "1.0",
+ IssuerId,
+ JsonSerializer.Serialize(new[] { "animal", "species" })
+ );
+ var (cd1, cd1Priv, k1) = CredentialDefinition.Create(
+ Schema1Id,
+ IssuerId,
+ s1,
+ "tag1",
+ "CL",
+ "{\"support_revocation\": true}"
+ );
+ var (cd2, cd2Priv, k2) = CredentialDefinition.Create(
+ Schema2Id,
+ IssuerId,
+ s2,
+ "tag2",
+ "CL",
+ "{\"support_revocation\": true}"
+ );
+ var (rev1, rev1Priv) = RevocationRegistryDefinition.Create(
+ cd1,
+ CredDef1Id,
+ IssuerId,
+ "tag1",
+ "CL_ACCUM",
+ 10,
+ null
+ );
+ var (rev2, rev2Priv) = RevocationRegistryDefinition.Create(
+ cd2,
+ CredDef2Id,
+ IssuerId,
+ "tag2",
+ "CL_ACCUM",
+ 10,
+ null
+ );
+ var t0 = 8ul;
+ var list1 = RevocationStatusList.Create(cd1, RevReg1Id, rev1, rev1Priv, IssuerId, true, t0);
+ var list2 = RevocationStatusList.Create(cd2, RevReg2Id, rev2, rev2Priv, IssuerId, true, t0);
+
+ var ls = LinkSecret.Create();
+ // IMPORTANT: Offer must be reused between request and issuance; don't recreate
+ var offer1 = CredentialOffer.Create(Schema1Id, CredDef1Id, k1);
+ var offer2 = CredentialOffer.Create(Schema2Id, CredDef2Id, k2);
+ var (req1, meta1) = CredentialRequest.Create(cd1, ls, "default", offer1, "entropy");
+ var (req2, meta2) = CredentialRequest.Create(cd2, ls, "default", offer2, "entropy");
+
+ var vals1 = JsonSerializer.Serialize(
+ new Dictionary
+ {
+ { "sex", "male" },
+ { "name", "Alex" },
+ { "height", "175" },
+ { "age", "28" },
+ }
+ );
+ var vals2 = JsonSerializer.Serialize(
+ new Dictionary { { "animal", "cat" }, { "species", "tabby" } }
+ );
+
+ var (cred1, _) = Credential.Create(
+ cd1,
+ cd1Priv,
+ offer1,
+ req1,
+ vals1,
+ null,
+ null,
+ list1,
+ new CredentialRevocationConfig
+ {
+ RevRegDef = rev1,
+ RevRegDefPrivate = rev1Priv,
+ RevStatusList = list1,
+ RevRegIndex = 9u,
+ }
+ );
+ var (cred2, _) = Credential.Create(
+ cd2,
+ cd2Priv,
+ offer2,
+ req2,
+ vals2,
+ null,
+ null,
+ list2,
+ new CredentialRevocationConfig
+ {
+ RevRegDef = rev2,
+ RevRegDefPrivate = rev2Priv,
+ RevStatusList = list2,
+ RevRegIndex = 7u,
+ }
+ );
+
+ var p1 = cred1.Process(meta1, ls, cd1, rev1);
+ var p2 = cred2.Process(meta2, ls, cd2, rev2);
+
+ // Issue at t=9 for first, t=11 for second
+ var list1Issued = list1.Update(cd1, rev1, rev1Priv, new[] { 9ul }, null, 9ul);
+ var list2Issued = list2.Update(cd2, rev2, rev2Priv, new[] { 7ul }, null, 11ul);
+ var rs1 = RevocationState.Create(rev1, list1Issued, 9u, rev1.TailsLocation);
+ var rs2 = RevocationState.Create(rev2, list2Issued, 7u, rev2.TailsLocation);
+
+ // Request: local windows require from=10 for referents bound to cred1, and from=12 for referents bound to cred2
+ var nonce = AnonCreds.GenerateNonce();
+ var presReqJson = JsonSerializer.Serialize(
+ new
+ {
+ nonce,
+ name = "multi_override",
+ version = "0.1",
+ requested_attributes = new
+ {
+ attr1_referent = new
+ {
+ name = "name",
+ issuer_id = IssuerId,
+ non_revoked = new { from = 10, to = 20 },
+ },
+ attrX_referent = new
+ {
+ names = new[] { "animal", "species" },
+ non_revoked = new { from = 12, to = 20 },
+ },
+ },
+ requested_predicates = new
+ {
+ predicate1_referent = new
+ {
+ name = "age",
+ p_type = ">=",
+ p_value = 18,
+ non_revoked = new { from = 10, to = 20 },
+ },
+ },
+ non_revoked = new { from = 5, to = 25 },
+ }
+ );
+ var presReq = PresentationRequest.FromJson(presReqJson);
+
+ var credsArray = JsonSerializer.Serialize(
+ new[]
+ {
+ new
+ {
+ credential = p1.ToJson(),
+ timestamp = (int?)9,
+ rev_state = (string?)rs1.ToJson(),
+ referents = new[] { "attr1_referent", "predicate1_referent" },
+ },
+ new
+ {
+ credential = p2.ToJson(),
+ timestamp = (int?)11,
+ rev_state = (string?)rs2.ToJson(),
+ referents = new[] { "attrX_referent" },
+ },
+ }
+ );
+
+ var schemasJson = JsonSerializer.Serialize(new[] { s1.ToJson(), s2.ToJson() });
+ var credDefsJson = JsonSerializer.Serialize(new[] { cd1.ToJson(), cd2.ToJson() });
+ var schemaIdsJson = JsonSerializer.Serialize(new[] { Schema1Id, Schema2Id });
+ var credDefIdsJson = JsonSerializer.Serialize(new[] { CredDef1Id, CredDef2Id });
+ var revRegsJson = JsonSerializer.Serialize(
+ new Dictionary
+ {
+ { RevReg1Id, rev1.ToJson() },
+ { RevReg2Id, rev2.ToJson() },
+ }
+ );
+ var revListsJson = JsonSerializer.Serialize(
+ new Dictionary
+ {
+ { RevReg1Id, list1Issued.ToJson() },
+ { RevReg2Id, list2Issued.ToJson() },
+ }
+ );
+
+ var presentation = Presentation.CreateFromJson(
+ presReq,
+ credsArray,
+ JsonSerializer.Serialize(new Dictionary()),
+ ls,
+ schemasJson,
+ credDefsJson,
+ schemaIdsJson,
+ credDefIdsJson,
+ revRegsJson,
+ revListsJson
+ );
+
+ // Without overrides, should fail
+ var okNoOverride = presentation.Verify(
+ presReq,
+ schemasJson,
+ credDefsJson,
+ schemaIdsJson,
+ credDefIdsJson,
+ revRegsJson,
+ revListsJson,
+ JsonSerializer.Serialize(new[] { RevReg1Id, RevReg2Id }),
+ null
+ );
+ Assert.False(okNoOverride);
+
+ // With overrides for 10->9 and 12->11
+ var overrideJson = JsonSerializer.Serialize(
+ new Dictionary>
+ {
+ {
+ RevReg1Id,
+ new Dictionary { { "10", 9 } }
+ },
+ {
+ RevReg2Id,
+ new Dictionary { { "12", 11 } }
+ },
+ }
+ );
+ var okWithOverride = presentation.Verify(
+ presReq,
+ schemasJson,
+ credDefsJson,
+ schemaIdsJson,
+ credDefIdsJson,
+ revRegsJson,
+ revListsJson,
+ JsonSerializer.Serialize(new[] { RevReg1Id, RevReg2Id }),
+ overrideJson
+ );
+ Assert.True(okWithOverride);
+ }
+}
diff --git a/wrappers/dotnet/tests/MultipleCredentialsTests.cs b/wrappers/dotnet/tests/MultipleCredentialsTests.cs
new file mode 100644
index 00000000..5dc2e3b6
--- /dev/null
+++ b/wrappers/dotnet/tests/MultipleCredentialsTests.cs
@@ -0,0 +1,243 @@
+using System.Text.Json;
+using AnonCredsNet.Models;
+using AnonCredsNet.Requests;
+using Xunit;
+
+namespace AnonCredsNet.Tests;
+
+public class MultipleCredentialsTests
+{
+ [Fact]
+ public void Multiple_Credentials_Global_NonRevoked_Succeeds()
+ {
+ // Based on tests/multiple-credentials.rs happy path for classic
+ var issuerId = "mock:uri";
+ var schema1Id = "mock:uri:schema1";
+ var schema2Id = "mock:uri:schema2";
+ var credDef1Id = "mock:uri:1";
+ var credDef2Id = "mock:uri:2";
+ var revReg1Id = "mock:uri:revregid1";
+ var entropy = "entropy";
+
+ // Create two schemas
+ var schema1 = Schema.Create(
+ "gvt",
+ "1.0",
+ issuerId,
+ JsonSerializer.Serialize(new[] { "name", "sex", "age", "height" })
+ );
+ var schema2 = Schema.Create(
+ "hogwarts",
+ "1.0",
+ issuerId,
+ JsonSerializer.Serialize(new[] { "wand", "house", "year" })
+ );
+
+ // cred def 1 supports revocation, cred def 2 does not
+ var (credDef1, credDef1Priv, k1) = CredentialDefinition.Create(
+ schema1Id,
+ issuerId,
+ schema1,
+ "tag1",
+ "CL",
+ "{\"support_revocation\": true}"
+ );
+ var (credDef2, credDef2Priv, k2) = CredentialDefinition.Create(
+ schema2Id,
+ issuerId,
+ schema2,
+ "tag2",
+ "CL",
+ "{\"support_revocation\": false}"
+ );
+
+ // Revocation registry for credDef1
+ var (revRegDef1, revRegPriv1) = RevocationRegistryDefinition.Create(
+ credDef1,
+ credDef1Id,
+ issuerId,
+ "tag",
+ "CL_ACCUM",
+ 10,
+ null
+ );
+ var t0 = 8ul;
+ var revList = RevocationStatusList.Create(
+ credDef1,
+ revReg1Id,
+ revRegDef1,
+ revRegPriv1,
+ issuerId,
+ true,
+ t0
+ );
+
+ // Link secret
+ var ls = LinkSecret.Create();
+ var lsId = "default";
+
+ // Offers and requests
+ var offer1 = CredentialOffer.Create(schema1Id, credDef1Id, k1);
+ var offer2 = CredentialOffer.Create(schema2Id, credDef2Id, k2);
+ var (req1, meta1) = CredentialRequest.Create(credDef1, ls, lsId, offer1, entropy);
+ var (req2, meta2) = CredentialRequest.Create(credDef2, ls, lsId, offer2, entropy);
+
+ // Issue creds
+ var values1 = JsonSerializer.Serialize(
+ new Dictionary
+ {
+ { "sex", "male" },
+ { "name", "Alex" },
+ { "height", "175" },
+ { "age", "28" },
+ }
+ );
+ var values2 = JsonSerializer.Serialize(
+ new Dictionary
+ {
+ { "wand", "dragon-heart-string" },
+ { "house", "Hufflepuff" },
+ { "year", "1990" },
+ }
+ );
+ var revCfg = new CredentialRevocationConfig
+ {
+ RevRegDef = revRegDef1,
+ RevRegDefPrivate = revRegPriv1,
+ RevStatusList = revList,
+ RevRegIndex = 9u,
+ };
+
+ var (cred1, _) = Credential.Create(
+ credDef1,
+ credDef1Priv,
+ offer1,
+ req1,
+ values1,
+ null,
+ null,
+ revList,
+ revCfg
+ );
+ var (cred2, _) = Credential.Create(
+ credDef2,
+ credDef2Priv,
+ offer2,
+ req2,
+ values2,
+ null,
+ null,
+ null,
+ null
+ );
+
+ // Process
+ var proc1 = cred1.Process(meta1, ls, credDef1, revRegDef1);
+ var proc2 = cred2.Process(meta2, ls, credDef2, null);
+
+ // Update rev list to issue index 9 at t=9
+ var tIssue = 9ul; // within global interval below
+ var revListIssued = revList.Update(
+ credDef1,
+ revRegDef1,
+ revRegPriv1,
+ new[] { 9ul },
+ null,
+ tIssue
+ );
+ var revState = RevocationState.Create(
+ revRegDef1,
+ revListIssued,
+ 9u,
+ revRegDef1.TailsLocation
+ );
+
+ // Request with global non_revoked window [5,25]
+ var nonce = AnonCreds.GenerateNonce();
+ var presReqJson = $$"""
+ {
+ "nonce":"{{nonce}}",
+ "name":"global_rev",
+ "version":"0.1",
+ "requested_attributes":{
+ "attr1_referent": {"name":"name","issuer_id":"{{issuerId}}"},
+ "attr2_referent": {"name":"sex"},
+ "attr4_referent": {"names":["height"]},
+ "attr5_referent": {"names":["wand","house","year"]}
+ },
+ "requested_predicates":{
+ "predicate1_referent": {"name":"age","p_type":">=","p_value":18}
+ },
+ "non_revoked": {"from":5, "to":25}
+ }
+ """;
+ presReqJson = presReqJson.Replace("{{nonce}}", nonce).Replace("{{issuerId}}", issuerId);
+ var presReq = PresentationRequest.FromJson(presReqJson);
+
+ // Build present credentials (two credentials, attach rev state to the revocable one)
+ var credsArray = JsonSerializer.Serialize(
+ new[]
+ {
+ new
+ {
+ credential = proc1.ToJson(),
+ timestamp = (int?)tIssue,
+ rev_state = (string?)revState.ToJson(),
+ referents = new[]
+ {
+ "attr1_referent",
+ "attr2_referent",
+ "attr4_referent",
+ "predicate1_referent",
+ },
+ },
+ new
+ {
+ credential = proc2.ToJson(),
+ timestamp = (int?)null,
+ rev_state = (string?)null,
+ referents = new[] { "attr5_referent" },
+ },
+ }
+ );
+
+ var selfAtt = JsonSerializer.Serialize(new Dictionary());
+ var schemasJson = JsonSerializer.Serialize(new[] { schema1.ToJson(), schema2.ToJson() });
+ var credDefsJson = JsonSerializer.Serialize(new[] { credDef1.ToJson(), credDef2.ToJson() });
+ var schemaIdsJson = JsonSerializer.Serialize(new[] { schema1Id, schema2Id });
+ var credDefIdsJson = JsonSerializer.Serialize(new[] { credDef1Id, credDef2Id });
+ var revRegsJson = JsonSerializer.Serialize(
+ new Dictionary { { revReg1Id, revRegDef1.ToJson() } }
+ );
+ var revListsJson = JsonSerializer.Serialize(
+ new Dictionary { { revReg1Id, revListIssued.ToJson() } }
+ );
+
+ var presentation = Presentation.CreateFromJson(
+ presReq,
+ credsArray,
+ selfAtt,
+ ls,
+ schemasJson,
+ credDefsJson,
+ schemaIdsJson,
+ credDefIdsJson,
+ revRegsJson,
+ revListsJson
+ );
+
+ var ok = presentation.Verify(
+ presReq,
+ schemasJson,
+ credDefsJson,
+ schemaIdsJson,
+ credDefIdsJson,
+ revRegsJson,
+ revListsJson,
+ JsonSerializer.Serialize(new[] { revReg1Id }),
+ null
+ );
+
+ Assert.True(ok);
+ }
+}
diff --git a/wrappers/dotnet/tests/NonRevocableIntervalsTests.cs b/wrappers/dotnet/tests/NonRevocableIntervalsTests.cs
new file mode 100644
index 00000000..642cb08d
--- /dev/null
+++ b/wrappers/dotnet/tests/NonRevocableIntervalsTests.cs
@@ -0,0 +1,111 @@
+using System.Text.Json;
+using AnonCredsNet.Models;
+using AnonCredsNet.Requests;
+using Xunit;
+
+namespace AnonCredsNet.Tests;
+
+public class NonRevocableIntervalsTests
+{
+ private const string IssuerId = "mock:uri";
+ private const string SchemaId = "mock:uri:schemaNR";
+ private const string CredDefId = "mock:uri:cdNR";
+
+ [Fact]
+ public void NonRevocable_Cred_Ignores_NonRevoked_Windows()
+ {
+ var schema = Schema.Create(
+ "hogwarts",
+ "1.0",
+ IssuerId,
+ JsonSerializer.Serialize(new[] { "wand", "house", "year" })
+ );
+ var (cd, cdPriv, k) = CredentialDefinition.Create(
+ SchemaId,
+ IssuerId,
+ schema,
+ "tag",
+ "CL",
+ "{\"support_revocation\": false}"
+ );
+
+ var ls = LinkSecret.Create();
+ var offer = CredentialOffer.Create(SchemaId, CredDefId, k);
+ var (req, meta) = CredentialRequest.Create(cd, ls, "default", offer, "entropy");
+
+ var values = JsonSerializer.Serialize(
+ new Dictionary
+ {
+ { "wand", "phoenix" },
+ { "house", "Gryffindor" },
+ { "year", "1997" },
+ }
+ );
+ var (cred, _) = Credential.Create(cd, cdPriv, offer, req, values, null, null, null, null);
+ var proc = cred.Process(meta, ls, cd, null);
+
+ var nonce = AnonCreds.GenerateNonce();
+ var presReqJson = JsonSerializer.Serialize(
+ new
+ {
+ nonce,
+ name = "nr_test",
+ version = "0.1",
+ requested_attributes = new
+ {
+ attr5_referent = new
+ {
+ names = new[] { "wand", "house", "year" },
+ non_revoked = new { from = 10, to = 20 },
+ },
+ },
+ requested_predicates = new { },
+ non_revoked = new { from = 5, to = 25 },
+ }
+ );
+ var presReq = PresentationRequest.FromJson(presReqJson);
+
+ var credsArray = JsonSerializer.Serialize(
+ new[]
+ {
+ new
+ {
+ credential = proc.ToJson(),
+ timestamp = (int?)null,
+ rev_state = (string?)null,
+ referents = new[] { "attr5_referent" },
+ },
+ }
+ );
+ var schemasJson = JsonSerializer.Serialize(new[] { schema.ToJson() });
+ var credDefsJson = JsonSerializer.Serialize(new[] { cd.ToJson() });
+ var schemaIdsJson = JsonSerializer.Serialize(new[] { SchemaId });
+ var credDefIdsJson = JsonSerializer.Serialize(new[] { CredDefId });
+
+ var presentation = Presentation.CreateFromJson(
+ presReq,
+ credsArray,
+ JsonSerializer.Serialize(new Dictionary()),
+ ls,
+ schemasJson,
+ credDefsJson,
+ schemaIdsJson,
+ credDefIdsJson,
+ null,
+ null
+ );
+
+ var ok = presentation.Verify(
+ presReq,
+ schemasJson,
+ credDefsJson,
+ schemaIdsJson,
+ credDefIdsJson,
+ null,
+ null,
+ null,
+ null
+ );
+ Assert.True(ok);
+ }
+}
diff --git a/wrappers/dotnet/tests/NonRevokedIntervalsTests.cs b/wrappers/dotnet/tests/NonRevokedIntervalsTests.cs
new file mode 100644
index 00000000..7b12f9d0
--- /dev/null
+++ b/wrappers/dotnet/tests/NonRevokedIntervalsTests.cs
@@ -0,0 +1,255 @@
+using System.Text.Json;
+using AnonCredsNet.Models;
+using AnonCredsNet.Requests;
+using Xunit;
+
+namespace AnonCredsNet.Tests;
+
+public class NonRevokedIntervalsTests
+{
+ private const string IssuerId = "mock:uri";
+ private const string Schema1Id = "mock:uri:schema1";
+ private const string Schema2Id = "mock:uri:schema2";
+ private const string CredDef1Id = "mock:uri:1";
+ private const string CredDef2Id = "mock:uri:2";
+ private const string RevReg1Id = "mock:uri:revregid1";
+
+ [Fact]
+ public void Global_Interval_Succeeds_Local_Fails_Without_Override()
+ {
+ // Setup two schemas and cred defs: one revocable, one not
+ var schema1 = Schema.Create(
+ "gvt",
+ "1.0",
+ IssuerId,
+ JsonSerializer.Serialize(new[] { "name", "sex", "age", "height" })
+ );
+ var schema2 = Schema.Create(
+ "hogwarts",
+ "1.0",
+ IssuerId,
+ JsonSerializer.Serialize(new[] { "wand", "house", "year" })
+ );
+ var (cd1, cd1Priv, k1) = CredentialDefinition.Create(
+ Schema1Id,
+ IssuerId,
+ schema1,
+ "tag1",
+ "CL",
+ "{\"support_revocation\": true}"
+ );
+ var (cd2, cd2Priv, k2) = CredentialDefinition.Create(
+ Schema2Id,
+ IssuerId,
+ schema2,
+ "tag2",
+ "CL",
+ "{\"support_revocation\": false}"
+ );
+
+ var (revDef, revPriv) = RevocationRegistryDefinition.Create(
+ cd1,
+ CredDef1Id,
+ IssuerId,
+ "tag",
+ "CL_ACCUM",
+ 10,
+ null
+ );
+ var t0 = 8ul; // initial list before issuance
+ var revList = RevocationStatusList.Create(
+ cd1,
+ RevReg1Id,
+ revDef,
+ revPriv,
+ IssuerId,
+ true,
+ t0
+ );
+
+ var ls = LinkSecret.Create();
+ var lsId = "default";
+ var offer1 = CredentialOffer.Create(Schema1Id, CredDef1Id, k1);
+ var offer2 = CredentialOffer.Create(Schema2Id, CredDef2Id, k2);
+ var (req1, meta1) = CredentialRequest.Create(cd1, ls, lsId, offer1, "entropy");
+ var (req2, meta2) = CredentialRequest.Create(cd2, ls, lsId, offer2, "entropy");
+
+ var values1 = JsonSerializer.Serialize(
+ new Dictionary
+ {
+ { "sex", "male" },
+ { "name", "Alex" },
+ { "height", "175" },
+ { "age", "28" },
+ }
+ );
+ var values2 = JsonSerializer.Serialize(
+ new Dictionary
+ {
+ { "wand", "dragon-heart-string" },
+ { "house", "Hufflepuff" },
+ { "year", "1990" },
+ }
+ );
+ var revCfg = new CredentialRevocationConfig
+ {
+ RevRegDef = revDef,
+ RevRegDefPrivate = revPriv,
+ RevStatusList = revList,
+ RevRegIndex = 9u,
+ };
+
+ var (cred1, _) = Credential.Create(
+ cd1,
+ cd1Priv,
+ offer1,
+ req1,
+ values1,
+ null,
+ null,
+ revList,
+ revCfg
+ );
+ var (cred2, _) = Credential.Create(
+ cd2,
+ cd2Priv,
+ offer2,
+ req2,
+ values2,
+ null,
+ null,
+ null,
+ null
+ );
+
+ var proc1 = cred1.Process(meta1, ls, cd1, revDef);
+ var proc2 = cred2.Process(meta2, ls, cd2, null);
+
+ // Issue revocation at t=9 (within global [5,25])
+ var tIssue = 9ul;
+ var revListIssued = revList.Update(cd1, revDef, revPriv, new[] { 9ul }, null, tIssue);
+ var revState = RevocationState.Create(revDef, revListIssued, 9u, revDef.TailsLocation);
+
+ // Request with global non_revoked window [5,25] and local windows for two referents [10,20]
+ var nonce = AnonCreds.GenerateNonce();
+ var presReqJson = JsonSerializer.Serialize(
+ new
+ {
+ nonce,
+ name = "both_rev_attr",
+ version = "0.1",
+ requested_attributes = new
+ {
+ attr1_referent = new { name = "name", issuer_id = IssuerId },
+ attr2_referent = new { name = "sex", non_revoked = new { from = 10, to = 20 } },
+ attr4_referent = new { names = new[] { "height" } },
+ attr5_referent = new
+ {
+ names = new[] { "wand", "house", "year" },
+ non_revoked = new { from = 10, to = 20 },
+ },
+ },
+ requested_predicates = new
+ {
+ predicate1_referent = new
+ {
+ name = "age",
+ p_type = ">=",
+ p_value = 18,
+ },
+ },
+ non_revoked = new { from = 5, to = 25 },
+ }
+ );
+ var presReq = PresentationRequest.FromJson(presReqJson);
+
+ // Build credentials array with referent mapping so attr5 (hogwarts) maps to second credential
+ var credsArray = JsonSerializer.Serialize(
+ new[]
+ {
+ new
+ {
+ credential = proc1.ToJson(),
+ timestamp = (int?)tIssue,
+ rev_state = (string?)revState.ToJson(),
+ referents = new[]
+ {
+ "attr1_referent",
+ "attr2_referent",
+ "attr4_referent",
+ "predicate1_referent",
+ },
+ },
+ new
+ {
+ credential = proc2.ToJson(),
+ timestamp = (int?)null,
+ rev_state = (string?)null,
+ referents = new[] { "attr5_referent" },
+ },
+ }
+ );
+
+ var selfAtt = JsonSerializer.Serialize(new Dictionary());
+ var schemasJson = JsonSerializer.Serialize(new[] { schema1.ToJson(), schema2.ToJson() });
+ var credDefsJson = JsonSerializer.Serialize(new[] { cd1.ToJson(), cd2.ToJson() });
+ var schemaIdsJson = JsonSerializer.Serialize(new[] { Schema1Id, Schema2Id });
+ var credDefIdsJson = JsonSerializer.Serialize(new[] { CredDef1Id, CredDef2Id });
+ var revRegsJson = JsonSerializer.Serialize(
+ new Dictionary { { RevReg1Id, revDef.ToJson() } }
+ );
+ var revListsJson = JsonSerializer.Serialize(
+ new Dictionary { { RevReg1Id, revListIssued.ToJson() } }
+ );
+
+ var presentation = Presentation.CreateFromJson(
+ presReq,
+ credsArray,
+ selfAtt,
+ ls,
+ schemasJson,
+ credDefsJson,
+ schemaIdsJson,
+ credDefIdsJson,
+ revRegsJson,
+ revListsJson
+ );
+
+ // Without overrides, local windows [10,20] require rev status at from=10; our proof is at 9 -> expect failure
+ var okNoOverride = presentation.Verify(
+ presReq,
+ schemasJson,
+ credDefsJson,
+ schemaIdsJson,
+ credDefIdsJson,
+ revRegsJson,
+ revListsJson,
+ JsonSerializer.Serialize(new[] { RevReg1Id }),
+ null
+ );
+ Assert.False(okNoOverride);
+
+ // With override mapping requested_from 10 -> use rev list at 9
+ var overrideJson = JsonSerializer.Serialize(
+ new Dictionary>
+ {
+ {
+ RevReg1Id,
+ new Dictionary { { "10", 9 } }
+ },
+ }
+ );
+ var okWithOverride = presentation.Verify(
+ presReq,
+ schemasJson,
+ credDefsJson,
+ schemaIdsJson,
+ credDefIdsJson,
+ revRegsJson,
+ revListsJson,
+ JsonSerializer.Serialize(new[] { RevReg1Id }),
+ overrideJson
+ );
+ Assert.True(okWithOverride);
+ }
+}
diff --git a/wrappers/dotnet/tests/PredicatesOnlyTests.cs b/wrappers/dotnet/tests/PredicatesOnlyTests.cs
new file mode 100644
index 00000000..fba2fe46
--- /dev/null
+++ b/wrappers/dotnet/tests/PredicatesOnlyTests.cs
@@ -0,0 +1,331 @@
+using System.Text.Json;
+using AnonCredsNet.Models;
+using AnonCredsNet.Requests;
+using Xunit;
+
+namespace AnonCredsNet.Tests;
+
+public class PredicatesOnlyTests
+{
+ private const string IssuerId = "mock:uri";
+ private const string SchemaId = "mock:uri:schemaP";
+ private const string CredDefId = "mock:uri:cdP";
+ private const string RevRegId = "mock:uri:revregP";
+
+ [Fact]
+ public void Predicates_Only_Passes_With_Revocation()
+ {
+ var schema = Schema.Create(
+ "gvt",
+ "1.0",
+ IssuerId,
+ JsonSerializer.Serialize(new[] { "name", "sex", "age", "height" })
+ );
+ var (cd, cdPriv, k) = CredentialDefinition.Create(
+ SchemaId,
+ IssuerId,
+ schema,
+ "tag",
+ "CL",
+ "{\"support_revocation\": true}"
+ );
+ var (revDef, revPriv) = RevocationRegistryDefinition.Create(
+ cd,
+ CredDefId,
+ IssuerId,
+ "tag",
+ "CL_ACCUM",
+ 10,
+ null
+ );
+ var t0 = 10ul;
+ var revList = RevocationStatusList.Create(
+ cd,
+ RevRegId,
+ revDef,
+ revPriv,
+ IssuerId,
+ true,
+ t0
+ );
+
+ var ls = LinkSecret.Create();
+ var offer = CredentialOffer.Create(SchemaId, CredDefId, k);
+ var (req, meta) = CredentialRequest.Create(cd, ls, "default", offer, "entropy");
+
+ var values = JsonSerializer.Serialize(
+ new Dictionary
+ {
+ { "sex", "male" },
+ { "name", "Alex" },
+ { "height", "175" },
+ { "age", "28" },
+ }
+ );
+ var revCfg = new CredentialRevocationConfig
+ {
+ RevRegDef = revDef,
+ RevRegDefPrivate = revPriv,
+ RevStatusList = revList,
+ RevRegIndex = 1u,
+ };
+ var (cred, _) = Credential.Create(
+ cd,
+ cdPriv,
+ offer,
+ req,
+ values,
+ null,
+ null,
+ revList,
+ revCfg
+ );
+ var proc = cred.Process(meta, ls, cd, revDef);
+
+ // timestamp > t0 and inside global window
+ var tIssue = t0 + 2; // 12
+ var revListIssued = revList.Update(cd, revDef, revPriv, new[] { 1ul }, null, tIssue);
+ var revState = RevocationState.Create(revDef, revListIssued, 1u, revDef.TailsLocation);
+
+ var nonce = AnonCreds.GenerateNonce();
+ var presReqJson = JsonSerializer.Serialize(
+ new
+ {
+ nonce,
+ name = "pred_only",
+ version = "0.1",
+ requested_attributes = new { },
+ requested_predicates = new
+ {
+ predicate1_referent = new
+ {
+ name = "age",
+ p_type = ">=",
+ p_value = 18,
+ },
+ },
+ non_revoked = new { from = 10, to = 200 },
+ }
+ );
+ var presReq = PresentationRequest.FromJson(presReqJson);
+
+ var credsArray = JsonSerializer.Serialize(
+ new[]
+ {
+ new
+ {
+ credential = proc.ToJson(),
+ timestamp = (int?)tIssue,
+ rev_state = (string?)revState.ToJson(),
+ referents = new[] { "predicate1_referent" },
+ },
+ }
+ );
+ var schemasJson = JsonSerializer.Serialize(new[] { schema.ToJson() });
+ var credDefsJson = JsonSerializer.Serialize(new[] { cd.ToJson() });
+ var schemaIdsJson = JsonSerializer.Serialize(new[] { SchemaId });
+ var credDefIdsJson = JsonSerializer.Serialize(new[] { CredDefId });
+ var revRegsJson = JsonSerializer.Serialize(
+ new Dictionary { { RevRegId, revDef.ToJson() } }
+ );
+ var revListsJson = JsonSerializer.Serialize(
+ new Dictionary { { RevRegId, revListIssued.ToJson() } }
+ );
+
+ var presentation = Presentation.CreateFromJson(
+ presReq,
+ credsArray,
+ JsonSerializer.Serialize(new Dictionary()),
+ ls,
+ schemasJson,
+ credDefsJson,
+ schemaIdsJson,
+ credDefIdsJson,
+ revRegsJson,
+ revListsJson
+ );
+
+ var ok = presentation.Verify(
+ presReq,
+ schemasJson,
+ credDefsJson,
+ schemaIdsJson,
+ credDefIdsJson,
+ revRegsJson,
+ revListsJson,
+ JsonSerializer.Serialize(new[] { RevRegId }),
+ null
+ );
+ Assert.True(ok);
+ }
+
+ [Fact]
+ public void Predicate_Fails_With_Local_Window_Then_Succeeds_With_Override()
+ {
+ var schema = Schema.Create(
+ "gvt",
+ "1.0",
+ IssuerId,
+ JsonSerializer.Serialize(new[] { "name", "sex", "age", "height" })
+ );
+ var (cd, cdPriv, k) = CredentialDefinition.Create(
+ SchemaId,
+ IssuerId,
+ schema,
+ "tag",
+ "CL",
+ "{\"support_revocation\": true}"
+ );
+ var (revDef, revPriv) = RevocationRegistryDefinition.Create(
+ cd,
+ CredDefId,
+ IssuerId,
+ "tag",
+ "CL_ACCUM",
+ 10,
+ null
+ );
+ var t0 = 8ul;
+ var revList = RevocationStatusList.Create(
+ cd,
+ RevRegId,
+ revDef,
+ revPriv,
+ IssuerId,
+ true,
+ t0
+ );
+
+ var ls = LinkSecret.Create();
+ var offer = CredentialOffer.Create(SchemaId, CredDefId, k);
+ var (req, meta) = CredentialRequest.Create(cd, ls, "default", offer, "entropy");
+
+ var values = JsonSerializer.Serialize(
+ new Dictionary
+ {
+ { "sex", "male" },
+ { "name", "Alex" },
+ { "height", "175" },
+ { "age", "28" },
+ }
+ );
+ var revCfg = new CredentialRevocationConfig
+ {
+ RevRegDef = revDef,
+ RevRegDefPrivate = revPriv,
+ RevStatusList = revList,
+ RevRegIndex = 9u,
+ };
+ var (cred, _) = Credential.Create(
+ cd,
+ cdPriv,
+ offer,
+ req,
+ values,
+ null,
+ null,
+ revList,
+ revCfg
+ );
+ var proc = cred.Process(meta, ls, cd, revDef);
+
+ // Issue at t=9, local window will require from=10 later
+ var tIssue = 9ul;
+ var revListIssued = revList.Update(cd, revDef, revPriv, new[] { 9ul }, null, tIssue);
+ var revState = RevocationState.Create(revDef, revListIssued, 9u, revDef.TailsLocation);
+
+ var nonce = AnonCreds.GenerateNonce();
+ var presReqJson = JsonSerializer.Serialize(
+ new
+ {
+ nonce,
+ name = "pred_local_window",
+ version = "0.1",
+ requested_attributes = new { },
+ requested_predicates = new
+ {
+ predicate1_referent = new
+ {
+ name = "age",
+ p_type = ">=",
+ p_value = 18,
+ non_revoked = new { from = 10, to = 20 },
+ },
+ },
+ non_revoked = new { from = 5, to = 25 },
+ }
+ );
+ var presReq = PresentationRequest.FromJson(presReqJson);
+
+ var credsArray = JsonSerializer.Serialize(
+ new[]
+ {
+ new
+ {
+ credential = proc.ToJson(),
+ timestamp = (int?)tIssue,
+ rev_state = (string?)revState.ToJson(),
+ referents = new[] { "predicate1_referent" },
+ },
+ }
+ );
+ var schemasJson = JsonSerializer.Serialize(new[] { schema.ToJson() });
+ var credDefsJson = JsonSerializer.Serialize(new[] { cd.ToJson() });
+ var schemaIdsJson = JsonSerializer.Serialize(new[] { SchemaId });
+ var credDefIdsJson = JsonSerializer.Serialize(new[] { CredDefId });
+ var revRegsJson = JsonSerializer.Serialize(
+ new Dictionary { { RevRegId, revDef.ToJson() } }
+ );
+ var revListsJson = JsonSerializer.Serialize(
+ new Dictionary { { RevRegId, revListIssued.ToJson() } }
+ );
+
+ var presentation = Presentation.CreateFromJson(
+ presReq,
+ credsArray,
+ JsonSerializer.Serialize(new Dictionary()),
+ ls,
+ schemasJson,
+ credDefsJson,
+ schemaIdsJson,
+ credDefIdsJson,
+ revRegsJson,
+ revListsJson
+ );
+
+ var okNoOverride = presentation.Verify(
+ presReq,
+ schemasJson,
+ credDefsJson,
+ schemaIdsJson,
+ credDefIdsJson,
+ revRegsJson,
+ revListsJson,
+ JsonSerializer.Serialize(new[] { RevRegId }),
+ null
+ );
+ Assert.False(okNoOverride);
+
+ var overrideJson = JsonSerializer.Serialize(
+ new Dictionary>
+ {
+ {
+ RevRegId,
+ new Dictionary { { "10", 9 } }
+ },
+ }
+ );
+ var okWithOverride = presentation.Verify(
+ presReq,
+ schemasJson,
+ credDefsJson,
+ schemaIdsJson,
+ credDefIdsJson,
+ revRegsJson,
+ revListsJson,
+ JsonSerializer.Serialize(new[] { RevRegId }),
+ overrideJson
+ );
+ Assert.True(okWithOverride);
+ }
+}
diff --git a/wrappers/dotnet/tests/W3CTests.cs b/wrappers/dotnet/tests/W3CTests.cs
new file mode 100644
index 00000000..8fb0c8ae
--- /dev/null
+++ b/wrappers/dotnet/tests/W3CTests.cs
@@ -0,0 +1,486 @@
+using System.Collections.Generic;
+using System.Text.Json;
+using AnonCredsNet.Models;
+using AnonCredsNet.Requests;
+using Xunit;
+
+namespace AnonCredsNet.Tests;
+
+public class W3cTests
+{
+ [Fact]
+ public void W3cEndToEnd()
+ {
+ var issuerId = "mock:uri";
+ var schemaId = "mock:uri";
+ var credDefId = "mock:uri";
+ var revRegId = "mock:uri:revregid";
+ var entropy = "entropy";
+ uint revIdx = 1;
+
+ var schema = Schema.Create(
+ "schema name",
+ "1.0.0",
+ issuerId,
+ JsonSerializer.Serialize(new[] { "name", "age", "sex", "height" })
+ );
+
+ var (credDef, credDefPriv, keyProof) = CredentialDefinition.Create(
+ schemaId,
+ issuerId,
+ schema,
+ "tag",
+ "CL",
+ "{\"support_revocation\": true}"
+ );
+
+ var (revRegDef, revRegPriv) = RevocationRegistryDefinition.Create(
+ credDef,
+ credDefId,
+ issuerId,
+ "some_tag",
+ "CL_ACCUM",
+ 10,
+ null
+ );
+
+ ulong timeCreateRevStatusList = 12;
+ var revocationStatusList = RevocationStatusList.Create(
+ credDef,
+ revRegId,
+ revRegDef,
+ revRegPriv,
+ issuerId,
+ true,
+ timeCreateRevStatusList
+ );
+
+ var linkSecret = LinkSecret.Create();
+ var linkSecretId = "default";
+ var credOffer = CredentialOffer.Create(schemaId, credDefId, keyProof);
+ var (credReq, credReqMeta) = CredentialRequest.Create(
+ credDef,
+ linkSecret,
+ linkSecretId,
+ credOffer,
+ entropy
+ );
+
+ var credValues = JsonSerializer.Serialize(
+ new Dictionary
+ {
+ ["sex"] = "male",
+ ["name"] = "Alex",
+ ["height"] = "175",
+ ["age"] = "28",
+ }
+ );
+ var revConfig = new CredentialRevocationConfig
+ {
+ RevRegDef = revRegDef,
+ RevRegDefPrivate = revRegPriv,
+ RevStatusList = revocationStatusList,
+ RevRegIndex = revIdx,
+ };
+
+ var w3cCred = W3cCredential.Create(
+ credDef,
+ credDefPriv,
+ credOffer,
+ credReq,
+ credValues,
+ revConfig,
+ null
+ );
+
+ var processedW3c = w3cCred.Process(credReqMeta, linkSecret, credDef, revRegDef);
+
+ // Convert to legacy and back to ensure conversions work
+ var legacy = processedW3c.ToLegacy();
+ var w3cAgain = W3cCredential.FromLegacy(legacy, issuerId);
+
+ // Prepare verification artifacts
+ var timeAfterCreatingCred = timeCreateRevStatusList + 1;
+ var issuedRevStatusList = revocationStatusList.Update(
+ credDef,
+ revRegDef,
+ revRegPriv,
+ new[] { (ulong)revIdx },
+ null,
+ timeAfterCreatingCred
+ );
+
+ var nonce = AnonCreds.GenerateNonce();
+ var presReqObj = new
+ {
+ nonce,
+ name = "pres_req_1",
+ version = "0.1",
+ requested_attributes = new Dictionary
+ {
+ ["attr1_referent"] = new Dictionary
+ {
+ ["name"] = "name",
+ ["issuer_id"] = issuerId,
+ },
+ ["attr2_referent"] = new Dictionary
+ {
+ ["names"] = new[] { "name", "height" },
+ },
+ },
+ requested_predicates = new Dictionary
+ {
+ ["predicate1_referent"] = new Dictionary
+ {
+ ["name"] = "age",
+ ["p_type"] = ">=",
+ ["p_value"] = 18,
+ },
+ },
+ non_revoked = new Dictionary { ["from"] = 10, ["to"] = 200 },
+ };
+ var presReqJson = JsonSerializer.Serialize(presReqObj);
+ var presReq = PresentationRequest.FromJson(presReqJson);
+
+ // Build revocation state using the issued status list at the matching timestamp
+ var revState = RevocationState.Create(
+ revRegDef,
+ issuedRevStatusList,
+ revIdx,
+ revRegDef.TailsLocation
+ );
+
+ var credentialsJson = JsonSerializer.Serialize(
+ new[]
+ {
+ new
+ {
+ credential = processedW3c.ToJson(),
+ timestamp = timeAfterCreatingCred,
+ rev_state = revState.ToJson(),
+ },
+ }
+ );
+ var schemasJson = JsonSerializer.Serialize(
+ new Dictionary { [schemaId] = schema.ToJson() }
+ );
+ var credDefsJson = JsonSerializer.Serialize(
+ new Dictionary { [credDefId] = credDef.ToJson() }
+ );
+
+ var presentation = W3cPresentation.CreateFromJson(
+ presReq,
+ credentialsJson,
+ linkSecret,
+ schemasJson,
+ credDefsJson,
+ JsonSerializer.Serialize(new[] { schemaId }),
+ JsonSerializer.Serialize(new[] { credDefId }),
+ null
+ );
+
+ var revRegDefsJson = JsonSerializer.Serialize(
+ new Dictionary { [revRegId] = revRegDef.ToJson() }
+ );
+ var revRegDefIdsJson = JsonSerializer.Serialize(new[] { revRegId });
+
+ var isValid = presentation.Verify(
+ presReq,
+ schemasJson,
+ credDefsJson,
+ JsonSerializer.Serialize(new[] { schemaId }),
+ JsonSerializer.Serialize(new[] { credDefId }),
+ revRegDefsJson,
+ JsonSerializer.Serialize(new[] { issuedRevStatusList.ToJson() }),
+ revRegDefIdsJson,
+ null
+ );
+
+ Assert.True(isValid);
+
+ // Revoke and verify should fail
+ var timeRevoke = timeAfterCreatingCred + 1;
+ var revokedStatusList = issuedRevStatusList.Update(
+ credDef,
+ revRegDef,
+ revRegPriv,
+ null,
+ new[] { (ulong)revIdx },
+ timeRevoke
+ );
+
+ var isValidAfterRevoke = presentation.Verify(
+ presReq,
+ schemasJson,
+ credDefsJson,
+ JsonSerializer.Serialize(new[] { schemaId }),
+ JsonSerializer.Serialize(new[] { credDefId }),
+ revRegDefsJson,
+ JsonSerializer.Serialize(new[] { revokedStatusList.ToJson() }),
+ revRegDefIdsJson,
+ null
+ );
+ Assert.False(isValidAfterRevoke);
+ }
+
+ [Fact]
+ public void W3cNonRevocableCredential_Verifies()
+ {
+ var issuerId = "mock:uri";
+ var schemaId = "mock:uri";
+ var credDefId = "mock:uri";
+ var entropy = "entropy";
+
+ var schema = Schema.Create(
+ "schema name",
+ "1.0.0",
+ issuerId,
+ JsonSerializer.Serialize(new[] { "name", "age" })
+ );
+
+ var (credDef, credDefPriv, keyProof) = CredentialDefinition.Create(
+ schemaId,
+ issuerId,
+ schema,
+ "tag",
+ "CL",
+ "{\"support_revocation\": false}"
+ );
+
+ var linkSecret = LinkSecret.Create();
+ var linkSecretId = "default";
+ var offer = CredentialOffer.Create(schemaId, credDefId, keyProof);
+ var (req, reqMeta) = CredentialRequest.Create(
+ credDef,
+ linkSecret,
+ linkSecretId,
+ offer,
+ entropy
+ );
+
+ var values = JsonSerializer.Serialize(
+ new Dictionary { ["name"] = "Alex", ["age"] = "28" }
+ );
+
+ var w3cCred = W3cCredential.Create(credDef, credDefPriv, offer, req, values, null, null);
+ var processed = w3cCred.Process(reqMeta, linkSecret, credDef, null);
+
+ var nonce = AnonCreds.GenerateNonce();
+ var presReqObj = new
+ {
+ nonce,
+ name = "pres_req_1",
+ version = "0.1",
+ requested_attributes = new Dictionary
+ {
+ ["attr1_referent"] = new Dictionary { ["name"] = "name" },
+ },
+ requested_predicates = new Dictionary
+ {
+ ["predicate1_referent"] = new Dictionary
+ {
+ ["name"] = "age",
+ ["p_type"] = ">=",
+ ["p_value"] = 18,
+ },
+ },
+ };
+ var presReq = PresentationRequest.FromJson(JsonSerializer.Serialize(presReqObj));
+
+ var credentialsJson = JsonSerializer.Serialize(
+ new[] { new { credential = processed.ToJson() } }
+ );
+ var schemasJson = JsonSerializer.Serialize(
+ new Dictionary { [schemaId] = schema.ToJson() }
+ );
+ var credDefsJson = JsonSerializer.Serialize(
+ new Dictionary { [credDefId] = credDef.ToJson() }
+ );
+
+ var presentation = W3cPresentation.CreateFromJson(
+ presReq,
+ credentialsJson,
+ linkSecret,
+ schemasJson,
+ credDefsJson,
+ JsonSerializer.Serialize(new[] { schemaId }),
+ JsonSerializer.Serialize(new[] { credDefId }),
+ null
+ );
+
+ var isValid = presentation.Verify(
+ presReq,
+ schemasJson,
+ credDefsJson,
+ JsonSerializer.Serialize(new[] { schemaId }),
+ JsonSerializer.Serialize(new[] { credDefId }),
+ null,
+ null,
+ null,
+ null
+ );
+
+ Assert.True(isValid);
+ }
+
+ [Fact]
+ public void W3c_Verify_WithIntervalOverride()
+ {
+ var issuerId = "mock:uri";
+ var schemaId = "mock:uri";
+ var credDefId = "mock:uri";
+ var revRegId = "mock:uri:revregid";
+ var entropy = "entropy";
+ uint revIdx = 3;
+
+ var schema = Schema.Create(
+ "schema name",
+ "1.0.0",
+ issuerId,
+ JsonSerializer.Serialize(new[] { "name" })
+ );
+
+ var (credDef, credDefPriv, keyProof) = CredentialDefinition.Create(
+ schemaId,
+ issuerId,
+ schema,
+ "tag",
+ "CL",
+ "{\"support_revocation\": true}"
+ );
+
+ var (revRegDef, revRegPriv) = RevocationRegistryDefinition.Create(
+ credDef,
+ credDefId,
+ issuerId,
+ "some_tag",
+ "CL_ACCUM",
+ 10,
+ null
+ );
+
+ ulong t0 = 100;
+ var status0 = RevocationStatusList.Create(
+ credDef,
+ revRegId,
+ revRegDef,
+ revRegPriv,
+ issuerId,
+ true,
+ t0
+ );
+
+ var linkSecret = LinkSecret.Create();
+ var offer = CredentialOffer.Create(schemaId, credDefId, keyProof);
+ var (req, reqMeta) = CredentialRequest.Create(
+ credDef,
+ linkSecret,
+ "default",
+ offer,
+ entropy
+ );
+
+ var values = JsonSerializer.Serialize(new Dictionary { ["name"] = "Al" });
+ var revConfig = new CredentialRevocationConfig
+ {
+ RevRegDef = revRegDef,
+ RevRegDefPrivate = revRegPriv,
+ RevStatusList = status0,
+ RevRegIndex = revIdx,
+ };
+
+ var w3cCred = W3cCredential.Create(
+ credDef,
+ credDefPriv,
+ offer,
+ req,
+ values,
+ revConfig,
+ null
+ );
+ var processed = w3cCred.Process(reqMeta, linkSecret, credDef, revRegDef);
+
+ var t1 = t0 + 1;
+ var status1 = status0.Update(
+ credDef,
+ revRegDef,
+ revRegPriv,
+ new[] { (ulong)revIdx },
+ null,
+ t1
+ );
+
+ var revState = RevocationState.Create(revRegDef, status1, revIdx, revRegDef.TailsLocation);
+
+ var credentialsJson = JsonSerializer.Serialize(
+ new[]
+ {
+ new
+ {
+ credential = processed.ToJson(),
+ timestamp = t1,
+ rev_state = revState.ToJson(),
+ },
+ }
+ );
+
+ var schemasJson = JsonSerializer.Serialize(
+ new Dictionary { [schemaId] = schema.ToJson() }
+ );
+ var credDefsJson = JsonSerializer.Serialize(
+ new Dictionary { [credDefId] = credDef.ToJson() }
+ );
+
+ var nonce = AnonCreds.GenerateNonce();
+ var presReqObj = new
+ {
+ nonce,
+ name = "pr",
+ version = "0.1",
+ requested_attributes = new Dictionary
+ {
+ ["a1"] = new Dictionary { ["name"] = "name" },
+ },
+ non_revoked = new Dictionary
+ {
+ ["from"] = (int)t0,
+ ["to"] = (int)(t1 + 10),
+ },
+ };
+ var presReq = PresentationRequest.FromJson(JsonSerializer.Serialize(presReqObj));
+
+ var presentation = W3cPresentation.CreateFromJson(
+ presReq,
+ credentialsJson,
+ linkSecret,
+ schemasJson,
+ credDefsJson,
+ JsonSerializer.Serialize(new[] { schemaId }),
+ JsonSerializer.Serialize(new[] { credDefId }),
+ null
+ );
+
+ var overrides = JsonSerializer.Serialize(
+ new Dictionary>
+ {
+ [revRegId] = new Dictionary { [t0.ToString()] = (int)t1 },
+ }
+ );
+
+ var isValid = presentation.Verify(
+ presReq,
+ schemasJson,
+ credDefsJson,
+ JsonSerializer.Serialize(new[] { schemaId }),
+ JsonSerializer.Serialize(new[] { credDefId }),
+ JsonSerializer.Serialize(
+ new Dictionary { [revRegId] = revRegDef.ToJson() }
+ ),
+ JsonSerializer.Serialize(new[] { status1.ToJson() }),
+ JsonSerializer.Serialize(new[] { revRegId }),
+ overrides
+ );
+
+ Assert.True(isValid);
+ }
+}