Skip to content

Chore/bls12-381#36

Open
d4rp4t wants to merge 3 commits into
Kukks:masterfrom
d4rp4t:chore/bls12-381
Open

Chore/bls12-381#36
d4rp4t wants to merge 3 commits into
Kukks:masterfrom
d4rp4t:chore/bls12-381

Conversation

@d4rp4t

@d4rp4t d4rp4t commented May 20, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

Release Notes

  • New Features

    • Added BLS12-381 elliptic curve cryptography support, enabling v3 keysets alongside existing secp256k1 implementations.
    • Implemented efficient batch signature verification to process multiple proofs in a single operation.
  • Tests

    • Added comprehensive test suite for BLS operations including signature creation, verification, key derivation, and keyset validation.

Review Change Stack

d4rp4t added 3 commits May 20, 2026 18:31
(maybe add a mandatory pairing validation b4 any interaction)
@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR introduces comprehensive BLS12-381 signature support (v3 keysets) to the DotNut Cashu library, alongside existing secp256k1 (v0/v1/v2). Changes span cryptographic primitives, polymorphic type representations, JSON serialization, derivation logic, integration points, and extensive test coverage.

Changes

BLS v3 Keyset Implementation

Layer / File(s) Summary
Submodule and Project Setup
.gitmodules, DotNut.sln, DotNut/DotNut.csproj
Git submodule for DotNut.BLS12-381 library added; solution and project files updated to include the BLS project reference and build configurations.
BLS Cryptographic Primitives
DotNut/BlsG1PubKey.cs, DotNut/BlsG2PubKey.cs, DotNut/Crypto/BlsCashu.cs
New BLS point wrappers (BlsG1PubKey, BlsG2PubKey) and comprehensive BlsCashu static class implementing hash-to-curve, blinding/unblinding, single and batch signature verification with deterministic Fiat-Shamir weighting, and scalar generation/reduction.
Polymorphic PubKey Type
DotNut/PubKey.cs
Extended to store secp256k1 ECPubKey or BLS compressed G1/G2 bytes; added IsBlsG1/IsBlsG2 flags and conversion methods GetBlsG1Point()/GetBlsG2Point(); updated ToString(), Equals(), and GetHashCode() to handle both representations.
Keyset v3 Infrastructure
DotNut/NUT01/BlsKeyset.cs, DotNut/NUT01/Keyset.cs, DotNut/NUT02/KeysetId.cs
New BlsKeyset subclass implements v3 deterministic keyset ID generation and verification with "02" prefix; base Keyset.VerifyKeysetId made virtual; KeysetId.IsBlsKeyset() added to identify BLS IDs by prefix and format.
Polymorphic JSON Converters
DotNut/JsonConverters/AnyKeysetJsonConverter.cs, DotNut/JsonConverters/BlsKeysetJsonConverter.cs, DotNut/JsonConverters/KeysetJsonConverter.cs, DotNut/JsonConverters/PubKeyJsonConverter.cs, DotNut/ApiModels/GetKeysResponse.cs
AnyKeysetJsonConverter infers and routes between Keyset (hex 66/96 chars) and BlsKeyset (hex 192 chars); BlsKeysetJsonConverter handles BLS-specific JSON mapping; existing converters updated to validate BLS vs secp256k1 formats; GetKeysResponse.Keys wired to AnyKeysetJsonConverter.
Output and Proof Derivation
DotNut/NUT13/Nut13.cs, DotNut/Abstractions/Utils.cs
Nut13 extended with v0x02 (BLS) secret/blinding-factor reduction; Utils.CreateOutputs branches on BLS keyset to use BlsCashu.BlindMessage vs Cashu.ComputeB_; new ConstructBlsProofFromPromise handles BLS unblinding and verification; ConstructProofsFromPromises overloaded for BlsKeyset.
Integration and Token Encoding
DotNut/Abstractions/SwapBuilder.cs, DotNut/Encoding/CashuTokenV4Encoder.cs
SwapBuilder.ProcessAsync skips DLEQ verification for BLS v3 proofs; CashuTokenV4Encoder.ToCBORObject conditionally serializes compressed BLS G1 bytes vs secp256k1 Key bytes.
Test Suite
DotNut.Tests/Unit/BlsTests.cs
Comprehensive test class covering BLS primitives (hash-to-curve, blinding/unblinding, signing/verification, batch verification), scalar operations, PubKey polymorphism, keyset ID identification and validation, BlsKeyset behavior, JSON round-tripping, Nut13 derivation, proof construction, and full integration scenarios with helper methods for keyset generation.

Sequence Diagram(s)

