From ccfadbf5a2fa1a6e50c6b32034ffaa376e71528e Mon Sep 17 00:00:00 2001 From: James Hancock Date: Tue, 2 Sep 2025 16:07:24 -0400 Subject: [PATCH 1/6] Updates to try and sort out the wrapper. --- README.md | 31 +- wrappers/dotnet/.gitignore | 484 ++++++++++++++++++ wrappers/dotnet/README.md | 17 + wrappers/dotnet/lib/AnonCredsClient.cs | 114 +++++ wrappers/dotnet/lib/AnonCredsNet.csproj | 108 ++++ .../lib/Exceptions/AnonCredsException.cs | 11 + .../dotnet/lib/Helpers/AnonCredsHelpers.cs | 129 +++++ wrappers/dotnet/lib/Interop/InteropTypes.cs | 69 +++ wrappers/dotnet/lib/Interop/NativeMethods.cs | 168 ++++++ .../dotnet/lib/Objects/AnonCredsObject.cs | 57 +++ wrappers/dotnet/lib/Objects/Credential.cs | 78 +++ .../lib/Objects/CredentialDefinition.cs | 56 ++ .../dotnet/lib/Objects/CredentialOffer.cs | 23 + .../dotnet/lib/Objects/KeyCorrectnessProof.cs | 10 + wrappers/dotnet/lib/Objects/LinkSecret.cs | 21 + wrappers/dotnet/lib/Objects/Presentation.cs | 69 +++ .../Objects/RevocationRegistryDefinition.cs | 10 + .../lib/Objects/RevocationRegistryPrivate.cs | 10 + .../lib/Objects/RevocationStatusList.cs | 108 ++++ .../lib/Objects/RevocationStatusListDelta.cs | 10 + .../dotnet/lib/Objects/RevokationState.cs | 71 +++ wrappers/dotnet/lib/Objects/Schema.cs | 32 ++ .../dotnet/lib/Requests/CredentialRequest.cs | 41 ++ .../lib/Requests/CredentialRequestMetadata.cs | 12 + .../lib/Requests/PresentationRequest.cs | 12 + .../dotnet/tests/AnonCredsNet.Tests.csproj | 45 ++ 26 files changed, 1781 insertions(+), 15 deletions(-) create mode 100644 wrappers/dotnet/.gitignore create mode 100644 wrappers/dotnet/README.md create mode 100644 wrappers/dotnet/lib/AnonCredsClient.cs create mode 100644 wrappers/dotnet/lib/AnonCredsNet.csproj create mode 100644 wrappers/dotnet/lib/Exceptions/AnonCredsException.cs create mode 100644 wrappers/dotnet/lib/Helpers/AnonCredsHelpers.cs create mode 100644 wrappers/dotnet/lib/Interop/InteropTypes.cs create mode 100644 wrappers/dotnet/lib/Interop/NativeMethods.cs create mode 100644 wrappers/dotnet/lib/Objects/AnonCredsObject.cs create mode 100644 wrappers/dotnet/lib/Objects/Credential.cs create mode 100644 wrappers/dotnet/lib/Objects/CredentialDefinition.cs create mode 100644 wrappers/dotnet/lib/Objects/CredentialOffer.cs create mode 100644 wrappers/dotnet/lib/Objects/KeyCorrectnessProof.cs create mode 100644 wrappers/dotnet/lib/Objects/LinkSecret.cs create mode 100644 wrappers/dotnet/lib/Objects/Presentation.cs create mode 100644 wrappers/dotnet/lib/Objects/RevocationRegistryDefinition.cs create mode 100644 wrappers/dotnet/lib/Objects/RevocationRegistryPrivate.cs create mode 100644 wrappers/dotnet/lib/Objects/RevocationStatusList.cs create mode 100644 wrappers/dotnet/lib/Objects/RevocationStatusListDelta.cs create mode 100644 wrappers/dotnet/lib/Objects/RevokationState.cs create mode 100644 wrappers/dotnet/lib/Objects/Schema.cs create mode 100644 wrappers/dotnet/lib/Requests/CredentialRequest.cs create mode 100644 wrappers/dotnet/lib/Requests/CredentialRequestMetadata.cs create mode 100644 wrappers/dotnet/lib/Requests/PresentationRequest.cs create mode 100644 wrappers/dotnet/tests/AnonCredsNet.Tests.csproj 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/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/AnonCredsClient.cs b/wrappers/dotnet/lib/AnonCredsClient.cs new file mode 100644 index 00000000..7014648e --- /dev/null +++ b/wrappers/dotnet/lib/AnonCredsClient.cs @@ -0,0 +1,114 @@ +// AnonCreds.cs +using AnonCredsNet.Helpers; +using AnonCredsNet.Objects; +using AnonCredsNet.Requests; + +namespace AnonCredsNet; + +public class AnonCredsClient +{ + public AnonCredsClient() + { + // Placeholder for initialization if needed + } + + public Presentation CreatePresentation( + PresentationRequest presReq, + string credentialsJson, + string? selfAttestJson, + LinkSecret linkSecret, + string schemasJson, + string credDefsJson + ) + { + if ( + presReq == null + || string.IsNullOrEmpty(credentialsJson) + || linkSecret == null + || string.IsNullOrEmpty(schemasJson) + || string.IsNullOrEmpty(credDefsJson) + ) + throw new ArgumentNullException("Required parameters cannot be null or empty"); + if ( + credentialsJson.Length > 100000 + || schemasJson.Length > 100000 + || credDefsJson.Length > 100000 + ) + throw new ArgumentException("JSON input too large"); + string presReqJson = presReq.ToJson(); + return Presentation.Create( + presReqJson, + credentialsJson, + selfAttestJson, + linkSecret, + schemasJson, + credDefsJson + ); + } + + public bool VerifyPresentation( + Presentation presentation, + PresentationRequest presReq, + string schemasJson, + string credDefsJson, + string? revRegDefsJson, + string? revStatusListsJson, + string? nonRevocJson + ) + { + if ( + presentation == null + || presReq == null + || string.IsNullOrEmpty(schemasJson) + || string.IsNullOrEmpty(credDefsJson) + ) + throw new ArgumentNullException("Required parameters cannot be null or empty"); + if (schemasJson.Length > 100000 || credDefsJson.Length > 100000) + throw new ArgumentException("JSON input too large"); + + return AnonCredsHelpers.VerifyPresentation( + presentation, + presReq, + schemasJson, + credDefsJson, + revRegDefsJson, + revStatusListsJson, + nonRevocJson + ); + } + + // Add more higher-level methods if needed, e.g., IssueCredential flow + public Credential IssueCredential( + CredentialDefinition credDef, + CredentialDefinitionPrivate credDefPvt, + CredentialOffer offer, + CredentialRequest request, + string credValues, + string? revRegId, + string? tailsPath, + RevocationStatusList? revStatusList + ) + { + if ( + credDef == null + || credDefPvt == null + || offer == null + || request == null + || string.IsNullOrEmpty(credValues) + ) + throw new ArgumentNullException("Required parameters cannot be null or empty"); + if (credValues.Length > 100000) + throw new ArgumentException("Credential values JSON too large"); + var (credential, _) = Credential.Create( + credDef, + credDefPvt, + offer, + request, + credValues, + revRegId, + tailsPath, + revStatusList + ); + return credential; + } +} diff --git a/wrappers/dotnet/lib/AnonCredsNet.csproj b/wrappers/dotnet/lib/AnonCredsNet.csproj new file mode 100644 index 00000000..bfc86785 --- /dev/null +++ b/wrappers/dotnet/lib/AnonCredsNet.csproj @@ -0,0 +1,108 @@ + + + net9.0 + enable + enable + true + true + true + + + + + + + + + + <_WinX64Dll Include="../../../target/x86_64-pc-windows-msvc/release/anoncreds.dll" + Condition="Exists('../../../target/x86_64-pc-windows-msvc/release/anoncreds.dll')" /> + <_WinX64Pdb Include="../../../target/x86_64-pc-windows-msvc/release/anoncreds.pdb" + Condition="Exists('../../../target/x86_64-pc-windows-msvc/release/anoncreds.pdb')" /> + + <_WinArm64Dll Include="../../../target/aarch64-pc-windows-msvc/release/anoncreds.dll" + Condition="Exists('../../../target/aarch64-pc-windows-msvc/release/anoncreds.dll')" /> + <_WinArm64Pdb Include="../../../target/aarch64-pc-windows-msvc/release/anoncreds.pdb" + Condition="Exists('../../../target/aarch64-pc-windows-msvc/release/anoncreds.pdb')" /> + + <_LinuxX64So Include="../../../target/x86_64-unknown-linux-gnu/release/libanoncreds.so" + Condition="Exists('../../../target/x86_64-unknown-linux-gnu/release/libanoncreds.so')" /> + + <_MacX64Dylib Include="../../../target/x86_64-apple-darwin/release/libanoncreds.dylib" + Condition="Exists('../../../target/x86_64-apple-darwin/release/libanoncreds.dylib')" /> + + <_MacArm64Dylib Include="../../../target/aarch64-apple-darwin/release/libanoncreds.dylib" + Condition="Exists('../../../target/aarch64-apple-darwin/release/libanoncreds.dylib')" /> + + <_HostDll Include="../../../target/release/anoncreds.dll" + Condition="Exists('../../../target/release/anoncreds.dll') And '@(_WinX64Dll)' == '' And '@(_WinArm64Dll)' == ''" /> + <_HostPdb Include="../../../target/release/anoncreds.pdb" + Condition="Exists('../../../target/release/anoncreds.pdb') And '@(_WinX64Pdb)' == '' And '@(_WinArm64Pdb)' == ''" /> + <_HostSo Include="../../../target/release/libanoncreds.so" + Condition="Exists('../../../target/release/libanoncreds.so') And '@(_LinuxX64So)' == ''" /> + <_HostDylib Include="../../../target/release/libanoncreds.dylib" + Condition="Exists('../../../target/release/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/Helpers/AnonCredsHelpers.cs b/wrappers/dotnet/lib/Helpers/AnonCredsHelpers.cs new file mode 100644 index 00000000..604d4593 --- /dev/null +++ b/wrappers/dotnet/lib/Helpers/AnonCredsHelpers.cs @@ -0,0 +1,129 @@ +using System.Runtime.InteropServices; +using System.Text.Json; +using AnonCredsNet.Exceptions; +using AnonCredsNet.Interop; +using AnonCredsNet.Objects; +using AnonCredsNet.Requests; + +namespace AnonCredsNet.Helpers; + +internal static class AnonCredsHelpers +{ + private static bool _initialized; + + internal static void Initialize() + { + if (_initialized) + return; + // No explicit initialization in anoncreds-rs FFI, but placeholder for future use + _initialized = true; + } + + 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; + } + + internal 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 FfiList CreateFfiList(string json, Func fromJson) + where T : AnonCredsObject + { + var items = + JsonSerializer.Deserialize(json) + ?? throw new InvalidOperationException("Invalid JSON array"); + var handles = new int[items.Length]; + for (var i = 0; i < items.Length; i++) + { + var item = fromJson(items[i]); + handles[i] = item.Handle; + item.Dispose(); // Dispose after capturing handle + } + var ptr = Marshal.AllocHGlobal(handles.Length * sizeof(int)); + Marshal.Copy(handles, 0, ptr, handles.Length); + return new FfiList { Handles = ptr, Count = handles.Length }; + } + + internal static void FreeFfiList(FfiList list) + { + if (list.Handles != IntPtr.Zero) + Marshal.FreeHGlobal(list.Handles); + } + + /// + /// Verifies a presentation. Ensure the and are disposed after use. + /// + public static bool VerifyPresentation( + Presentation presentation, + PresentationRequest presReq, + string schemasJson, + string credDefsJson, + string? revRegDefsJson, + string? revStatusListsJson, + string? nonRevocJson + ) + { + if ( + presentation == null + || presReq == null + || string.IsNullOrEmpty(schemasJson) + || string.IsNullOrEmpty(credDefsJson) + ) + throw new ArgumentNullException("Required parameters cannot be null or empty"); + if (presentation.Handle == 0 || presReq.Handle == 0) + throw new ObjectDisposedException("Presentation or PresentationRequest is disposed"); + if (schemasJson.Length > 100000 || credDefsJson.Length > 100000) + throw new ArgumentException("JSON input too large"); + + var schemasList = CreateFfiList(schemasJson, Schema.FromJson); + var credDefsList = CreateFfiList(credDefsJson, CredentialDefinition.FromJson); + var revRegDefsList = string.IsNullOrEmpty(revRegDefsJson) + ? new FfiList() + : CreateFfiList(revRegDefsJson, RevocationRegistryDefinition.FromJson); + var revStatusLists = string.IsNullOrEmpty(revStatusListsJson) + ? new FfiList() + : CreateFfiList(revStatusListsJson, RevocationStatusList.FromJson); + var nonRevocList = new FfiList(); // Empty list, as non-revocation proof is not supported + + try + { + var code = NativeMethods.anoncreds_verify_presentation( + presentation.Handle, + presReq.Handle, + schemasList, + credDefsList, + revRegDefsList, + revStatusLists, + nonRevocList, + out var valid + ); + if (code != ErrorCode.Success) + throw new AnonCredsException(code, GetCurrentError()); + return valid; + } + finally + { + FreeFfiList(schemasList); + FreeFfiList(credDefsList); + FreeFfiList(revRegDefsList); + FreeFfiList(revStatusLists); + FreeFfiList(nonRevocList); + } + } +} diff --git a/wrappers/dotnet/lib/Interop/InteropTypes.cs b/wrappers/dotnet/lib/Interop/InteropTypes.cs new file mode 100644 index 00000000..678b77fa --- /dev/null +++ b/wrappers/dotnet/lib/Interop/InteropTypes.cs @@ -0,0 +1,69 @@ +// 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)] +internal struct ObjectHandle +{ + public int Value { get; } + + public ObjectHandle(int value) => Value = value; + + public static implicit operator int(ObjectHandle h) => h.Value; + + public static implicit operator ObjectHandle(int v) => new(v); +} + +[StructLayout(LayoutKind.Sequential)] +internal struct ByteBuffer +{ + public int Len; + public IntPtr Data; +} + +[StructLayout(LayoutKind.Sequential)] +public struct FfiList +{ + public IntPtr Handles; // Pointer to array of handles + public int Count; +} + +// 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 AnoncredsCredentialRevocationInfo +{ + public IntPtr Json; // Pointer to JSON string +} diff --git a/wrappers/dotnet/lib/Interop/NativeMethods.cs b/wrappers/dotnet/lib/Interop/NativeMethods.cs new file mode 100644 index 00000000..43586241 --- /dev/null +++ b/wrappers/dotnet/lib/Interop/NativeMethods.cs @@ -0,0 +1,168 @@ +// 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 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(int handle); + + [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)] + internal static partial ErrorCode anoncreds_object_get_json(int handle, out IntPtr json); + + [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)] + internal static partial ErrorCode anoncreds_object_from_json(string json, out int 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 issuerId, + string name, + string version, + string attrNames, + out int handle + ); + + [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)] + internal static partial ErrorCode anoncreds_create_credential_definition( + string issuerId, + int schema, + string tag, + string sigType, + string config, + out int credDef, + out int credDefPvt, + out int keyProof + ); + + [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)] + internal static partial ErrorCode anoncreds_create_credential_offer(int credDef, out int offer); + + [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)] + internal static partial ErrorCode anoncreds_create_link_secret(out int linkSecret); + + [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)] + internal static partial ErrorCode anoncreds_create_credential_request( + int credDef, + int linkSecret, + string linkSecretId, + int credOffer, + out int request, + out int metadata + ); + + [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)] + internal static partial ErrorCode anoncreds_create_credential( + int credDef, + int credDefPvt, + int credOffer, + int credRequest, + string credValues, + string revRegId, + string tailsPath, + int revStatusList, + out int credential, + out int revStatusListDelta + ); + + [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)] + internal static partial ErrorCode anoncreds_process_credential( + int credential, + int requestMetadata, + int linkSecret, + int credDef, + int revRegDef, + out int processedCredential + ); + + [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)] + internal static partial ErrorCode anoncreds_create_presentation( + int presReq, + FfiList credentials, + string selfAttestJson, + int linkSecret, + FfiList schemas, + FfiList credDefs, + out int presentation + ); + + [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)] + internal static partial ErrorCode anoncreds_verify_presentation( + int presentation, + int presReq, + FfiList schemas, + FfiList credDefs, + FfiList revRegDefs, + FfiList revStatusLists, + FfiList nonRevoc, + [MarshalAs(UnmanagedType.U1)] out bool isValid + ); + + [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)] + internal static partial ErrorCode anoncreds_create_revocation_registry_def( + int credDef, + string issuerId, + string tag, + string revType, + string config, + string tailsPath, + out int revRegDef, + out int revRegPvt, + out int revStatusList + ); + + [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)] + internal static partial ErrorCode anoncreds_create_revocation_status_list( + string issuerId, + int revRegDef, + string timestamp, + [MarshalAs(UnmanagedType.U1)] bool issued, + [MarshalAs(UnmanagedType.U1)] bool revoked, + string tailsPath, + out int statusList + ); + + [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)] + internal static partial ErrorCode anoncreds_update_revocation_status_list( + int statusList, + string issuedJson, + string revokedJson, + string timestamp, + out int updatedList, + out int delta + ); + + [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)] + internal static partial ErrorCode anoncreds_create_revocation_state( + AnoncredsCredentialRevocationInfo credRevInfo, + int revRegDef, + int statusList, + string timestamp, + string tailsPath, + out int revState + ); + + [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)] + internal static partial ErrorCode anoncreds_update_revocation_state( + int revState, + int revRegDef, + int statusListDelta, + string timestamp, + string tailsPath, + out int updatedState + ); +} diff --git a/wrappers/dotnet/lib/Objects/AnonCredsObject.cs b/wrappers/dotnet/lib/Objects/AnonCredsObject.cs new file mode 100644 index 00000000..9a2e1692 --- /dev/null +++ b/wrappers/dotnet/lib/Objects/AnonCredsObject.cs @@ -0,0 +1,57 @@ +using System.Runtime.InteropServices; +using AnonCredsNet.Exceptions; +using AnonCredsNet.Helpers; +using AnonCredsNet.Interop; + +namespace AnonCredsNet.Objects; + +public abstract class AnonCredsObject : IDisposable +{ + internal int Handle { get; private set; } + + protected AnonCredsObject(int 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 ptr); + if (code != ErrorCode.Success) + throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); + var json = Marshal.PtrToStringUTF8(ptr) ?? throw new InvalidOperationException("Null JSON"); + NativeMethods.anoncreds_string_free(ptr); + return json; + } + + protected static T FromJson(string json) + where T : AnonCredsObject + { + if (string.IsNullOrEmpty(json)) + throw new ArgumentNullException(nameof(json)); + var code = NativeMethods.anoncreds_object_from_json(json, out var handle); + if (code != ErrorCode.Success) + throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); + return (T)Activator.CreateInstance(typeof(T), handle)!; + } + + 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/Objects/Credential.cs b/wrappers/dotnet/lib/Objects/Credential.cs new file mode 100644 index 00000000..1035780b --- /dev/null +++ b/wrappers/dotnet/lib/Objects/Credential.cs @@ -0,0 +1,78 @@ +using AnonCredsNet.Exceptions; +using AnonCredsNet.Helpers; +using AnonCredsNet.Interop; +using AnonCredsNet.Requests; + +namespace AnonCredsNet.Objects; + +public sealed class Credential : AnonCredsObject +{ + private Credential(int handle) + : base(handle) { } + + /// + /// Creates a credential and its revocation delta. Both returned objects must be disposed using using statements. + /// + internal static (Credential Credential, RevocationStatusListDelta Delta) Create( + CredentialDefinition credDef, + CredentialDefinitionPrivate credDefPvt, + CredentialOffer offer, + CredentialRequest request, + string credValues, + string? revRegId, + string? tailsPath, + RevocationStatusList? revStatusList + ) + { + if ( + credDef == null + || credDefPvt == null + || offer == null + || request == null + || string.IsNullOrEmpty(credValues) + ) + throw new ArgumentNullException("Input parameters cannot be null or empty"); + var code = NativeMethods.anoncreds_create_credential( + credDef.Handle, + credDefPvt.Handle, + offer.Handle, + request.Handle, + credValues, + revRegId ?? "", + tailsPath ?? "", + revStatusList?.Handle ?? 0, + out var cred, + out var delta + ); + if (code != ErrorCode.Success) + throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); + return (new Credential(cred), new RevocationStatusListDelta(delta)); + } + + internal static Credential Process( + Credential credential, + CredentialRequestMetadata metadata, + LinkSecret linkSecret, + CredentialDefinition credDef, + RevocationRegistryDefinition? revRegDef + ) + { + if (credential == null || metadata == null || linkSecret == null || credDef == null) + throw new ArgumentNullException("Input parameters cannot be null"); + if (credential.Handle == 0) + throw new ObjectDisposedException(nameof(Credential)); + var code = NativeMethods.anoncreds_process_credential( + credential.Handle, + metadata.Handle, + linkSecret.Handle, + credDef.Handle, + revRegDef?.Handle ?? 0, + out var processed + ); + if (code != ErrorCode.Success) + throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); + return new Credential(processed); + } + + internal static Credential FromJson(string json) => FromJson(json); +} diff --git a/wrappers/dotnet/lib/Objects/CredentialDefinition.cs b/wrappers/dotnet/lib/Objects/CredentialDefinition.cs new file mode 100644 index 00000000..563818f0 --- /dev/null +++ b/wrappers/dotnet/lib/Objects/CredentialDefinition.cs @@ -0,0 +1,56 @@ +using AnonCredsNet.Exceptions; +using AnonCredsNet.Helpers; +using AnonCredsNet.Interop; + +namespace AnonCredsNet.Objects; + +public class CredentialDefinition : AnonCredsObject +{ + private CredentialDefinition(int handle) + : base(handle) { } + + internal static ( + CredentialDefinition CredDef, + CredentialDefinitionPrivate CredDefPvt, + KeyCorrectnessProof KeyProof + ) Create(string issuerId, Schema schema, string tag, string sigType, string config) + { + if ( + string.IsNullOrEmpty(issuerId) + || schema == null + || string.IsNullOrEmpty(tag) + || string.IsNullOrEmpty(sigType) + || string.IsNullOrEmpty(config) + ) + throw new ArgumentNullException("Input parameters cannot be null or empty"); + var code = NativeMethods.anoncreds_create_credential_definition( + issuerId, + schema.Handle, + tag, + sigType, + config, + out var cd, + out var pvt, + out var proof + ); + if (code != ErrorCode.Success) + throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); + return ( + new CredentialDefinition(cd), + new CredentialDefinitionPrivate(pvt), + new KeyCorrectnessProof(proof) + ); + } + + internal static CredentialDefinition FromJson(string json) => + FromJson(json); +} + +public class CredentialDefinitionPrivate : AnonCredsObject +{ + internal CredentialDefinitionPrivate(int handle) + : base(handle) { } + + internal static CredentialDefinitionPrivate FromJson(string json) => + FromJson(json); +} diff --git a/wrappers/dotnet/lib/Objects/CredentialOffer.cs b/wrappers/dotnet/lib/Objects/CredentialOffer.cs new file mode 100644 index 00000000..538919f0 --- /dev/null +++ b/wrappers/dotnet/lib/Objects/CredentialOffer.cs @@ -0,0 +1,23 @@ +using AnonCredsNet.Exceptions; +using AnonCredsNet.Helpers; +using AnonCredsNet.Interop; + +namespace AnonCredsNet.Objects; + +public class CredentialOffer : AnonCredsObject +{ + private CredentialOffer(int handle) + : base(handle) { } + + internal static CredentialOffer Create(CredentialDefinition credDef) + { + if (credDef == null) + throw new ArgumentNullException(nameof(credDef)); + var code = NativeMethods.anoncreds_create_credential_offer(credDef.Handle, out var handle); + if (code != ErrorCode.Success) + throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); + return new CredentialOffer(handle); + } + + internal static CredentialOffer FromJson(string json) => FromJson(json); +} diff --git a/wrappers/dotnet/lib/Objects/KeyCorrectnessProof.cs b/wrappers/dotnet/lib/Objects/KeyCorrectnessProof.cs new file mode 100644 index 00000000..cfce2c0d --- /dev/null +++ b/wrappers/dotnet/lib/Objects/KeyCorrectnessProof.cs @@ -0,0 +1,10 @@ +namespace AnonCredsNet.Objects; + +public class KeyCorrectnessProof : AnonCredsObject +{ + internal KeyCorrectnessProof(int handle) + : base(handle) { } + + internal static KeyCorrectnessProof FromJson(string json) => + FromJson(json); +} diff --git a/wrappers/dotnet/lib/Objects/LinkSecret.cs b/wrappers/dotnet/lib/Objects/LinkSecret.cs new file mode 100644 index 00000000..8c093f31 --- /dev/null +++ b/wrappers/dotnet/lib/Objects/LinkSecret.cs @@ -0,0 +1,21 @@ +using AnonCredsNet.Exceptions; +using AnonCredsNet.Helpers; +using AnonCredsNet.Interop; + +namespace AnonCredsNet.Objects; + +public sealed class LinkSecret : AnonCredsObject +{ + private LinkSecret(int handle) + : base(handle) { } + + internal static LinkSecret Create() + { + var code = NativeMethods.anoncreds_create_link_secret(out var handle); + if (code != ErrorCode.Success) + throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); + return new LinkSecret(handle); + } + + internal static LinkSecret FromJson(string json) => FromJson(json); +} diff --git a/wrappers/dotnet/lib/Objects/Presentation.cs b/wrappers/dotnet/lib/Objects/Presentation.cs new file mode 100644 index 00000000..c8174d61 --- /dev/null +++ b/wrappers/dotnet/lib/Objects/Presentation.cs @@ -0,0 +1,69 @@ +using AnonCredsNet.Exceptions; +using AnonCredsNet.Helpers; +using AnonCredsNet.Interop; +using AnonCredsNet.Requests; + +namespace AnonCredsNet.Objects; + +public sealed class Presentation : AnonCredsObject +{ + private Presentation(int handle) + : base(handle) { } + + /// + /// Creates a presentation. The returned object must be disposed using a using statement. + /// + public static Presentation Create( + string presReqJson, + string credentialsJson, + string? selfAttestJson, + LinkSecret linkSecret, + string schemasJson, + string credDefsJson + ) + { + if ( + string.IsNullOrEmpty(presReqJson) + || string.IsNullOrEmpty(credentialsJson) + || linkSecret == null + || string.IsNullOrEmpty(schemasJson) + || string.IsNullOrEmpty(credDefsJson) + ) + throw new ArgumentNullException("Input parameters cannot be null or empty"); + if (linkSecret.Handle == 0) + throw new ObjectDisposedException(nameof(LinkSecret)); + + var presReq = PresentationRequest.FromJson(presReqJson); + var schemasList = AnonCredsHelpers.CreateFfiList(schemasJson, Schema.FromJson); + var credDefsList = AnonCredsHelpers.CreateFfiList( + credDefsJson, + CredentialDefinition.FromJson + ); + var credentialsList = AnonCredsHelpers.CreateFfiList(credentialsJson, Credential.FromJson); + + try + { + var code = NativeMethods.anoncreds_create_presentation( + presReq.Handle, + credentialsList, + selfAttestJson ?? "{}", + linkSecret.Handle, + schemasList, + credDefsList, + out var handle + ); + if (code != ErrorCode.Success) + throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); + return new Presentation(handle); + } + finally + { + presReq.Dispose(); + AnonCredsHelpers.FreeFfiList(schemasList); + AnonCredsHelpers.FreeFfiList(credDefsList); + AnonCredsHelpers.FreeFfiList(credentialsList); + } + } + + public static Presentation FromJson(string json) => FromJson(json); +} diff --git a/wrappers/dotnet/lib/Objects/RevocationRegistryDefinition.cs b/wrappers/dotnet/lib/Objects/RevocationRegistryDefinition.cs new file mode 100644 index 00000000..edf2467d --- /dev/null +++ b/wrappers/dotnet/lib/Objects/RevocationRegistryDefinition.cs @@ -0,0 +1,10 @@ +namespace AnonCredsNet.Objects; + +public class RevocationRegistryDefinition : AnonCredsObject +{ + internal RevocationRegistryDefinition(int handle) + : base(handle) { } + + public static RevocationRegistryDefinition FromJson(string json) => + FromJson(json); +} diff --git a/wrappers/dotnet/lib/Objects/RevocationRegistryPrivate.cs b/wrappers/dotnet/lib/Objects/RevocationRegistryPrivate.cs new file mode 100644 index 00000000..6169966a --- /dev/null +++ b/wrappers/dotnet/lib/Objects/RevocationRegistryPrivate.cs @@ -0,0 +1,10 @@ +namespace AnonCredsNet.Objects; + +public class RevocationRegistryPrivate : AnonCredsObject +{ + internal RevocationRegistryPrivate(int handle) + : base(handle) { } + + public static RevocationRegistryPrivate FromJson(string json) => + FromJson(json); +} diff --git a/wrappers/dotnet/lib/Objects/RevocationStatusList.cs b/wrappers/dotnet/lib/Objects/RevocationStatusList.cs new file mode 100644 index 00000000..346706ef --- /dev/null +++ b/wrappers/dotnet/lib/Objects/RevocationStatusList.cs @@ -0,0 +1,108 @@ +using AnonCredsNet.Exceptions; +using AnonCredsNet.Helpers; +using AnonCredsNet.Interop; + +namespace AnonCredsNet.Objects; + +public class RevocationStatusList : AnonCredsObject +{ + private RevocationStatusList(int handle) + : base(handle) { } + + public static ( + RevocationRegistryDefinition RevRegDef, + RevocationRegistryPrivate RevRegPvt, + RevocationStatusList StatusList + ) CreateRevocationRegistryDefinition( + CredentialDefinition credDef, + string issuerId, + string tag, + string revType, + string config, + string tailsPath + ) + { + if ( + credDef == null + || string.IsNullOrEmpty(issuerId) + || string.IsNullOrEmpty(tag) + || string.IsNullOrEmpty(revType) + || string.IsNullOrEmpty(config) + || string.IsNullOrEmpty(tailsPath) + ) + throw new ArgumentNullException("Input parameters cannot be null or empty"); + var code = NativeMethods.anoncreds_create_revocation_registry_def( + credDef.Handle, + issuerId, + tag, + revType, + config, + tailsPath, + out var def, + out var pvt, + out var list + ); + if (code != ErrorCode.Success) + throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); + return ( + new RevocationRegistryDefinition(def), + new RevocationRegistryPrivate(pvt), + new RevocationStatusList(list) + ); + } + + public static RevocationStatusList Create( + string issuerId, + RevocationRegistryDefinition revRegDef, + string timestamp, + bool issued, + bool revoked, + string tailsPath + ) + { + if ( + string.IsNullOrEmpty(issuerId) + || revRegDef == null + || string.IsNullOrEmpty(timestamp) + || string.IsNullOrEmpty(tailsPath) + ) + throw new ArgumentNullException("Input parameters cannot be null or empty"); + var code = NativeMethods.anoncreds_create_revocation_status_list( + issuerId, + revRegDef.Handle, + timestamp, + issued, + revoked, + tailsPath, + out var handle + ); + if (code != ErrorCode.Success) + throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); + return new RevocationStatusList(handle); + } + + public static (RevocationStatusList UpdatedList, RevocationStatusListDelta Delta) Update( + RevocationStatusList statusList, + string? issuedJson, + string? revokedJson, + string timestamp + ) + { + if (statusList == null || string.IsNullOrEmpty(timestamp)) + throw new ArgumentNullException("Input parameters cannot be null or empty"); + var code = NativeMethods.anoncreds_update_revocation_status_list( + statusList.Handle, + issuedJson ?? "{}", + revokedJson ?? "{}", + timestamp, + out var updated, + out var delta + ); + if (code != ErrorCode.Success) + throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); + return (new RevocationStatusList(updated), new RevocationStatusListDelta(delta)); + } + + public static RevocationStatusList FromJson(string json) => + FromJson(json); +} diff --git a/wrappers/dotnet/lib/Objects/RevocationStatusListDelta.cs b/wrappers/dotnet/lib/Objects/RevocationStatusListDelta.cs new file mode 100644 index 00000000..8326a879 --- /dev/null +++ b/wrappers/dotnet/lib/Objects/RevocationStatusListDelta.cs @@ -0,0 +1,10 @@ +namespace AnonCredsNet.Objects; + +public class RevocationStatusListDelta : AnonCredsObject +{ + internal RevocationStatusListDelta(int handle) + : base(handle) { } + + public static RevocationStatusListDelta FromJson(string json) => + FromJson(json); +} diff --git a/wrappers/dotnet/lib/Objects/RevokationState.cs b/wrappers/dotnet/lib/Objects/RevokationState.cs new file mode 100644 index 00000000..7ae50af8 --- /dev/null +++ b/wrappers/dotnet/lib/Objects/RevokationState.cs @@ -0,0 +1,71 @@ +using AnonCredsNet.Exceptions; +using AnonCredsNet.Helpers; +using AnonCredsNet.Interop; + +namespace AnonCredsNet.Objects; + +public class RevocationState : AnonCredsObject +{ + private RevocationState(int handle) + : base(handle) { } + + public static RevocationState Create( + int credRevInfo, + RevocationRegistryDefinition revRegDef, + RevocationStatusList statusList, + string timestamp, + string tailsPath + ) + { + if ( + credRevInfo == 0 + || revRegDef == null + || statusList == null + || string.IsNullOrEmpty(timestamp) + || string.IsNullOrEmpty(tailsPath) + ) + throw new ArgumentNullException("Input parameters cannot be null or empty"); + var code = NativeMethods.anoncreds_create_revocation_state( + credRevInfo, + revRegDef.Handle, + statusList.Handle, + timestamp, + tailsPath, + out var handle + ); + if (code != ErrorCode.Success) + throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); + return new RevocationState(handle); + } + + public static RevocationState Update( + RevocationState revState, + RevocationRegistryDefinition revRegDef, + RevocationStatusListDelta delta, + string timestamp, + string tailsPath + ) + { + if ( + revState == null + || revRegDef == null + || delta == null + || string.IsNullOrEmpty(timestamp) + || string.IsNullOrEmpty(tailsPath) + ) + throw new ArgumentNullException("Input parameters cannot be null or empty"); + var code = NativeMethods.anoncreds_update_revocation_state( + revState.Handle, + revRegDef.Handle, + delta.Handle, + timestamp, + tailsPath, + out var updated + ); + if (code != ErrorCode.Success) + throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); + return new RevocationState(updated); + } + + public static RevocationState FromJson(string json) => FromJson(json); +} diff --git a/wrappers/dotnet/lib/Objects/Schema.cs b/wrappers/dotnet/lib/Objects/Schema.cs new file mode 100644 index 00000000..3e0e1d9b --- /dev/null +++ b/wrappers/dotnet/lib/Objects/Schema.cs @@ -0,0 +1,32 @@ +using AnonCredsNet.Exceptions; +using AnonCredsNet.Helpers; +using AnonCredsNet.Interop; + +namespace AnonCredsNet.Objects; + +public class Schema : AnonCredsObject +{ + private Schema(int handle) + : base(handle) { } + + internal static Schema Create( + string issuerId, + string name, + string version, + string attrNamesJson + ) + { + var code = NativeMethods.anoncreds_create_schema( + issuerId, + name, + version, + attrNamesJson, + out var handle + ); + if (code != ErrorCode.Success) + throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); + return new Schema(handle); + } + + internal static Schema FromJson(string json) => FromJson(json); +} diff --git a/wrappers/dotnet/lib/Requests/CredentialRequest.cs b/wrappers/dotnet/lib/Requests/CredentialRequest.cs new file mode 100644 index 00000000..ea5705e9 --- /dev/null +++ b/wrappers/dotnet/lib/Requests/CredentialRequest.cs @@ -0,0 +1,41 @@ +using AnonCredsNet.Exceptions; +using AnonCredsNet.Helpers; +using AnonCredsNet.Interop; +using AnonCredsNet.Objects; + +namespace AnonCredsNet.Requests; + +public class CredentialRequest : AnonCredsObject +{ + private CredentialRequest(int handle) + : base(handle) { } + + internal static (CredentialRequest Request, CredentialRequestMetadata Metadata) Create( + CredentialDefinition credDef, + LinkSecret linkSecret, + string linkSecretId, + CredentialOffer credOffer + ) + { + if ( + credDef == null + || linkSecret == null + || string.IsNullOrEmpty(linkSecretId) + || credOffer == null + ) + throw new ArgumentNullException("Input parameters cannot be null or empty"); + var code = NativeMethods.anoncreds_create_credential_request( + credDef.Handle, + linkSecret.Handle, + linkSecretId, + credOffer.Handle, + out var req, + out var meta + ); + if (code != ErrorCode.Success) + throw new AnonCredsException(code, AnonCredsHelpers.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..b8339565 --- /dev/null +++ b/wrappers/dotnet/lib/Requests/CredentialRequestMetadata.cs @@ -0,0 +1,12 @@ +using AnonCredsNet.Objects; + +namespace AnonCredsNet.Requests; + +public class CredentialRequestMetadata : AnonCredsObject +{ + internal CredentialRequestMetadata(int 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..8be0240e --- /dev/null +++ b/wrappers/dotnet/lib/Requests/PresentationRequest.cs @@ -0,0 +1,12 @@ +using AnonCredsNet.Objects; + +namespace AnonCredsNet.Requests; + +public sealed class PresentationRequest : AnonCredsObject +{ + internal PresentationRequest(int handle) + : base(handle) { } + + internal 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 From e2d58336c82885a4b7bf372ae9c2ca80389a56c3 Mon Sep 17 00:00:00 2001 From: James Hancock Date: Mon, 8 Sep 2025 13:23:38 -0400 Subject: [PATCH 2/6] C# Wrapper Implementation --- .cargo/config.toml | 11 + src/ffi/presentation.rs | 75 +- src/services/verifier.rs | 59 ++ wrappers/dotnet/lib/AnonCreds.cs | 652 +++++++++++++++++ wrappers/dotnet/lib/AnonCredsClient.cs | 662 +++++++++++++++++- wrappers/dotnet/lib/AnonCredsNet.csproj | 50 +- .../dotnet/lib/Helpers/AnonCredsHelpers.cs | 129 ---- wrappers/dotnet/lib/Interop/InteropTypes.cs | 97 ++- wrappers/dotnet/lib/Interop/NativeMethods.cs | 370 +++++++--- wrappers/dotnet/lib/Models/AnonCredsObject.cs | 139 ++++ wrappers/dotnet/lib/Models/Credential.cs | 141 ++++ .../CredentialDefinition.cs | 38 +- wrappers/dotnet/lib/Models/CredentialOffer.cs | 35 + .../lib/Models/CredentialRevocationConfig.cs | 9 + .../KeyCorrectnessProof.cs | 4 +- wrappers/dotnet/lib/Models/LinkSecret.cs | 26 + wrappers/dotnet/lib/Models/Presentation.cs | 174 +++++ .../Models/RevocationRegistryDefinition.cs | 76 ++ .../RevocationRegistryDefinitionPrivate.cs | 10 + .../dotnet/lib/Models/RevocationStatusList.cs | 128 ++++ .../RevocationStatusListDelta.cs | 4 +- .../{Objects => Models}/RevokationState.cs | 47 +- wrappers/dotnet/lib/Models/Schema.cs | 34 + wrappers/dotnet/lib/Models/W3cCredential.cs | 127 ++++ wrappers/dotnet/lib/Models/W3cPresentation.cs | 214 ++++++ .../dotnet/lib/Objects/AnonCredsObject.cs | 57 -- wrappers/dotnet/lib/Objects/Credential.cs | 78 --- .../dotnet/lib/Objects/CredentialOffer.cs | 23 - wrappers/dotnet/lib/Objects/LinkSecret.cs | 21 - wrappers/dotnet/lib/Objects/Presentation.cs | 69 -- .../Objects/RevocationRegistryDefinition.cs | 10 - .../lib/Objects/RevocationRegistryPrivate.cs | 10 - .../lib/Objects/RevocationStatusList.cs | 108 --- wrappers/dotnet/lib/Objects/Schema.cs | 32 - .../dotnet/lib/Requests/CredentialRequest.cs | 21 +- .../lib/Requests/CredentialRequestMetadata.cs | 4 +- .../lib/Requests/PresentationRequest.cs | 8 +- wrappers/dotnet/tests/AnonCredsNetTests.cs | 219 ++++++ .../tests/ClassicRevocationFailureTests.cs | 255 +++++++ .../tests/MultiOverrideRevocationTests.cs | 266 +++++++ .../dotnet/tests/MultipleCredentialsTests.cs | 243 +++++++ .../tests/NonRevocableIntervalsTests.cs | 111 +++ .../dotnet/tests/NonRevokedIntervalsTests.cs | 255 +++++++ wrappers/dotnet/tests/PredicatesOnlyTests.cs | 331 +++++++++ wrappers/dotnet/tests/W3CTests.cs | 486 +++++++++++++ 45 files changed, 5169 insertions(+), 749 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 wrappers/dotnet/lib/AnonCreds.cs delete mode 100644 wrappers/dotnet/lib/Helpers/AnonCredsHelpers.cs create mode 100644 wrappers/dotnet/lib/Models/AnonCredsObject.cs create mode 100644 wrappers/dotnet/lib/Models/Credential.cs rename wrappers/dotnet/lib/{Objects => Models}/CredentialDefinition.cs (57%) create mode 100644 wrappers/dotnet/lib/Models/CredentialOffer.cs create mode 100644 wrappers/dotnet/lib/Models/CredentialRevocationConfig.cs rename wrappers/dotnet/lib/{Objects => Models}/KeyCorrectnessProof.cs (71%) create mode 100644 wrappers/dotnet/lib/Models/LinkSecret.cs create mode 100644 wrappers/dotnet/lib/Models/Presentation.cs create mode 100644 wrappers/dotnet/lib/Models/RevocationRegistryDefinition.cs create mode 100644 wrappers/dotnet/lib/Models/RevocationRegistryDefinitionPrivate.cs create mode 100644 wrappers/dotnet/lib/Models/RevocationStatusList.cs rename wrappers/dotnet/lib/{Objects => Models}/RevocationStatusListDelta.cs (71%) rename wrappers/dotnet/lib/{Objects => Models}/RevokationState.cs (57%) create mode 100644 wrappers/dotnet/lib/Models/Schema.cs create mode 100644 wrappers/dotnet/lib/Models/W3cCredential.cs create mode 100644 wrappers/dotnet/lib/Models/W3cPresentation.cs delete mode 100644 wrappers/dotnet/lib/Objects/AnonCredsObject.cs delete mode 100644 wrappers/dotnet/lib/Objects/Credential.cs delete mode 100644 wrappers/dotnet/lib/Objects/CredentialOffer.cs delete mode 100644 wrappers/dotnet/lib/Objects/LinkSecret.cs delete mode 100644 wrappers/dotnet/lib/Objects/Presentation.cs delete mode 100644 wrappers/dotnet/lib/Objects/RevocationRegistryDefinition.cs delete mode 100644 wrappers/dotnet/lib/Objects/RevocationRegistryPrivate.cs delete mode 100644 wrappers/dotnet/lib/Objects/RevocationStatusList.cs delete mode 100644 wrappers/dotnet/lib/Objects/Schema.cs create mode 100644 wrappers/dotnet/tests/AnonCredsNetTests.cs create mode 100644 wrappers/dotnet/tests/ClassicRevocationFailureTests.cs create mode 100644 wrappers/dotnet/tests/MultiOverrideRevocationTests.cs create mode 100644 wrappers/dotnet/tests/MultipleCredentialsTests.cs create mode 100644 wrappers/dotnet/tests/NonRevocableIntervalsTests.cs create mode 100644 wrappers/dotnet/tests/NonRevokedIntervalsTests.cs create mode 100644 wrappers/dotnet/tests/PredicatesOnlyTests.cs create mode 100644 wrappers/dotnet/tests/W3CTests.cs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..12fc4306 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,11 @@ +[target.aarch64-linux-android] +rustflags = ["-C", "link-arg=-Wl,-z,max-page-size=16384"] + +[target.armv7-linux-androideabi] +rustflags = ["-C", "link-arg=-Wl,-z,max-page-size=16384"] + +[target.i686-linux-android] +rustflags = ["-C", "link-arg=-Wl,-z,max-page-size=16384"] + +[target.x86_64-linux-android] +rustflags = ["-C", "link-arg=-Wl,-z,max-page-size=16384"] diff --git a/src/ffi/presentation.rs b/src/ffi/presentation.rs index cef906d5..ebc7853d 100644 --- a/src/ffi/presentation.rs +++ b/src/ffi/presentation.rs @@ -4,6 +4,7 @@ use super::util::{FfiList, FfiStrList}; use crate::data_types::cred_def::{CredentialDefinition, CredentialDefinitionId}; use crate::data_types::link_secret::LinkSecret; use crate::data_types::presentation::Presentation; +use crate::data_types::pres_request::PresentationRequest; use crate::data_types::rev_reg_def::RevocationRegistryDefinition; use crate::data_types::rev_reg_def::RevocationRegistryDefinitionId; use crate::data_types::rev_status_list::RevocationStatusList; @@ -75,26 +76,50 @@ pub extern "C" fn anoncreds_create_presentation( presentation_p: *mut ObjectHandle, ) -> ErrorCode { catch_error(|| { + eprintln!("=== CREATE_PRESENTATION FFI START ==="); check_useful_c_ptr!(presentation_p); + eprintln!("Loading link_secret..."); let link_secret = _link_secret(link_secret)?; + eprintln!("Link secret loaded successfully"); + + eprintln!("Processing self-attested attributes..."); let self_attested = _self_attested(self_attest_names, self_attest_values)?; + eprintln!("Self-attested attributes processed successfully"); + + eprintln!("Preparing credential definitions..."); let cred_defs = _prepare_cred_defs(cred_defs, cred_def_ids)?; + eprintln!("Credential definitions prepared: {} entries", cred_defs.len()); + + eprintln!("Preparing schemas..."); let schemas = _prepare_schemas(schemas, schema_ids)?; + eprintln!("Schemas prepared: {} entries", schemas.len()); + + eprintln!("Loading credentials..."); let credentials = _credentials(credentials)?; + eprintln!("Credentials loaded: {} entries", credentials.len()); + + eprintln!("Creating present credentials structure..."); let present_creds = _present_credentials(&credentials, credentials_prove)?; + eprintln!("Present credentials created successfully"); + eprintln!("About to call core create_presentation function..."); + let pres_req_binding = pres_req.load()?; + let pres_req_obj: &PresentationRequest = pres_req_binding.cast_ref()?; + eprintln!("Pres_req nonce during creation: {:?}", pres_req_obj.value().nonce); let presentation = create_presentation( - pres_req.load()?.cast_ref()?, + pres_req_obj, present_creds, self_attested, &link_secret, &schemas, &cred_defs, )?; + eprintln!("Core create_presentation completed successfully"); let presentation = ObjectHandle::create(presentation)?; unsafe { *presentation_p = presentation }; + eprintln!("=== CREATE_PRESENTATION FFI END - SUCCESS ==="); Ok(()) }) } @@ -158,23 +183,67 @@ pub extern "C" fn anoncreds_verify_presentation( result_p: *mut i8, ) -> ErrorCode { catch_error(|| { + eprintln!("=== RUST VERIFY PRESENTATION DEBUG ==="); + eprintln!("Input parameters:"); + eprintln!(" presentation handle: {:?}", presentation); + eprintln!(" pres_req handle: {:?}", pres_req); + eprintln!(" schemas count: {}", schemas.len()); + eprintln!(" schema_ids count: {}", schema_ids.len()); + eprintln!(" cred_defs count: {}", cred_defs.len()); + eprintln!(" cred_def_ids count: {}", cred_def_ids.len()); + + eprintln!("Schema IDs received:"); + for (i, schema_id) in schema_ids.as_slice().iter().enumerate() { + if let Some(id_str) = schema_id.as_opt_str() { + eprintln!(" [{}]: '{}'", i, id_str); + } + } + + eprintln!("CredDef IDs received:"); + for (i, cred_def_id) in cred_def_ids.as_slice().iter().enumerate() { + if let Some(id_str) = cred_def_id.as_opt_str() { + eprintln!(" [{}]: '{}'", i, id_str); + } + } + let cred_defs = _prepare_cred_defs(cred_defs, cred_def_ids)?; + eprintln!("Prepared cred_defs: {} entries", cred_defs.len()); + for (id, _) in &cred_defs { + eprintln!(" CredDef ID: '{}'", id); + } + let schemas = _prepare_schemas(schemas, schema_ids)?; + eprintln!("Prepared schemas: {} entries", schemas.len()); + for (id, _) in &schemas { + eprintln!(" Schema ID: '{}'", id); + } + let rev_reg_defs = _rev_reg_defs(rev_reg_defs, rev_reg_def_ids)?; let rev_status_lists = _rev_status_list(rev_status_list)?; let map_nonrevoked_interval_override = _nonrevoke_interval_override(nonrevoked_interval_override)?; + eprintln!("Loading presentation and pres_req objects..."); + let presentation_binding = presentation.load()?; + let presentation_obj = presentation_binding.cast_ref()?; + let pres_req_binding = pres_req.load()?; + let pres_req_obj = pres_req_binding.cast_ref()?; + + eprintln!("Calling verify_presentation..."); let verify = verify_presentation( - presentation.load()?.cast_ref()?, - pres_req.load()?.cast_ref()?, + presentation_obj, + pres_req_obj, &schemas, &cred_defs, rev_reg_defs.as_ref(), rev_status_lists, Some(&map_nonrevoked_interval_override), )?; + + eprintln!("Verification result: {}", verify); + eprintln!("Setting result_p to: {}", i8::from(verify)); unsafe { *result_p = i8::from(verify) }; + eprintln!("=== END RUST VERIFY PRESENTATION DEBUG ==="); Ok(()) }) } diff --git a/src/services/verifier.rs b/src/services/verifier.rs index 27505fdb..e987f95c 100644 --- a/src/services/verifier.rs +++ b/src/services/verifier.rs @@ -52,6 +52,22 @@ pub fn verify_presentation( &HashMap>, >, ) -> Result { + eprintln!("=== VERIFY_PRESENTATION FUNCTION START ==="); + eprintln!("Schemas received ({} total):", schemas.len()); + for (id, schema) in schemas { + eprintln!(" Schema ID: '{}', Name: '{}'", id, schema.name); + } + + eprintln!("CredDefs received ({} total):", cred_defs.len()); + for (id, cred_def) in cred_defs { + eprintln!(" CredDef ID: '{}', Schema ID: '{}'", id, cred_def.schema_id); + } + + eprintln!("Presentation identifiers:"); + for (i, identifier) in presentation.identifiers.iter().enumerate() { + eprintln!(" [{}] Schema ID: '{}', CredDef ID: '{}'", i, identifier.schema_id, identifier.cred_def_id); + } + trace!("verify >>> presentation: {:?}, pres_req: {:?}, schemas: {:?}, cred_defs: {:?}, rev_reg_defs: {:?} rev_status_lists: {:?}", presentation, pres_req, schemas, cred_defs, rev_reg_defs, rev_status_lists); @@ -63,8 +79,19 @@ pub fn verify_presentation( let received_predicates: HashMap = received_predicates(presentation)?; let received_self_attested_attrs: HashSet = received_self_attested_attrs(presentation); + eprintln!("Received revealed attrs: {} entries", received_revealed_attrs.len()); + for (attr, identifier) in &received_revealed_attrs { + eprintln!(" '{}' -> Schema: '{}', CredDef: '{}'", attr, identifier.schema_id, identifier.cred_def_id); + } + + eprintln!("Received predicates: {} entries", received_predicates.len()); + for (pred, identifier) in &received_predicates { + eprintln!(" '{}' -> Schema: '{}', CredDef: '{}'", pred, identifier.schema_id, identifier.cred_def_id); + } + let pres_req = pres_req.value(); + eprintln!("Starting attribute comparison..."); // Ensures that all attributes in the request is also in the presentation compare_attr_from_proof_and_request( pres_req, @@ -73,10 +100,14 @@ pub fn verify_presentation( &received_self_attested_attrs, &received_predicates, )?; + eprintln!("Attribute comparison passed"); + eprintln!("Starting revealed attribute value verification..."); // Ensures the encoded values are same as request verify_revealed_attribute_values(pres_req, presentation)?; + eprintln!("Revealed attribute values verified"); + eprintln!("Starting restriction verification..."); // Ensures the restrictions set out in the request is met verify_requested_restrictions( pres_req, @@ -88,7 +119,9 @@ pub fn verify_presentation( &received_predicates, &received_self_attested_attrs, )?; + eprintln!("Restriction verification passed"); + eprintln!("Creating CLProofVerifier..."); let mut proof_verifier = CLProofVerifier::new( pres_req, schemas, @@ -96,8 +129,16 @@ pub fn verify_presentation( rev_reg_defs, rev_status_lists.as_ref(), )?; + eprintln!("CLProofVerifier created successfully"); + eprintln!("Processing {} sub-proofs...", presentation.identifiers.len()); for (sub_proof_index, identifier) in presentation.identifiers.iter().enumerate() { + eprintln!("Processing sub-proof {} with identifier:", sub_proof_index); + eprintln!(" Schema ID: '{}'", identifier.schema_id); + eprintln!(" CredDef ID: '{}'", identifier.cred_def_id); + eprintln!(" RevReg ID: {:?}", identifier.rev_reg_id); + eprintln!(" Timestamp: {:?}", identifier.timestamp); + let attributes = presentation .requested_proof .get_attributes_for_credential(sub_proof_index as u32); @@ -105,10 +146,14 @@ pub fn verify_presentation( .requested_proof .get_predicates_for_credential(sub_proof_index as u32); + eprintln!(" Attributes for this credential: {} items", attributes.len()); + eprintln!(" Predicates for this credential: {} items", predicates.len()); + let (_, attrs_nonrevoked_interval) = pres_req.get_requested_attributes(&attributes)?; let (_, pred_nonrevoked_interval) = pres_req.get_requested_predicates(&predicates)?; { + eprintln!(" Checking non-revoked interval..."); check_non_revoked_interval( proof_verifier.get_credential_definition(&identifier.cred_def_id)?, attrs_nonrevoked_interval, @@ -118,8 +163,10 @@ pub fn verify_presentation( nonrevoke_interval_override, identifier.timestamp, )?; + eprintln!(" Non-revoked interval check passed"); } + eprintln!(" Adding sub-proof to verifier..."); proof_verifier.add_sub_proof( &presentation.proof.proofs[sub_proof_index], &identifier.schema_id, @@ -127,11 +174,23 @@ pub fn verify_presentation( identifier.rev_reg_id.as_ref(), identifier.timestamp, )?; + eprintln!(" Sub-proof {} added successfully", sub_proof_index); } + eprintln!("All sub-proofs processed. Starting final verification..."); + eprintln!("About to call proof_verifier.verify() with {} sub-proofs", presentation.proof.proofs.len()); + eprintln!("Pres_req nonce: {:?}", pres_req.nonce); let valid = proof_verifier.verify(&presentation.proof)?; + eprintln!("Final verification result: {}", valid); + + if !valid { + eprintln!("VERIFICATION FAILED - Debug info:"); + eprintln!(" Proof proofs length: {}", presentation.proof.proofs.len()); + eprintln!(" This indicates the cryptographic proof verification failed"); + } trace!("verify <<< valid: {:?}", valid); + eprintln!("=== VERIFY_PRESENTATION FUNCTION END ==="); Ok(valid) } diff --git a/wrappers/dotnet/lib/AnonCreds.cs b/wrappers/dotnet/lib/AnonCreds.cs new file mode 100644 index 00000000..50ce0042 --- /dev/null +++ b/wrappers/dotnet/lib/AnonCreds.cs @@ -0,0 +1,652 @@ +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; + } + + // Generic FFI helpers (migrated from Helpers/AnonCredsHelpers.cs) + 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 index 7014648e..a10bdf41 100644 --- a/wrappers/dotnet/lib/AnonCredsClient.cs +++ b/wrappers/dotnet/lib/AnonCredsClient.cs @@ -1,6 +1,11 @@ -// AnonCreds.cs -using AnonCredsNet.Helpers; -using AnonCredsNet.Objects; +// 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; @@ -12,37 +17,224 @@ 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, - LinkSecret linkSecret, + 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 credDefsJson, + string? schemaIdsJson, + string? credDefIdsJson, + string? revRegsJson, + string? revListsJson ) { + Console.WriteLine(" DEBUG: Entering CreatePresentation"); if ( presReq == null || string.IsNullOrEmpty(credentialsJson) - || linkSecret == null + || 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"); - if ( - credentialsJson.Length > 100000 - || schemasJson.Length > 100000 - || credDefsJson.Length > 100000 - ) - throw new ArgumentException("JSON input too large"); - string presReqJson = presReq.ToJson(); - return Presentation.Create( - presReqJson, - credentialsJson, + + Console.WriteLine(" DEBUG: Creating schemas list from JSON"); + var (schemasList, schemasObjects) = AnonCredsHelpers.CreateFfiObjectHandleListWithObjects( + schemasJson, + Schema.FromJson + ); + Console.WriteLine(" DEBUG: Created schemas list successfully"); + + Console.WriteLine(" DEBUG: Creating credDefs list from JSON"); + var (credDefsList, credDefsObjects) = AnonCredsHelpers.CreateFfiObjectHandleListWithObjects( + credDefsJson, + CredentialDefinition.FromJson + ); + Console.WriteLine(" DEBUG: Created credDefs list successfully"); + + Console.WriteLine(" DEBUG: Parsing credentials JSON"); + FfiCredentialEntryList credentialsList = ParseCredentialsJson(credentialsJson); + Console.WriteLine(" DEBUG: Parsed credentials JSON successfully"); + // Debug each entry for timestamp/rev_state presence + try + { + var dbgEntries = System.Text.Json.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 { } + + Console.WriteLine(" DEBUG: Creating schema IDs list"); + var schemaIds = AnonCredsHelpers.CreateFfiStrList(schemaIdsJson); + Console.WriteLine(" DEBUG: Created schema IDs list successfully"); + + Console.WriteLine(" DEBUG: Creating credDef IDs list"); + var credDefIds = AnonCredsHelpers.CreateFfiStrList(credDefIdsJson); + Console.WriteLine(" DEBUG: Created credDef IDs list successfully"); + + 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; + } + + Console.WriteLine(" DEBUG: Creating credentials prove list"); + // Create credentials_prove list based on presentation request, excluding self-attested referents + var credentialsProve = CreateCredentialsProveList( + presReq.ToJson(), selfAttestJson, + credentialsJson + ); + Console.WriteLine(" DEBUG: Created credentials prove list successfully"); + + 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); + Console.WriteLine( + $"DEBUG Credentials entry -> Timestamp: {entry.Timestamp}, RevState: {entry.RevState}" + ); + } + + var presentation = Presentation.Create( + presReq.Handle, + credentialsList, + credentialsProve, + selfAttestNames, + selfAttestValues, linkSecret, - schemasJson, - credDefsJson + schemasList, + schemaIds, + credDefsList, + credDefIds + ); + + return ( + presentation, + schemaIds, + schemasList, + credDefIds, + credDefsList, + revRegIds, + revRegsList, + new FfiStrList(), + revListsList, + credentialsList ); } @@ -56,28 +248,38 @@ public bool VerifyPresentation( string? nonRevocJson ) { - if ( - presentation == null - || presReq == null - || string.IsNullOrEmpty(schemasJson) - || string.IsNullOrEmpty(credDefsJson) - ) - throw new ArgumentNullException("Required parameters cannot be null or empty"); - if (schemasJson.Length > 100000 || credDefsJson.Length > 100000) - throw new ArgumentException("JSON input too large"); + // 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 ); } - // Add more higher-level methods if needed, e.g., IssueCredential flow public Credential IssueCredential( CredentialDefinition credDef, CredentialDefinitionPrivate credDefPvt, @@ -85,8 +287,8 @@ public Credential IssueCredential( CredentialRequest request, string credValues, string? revRegId, - string? tailsPath, - RevocationStatusList? revStatusList + CredentialRevocationConfig? revConfig, + string? tailsPath ) { if ( @@ -97,8 +299,7 @@ public Credential IssueCredential( || string.IsNullOrEmpty(credValues) ) throw new ArgumentNullException("Required parameters cannot be null or empty"); - if (credValues.Length > 100000) - throw new ArgumentException("Credential values JSON too large"); + var (credential, _) = Credential.Create( credDef, credDefPvt, @@ -107,8 +308,403 @@ public Credential IssueCredential( credValues, revRegId, tailsPath, - revStatusList + 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 + Console.WriteLine("Starting VerifyW3cPresentation..."); + 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 + ) + ) + ) + { + Console.WriteLine($"Verification returned error: {err}"); + Console.WriteLine("Interpreting verification error as invalid=false"); + 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 index bfc86785..aaecb976 100644 --- a/wrappers/dotnet/lib/AnonCredsNet.csproj +++ b/wrappers/dotnet/lib/AnonCredsNet.csproj @@ -12,36 +12,42 @@ + + + + + + - <_WinX64Dll Include="../../../target/x86_64-pc-windows-msvc/release/anoncreds.dll" - Condition="Exists('../../../target/x86_64-pc-windows-msvc/release/anoncreds.dll')" /> - <_WinX64Pdb Include="../../../target/x86_64-pc-windows-msvc/release/anoncreds.pdb" - Condition="Exists('../../../target/x86_64-pc-windows-msvc/release/anoncreds.pdb')" /> + <_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/release/anoncreds.dll" - Condition="Exists('../../../target/aarch64-pc-windows-msvc/release/anoncreds.dll')" /> - <_WinArm64Pdb Include="../../../target/aarch64-pc-windows-msvc/release/anoncreds.pdb" - Condition="Exists('../../../target/aarch64-pc-windows-msvc/release/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/release/libanoncreds.so" - Condition="Exists('../../../target/x86_64-unknown-linux-gnu/release/libanoncreds.so')" /> + <_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/release/libanoncreds.dylib" - Condition="Exists('../../../target/x86_64-apple-darwin/release/libanoncreds.dylib')" /> + <_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/release/libanoncreds.dylib" - Condition="Exists('../../../target/aarch64-apple-darwin/release/libanoncreds.dylib')" /> + <_MacArm64Dylib Include="../../../target/aarch64-apple-darwin/debug/libanoncreds.dylib" + Condition="Exists('../../../target/aarch64-apple-darwin/debug/libanoncreds.dylib')" /> - <_HostDll Include="../../../target/release/anoncreds.dll" - Condition="Exists('../../../target/release/anoncreds.dll') And '@(_WinX64Dll)' == '' And '@(_WinArm64Dll)' == ''" /> - <_HostPdb Include="../../../target/release/anoncreds.pdb" - Condition="Exists('../../../target/release/anoncreds.pdb') And '@(_WinX64Pdb)' == '' And '@(_WinArm64Pdb)' == ''" /> - <_HostSo Include="../../../target/release/libanoncreds.so" - Condition="Exists('../../../target/release/libanoncreds.so') And '@(_LinuxX64So)' == ''" /> - <_HostDylib Include="../../../target/release/libanoncreds.dylib" - Condition="Exists('../../../target/release/libanoncreds.dylib') And '@(_MacX64Dylib)' == '' And '@(_MacArm64Dylib)' == ''" /> + <_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)' == ''" /> diff --git a/wrappers/dotnet/lib/Helpers/AnonCredsHelpers.cs b/wrappers/dotnet/lib/Helpers/AnonCredsHelpers.cs deleted file mode 100644 index 604d4593..00000000 --- a/wrappers/dotnet/lib/Helpers/AnonCredsHelpers.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System.Runtime.InteropServices; -using System.Text.Json; -using AnonCredsNet.Exceptions; -using AnonCredsNet.Interop; -using AnonCredsNet.Objects; -using AnonCredsNet.Requests; - -namespace AnonCredsNet.Helpers; - -internal static class AnonCredsHelpers -{ - private static bool _initialized; - - internal static void Initialize() - { - if (_initialized) - return; - // No explicit initialization in anoncreds-rs FFI, but placeholder for future use - _initialized = true; - } - - 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; - } - - internal 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 FfiList CreateFfiList(string json, Func fromJson) - where T : AnonCredsObject - { - var items = - JsonSerializer.Deserialize(json) - ?? throw new InvalidOperationException("Invalid JSON array"); - var handles = new int[items.Length]; - for (var i = 0; i < items.Length; i++) - { - var item = fromJson(items[i]); - handles[i] = item.Handle; - item.Dispose(); // Dispose after capturing handle - } - var ptr = Marshal.AllocHGlobal(handles.Length * sizeof(int)); - Marshal.Copy(handles, 0, ptr, handles.Length); - return new FfiList { Handles = ptr, Count = handles.Length }; - } - - internal static void FreeFfiList(FfiList list) - { - if (list.Handles != IntPtr.Zero) - Marshal.FreeHGlobal(list.Handles); - } - - /// - /// Verifies a presentation. Ensure the and are disposed after use. - /// - public static bool VerifyPresentation( - Presentation presentation, - PresentationRequest presReq, - string schemasJson, - string credDefsJson, - string? revRegDefsJson, - string? revStatusListsJson, - string? nonRevocJson - ) - { - if ( - presentation == null - || presReq == null - || string.IsNullOrEmpty(schemasJson) - || string.IsNullOrEmpty(credDefsJson) - ) - throw new ArgumentNullException("Required parameters cannot be null or empty"); - if (presentation.Handle == 0 || presReq.Handle == 0) - throw new ObjectDisposedException("Presentation or PresentationRequest is disposed"); - if (schemasJson.Length > 100000 || credDefsJson.Length > 100000) - throw new ArgumentException("JSON input too large"); - - var schemasList = CreateFfiList(schemasJson, Schema.FromJson); - var credDefsList = CreateFfiList(credDefsJson, CredentialDefinition.FromJson); - var revRegDefsList = string.IsNullOrEmpty(revRegDefsJson) - ? new FfiList() - : CreateFfiList(revRegDefsJson, RevocationRegistryDefinition.FromJson); - var revStatusLists = string.IsNullOrEmpty(revStatusListsJson) - ? new FfiList() - : CreateFfiList(revStatusListsJson, RevocationStatusList.FromJson); - var nonRevocList = new FfiList(); // Empty list, as non-revocation proof is not supported - - try - { - var code = NativeMethods.anoncreds_verify_presentation( - presentation.Handle, - presReq.Handle, - schemasList, - credDefsList, - revRegDefsList, - revStatusLists, - nonRevocList, - out var valid - ); - if (code != ErrorCode.Success) - throw new AnonCredsException(code, GetCurrentError()); - return valid; - } - finally - { - FreeFfiList(schemasList); - FreeFfiList(credDefsList); - FreeFfiList(revRegDefsList); - FreeFfiList(revStatusLists); - FreeFfiList(nonRevocList); - } - } -} diff --git a/wrappers/dotnet/lib/Interop/InteropTypes.cs b/wrappers/dotnet/lib/Interop/InteropTypes.cs index 678b77fa..4aeafec6 100644 --- a/wrappers/dotnet/lib/Interop/InteropTypes.cs +++ b/wrappers/dotnet/lib/Interop/InteropTypes.cs @@ -30,29 +30,33 @@ public enum ErrorCode : int } [StructLayout(LayoutKind.Sequential)] -internal struct ObjectHandle +public struct ObjectHandle { - public int Value { get; } - - public ObjectHandle(int value) => Value = value; - - public static implicit operator int(ObjectHandle h) => h.Value; - - public static implicit operator ObjectHandle(int v) => new(v); + // Match C typedef i64 ObjectHandle; + public long Value; } [StructLayout(LayoutKind.Sequential)] internal struct ByteBuffer { - public int Len; + // C layout: int64_t len; uint8_t* data; + public long Len; public IntPtr Data; } [StructLayout(LayoutKind.Sequential)] public struct FfiList { - public IntPtr Handles; // Pointer to array of handles - public int Count; + // 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 @@ -63,7 +67,74 @@ internal struct AnoncredsPresentationRequest } [StructLayout(LayoutKind.Sequential)] -internal struct AnoncredsCredentialRevocationInfo +internal struct FfiCredentialEntry { - public IntPtr Json; // Pointer to JSON string + // 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 index 43586241..a8cf0b94 100644 --- a/wrappers/dotnet/lib/Interop/NativeMethods.cs +++ b/wrappers/dotnet/lib/Interop/NativeMethods.cs @@ -10,6 +10,9 @@ internal static partial class NativeMethods [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); @@ -17,152 +20,349 @@ internal static partial class NativeMethods internal static partial void anoncreds_buffer_free(ByteBuffer buf); [LibraryImport(Library)] - internal static partial void anoncreds_object_free(int handle); + internal static partial void anoncreds_object_free(long handle); - [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)] - internal static partial ErrorCode anoncreds_object_get_json(int handle, out IntPtr json); + [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 int handle); + 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 issuerId, string name, string version, - string attrNames, - out int handle + string issuerId, + FfiStrList attrNames, + out long handle ); [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)] internal static partial ErrorCode anoncreds_create_credential_definition( - string issuerId, - int schema, + string schemaId, + long schema, string tag, - string sigType, - string config, - out int credDef, - out int credDefPvt, - out int keyProof + 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(int credDef, out int offer); - - [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)] - internal static partial ErrorCode anoncreds_create_link_secret(out int linkSecret); + 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( - int credDef, - int linkSecret, + string? entropy, + string? proverDid, + long credDef, + string linkSecret, string linkSecretId, - int credOffer, - out int request, - out int metadata + long credOffer, + out long request, + out long metadata ); [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)] internal static partial ErrorCode anoncreds_create_credential( - int credDef, - int credDefPvt, - int credOffer, - int credRequest, - string credValues, - string revRegId, - string tailsPath, - int revStatusList, - out int credential, - out int revStatusListDelta + 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( - int credential, - int requestMetadata, - int linkSecret, - int credDef, - int revRegDef, - out int processedCredential + 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( - int presReq, - FfiList credentials, - string selfAttestJson, - int linkSecret, - FfiList schemas, - FfiList credDefs, - out int 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( - int presentation, - int presReq, - FfiList schemas, - FfiList credDefs, - FfiList revRegDefs, - FfiList revStatusLists, - FfiList nonRevoc, - [MarshalAs(UnmanagedType.U1)] out bool isValid + 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( - int credDef, + long credDef, + string credDefId, string issuerId, string tag, string revType, - string config, + long maxCredNum, string tailsPath, - out int revRegDef, - out int revRegPvt, - out int revStatusList + 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, - int revRegDef, - string timestamp, - [MarshalAs(UnmanagedType.U1)] bool issued, - [MarshalAs(UnmanagedType.U1)] bool revoked, - string tailsPath, - out int statusList + [MarshalAs(UnmanagedType.I1)] bool issuanceByDefault, + long timestamp, + out long statusList ); - [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)] + [LibraryImport(Library)] internal static partial ErrorCode anoncreds_update_revocation_status_list( - int statusList, - string issuedJson, - string revokedJson, - string timestamp, - out int updatedList, - out int delta + 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_revocation_state( - AnoncredsCredentialRevocationInfo credRevInfo, - int revRegDef, - int statusList, - string timestamp, + internal static partial ErrorCode anoncreds_create_or_update_revocation_state( + long revRegDef, + long revStatusList, + long revRegIndex, string tailsPath, - out int revState + long revState, + long oldRevStatusList, + out long revStateOut ); [LibraryImport(Library, StringMarshalling = StringMarshalling.Utf8)] - internal static partial ErrorCode anoncreds_update_revocation_state( - int revState, - int revRegDef, - int statusListDelta, - string timestamp, - string tailsPath, - out int updatedState + 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/Objects/CredentialDefinition.cs b/wrappers/dotnet/lib/Models/CredentialDefinition.cs similarity index 57% rename from wrappers/dotnet/lib/Objects/CredentialDefinition.cs rename to wrappers/dotnet/lib/Models/CredentialDefinition.cs index 563818f0..1a3dc1ed 100644 --- a/wrappers/dotnet/lib/Objects/CredentialDefinition.cs +++ b/wrappers/dotnet/lib/Models/CredentialDefinition.cs @@ -1,40 +1,56 @@ using AnonCredsNet.Exceptions; -using AnonCredsNet.Helpers; using AnonCredsNet.Interop; -namespace AnonCredsNet.Objects; +namespace AnonCredsNet.Models; public class CredentialDefinition : AnonCredsObject { - private CredentialDefinition(int handle) + private CredentialDefinition(long handle) : base(handle) { } - internal static ( + public static ( CredentialDefinition CredDef, CredentialDefinitionPrivate CredDefPvt, KeyCorrectnessProof KeyProof - ) Create(string issuerId, Schema schema, string tag, string sigType, string config) + ) Create( + string schemaId, + string issuerId, + Schema schema, + string tag, + string sigType, + string config + ) { if ( - string.IsNullOrEmpty(issuerId) + 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( - issuerId, + schemaId, schema.Handle, tag, + issuerId, sigType, - config, + supportRevocation, out var cd, out var pvt, out var proof ); if (code != ErrorCode.Success) - throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); + throw new AnonCredsException(code, AnonCreds.GetCurrentError()); return ( new CredentialDefinition(cd), new CredentialDefinitionPrivate(pvt), @@ -42,13 +58,13 @@ out var proof ); } - internal static CredentialDefinition FromJson(string json) => + public static CredentialDefinition FromJson(string json) => FromJson(json); } public class CredentialDefinitionPrivate : AnonCredsObject { - internal CredentialDefinitionPrivate(int handle) + internal CredentialDefinitionPrivate(long handle) : base(handle) { } internal static CredentialDefinitionPrivate FromJson(string 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/Objects/KeyCorrectnessProof.cs b/wrappers/dotnet/lib/Models/KeyCorrectnessProof.cs similarity index 71% rename from wrappers/dotnet/lib/Objects/KeyCorrectnessProof.cs rename to wrappers/dotnet/lib/Models/KeyCorrectnessProof.cs index cfce2c0d..3b4d7f97 100644 --- a/wrappers/dotnet/lib/Objects/KeyCorrectnessProof.cs +++ b/wrappers/dotnet/lib/Models/KeyCorrectnessProof.cs @@ -1,8 +1,8 @@ -namespace AnonCredsNet.Objects; +namespace AnonCredsNet.Models; public class KeyCorrectnessProof : AnonCredsObject { - internal KeyCorrectnessProof(int handle) + internal KeyCorrectnessProof(long handle) : base(handle) { } internal static KeyCorrectnessProof FromJson(string 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..b878900b --- /dev/null +++ b/wrappers/dotnet/lib/Models/Presentation.cs @@ -0,0 +1,174 @@ +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(); + Console.WriteLine($"[DEBUG] CredentialsList count: {count}"); + 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 + ); + Console.WriteLine( + $"[DEBUG] Entry {i}: cred={e.Credential}, ts={e.Timestamp}, revState={e.RevState}" + ); + } + } + } + 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/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/Objects/RevocationStatusListDelta.cs b/wrappers/dotnet/lib/Models/RevocationStatusListDelta.cs similarity index 71% rename from wrappers/dotnet/lib/Objects/RevocationStatusListDelta.cs rename to wrappers/dotnet/lib/Models/RevocationStatusListDelta.cs index 8326a879..4b1eb2ed 100644 --- a/wrappers/dotnet/lib/Objects/RevocationStatusListDelta.cs +++ b/wrappers/dotnet/lib/Models/RevocationStatusListDelta.cs @@ -1,8 +1,8 @@ -namespace AnonCredsNet.Objects; +namespace AnonCredsNet.Models; public class RevocationStatusListDelta : AnonCredsObject { - internal RevocationStatusListDelta(int handle) + internal RevocationStatusListDelta(long handle) : base(handle) { } public static RevocationStatusListDelta FromJson(string json) => diff --git a/wrappers/dotnet/lib/Objects/RevokationState.cs b/wrappers/dotnet/lib/Models/RevokationState.cs similarity index 57% rename from wrappers/dotnet/lib/Objects/RevokationState.cs rename to wrappers/dotnet/lib/Models/RevokationState.cs index 7ae50af8..d64dca34 100644 --- a/wrappers/dotnet/lib/Objects/RevokationState.cs +++ b/wrappers/dotnet/lib/Models/RevokationState.cs @@ -1,69 +1,64 @@ using AnonCredsNet.Exceptions; -using AnonCredsNet.Helpers; using AnonCredsNet.Interop; -namespace AnonCredsNet.Objects; +namespace AnonCredsNet.Models; public class RevocationState : AnonCredsObject { - private RevocationState(int handle) + private RevocationState(long handle) : base(handle) { } public static RevocationState Create( - int credRevInfo, RevocationRegistryDefinition revRegDef, RevocationStatusList statusList, - string timestamp, + uint revRegIndex, string tailsPath ) { - if ( - credRevInfo == 0 - || revRegDef == null - || statusList == null - || string.IsNullOrEmpty(timestamp) - || string.IsNullOrEmpty(tailsPath) - ) + if (revRegDef == null || statusList == null || string.IsNullOrEmpty(tailsPath)) throw new ArgumentNullException("Input parameters cannot be null or empty"); - var code = NativeMethods.anoncreds_create_revocation_state( - credRevInfo, + + var code = NativeMethods.anoncreds_create_or_update_revocation_state( revRegDef.Handle, statusList.Handle, - timestamp, + (long)revRegIndex, tailsPath, + 0, + 0, out var handle ); if (code != ErrorCode.Success) - throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); + throw new AnonCredsException(code, AnonCreds.GetCurrentError()); return new RevocationState(handle); } public static RevocationState Update( RevocationState revState, RevocationRegistryDefinition revRegDef, - RevocationStatusListDelta delta, - string timestamp, - string tailsPath + RevocationStatusList newStatusList, + uint revRegIndex, + string tailsPath, + RevocationStatusList? oldStatusList = null ) { if ( revState == null || revRegDef == null - || delta == null - || string.IsNullOrEmpty(timestamp) + || newStatusList == null || string.IsNullOrEmpty(tailsPath) ) throw new ArgumentNullException("Input parameters cannot be null or empty"); - var code = NativeMethods.anoncreds_update_revocation_state( - revState.Handle, + var code = NativeMethods.anoncreds_create_or_update_revocation_state( revRegDef.Handle, - delta.Handle, - timestamp, + newStatusList.Handle, + (long)revRegIndex, tailsPath, + revState.Handle, + oldStatusList?.Handle ?? 0, out var updated ); if (code != ErrorCode.Success) - throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); + throw new AnonCredsException(code, AnonCreds.GetCurrentError()); return new RevocationState(updated); } 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..2d3abc9b --- /dev/null +++ b/wrappers/dotnet/lib/Models/W3cPresentation.cs @@ -0,0 +1,214 @@ +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(); + Console.WriteLine($"[DEBUG] (W3C) CredentialsList count: {count}"); + 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 + ); + Console.WriteLine( + $"[DEBUG] (W3C) Entry {i}: cred={e.Credential}, ts={e.Timestamp}, revState={e.RevState}" + ); + } + } + } + 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/Objects/AnonCredsObject.cs b/wrappers/dotnet/lib/Objects/AnonCredsObject.cs deleted file mode 100644 index 9a2e1692..00000000 --- a/wrappers/dotnet/lib/Objects/AnonCredsObject.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Runtime.InteropServices; -using AnonCredsNet.Exceptions; -using AnonCredsNet.Helpers; -using AnonCredsNet.Interop; - -namespace AnonCredsNet.Objects; - -public abstract class AnonCredsObject : IDisposable -{ - internal int Handle { get; private set; } - - protected AnonCredsObject(int 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 ptr); - if (code != ErrorCode.Success) - throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); - var json = Marshal.PtrToStringUTF8(ptr) ?? throw new InvalidOperationException("Null JSON"); - NativeMethods.anoncreds_string_free(ptr); - return json; - } - - protected static T FromJson(string json) - where T : AnonCredsObject - { - if (string.IsNullOrEmpty(json)) - throw new ArgumentNullException(nameof(json)); - var code = NativeMethods.anoncreds_object_from_json(json, out var handle); - if (code != ErrorCode.Success) - throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); - return (T)Activator.CreateInstance(typeof(T), handle)!; - } - - 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/Objects/Credential.cs b/wrappers/dotnet/lib/Objects/Credential.cs deleted file mode 100644 index 1035780b..00000000 --- a/wrappers/dotnet/lib/Objects/Credential.cs +++ /dev/null @@ -1,78 +0,0 @@ -using AnonCredsNet.Exceptions; -using AnonCredsNet.Helpers; -using AnonCredsNet.Interop; -using AnonCredsNet.Requests; - -namespace AnonCredsNet.Objects; - -public sealed class Credential : AnonCredsObject -{ - private Credential(int handle) - : base(handle) { } - - /// - /// Creates a credential and its revocation delta. Both returned objects must be disposed using using statements. - /// - internal static (Credential Credential, RevocationStatusListDelta Delta) Create( - CredentialDefinition credDef, - CredentialDefinitionPrivate credDefPvt, - CredentialOffer offer, - CredentialRequest request, - string credValues, - string? revRegId, - string? tailsPath, - RevocationStatusList? revStatusList - ) - { - if ( - credDef == null - || credDefPvt == null - || offer == null - || request == null - || string.IsNullOrEmpty(credValues) - ) - throw new ArgumentNullException("Input parameters cannot be null or empty"); - var code = NativeMethods.anoncreds_create_credential( - credDef.Handle, - credDefPvt.Handle, - offer.Handle, - request.Handle, - credValues, - revRegId ?? "", - tailsPath ?? "", - revStatusList?.Handle ?? 0, - out var cred, - out var delta - ); - if (code != ErrorCode.Success) - throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); - return (new Credential(cred), new RevocationStatusListDelta(delta)); - } - - internal static Credential Process( - Credential credential, - CredentialRequestMetadata metadata, - LinkSecret linkSecret, - CredentialDefinition credDef, - RevocationRegistryDefinition? revRegDef - ) - { - if (credential == null || metadata == null || linkSecret == null || credDef == null) - throw new ArgumentNullException("Input parameters cannot be null"); - if (credential.Handle == 0) - throw new ObjectDisposedException(nameof(Credential)); - var code = NativeMethods.anoncreds_process_credential( - credential.Handle, - metadata.Handle, - linkSecret.Handle, - credDef.Handle, - revRegDef?.Handle ?? 0, - out var processed - ); - if (code != ErrorCode.Success) - throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); - return new Credential(processed); - } - - internal static Credential FromJson(string json) => FromJson(json); -} diff --git a/wrappers/dotnet/lib/Objects/CredentialOffer.cs b/wrappers/dotnet/lib/Objects/CredentialOffer.cs deleted file mode 100644 index 538919f0..00000000 --- a/wrappers/dotnet/lib/Objects/CredentialOffer.cs +++ /dev/null @@ -1,23 +0,0 @@ -using AnonCredsNet.Exceptions; -using AnonCredsNet.Helpers; -using AnonCredsNet.Interop; - -namespace AnonCredsNet.Objects; - -public class CredentialOffer : AnonCredsObject -{ - private CredentialOffer(int handle) - : base(handle) { } - - internal static CredentialOffer Create(CredentialDefinition credDef) - { - if (credDef == null) - throw new ArgumentNullException(nameof(credDef)); - var code = NativeMethods.anoncreds_create_credential_offer(credDef.Handle, out var handle); - if (code != ErrorCode.Success) - throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); - return new CredentialOffer(handle); - } - - internal static CredentialOffer FromJson(string json) => FromJson(json); -} diff --git a/wrappers/dotnet/lib/Objects/LinkSecret.cs b/wrappers/dotnet/lib/Objects/LinkSecret.cs deleted file mode 100644 index 8c093f31..00000000 --- a/wrappers/dotnet/lib/Objects/LinkSecret.cs +++ /dev/null @@ -1,21 +0,0 @@ -using AnonCredsNet.Exceptions; -using AnonCredsNet.Helpers; -using AnonCredsNet.Interop; - -namespace AnonCredsNet.Objects; - -public sealed class LinkSecret : AnonCredsObject -{ - private LinkSecret(int handle) - : base(handle) { } - - internal static LinkSecret Create() - { - var code = NativeMethods.anoncreds_create_link_secret(out var handle); - if (code != ErrorCode.Success) - throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); - return new LinkSecret(handle); - } - - internal static LinkSecret FromJson(string json) => FromJson(json); -} diff --git a/wrappers/dotnet/lib/Objects/Presentation.cs b/wrappers/dotnet/lib/Objects/Presentation.cs deleted file mode 100644 index c8174d61..00000000 --- a/wrappers/dotnet/lib/Objects/Presentation.cs +++ /dev/null @@ -1,69 +0,0 @@ -using AnonCredsNet.Exceptions; -using AnonCredsNet.Helpers; -using AnonCredsNet.Interop; -using AnonCredsNet.Requests; - -namespace AnonCredsNet.Objects; - -public sealed class Presentation : AnonCredsObject -{ - private Presentation(int handle) - : base(handle) { } - - /// - /// Creates a presentation. The returned object must be disposed using a using statement. - /// - public static Presentation Create( - string presReqJson, - string credentialsJson, - string? selfAttestJson, - LinkSecret linkSecret, - string schemasJson, - string credDefsJson - ) - { - if ( - string.IsNullOrEmpty(presReqJson) - || string.IsNullOrEmpty(credentialsJson) - || linkSecret == null - || string.IsNullOrEmpty(schemasJson) - || string.IsNullOrEmpty(credDefsJson) - ) - throw new ArgumentNullException("Input parameters cannot be null or empty"); - if (linkSecret.Handle == 0) - throw new ObjectDisposedException(nameof(LinkSecret)); - - var presReq = PresentationRequest.FromJson(presReqJson); - var schemasList = AnonCredsHelpers.CreateFfiList(schemasJson, Schema.FromJson); - var credDefsList = AnonCredsHelpers.CreateFfiList( - credDefsJson, - CredentialDefinition.FromJson - ); - var credentialsList = AnonCredsHelpers.CreateFfiList(credentialsJson, Credential.FromJson); - - try - { - var code = NativeMethods.anoncreds_create_presentation( - presReq.Handle, - credentialsList, - selfAttestJson ?? "{}", - linkSecret.Handle, - schemasList, - credDefsList, - out var handle - ); - if (code != ErrorCode.Success) - throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); - return new Presentation(handle); - } - finally - { - presReq.Dispose(); - AnonCredsHelpers.FreeFfiList(schemasList); - AnonCredsHelpers.FreeFfiList(credDefsList); - AnonCredsHelpers.FreeFfiList(credentialsList); - } - } - - public static Presentation FromJson(string json) => FromJson(json); -} diff --git a/wrappers/dotnet/lib/Objects/RevocationRegistryDefinition.cs b/wrappers/dotnet/lib/Objects/RevocationRegistryDefinition.cs deleted file mode 100644 index edf2467d..00000000 --- a/wrappers/dotnet/lib/Objects/RevocationRegistryDefinition.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace AnonCredsNet.Objects; - -public class RevocationRegistryDefinition : AnonCredsObject -{ - internal RevocationRegistryDefinition(int handle) - : base(handle) { } - - public static RevocationRegistryDefinition FromJson(string json) => - FromJson(json); -} diff --git a/wrappers/dotnet/lib/Objects/RevocationRegistryPrivate.cs b/wrappers/dotnet/lib/Objects/RevocationRegistryPrivate.cs deleted file mode 100644 index 6169966a..00000000 --- a/wrappers/dotnet/lib/Objects/RevocationRegistryPrivate.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace AnonCredsNet.Objects; - -public class RevocationRegistryPrivate : AnonCredsObject -{ - internal RevocationRegistryPrivate(int handle) - : base(handle) { } - - public static RevocationRegistryPrivate FromJson(string json) => - FromJson(json); -} diff --git a/wrappers/dotnet/lib/Objects/RevocationStatusList.cs b/wrappers/dotnet/lib/Objects/RevocationStatusList.cs deleted file mode 100644 index 346706ef..00000000 --- a/wrappers/dotnet/lib/Objects/RevocationStatusList.cs +++ /dev/null @@ -1,108 +0,0 @@ -using AnonCredsNet.Exceptions; -using AnonCredsNet.Helpers; -using AnonCredsNet.Interop; - -namespace AnonCredsNet.Objects; - -public class RevocationStatusList : AnonCredsObject -{ - private RevocationStatusList(int handle) - : base(handle) { } - - public static ( - RevocationRegistryDefinition RevRegDef, - RevocationRegistryPrivate RevRegPvt, - RevocationStatusList StatusList - ) CreateRevocationRegistryDefinition( - CredentialDefinition credDef, - string issuerId, - string tag, - string revType, - string config, - string tailsPath - ) - { - if ( - credDef == null - || string.IsNullOrEmpty(issuerId) - || string.IsNullOrEmpty(tag) - || string.IsNullOrEmpty(revType) - || string.IsNullOrEmpty(config) - || string.IsNullOrEmpty(tailsPath) - ) - throw new ArgumentNullException("Input parameters cannot be null or empty"); - var code = NativeMethods.anoncreds_create_revocation_registry_def( - credDef.Handle, - issuerId, - tag, - revType, - config, - tailsPath, - out var def, - out var pvt, - out var list - ); - if (code != ErrorCode.Success) - throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); - return ( - new RevocationRegistryDefinition(def), - new RevocationRegistryPrivate(pvt), - new RevocationStatusList(list) - ); - } - - public static RevocationStatusList Create( - string issuerId, - RevocationRegistryDefinition revRegDef, - string timestamp, - bool issued, - bool revoked, - string tailsPath - ) - { - if ( - string.IsNullOrEmpty(issuerId) - || revRegDef == null - || string.IsNullOrEmpty(timestamp) - || string.IsNullOrEmpty(tailsPath) - ) - throw new ArgumentNullException("Input parameters cannot be null or empty"); - var code = NativeMethods.anoncreds_create_revocation_status_list( - issuerId, - revRegDef.Handle, - timestamp, - issued, - revoked, - tailsPath, - out var handle - ); - if (code != ErrorCode.Success) - throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); - return new RevocationStatusList(handle); - } - - public static (RevocationStatusList UpdatedList, RevocationStatusListDelta Delta) Update( - RevocationStatusList statusList, - string? issuedJson, - string? revokedJson, - string timestamp - ) - { - if (statusList == null || string.IsNullOrEmpty(timestamp)) - throw new ArgumentNullException("Input parameters cannot be null or empty"); - var code = NativeMethods.anoncreds_update_revocation_status_list( - statusList.Handle, - issuedJson ?? "{}", - revokedJson ?? "{}", - timestamp, - out var updated, - out var delta - ); - if (code != ErrorCode.Success) - throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); - return (new RevocationStatusList(updated), new RevocationStatusListDelta(delta)); - } - - public static RevocationStatusList FromJson(string json) => - FromJson(json); -} diff --git a/wrappers/dotnet/lib/Objects/Schema.cs b/wrappers/dotnet/lib/Objects/Schema.cs deleted file mode 100644 index 3e0e1d9b..00000000 --- a/wrappers/dotnet/lib/Objects/Schema.cs +++ /dev/null @@ -1,32 +0,0 @@ -using AnonCredsNet.Exceptions; -using AnonCredsNet.Helpers; -using AnonCredsNet.Interop; - -namespace AnonCredsNet.Objects; - -public class Schema : AnonCredsObject -{ - private Schema(int handle) - : base(handle) { } - - internal static Schema Create( - string issuerId, - string name, - string version, - string attrNamesJson - ) - { - var code = NativeMethods.anoncreds_create_schema( - issuerId, - name, - version, - attrNamesJson, - out var handle - ); - if (code != ErrorCode.Success) - throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); - return new Schema(handle); - } - - internal static Schema FromJson(string json) => FromJson(json); -} diff --git a/wrappers/dotnet/lib/Requests/CredentialRequest.cs b/wrappers/dotnet/lib/Requests/CredentialRequest.cs index ea5705e9..7315c026 100644 --- a/wrappers/dotnet/lib/Requests/CredentialRequest.cs +++ b/wrappers/dotnet/lib/Requests/CredentialRequest.cs @@ -1,39 +1,42 @@ using AnonCredsNet.Exceptions; -using AnonCredsNet.Helpers; using AnonCredsNet.Interop; -using AnonCredsNet.Objects; +using AnonCredsNet.Models; namespace AnonCredsNet.Requests; public class CredentialRequest : AnonCredsObject { - private CredentialRequest(int handle) + private CredentialRequest(long handle) : base(handle) { } - internal static (CredentialRequest Request, CredentialRequestMetadata Metadata) Create( + public static (CredentialRequest Request, CredentialRequestMetadata Metadata) Create( CredentialDefinition credDef, - LinkSecret linkSecret, + string linkSecret, string linkSecretId, - CredentialOffer credOffer + CredentialOffer credOffer, + string? entropy = null, + string? proverDid = null ) { if ( credDef == null - || linkSecret == 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.Handle, + linkSecret, linkSecretId, credOffer.Handle, out var req, out var meta ); if (code != ErrorCode.Success) - throw new AnonCredsException(code, AnonCredsHelpers.GetCurrentError()); + throw new AnonCredsException(code, AnonCreds.GetCurrentError()); return (new CredentialRequest(req), new CredentialRequestMetadata(meta)); } diff --git a/wrappers/dotnet/lib/Requests/CredentialRequestMetadata.cs b/wrappers/dotnet/lib/Requests/CredentialRequestMetadata.cs index b8339565..b94fbe90 100644 --- a/wrappers/dotnet/lib/Requests/CredentialRequestMetadata.cs +++ b/wrappers/dotnet/lib/Requests/CredentialRequestMetadata.cs @@ -1,10 +1,10 @@ -using AnonCredsNet.Objects; +using AnonCredsNet.Models; namespace AnonCredsNet.Requests; public class CredentialRequestMetadata : AnonCredsObject { - internal CredentialRequestMetadata(int handle) + internal CredentialRequestMetadata(long handle) : base(handle) { } internal static CredentialRequestMetadata FromJson(string json) => diff --git a/wrappers/dotnet/lib/Requests/PresentationRequest.cs b/wrappers/dotnet/lib/Requests/PresentationRequest.cs index 8be0240e..02f0044e 100644 --- a/wrappers/dotnet/lib/Requests/PresentationRequest.cs +++ b/wrappers/dotnet/lib/Requests/PresentationRequest.cs @@ -1,12 +1,12 @@ -using AnonCredsNet.Objects; +using AnonCredsNet.Models; namespace AnonCredsNet.Requests; public sealed class PresentationRequest : AnonCredsObject { - internal PresentationRequest(int handle) + internal PresentationRequest(long handle) : base(handle) { } - internal static PresentationRequest FromJson(string json) => - FromJson(json); + public static PresentationRequest FromJson(string json) => + AnonCredsObject.FromJson(json); } 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); + } +} From 14d7ce81e4224ef7caec9ba364b64bac97609386 Mon Sep 17 00:00:00 2001 From: James Hancock Date: Mon, 8 Sep 2025 13:27:50 -0400 Subject: [PATCH 3/6] Minor tweaks to clean up some warnings. --- wrappers/dotnet/lib/AnonCreds.cs | 1 - wrappers/dotnet/lib/Requests/PresentationRequest.cs | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/wrappers/dotnet/lib/AnonCreds.cs b/wrappers/dotnet/lib/AnonCreds.cs index 50ce0042..5a494fcf 100644 --- a/wrappers/dotnet/lib/AnonCreds.cs +++ b/wrappers/dotnet/lib/AnonCreds.cs @@ -38,7 +38,6 @@ public static string GenerateNonce() return nonce; } - // Generic FFI helpers (migrated from Helpers/AnonCredsHelpers.cs) internal static ByteBuffer CreateByteBuffer(string json) { var bytes = Encoding.UTF8.GetBytes(json); diff --git a/wrappers/dotnet/lib/Requests/PresentationRequest.cs b/wrappers/dotnet/lib/Requests/PresentationRequest.cs index 02f0044e..3d17a819 100644 --- a/wrappers/dotnet/lib/Requests/PresentationRequest.cs +++ b/wrappers/dotnet/lib/Requests/PresentationRequest.cs @@ -7,6 +7,5 @@ public sealed class PresentationRequest : AnonCredsObject internal PresentationRequest(long handle) : base(handle) { } - public static PresentationRequest FromJson(string json) => - AnonCredsObject.FromJson(json); + public static PresentationRequest FromJson(string json) => FromJson(json); } From 1e77bbea9dbe091a61f764e01fb4cdc89b8e2ab3 Mon Sep 17 00:00:00 2001 From: James Hancock Date: Mon, 8 Sep 2025 13:32:17 -0400 Subject: [PATCH 4/6] Remove Console.Writeline --- wrappers/dotnet/lib/AnonCredsClient.cs | 53 ++++++------------- wrappers/dotnet/lib/Models/Presentation.cs | 9 +--- wrappers/dotnet/lib/Models/W3cPresentation.cs | 9 +--- 3 files changed, 20 insertions(+), 51 deletions(-) diff --git a/wrappers/dotnet/lib/AnonCredsClient.cs b/wrappers/dotnet/lib/AnonCredsClient.cs index a10bdf41..b752a5fb 100644 --- a/wrappers/dotnet/lib/AnonCredsClient.cs +++ b/wrappers/dotnet/lib/AnonCredsClient.cs @@ -99,7 +99,6 @@ FfiCredentialEntryList credentials string? revListsJson ) { - Console.WriteLine(" DEBUG: Entering CreatePresentation"); if ( presReq == null || string.IsNullOrEmpty(credentialsJson) @@ -111,48 +110,36 @@ FfiCredentialEntryList credentials ) throw new ArgumentNullException("Required parameters cannot be null or empty"); - Console.WriteLine(" DEBUG: Creating schemas list from JSON"); var (schemasList, schemasObjects) = AnonCredsHelpers.CreateFfiObjectHandleListWithObjects( schemasJson, Schema.FromJson ); - Console.WriteLine(" DEBUG: Created schemas list successfully"); - - Console.WriteLine(" DEBUG: Creating credDefs list from JSON"); var (credDefsList, credDefsObjects) = AnonCredsHelpers.CreateFfiObjectHandleListWithObjects( credDefsJson, CredentialDefinition.FromJson ); - Console.WriteLine(" DEBUG: Created credDefs list successfully"); - - Console.WriteLine(" DEBUG: Parsing credentials JSON"); FfiCredentialEntryList credentialsList = ParseCredentialsJson(credentialsJson); - Console.WriteLine(" DEBUG: Parsed credentials JSON successfully"); // Debug each entry for timestamp/rev_state presence - try - { - var dbgEntries = System.Text.Json.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 { } + // 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 { } - Console.WriteLine(" DEBUG: Creating schema IDs list"); var schemaIds = AnonCredsHelpers.CreateFfiStrList(schemaIdsJson); - Console.WriteLine(" DEBUG: Created schema IDs list successfully"); - Console.WriteLine(" DEBUG: Creating credDef IDs list"); var credDefIds = AnonCredsHelpers.CreateFfiStrList(credDefIdsJson); - Console.WriteLine(" DEBUG: Created credDef IDs list successfully"); var revRegIds = new FfiStrList(); var revRegsList = new FfiObjectHandleList(); @@ -176,14 +163,12 @@ FfiCredentialEntryList credentials revListsList = revLists; } - Console.WriteLine(" DEBUG: Creating credentials prove list"); // Create credentials_prove list based on presentation request, excluding self-attested referents var credentialsProve = CreateCredentialsProveList( presReq.ToJson(), selfAttestJson, credentialsJson ); - Console.WriteLine(" DEBUG: Created credentials prove list successfully"); var selfAttestNames = new FfiStrList(); var selfAttestValues = new FfiStrList(); @@ -206,9 +191,6 @@ FfiCredentialEntryList credentials { var entryPtr = credentialsList.Data; var entry = Marshal.PtrToStructure(entryPtr); - Console.WriteLine( - $"DEBUG Credentials entry -> Timestamp: {entry.Timestamp}, RevState: {entry.RevState}" - ); } var presentation = Presentation.Create( @@ -411,7 +393,6 @@ public bool VerifyW3cPresentation( ) { // Reuse the same helper structure for list creation and IDs - Console.WriteLine("Starting VerifyW3cPresentation..."); try { var (schemasList, schemasObjects) = @@ -481,8 +462,6 @@ out var valid ) ) { - Console.WriteLine($"Verification returned error: {err}"); - Console.WriteLine("Interpreting verification error as invalid=false"); return false; } throw new AnonCredsException(code, err); diff --git a/wrappers/dotnet/lib/Models/Presentation.cs b/wrappers/dotnet/lib/Models/Presentation.cs index b878900b..c3c96ef6 100644 --- a/wrappers/dotnet/lib/Models/Presentation.cs +++ b/wrappers/dotnet/lib/Models/Presentation.cs @@ -38,21 +38,16 @@ FfiStrList credDefIds try { var count = (int)credentialsList.Count.ToUInt32(); - Console.WriteLine($"[DEBUG] CredentialsList count: {count}"); if (credentialsList.Data != IntPtr.Zero) { - var size = - System.Runtime.InteropServices.Marshal.SizeOf(); + 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( + System.Runtime.InteropServices.Marshal.PtrToStructure( ptr ); - Console.WriteLine( - $"[DEBUG] Entry {i}: cred={e.Credential}, ts={e.Timestamp}, revState={e.RevState}" - ); } } } diff --git a/wrappers/dotnet/lib/Models/W3cPresentation.cs b/wrappers/dotnet/lib/Models/W3cPresentation.cs index 2d3abc9b..fbfeefca 100644 --- a/wrappers/dotnet/lib/Models/W3cPresentation.cs +++ b/wrappers/dotnet/lib/Models/W3cPresentation.cs @@ -29,21 +29,16 @@ public static W3cPresentation Create( try { var count = (int)credentialsList.Count.ToUInt32(); - Console.WriteLine($"[DEBUG] (W3C) CredentialsList count: {count}"); if (credentialsList.Data != IntPtr.Zero) { - var size = - System.Runtime.InteropServices.Marshal.SizeOf(); + 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( + System.Runtime.InteropServices.Marshal.PtrToStructure( ptr ); - Console.WriteLine( - $"[DEBUG] (W3C) Entry {i}: cred={e.Credential}, ts={e.Timestamp}, revState={e.RevState}" - ); } } } From efc386837a8bd552311c167ed7d0d1d4af95e7de Mon Sep 17 00:00:00 2001 From: James Hancock Date: Mon, 8 Sep 2025 13:36:37 -0400 Subject: [PATCH 5/6] Revert files accidently changed. --- src/ffi/presentation.rs | 77 +++------------------------------------- src/services/verifier.rs | 61 +------------------------------ 2 files changed, 5 insertions(+), 133 deletions(-) diff --git a/src/ffi/presentation.rs b/src/ffi/presentation.rs index ebc7853d..10bb6ace 100644 --- a/src/ffi/presentation.rs +++ b/src/ffi/presentation.rs @@ -4,7 +4,6 @@ use super::util::{FfiList, FfiStrList}; use crate::data_types::cred_def::{CredentialDefinition, CredentialDefinitionId}; use crate::data_types::link_secret::LinkSecret; use crate::data_types::presentation::Presentation; -use crate::data_types::pres_request::PresentationRequest; use crate::data_types::rev_reg_def::RevocationRegistryDefinition; use crate::data_types::rev_reg_def::RevocationRegistryDefinitionId; use crate::data_types::rev_status_list::RevocationStatusList; @@ -76,50 +75,26 @@ pub extern "C" fn anoncreds_create_presentation( presentation_p: *mut ObjectHandle, ) -> ErrorCode { catch_error(|| { - eprintln!("=== CREATE_PRESENTATION FFI START ==="); check_useful_c_ptr!(presentation_p); - eprintln!("Loading link_secret..."); let link_secret = _link_secret(link_secret)?; - eprintln!("Link secret loaded successfully"); - - eprintln!("Processing self-attested attributes..."); let self_attested = _self_attested(self_attest_names, self_attest_values)?; - eprintln!("Self-attested attributes processed successfully"); - - eprintln!("Preparing credential definitions..."); let cred_defs = _prepare_cred_defs(cred_defs, cred_def_ids)?; - eprintln!("Credential definitions prepared: {} entries", cred_defs.len()); - - eprintln!("Preparing schemas..."); let schemas = _prepare_schemas(schemas, schema_ids)?; - eprintln!("Schemas prepared: {} entries", schemas.len()); - - eprintln!("Loading credentials..."); let credentials = _credentials(credentials)?; - eprintln!("Credentials loaded: {} entries", credentials.len()); - - eprintln!("Creating present credentials structure..."); let present_creds = _present_credentials(&credentials, credentials_prove)?; - eprintln!("Present credentials created successfully"); - eprintln!("About to call core create_presentation function..."); - let pres_req_binding = pres_req.load()?; - let pres_req_obj: &PresentationRequest = pres_req_binding.cast_ref()?; - eprintln!("Pres_req nonce during creation: {:?}", pres_req_obj.value().nonce); let presentation = create_presentation( - pres_req_obj, + pres_req.load()?.cast_ref()?, present_creds, self_attested, &link_secret, &schemas, &cred_defs, )?; - eprintln!("Core create_presentation completed successfully"); let presentation = ObjectHandle::create(presentation)?; unsafe { *presentation_p = presentation }; - eprintln!("=== CREATE_PRESENTATION FFI END - SUCCESS ==="); Ok(()) }) } @@ -183,67 +158,23 @@ pub extern "C" fn anoncreds_verify_presentation( result_p: *mut i8, ) -> ErrorCode { catch_error(|| { - eprintln!("=== RUST VERIFY PRESENTATION DEBUG ==="); - eprintln!("Input parameters:"); - eprintln!(" presentation handle: {:?}", presentation); - eprintln!(" pres_req handle: {:?}", pres_req); - eprintln!(" schemas count: {}", schemas.len()); - eprintln!(" schema_ids count: {}", schema_ids.len()); - eprintln!(" cred_defs count: {}", cred_defs.len()); - eprintln!(" cred_def_ids count: {}", cred_def_ids.len()); - - eprintln!("Schema IDs received:"); - for (i, schema_id) in schema_ids.as_slice().iter().enumerate() { - if let Some(id_str) = schema_id.as_opt_str() { - eprintln!(" [{}]: '{}'", i, id_str); - } - } - - eprintln!("CredDef IDs received:"); - for (i, cred_def_id) in cred_def_ids.as_slice().iter().enumerate() { - if let Some(id_str) = cred_def_id.as_opt_str() { - eprintln!(" [{}]: '{}'", i, id_str); - } - } - let cred_defs = _prepare_cred_defs(cred_defs, cred_def_ids)?; - eprintln!("Prepared cred_defs: {} entries", cred_defs.len()); - for (id, _) in &cred_defs { - eprintln!(" CredDef ID: '{}'", id); - } - let schemas = _prepare_schemas(schemas, schema_ids)?; - eprintln!("Prepared schemas: {} entries", schemas.len()); - for (id, _) in &schemas { - eprintln!(" Schema ID: '{}'", id); - } - let rev_reg_defs = _rev_reg_defs(rev_reg_defs, rev_reg_def_ids)?; let rev_status_lists = _rev_status_list(rev_status_list)?; let map_nonrevoked_interval_override = _nonrevoke_interval_override(nonrevoked_interval_override)?; - eprintln!("Loading presentation and pres_req objects..."); - let presentation_binding = presentation.load()?; - let presentation_obj = presentation_binding.cast_ref()?; - let pres_req_binding = pres_req.load()?; - let pres_req_obj = pres_req_binding.cast_ref()?; - - eprintln!("Calling verify_presentation..."); let verify = verify_presentation( - presentation_obj, - pres_req_obj, + presentation.load()?.cast_ref()?, + pres_req.load()?.cast_ref()?, &schemas, &cred_defs, rev_reg_defs.as_ref(), rev_status_lists, Some(&map_nonrevoked_interval_override), )?; - - eprintln!("Verification result: {}", verify); - eprintln!("Setting result_p to: {}", i8::from(verify)); unsafe { *result_p = i8::from(verify) }; - eprintln!("=== END RUST VERIFY PRESENTATION DEBUG ==="); Ok(()) }) } @@ -463,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 e987f95c..d5ff5da7 100644 --- a/src/services/verifier.rs +++ b/src/services/verifier.rs @@ -52,22 +52,6 @@ pub fn verify_presentation( &HashMap>, >, ) -> Result { - eprintln!("=== VERIFY_PRESENTATION FUNCTION START ==="); - eprintln!("Schemas received ({} total):", schemas.len()); - for (id, schema) in schemas { - eprintln!(" Schema ID: '{}', Name: '{}'", id, schema.name); - } - - eprintln!("CredDefs received ({} total):", cred_defs.len()); - for (id, cred_def) in cred_defs { - eprintln!(" CredDef ID: '{}', Schema ID: '{}'", id, cred_def.schema_id); - } - - eprintln!("Presentation identifiers:"); - for (i, identifier) in presentation.identifiers.iter().enumerate() { - eprintln!(" [{}] Schema ID: '{}', CredDef ID: '{}'", i, identifier.schema_id, identifier.cred_def_id); - } - trace!("verify >>> presentation: {:?}, pres_req: {:?}, schemas: {:?}, cred_defs: {:?}, rev_reg_defs: {:?} rev_status_lists: {:?}", presentation, pres_req, schemas, cred_defs, rev_reg_defs, rev_status_lists); @@ -79,19 +63,8 @@ pub fn verify_presentation( let received_predicates: HashMap = received_predicates(presentation)?; let received_self_attested_attrs: HashSet = received_self_attested_attrs(presentation); - eprintln!("Received revealed attrs: {} entries", received_revealed_attrs.len()); - for (attr, identifier) in &received_revealed_attrs { - eprintln!(" '{}' -> Schema: '{}', CredDef: '{}'", attr, identifier.schema_id, identifier.cred_def_id); - } - - eprintln!("Received predicates: {} entries", received_predicates.len()); - for (pred, identifier) in &received_predicates { - eprintln!(" '{}' -> Schema: '{}', CredDef: '{}'", pred, identifier.schema_id, identifier.cred_def_id); - } - let pres_req = pres_req.value(); - eprintln!("Starting attribute comparison..."); // Ensures that all attributes in the request is also in the presentation compare_attr_from_proof_and_request( pres_req, @@ -100,14 +73,10 @@ pub fn verify_presentation( &received_self_attested_attrs, &received_predicates, )?; - eprintln!("Attribute comparison passed"); - eprintln!("Starting revealed attribute value verification..."); // Ensures the encoded values are same as request verify_revealed_attribute_values(pres_req, presentation)?; - eprintln!("Revealed attribute values verified"); - eprintln!("Starting restriction verification..."); // Ensures the restrictions set out in the request is met verify_requested_restrictions( pres_req, @@ -119,9 +88,7 @@ pub fn verify_presentation( &received_predicates, &received_self_attested_attrs, )?; - eprintln!("Restriction verification passed"); - eprintln!("Creating CLProofVerifier..."); let mut proof_verifier = CLProofVerifier::new( pres_req, schemas, @@ -129,16 +96,8 @@ pub fn verify_presentation( rev_reg_defs, rev_status_lists.as_ref(), )?; - eprintln!("CLProofVerifier created successfully"); - eprintln!("Processing {} sub-proofs...", presentation.identifiers.len()); for (sub_proof_index, identifier) in presentation.identifiers.iter().enumerate() { - eprintln!("Processing sub-proof {} with identifier:", sub_proof_index); - eprintln!(" Schema ID: '{}'", identifier.schema_id); - eprintln!(" CredDef ID: '{}'", identifier.cred_def_id); - eprintln!(" RevReg ID: {:?}", identifier.rev_reg_id); - eprintln!(" Timestamp: {:?}", identifier.timestamp); - let attributes = presentation .requested_proof .get_attributes_for_credential(sub_proof_index as u32); @@ -146,14 +105,10 @@ pub fn verify_presentation( .requested_proof .get_predicates_for_credential(sub_proof_index as u32); - eprintln!(" Attributes for this credential: {} items", attributes.len()); - eprintln!(" Predicates for this credential: {} items", predicates.len()); - let (_, attrs_nonrevoked_interval) = pres_req.get_requested_attributes(&attributes)?; let (_, pred_nonrevoked_interval) = pres_req.get_requested_predicates(&predicates)?; { - eprintln!(" Checking non-revoked interval..."); check_non_revoked_interval( proof_verifier.get_credential_definition(&identifier.cred_def_id)?, attrs_nonrevoked_interval, @@ -163,10 +118,8 @@ pub fn verify_presentation( nonrevoke_interval_override, identifier.timestamp, )?; - eprintln!(" Non-revoked interval check passed"); } - eprintln!(" Adding sub-proof to verifier..."); proof_verifier.add_sub_proof( &presentation.proof.proofs[sub_proof_index], &identifier.schema_id, @@ -174,23 +127,11 @@ pub fn verify_presentation( identifier.rev_reg_id.as_ref(), identifier.timestamp, )?; - eprintln!(" Sub-proof {} added successfully", sub_proof_index); } - eprintln!("All sub-proofs processed. Starting final verification..."); - eprintln!("About to call proof_verifier.verify() with {} sub-proofs", presentation.proof.proofs.len()); - eprintln!("Pres_req nonce: {:?}", pres_req.nonce); let valid = proof_verifier.verify(&presentation.proof)?; - eprintln!("Final verification result: {}", valid); - - if !valid { - eprintln!("VERIFICATION FAILED - Debug info:"); - eprintln!(" Proof proofs length: {}", presentation.proof.proofs.len()); - eprintln!(" This indicates the cryptographic proof verification failed"); - } trace!("verify <<< valid: {:?}", valid); - eprintln!("=== VERIFY_PRESENTATION FUNCTION END ==="); Ok(valid) } @@ -1358,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 From 45329d18f6dc6cd4dadf7755f88fd0477551eecb Mon Sep 17 00:00:00 2001 From: James Hancock Date: Mon, 8 Sep 2025 13:38:18 -0400 Subject: [PATCH 6/6] Rename file for code review. --- .../dotnet/lib/Models/{RevokationState.cs => RevocationState.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename wrappers/dotnet/lib/Models/{RevokationState.cs => RevocationState.cs} (100%) diff --git a/wrappers/dotnet/lib/Models/RevokationState.cs b/wrappers/dotnet/lib/Models/RevocationState.cs similarity index 100% rename from wrappers/dotnet/lib/Models/RevokationState.cs rename to wrappers/dotnet/lib/Models/RevocationState.cs