sequenceDiagram
  participant Wallet as Wallet<br/>(Client)
  participant BlsCashu as BlsCashu<br/>(Crypto)
  participant Mint as Mint<br/>(Server)
  Wallet->>BlsCashu: HashToCurveG1(secret) → Y
  Wallet->>BlsCashu: BlindMessage(secret, r) → B_
  Wallet->>Mint: POST /request_mint (B_)
  Mint->>BlsCashu: CreateBlindSignature(B_, privkey) → C_
  Mint->>Wallet: blind_signature: C_
  Wallet->>BlsCashu: UnblindSignature(C_, r) → C
  Wallet->>BlsCashu: VerifySignature(K2, C, secret) → true
  Wallet->>Wallet: Store proof {C, secret}
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Kukks/DotNut#15: Both PRs extend keyset/version handling by modifying DotNut/NUT02/KeysetId.cs and DotNut/NUT13/Nut13.cs version-switching logic (main PR adds v0x02 BLS/v3 branching and IsBlsKeyset(), retrieved PR adds GetVersion() and keyset-version/unit-aware derivation).
  • Kukks/DotNut#23: Main PR updates DotNut/Abstractions/SwapBuilder.ProcessAsync to skip DLEQ verification for BLS v3 proofs; retrieved PR introduced the SwapBuilder abstraction and its ProcessAsync flow.

Suggested reviewers

  • Kukks

Poem

🐰 A rabbit hops through cryptographic fields so green,
With G1, G2, and Fiat-Shamir—a pairing machine!
BLS-381 now signs each proof with care,
While keyset v3 blooms with blinded flair.
From secp to curves, Cashu takes its flight,
Hop-hop-hop! 🥕 The signature's just right!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Chore/bls12-381' is vague and does not clearly convey the primary changes of adding BLS12-381 support to the codebase. Consider using a more descriptive title such as 'Add BLS12-381 cryptographic support' or 'Implement Cashu v3 BLS signature operations' to better reflect the substantial changes being made.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
DotNut/NUT01/Keyset.cs (1)

32-35: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard secp256k1-only assumption in v0 keyset ID derivation.

pair.Value.Key.ToBytes() now dereferences a nullable key. If a BLS PubKey slips into a v0 keyset, this becomes a NullReferenceException instead of a clear contract error.

💡 Proposed fix
 var sortedBytes = sortedKeys
-    .Select(pair => pair.Value.Key.ToBytes())
+    .Select(pair => pair.Value.Key?.ToBytes()
+        ?? throw new ArgumentException("Version 0x00 keyset IDs require secp256k1 public keys only."))
     .SelectMany(b => b)
     .ToArray();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNut/NUT01/Keyset.cs` around lines 32 - 35, The current v0 keyset ID
derivation dereferences nullable keys when building sortedBytes (using
pair.Value.Key.ToBytes()), which can cause a NullReferenceException if a BLS
PubKey is present; update the logic around sortedKeys/sortedBytes in Keyset.cs
to explicitly validate each pair.Value.Key is non-null and of the expected
secp256k1 kind (or throw a clear contract exception) before calling ToBytes(),
and fail fast with a descriptive error when a non-secp256k1 or null key is
encountered to preserve the secp256k1-only assumption.
DotNut/Encoding/CashuTokenV4Encoder.cs (1)

109-109: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Decoding does not handle BLS G1 points, causing asymmetric encode/decode.

The encoding at line 44 correctly branches on IsBlsG1 to emit compressed BLS G1 bytes (48 bytes), but decoding unconditionally uses ECPubKey.Create(...) which expects 33-byte secp256k1 compressed keys. BLS v3 tokens will fail to decode with an exception from ECPubKey.Create.

The decoder needs to detect the key type (by length or by inspecting the keyset ID prefix) and construct the appropriate PubKey.

🐛 Proposed fix
-                                    C = ECPubKey.Create(proof["c"].GetByteString()),
+                                    C = id.IsBlsKeyset()
+                                        ? new PubKey(Convert.ToHexString(proof["c"].GetByteString()).ToLowerInvariant())
+                                        : ECPubKey.Create(proof["c"].GetByteString()),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNut/Encoding/CashuTokenV4Encoder.cs` at line 109, The decoder in
CashuTokenV4Encoder currently always calls ECPubKey.Create(...) for proof["c"],
which breaks for BLS G1 keys; change the decode path to detect key type (check
IsBlsG1 flag on the token or inspect proof["c"].GetByteString().Length == 48 vs
33 or keyset-id prefix) and construct the matching PubKey implementation instead
of unconditionally using ECPubKey.Create; when BLS/G1 is detected, call the
BLS/G1 public-key constructor (the project’s BLS pubkey factory/constructor) to
create the PubKey, otherwise keep using ECPubKey.Create so encode/decode are
symmetric.
🧹 Nitpick comments (4)
DotNut.Tests/Unit/BlsTests.cs (2)

148-174: 💤 Low value

Inconsistent indentation on line 159.

Line 159 has 4-space indentation instead of 8-space like the surrounding code.

🧹 Proposed fix
-        var B_ = BlsCashu.BlindMessage(secretBytes, r);
+            var B_ = BlsCashu.BlindMessage(secretBytes, r);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNut.Tests/Unit/BlsTests.cs` around lines 148 - 174, Fix the inconsistent
indentation inside the test method
BatchVerifySignatures_OneForgedSignature_ReturnsFalse: align the line that
declares var B_ = BlsCashu.BlindMessage(secretBytes, r); to use the same 8-space
indentation as the surrounding block so it matches the other statements inside
the for loop (and keep indentation consistent for related lines using variables
r, B_, C_, C and items.Add).

111-146: 💤 Low value

Inconsistent indentation on line 139.

Line 139 has 4-space indentation instead of 8-space like the surrounding code.

🧹 Proposed fix
-        var B_ = BlsCashu.BlindMessage(secretBytes, r);
+            var B_ = BlsCashu.BlindMessage(secretBytes, r);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNut.Tests/Unit/BlsTests.cs` around lines 111 - 146, In the
BatchVerifySignatures_MultipleValidItems test, one line inside the loop (the
BlsCashu.BlindMessage call that assigns B_) is indented with 4 spaces instead of
matching the surrounding 8-space block; fix by adjusting that line's indentation
to align with the other statements in the for loop (inside the method
BatchVerifySignatures_MultipleValidItems in BlsTests.cs) so formatting is
consistent.
DotNut/Abstractions/Utils.cs (1)

2-2: 💤 Low value

Unused import.

System.Text appears to be unused in this file. The Encoding class is not referenced anywhere in the visible code.

🧹 Proposed fix
-using System.Text;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNut/Abstractions/Utils.cs` at line 2, Remove the unused using directive
"using System.Text" from the top of Utils.cs; locate the using statement in the
file (near the namespace/class Utils declaration) and delete it, ensuring no
references to Encoding or System.Text remain in methods like any helper
functions inside the Utils class.
DotNut/Abstractions/SwapBuilder.cs (1)

152-159: ⚡ Quick win

BLS proofs receive no client-side verification when _verifyDleq is true.

When _verifyDleq is enabled, secp256k1 proofs get DLEQ verification, but BLS proofs skip the entire verification block via continue. If BLS signature verification should happen client-side (similar to how secp256k1 proofs are verified), consider adding a BLS-specific verification path using BlsCashu.VerifySignature.

If verification is intentionally deferred (e.g., performed during proof construction in ConstructBlsProofFromPromise), a brief comment clarifying this design decision would help maintainability.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNut/Abstractions/SwapBuilder.cs` around lines 152 - 159, The current block
skips client-side verification for BLS proofs (proof.DLEQ == null &&
proof.Id.IsBlsKeyset()) which causes BLS signatures to be unverified when
_verifyDleq is true; update the logic in SwapBuilder to either (a) add a
BLS-specific verification path that calls BlsCashu.VerifySignature for BLS
keysets when proof.DLEQ is null and _verifyDleq is enabled, locating the check
around proof.DLEQ and proof.Id.IsBlsKeyset(), or (b) if verification is
intentionally done elsewhere (e.g., ConstructBlsProofFromPromise), replace the
continue with a concise comment referencing ConstructBlsProofFromPromise to
document why client-side verification is skipped; ensure the behavior is gated
by _verifyDleq and that any thrown exceptions remain for unexpected missing
proofs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.gitmodules:
- Around line 1-3: The .gitmodules entry adds a submodule named
"DotNut.BLS12-381" pointing to a personal GitHub repo; replace or validate this
by either swapping the submodule to an established, audited BLS12-381
implementation (e.g., Neo.Cryptography.BLS12_381 NuGet package or blst with C#
bindings) or documenting and approving the security review for DotNut.BLS12-381;
update the submodule reference in .gitmodules (submodule "DotNut.BLS12-381")
accordingly and ensure build/CI uses the chosen source and that dependency
provenance and maintenance rationale are recorded.

In `@DotNut.BLS12-381`:
- Line 1: The submodule entry DotNut.BLS12-381 points to a non-existent commit
9eaeb6730144dd2008663d859a0c008b9176432f; update the submodule reference in
.gitmodules (and the gitlink in the index) to a valid commit or branch/tag
present in https://github.com/d4rp4t/DotNut.BLS12-381.git, then run git
submodule sync && git submodule update --init --recursive (or set the submodule
to track a branch and update that ref); ensure the commit hash you set actually
exists in the remote and commit the .gitmodules/index change so CI can clone the
submodule successfully.

In `@DotNut/Crypto/BlsCashu.cs`:
- Around line 26-30: BlindMessage accepts a zero blinding scalar which yields
the point at infinity; change BlindMessage to convert r to a Scalar (use
Scalar.FromBytesBigEndian or equivalent), check that the resulting scalar is not
zero (Scalar.IsZero or compare to Scalar.Zero) and throw an ArgumentException
(or other fast-fail) if it is zero before computing HashToCurveG1(secret) *
scalar; reference BlindMessage, HashToCurveG1, and Scalar.FromBytesBigEndian
when applying the check.

In `@DotNut/JsonConverters/BlsKeysetJsonConverter.cs`:
- Around line 35-43: The code currently uses ulong.Parse and reads
reader.GetString() without verifying the token type; update the Read logic in
BlsKeysetJsonConverter (the branch handling reader.TokenType ==
JsonTokenType.String or PropertyName and the subsequent hex-reading branch) to
first validate the token type before calling reader.GetString(), then replace
ulong.Parse(s) with ulong.TryParse(s, out amount) and throw a JsonException if
TryParse returns false (so parsing failures produce JsonException rather than
FormatException/OverflowException); apply the same pattern to any other
numeric/hex parsing in the method to ensure consistent JsonException error
handling.

In `@DotNut/PubKey.cs`:
- Around line 26-34: The hex constructor for PubKey currently accepts
96/192-char BLS compressed byte arrays and stores them in
_blsG1Bytes/_blsG2Bytes without validating the decoded points or rejecting the
infinity point, which is inconsistent with PubKey(G1Affine)/PubKey(G2Affine);
update the hex constructor to decode the bytes, convert to G1/G2 affine points
and throw the same error used by PubKey(G1Affine)/PubKey(G2Affine) if the point
is infinity, and update GetBlsG1Point() and GetBlsG2Point() to likewise parse
the stored bytes into affine points and throw on infinity (mirroring the checks
in BlsG1PubKey and BlsG2PubKey) so all paths uniformly reject infinity points.

---

Outside diff comments:
In `@DotNut/Encoding/CashuTokenV4Encoder.cs`:
- Line 109: The decoder in CashuTokenV4Encoder currently always calls
ECPubKey.Create(...) for proof["c"], which breaks for BLS G1 keys; change the
decode path to detect key type (check IsBlsG1 flag on the token or inspect
proof["c"].GetByteString().Length == 48 vs 33 or keyset-id prefix) and construct
the matching PubKey implementation instead of unconditionally using
ECPubKey.Create; when BLS/G1 is detected, call the BLS/G1 public-key constructor
(the project’s BLS pubkey factory/constructor) to create the PubKey, otherwise
keep using ECPubKey.Create so encode/decode are symmetric.

In `@DotNut/NUT01/Keyset.cs`:
- Around line 32-35: The current v0 keyset ID derivation dereferences nullable
keys when building sortedBytes (using pair.Value.Key.ToBytes()), which can cause
a NullReferenceException if a BLS PubKey is present; update the logic around
sortedKeys/sortedBytes in Keyset.cs to explicitly validate each pair.Value.Key
is non-null and of the expected secp256k1 kind (or throw a clear contract
exception) before calling ToBytes(), and fail fast with a descriptive error when
a non-secp256k1 or null key is encountered to preserve the secp256k1-only
assumption.

---

Nitpick comments:
In `@DotNut.Tests/Unit/BlsTests.cs`:
- Around line 148-174: Fix the inconsistent indentation inside the test method
BatchVerifySignatures_OneForgedSignature_ReturnsFalse: align the line that
declares var B_ = BlsCashu.BlindMessage(secretBytes, r); to use the same 8-space
indentation as the surrounding block so it matches the other statements inside
the for loop (and keep indentation consistent for related lines using variables
r, B_, C_, C and items.Add).
- Around line 111-146: In the BatchVerifySignatures_MultipleValidItems test, one
line inside the loop (the BlsCashu.BlindMessage call that assigns B_) is
indented with 4 spaces instead of matching the surrounding 8-space block; fix by
adjusting that line's indentation to align with the other statements in the for
loop (inside the method BatchVerifySignatures_MultipleValidItems in BlsTests.cs)
so formatting is consistent.

In `@DotNut/Abstractions/SwapBuilder.cs`:
- Around line 152-159: The current block skips client-side verification for BLS
proofs (proof.DLEQ == null && proof.Id.IsBlsKeyset()) which causes BLS
signatures to be unverified when _verifyDleq is true; update the logic in
SwapBuilder to either (a) add a BLS-specific verification path that calls
BlsCashu.VerifySignature for BLS keysets when proof.DLEQ is null and _verifyDleq
is enabled, locating the check around proof.DLEQ and proof.Id.IsBlsKeyset(), or
(b) if verification is intentionally done elsewhere (e.g.,
ConstructBlsProofFromPromise), replace the continue with a concise comment
referencing ConstructBlsProofFromPromise to document why client-side
verification is skipped; ensure the behavior is gated by _verifyDleq and that
any thrown exceptions remain for unexpected missing proofs.

In `@DotNut/Abstractions/Utils.cs`:
- Line 2: Remove the unused using directive "using System.Text" from the top of
Utils.cs; locate the using statement in the file (near the namespace/class Utils
declaration) and delete it, ensuring no references to Encoding or System.Text
remain in methods like any helper functions inside the Utils class.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 082af3f6-3ec8-4c47-880a-73156fcec681

📥 Commits

Reviewing files that changed from the base of the PR and between d6b4d36 and 925c598.

📒 Files selected for processing (22)
  • .gitmodules
  • DotNut.BLS12-381
  • DotNut.Tests/Unit/BlsTests.cs
  • DotNut.sln
  • DotNut/Abstractions/SwapBuilder.cs
  • DotNut/Abstractions/Utils.cs
  • DotNut/ApiModels/GetKeysResponse.cs
  • DotNut/BlsG1PubKey.cs
  • DotNut/BlsG2PubKey.cs
  • DotNut/Crypto/BlsCashu.cs
  • DotNut/DotNut.csproj
  • DotNut/Encoding/CashuTokenV4Encoder.cs
  • DotNut/JsonConverters/AnyKeysetJsonConverter.cs
  • DotNut/JsonConverters/BlsKeysetJsonConverter.cs
  • DotNut/JsonConverters/KeysetJsonConverter.cs
  • DotNut/JsonConverters/Nut10SecretJsonConverter.cs
  • DotNut/JsonConverters/PubKeyJsonConverter.cs
  • DotNut/NUT01/BlsKeyset.cs
  • DotNut/NUT01/Keyset.cs
  • DotNut/NUT02/KeysetId.cs
  • DotNut/NUT13/Nut13.cs
  • DotNut/PubKey.cs

Comment thread .gitmodules
Comment on lines +1 to +3
[submodule "DotNut.BLS12-381"]
path = DotNut.BLS12-381
url = https://github.com/d4rp4t/DotNut.BLS12-381.git

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the submodule repository is accessible
echo "=== Checking submodule repository accessibility ==="
curl -s -o /dev/null -w "%{http_code}" https://github.com/d4rp4t/DotNut.BLS12-381 | grep -q "200" && echo "Repository is accessible" || echo "Repository may not be accessible"

# Check repository metadata
echo -e "\n=== Repository metadata ==="
gh repo view d4rp4t/DotNut.BLS12-381 --json name,description,createdAt,pushedAt,stargazerCount,forkCount,isArchived,isFork,parent || echo "Failed to fetch repository metadata"

Repository: Kukks/DotNut

Length of output: 357


🌐 Web query:

BLS12-381 .NET library audited cryptographic

💡 Result:

There isn’t evidence (from authoritative/official sources) that a specific.NET library implementing BLS12-381 has been independently “audited cryptographically” (i.e., has a published third-party audit report) as a.NET library. What I did find: 1) Neo.Cryptography.BLS12_381 (.NET) - The NuGet package exists (example: Neo.Cryptography.BLS12_381 3.9.0) [1]. - The repository’s GitHub “Security” page shows no published security advisories and indicates no SECURITY.md policy file is detected [2]. This is not a cryptographic audit confirmation. 2) BLS12-381 audit evidence exists for non-.NET implementations, and sometimes they have C#/bindings - The supranational blst library (C/assembly; provides C# bindings via SWIG) states: “An initial audit of this library was conducted by NCC Group in January 2021” and links to where the audit is “here” [3]. This is evidence of third-party audit for the underlying library, but it is not an audit claim specifically for any.NET wrapper quality. 3) gnark-crypto - gnark-crypto’s README states: “gnark-crypto is not fully audited … [and] makes no security guarantees such as constant time … or side-channel attack resistance” [4]. So even if audit reports exist for certain curves (including bls12-381), it does not amount to a fully-audited security claim for the whole package. Conclusion - For “BLS12-381.NET library audited cryptographic”: the closest directly sourced third-party audit evidence I found is for the underlying blst implementation (NCC Group audit in Jan 2021) and that it offers C# bindings [3]. For.NET libraries specifically (e.g., Neo.Cryptography.BLS12_381), I found no published third-party cryptographic audit status in the sources retrieved. If you share the exact NuGet package name (and version), I can re-check whether it references a specific audit (for the exact code version) or whether there are any published third-party audit reports tied to that.NET library.

Citations:


Verify the cryptographic library source and consider established alternatives.

The submodule points to a personal repository (d4rp4t/DotNut.BLS12-381) for BLS12-381 cryptographic operations. While the repository is accessible and recently active (last updated 2026-05-20), using a novel, community-unreviewed implementation for cryptographic operations raises significant concerns:

  • Supply chain security: This is a novel implementation (not a fork of an established library) with zero community adoption (0 stars, 0 forks). Cryptographic libraries require rigorous security review and established trust.
  • Availability risk: Personal repositories can be deleted, made private, or renamed, breaking builds for all users.
  • Maintenance: Long-term maintenance and security updates may not be guaranteed.

Verify whether established .NET BLS12-381 libraries meet your requirements before committing to this implementation. Known alternatives include:

  1. Neo.Cryptography.BLS12_381 — Available on NuGet with version history
  2. Supranational blst — C library with C# bindings; underwent third-party audit by NCC Group (January 2021)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.gitmodules around lines 1 - 3, The .gitmodules entry adds a submodule named
"DotNut.BLS12-381" pointing to a personal GitHub repo; replace or validate this
by either swapping the submodule to an established, audited BLS12-381
implementation (e.g., Neo.Cryptography.BLS12_381 NuGet package or blst with C#
bindings) or documenting and approving the security review for DotNut.BLS12-381;
update the submodule reference in .gitmodules (submodule "DotNut.BLS12-381")
accordingly and ensure build/CI uses the chosen source and that dependency
provenance and maintenance rationale are recorded.

Comment thread DotNut.BLS12-381
@@ -0,0 +1 @@
Subproject commit 9eaeb6730144dd2008663d859a0c008b9176432f

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚖️ Poor tradeoff

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify submodule configuration and commit accessibility

# Check .gitmodules for repository URL
echo "=== Submodule Configuration ==="
cat .gitmodules | grep -A 2 "DotNut.BLS12-381"

# Verify the submodule commit exists and is accessible
cd DotNut.BLS12-381 2>/dev/null || {
  echo "ERROR: Submodule not initialized"
  exit 1
}

echo -e "\n=== Commit Information ==="
git log -1 --format="%H%n%an <%ae>%n%ad%n%s" 9eaeb6730144dd2008663d859a0c008b9176432f

echo -e "\n=== Remote Repository ==="
git remote get-url origin

# Check recent commits/tags to see if this is latest
echo -e "\n=== Recent Tags ==="
git tag --sort=-version:refname | head -5

echo -e "\n=== Commits Since Referenced Commit ==="
git log --oneline 9eaeb6730144dd2008663d859a0c008b9176432f..HEAD 2>/dev/null || echo "This is the latest commit in the current branch"

Repository: Kukks/DotNut

Length of output: 589


The submodule references a non-existent commit in the target repository.

The commit 9eaeb6730144dd2008663d859a0c008b9176432f does not exist in https://github.com/d4rp4t/DotNut.BLS12-381.git. Verification failed with: fatal: remote error: upload-pack: not our ref.

This prevents cloning and initializing the submodule. Confirm the commit hash is correct or use a valid commit that exists in the target repository.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNut.BLS12-381` at line 1, The submodule entry DotNut.BLS12-381 points to a
non-existent commit 9eaeb6730144dd2008663d859a0c008b9176432f; update the
submodule reference in .gitmodules (and the gitlink in the index) to a valid
commit or branch/tag present in https://github.com/d4rp4t/DotNut.BLS12-381.git,
then run git submodule sync && git submodule update --init --recursive (or set
the submodule to track a branch and update that ref); ensure the commit hash you
set actually exists in the remote and commit the .gitmodules/index change so CI
can clone the submodule successfully.

Comment thread DotNut/Crypto/BlsCashu.cs
Comment on lines +26 to +30
public static G1Affine BlindMessage(byte[] secret, byte[] r)
{
var Y = HashToCurveG1(secret);
return (Y.ToProjective() * Scalar.FromBytesBigEndian(r)).ToAffine();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject zero blinding factor in BlindMessage.

BlindMessage currently accepts r = 0 mod Fr, which yields the point at infinity and creates invalid blinded messages instead of failing fast.

💡 Proposed fix
 public static G1Affine BlindMessage(byte[] secret, byte[] r)
 {
     var Y = HashToCurveG1(secret);
-    return (Y.ToProjective() * Scalar.FromBytesBigEndian(r)).ToAffine();
+    var rScalar = Scalar.FromBytesBigEndian(r);
+    if (Scalar.IsZero(rScalar))
+        throw new ArgumentException("Blinding factor reduces to zero mod Fr");
+    return (Y.ToProjective() * rScalar).ToAffine();
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static G1Affine BlindMessage(byte[] secret, byte[] r)
{
var Y = HashToCurveG1(secret);
return (Y.ToProjective() * Scalar.FromBytesBigEndian(r)).ToAffine();
}
public static G1Affine BlindMessage(byte[] secret, byte[] r)
{
var Y = HashToCurveG1(secret);
var rScalar = Scalar.FromBytesBigEndian(r);
if (Scalar.IsZero(rScalar))
throw new ArgumentException("Blinding factor reduces to zero mod Fr");
return (Y.ToProjective() * rScalar).ToAffine();
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNut/Crypto/BlsCashu.cs` around lines 26 - 30, BlindMessage accepts a zero
blinding scalar which yields the point at infinity; change BlindMessage to
convert r to a Scalar (use Scalar.FromBytesBigEndian or equivalent), check that
the resulting scalar is not zero (Scalar.IsZero or compare to Scalar.Zero) and
throw an ArgumentException (or other fast-fail) if it is zero before computing
HashToCurveG1(secret) * scalar; reference BlindMessage, HashToCurveG1, and
Scalar.FromBytesBigEndian when applying the check.

Comment on lines +35 to +43
else if (reader.TokenType is JsonTokenType.String or JsonTokenType.PropertyName)
{
var s = reader.GetString();
if (string.IsNullOrEmpty(s))
{
throw new JsonException("Expected string key");
}
amount = ulong.Parse(s);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In .NET 8, what exceptions can ulong.Parse(string)throw, and isulong.TryParsepreferred inSystem.Text.Jsonconverters to preserveJsonException-based error handling?

💡 Result:

In.NET 8, calling ulong.Parse(string) can throw the following exceptions: - ArgumentNullException: Thrown if the input string is null [1]. - FormatException: Thrown if the input string is not in the correct numeric format [1]. - OverflowException: Thrown if the value represented by the string is less than UInt64.MinValue or greater than UInt64.MaxValue [1]. Regarding System.Text.Json custom converters, using ulong.TryParse is often preferred, but not specifically to preserve JsonException behavior; rather, it is a defensive programming best practice to handle invalid data gracefully [2]. System.Text.Json itself relies on JsonException to communicate deserialization failures [3][4]. If you are writing a custom converter, you should throw a JsonException when data is malformed or invalid [5]. Using ulong.TryParse allows you to detect parsing failures without relying on the overhead of catching exceptions [2]. If TryParse fails, you can then manually throw a descriptive JsonException, which ensures that your custom converter remains consistent with the framework's standard error reporting mechanism [3].

Citations:


🏁 Script executed:

find . -name "BlsKeysetJsonConverter.cs" -type f

Repository: Kukks/DotNut

Length of output: 106


🏁 Script executed:

cat -n ./DotNut/JsonConverters/BlsKeysetJsonConverter.cs | head -60

Repository: Kukks/DotNut

Length of output: 2287


Use TryParse for amount parsing and validate token type before reading hex value.

On line 42, ulong.Parse can throw FormatException or OverflowException instead of JsonException, breaking error consistency. Use TryParse and throw JsonException on failure.

On line 49-50, the code calls reader.GetString() without validating the token type first. Add a token type check before reading the hex value to prevent unexpected exceptions.

🔧 Proposed fix
-                amount = ulong.Parse(s);
+                if (!ulong.TryParse(s, out amount))
+                {
+                    throw new JsonException($"Invalid amount key: '{s}'");
+                }
             reader.Read();
+            if (reader.TokenType != JsonTokenType.String)
+            {
+                throw new JsonException("Expected string value for public key");
+            }
             var hex = reader.GetString();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
else if (reader.TokenType is JsonTokenType.String or JsonTokenType.PropertyName)
{
var s = reader.GetString();
if (string.IsNullOrEmpty(s))
{
throw new JsonException("Expected string key");
}
amount = ulong.Parse(s);
}
else if (reader.TokenType is JsonTokenType.String or JsonTokenType.PropertyName)
{
var s = reader.GetString();
if (string.IsNullOrEmpty(s))
{
throw new JsonException("Expected string key");
}
if (!ulong.TryParse(s, out amount))
{
throw new JsonException($"Invalid amount key: '{s}'");
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNut/JsonConverters/BlsKeysetJsonConverter.cs` around lines 35 - 43, The
code currently uses ulong.Parse and reads reader.GetString() without verifying
the token type; update the Read logic in BlsKeysetJsonConverter (the branch
handling reader.TokenType == JsonTokenType.String or PropertyName and the
subsequent hex-reading branch) to first validate the token type before calling
reader.GetString(), then replace ulong.Parse(s) with ulong.TryParse(s, out
amount) and throw a JsonException if TryParse returns false (so parsing failures
produce JsonException rather than FormatException/OverflowException); apply the
same pattern to any other numeric/hex parsing in the method to ensure consistent
JsonException error handling.

Comment thread DotNut/PubKey.cs
Comment on lines +26 to +34
if (hex.Length == 96) // BLS G1 compressed: 48 bytes
{
throw new ArgumentException("Only compressed public keys are allowed");
_blsG1Bytes = Convert.FromHexString(hex);
return;
}
if (hex.Length == 192) // BLS G2 compressed: 96 bytes
{
_blsG2Bytes = Convert.FromHexString(hex);
return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

find . -name "PubKey.cs" -type f

Repository: Kukks/DotNut

Length of output: 75


🏁 Script executed:

cat -n ./DotNut/PubKey.cs | head -60

Repository: Kukks/DotNut

Length of output: 2322


🏁 Script executed:

cat -n ./DotNut/PubKey.cs | sed -n '60,100p'

Repository: Kukks/DotNut

Length of output: 2026


🏁 Script executed:

find . -name "*.cs" -type f | xargs grep -l "class G1Affine\|struct G1Affine" | head -5

Repository: Kukks/DotNut

Length of output: 38


🏁 Script executed:

rg "G1Affine.*TryFromCompressed" -A 2 -B 2

Repository: Kukks/DotNut

Length of output: 1091


🏁 Script executed:

rg "G2Affine.*TryFromCompressed" -A 2 -B 2

Repository: Kukks/DotNut

Length of output: 1105


🏁 Script executed:

rg "_blsG1Bytes|_blsG2Bytes" -B 2 -A 2

Repository: Kukks/DotNut

Length of output: 4135


🏁 Script executed:

rg "new PubKey\(" | head -20

Repository: Kukks/DotNut

Length of output: 1884


Add BLS infinity point validation to the hex constructor and getter methods.

The hex constructor accepts 96/192-char payloads and stores them as raw bytes without validating that they represent valid curve points or checking for infinity points. This creates an inconsistency with the internal constructors PubKey(G1Affine) and PubKey(G2Affine), which explicitly reject infinity points. Additionally, GetBlsG1Point() and GetBlsG2Point() should validate against infinity, consistent with the pattern used in BlsG1PubKey and BlsG2PubKey.

💡 Proposed fix
 if (hex.Length == 96)  // BLS G1 compressed: 48 bytes
 {
-    _blsG1Bytes = Convert.FromHexString(hex);
+    var g1 = Convert.FromHexString(hex);
+    if (!G1Affine.TryFromCompressed(g1, out var p) || p.IsInfinity)
+        throw new ArgumentException("Invalid BLS G1 compressed public key");
+    _blsG1Bytes = g1;
     return;
 }
 if (hex.Length == 192)  // BLS G2 compressed: 96 bytes
 {
-    _blsG2Bytes = Convert.FromHexString(hex);
+    var g2 = Convert.FromHexString(hex);
+    if (!G2Affine.TryFromCompressed(g2, out var p) || p.IsInfinity)
+        throw new ArgumentException("Invalid BLS G2 compressed public key");
+    _blsG2Bytes = g2;
     return;
 }

Also add infinity checks to the getter methods to match BlsG1PubKey/BlsG2PubKey:

 if (!G1Affine.TryFromCompressed(_blsG1Bytes, out var p))
     throw new InvalidOperationException("Stored BLS G1 bytes are invalid");
+if (p.IsInfinity)
+    throw new InvalidOperationException("BLS G1 point at infinity is not valid");
 return p;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DotNut/PubKey.cs` around lines 26 - 34, The hex constructor for PubKey
currently accepts 96/192-char BLS compressed byte arrays and stores them in
_blsG1Bytes/_blsG2Bytes without validating the decoded points or rejecting the
infinity point, which is inconsistent with PubKey(G1Affine)/PubKey(G2Affine);
update the hex constructor to decode the bytes, convert to G1/G2 affine points
and throw the same error used by PubKey(G1Affine)/PubKey(G2Affine) if the point
is infinity, and update GetBlsG1Point() and GetBlsG2Point() to likewise parse
the stored bytes into affine points and throw on infinity (mirroring the checks
in BlsG1PubKey and BlsG2PubKey) so all paths uniformly reject infinity points.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant