NetCrypto 1.6.0
dotnet add package NetCrypto --version 1.6.0
NuGet\Install-Package NetCrypto -Version 1.6.0
<PackageReference Include="NetCrypto" Version="1.6.0" />
<PackageVersion Include="NetCrypto" Version="1.6.0" />
<PackageReference Include="NetCrypto" />
paket add NetCrypto --version 1.6.0
#r "nuget: NetCrypto, 1.6.0"
#:package NetCrypto@1.6.0
#addin nuget:?package=NetCrypto&version=1.6.0
#tool nuget:?package=NetCrypto&version=1.6.0
NetCrypto
Unified cryptographic primitives for the NetCid/NetDid library stack: EdDSA, ECDSA (NIST curves and secp256k1, including recoverable), BLS12-381, BBS selective-disclosure signatures, X25519/ECDH, KDFs, AEADs, hashing (including Keccak-256), a key model with multibase/multicodec encoding, signing and key-store abstractions, and JWK conversion.
NetCrypto is the single home for every cryptographic primitive in the stack, behind
stable interfaces, so that no domain library (net-did, dataproofs-dotnet,
credentials-dotnet, didcomm-dotnet) binds directly to a specific crypto backend.
dotnet add package NetCrypto
Stable release. NetCrypto has been generally available since
1.0.0and ships as a stable package — no--prereleaseflag is required.PublicAPI.Shipped.txtis the authoritative contract for every published API;PublicAPI.Unshipped.txtholds only surface added since the last release and is empty on a tagged release. Semantic versioning applies — additive changes bump the minor version, breaking changes the major; any pre-GA1.0.0-preview.*packages are superseded. See CHANGELOG.md for the release history and the current version.
Target framework: net10.0. Depends on NetCid
for multibase/multicodec encoding.
Quick start
using NetCrypto;
var keyGen = new DefaultKeyGenerator();
var crypto = new DefaultCryptoProvider();
using var keyPair = keyGen.Generate(KeyType.Ed25519); // Dispose zeroizes the key material
byte[] signature = keyPair.WithPrivateKey( // borrow the secret — no heap copy escapes
privateKey => crypto.Sign(keyPair.KeyType, privateKey, data));
bool valid = crypto.Verify(keyPair.KeyType, keyPair.PublicKey, data, signature);
Console.WriteLine(keyPair.MultibasePublicKey); // z6Mk... (multicodec + base58btc)
With dependency injection (Microsoft.Extensions.DependencyInjection):
services.AddNetCrypto(); // TryAdd: your own ICryptoProvider/IBbsCryptoProvider/IKeyGenerator
// registrations made BEFORE this call win (the swap seam).
Learning the API: samples
The primary usage documentation is samples/README.md — eleven
standalone, runnable console programs covering 100% of the public API surface
(enforced in CI by tools/ApiCoverageCheck). Start with NetCrypto.Samples.Keys and
follow the reading order in the samples index.
Implementing a capable key store
IKeyStore says "sign with the key behind this alias". That is enough for an in-memory store
and not enough for a custody backend — a cloud KMS, an HSM partition, an encrypted software
keystore — where the consumer additionally needs to know what the backend can do before first
use, which tenant it may touch, which key instance an alias currently holds, how to retry a
mutation that may or may not have been applied, and which encoding a signature comes back in.
ICapableKeyStore : IKeyStore, IKeyStoreCapabilityProvider (1.6.0) is that contract. It is
purely additive: IKeyStore gains no member, so every existing store, ISigner,
KeyStoreSigner, and InMemoryKeyStore caller is source- and binary-compatible. It adds no
export operation anywhere, no KDF, and no protocol semantics.
CapableInMemoryKeyStore is the reference implementation and the contract oracle;
samples/NetCrypto.Samples.CapableKeyStore is the worked example.
If you are writing a backend adapter, these ten rules are the contract — the shape alone is not:
- Discovery honesty is bidirectional. Everything you advertise must work; everything you do
not advertise must fail with
KeyStoreError.Unsupportedbefore any key creation, signing, or agreement. Never silently downgrade the algorithm, curve, hash, or encoding. Discovery is side-effect-free and returns a deeply immutable snapshot whoseRevisionis stable for the instance lifetime. A temporary outage isUnavailable, never an empty capability set — an empty set is indistinguishable from "this backend can do nothing" and makes callers reroute permanently around a backend that is merely down. - Algorithm ids bind the observable encoding.
es256-derandes256-p1363are the same curve and hash but different wire bytes; JOSE/JWS/COSE/WebAuthn mandate the latter, X.509/CMS the former. Use the identifiers onKeyStoreAlgorithms, and never reuse one of them for different bytes. You may advertise identifiers of your own — that is whyKeyStoreAlgorithmIdis a string rather than a closed enum. - Instance identity is immutable and never reused. Mint a fresh
KeyInstanceIdon generate and import; make it permanently unusable on delete; yield a different one when the alias is re-created. This is what makes alias rebinding detectable rather than silent. - Namespace scoping is least-privilege, and it applies to the inherited
IKeyStoremembers too. Naming is not authorization. - Mutations are durably idempotent under
(NamespaceId, KeyMutationKind, KeyOperationId). Commit the mutation, a canonical request fingerprint, and the receipt atomically. Same id and same request replays; same id and a different request isIdempotencyConflict. Make the fingerprint encoding unambiguous across field boundaries — length-prefix it; a delimiter-joined encoding lets an alias containing the delimiter collide with another request. - Cancellation never lies. Before irreversible acceptance it changes nothing and throws
OperationCanceledException. After acceptance, return success, definite failure, orOutcomeUnknown— never cancellation as an implied rollback. - Import transfers ownership exactly once, through
TransferableKeyMaterial. Read it once at acceptance; a failure before acceptance leaves it usable for exactly one retry; a recognized replay must destroy it without reading it, because anOutcomeUnknownimport is reconciled throughGetMutationOutcomeAsynconly — private material is never resubmitted. - BBS is advertised only where you can really do it, and plain BLS signing is never advertised or accepted as BBS.
- Bounds are finite. Every capability carries a positive
MaxInputBytes;nulland0are not available to mean "unbounded". - Errors are portable. Surface
KeyStoreExceptionwith the taxonomy (Unsupported,IdempotencyConflict,AccessDenied,Throttled,Unavailable,OutcomeUnknown) plusRetryAfterwhere the backend gives one, and let no vendor SDK exception type escape. Keep argument faults as parameter-namedArgumentException— a caller's own mistake must not be retried as a backend condition.
Algorithm identifiers
| Id | KeyType | Operation | Observable encoding |
|---|---|---|---|
ed25519 |
Ed25519 | Sign | 64-byte EdDSA (RFC 8032) |
es256-der / es256-p1363 |
P-256 | Sign | DER / 64-byte R‖S |
es384-der / es384-p1363 |
P-384 | Sign | DER / 96-byte R‖S |
es512-der / es512-p1363 |
P-521 | Sign | DER / 132-byte R‖S |
es256k |
secp256k1 | Sign | 64-byte compact R‖S (no DER variant exists) |
bls12381g1-basic / bls12381g2-basic |
BLS12-381 G1 / G2 | Sign | 96- / 48-byte BLS basic |
ecdh-x25519 / ecdh-p256 / ecdh-p384 / ecdh-p521 |
resp. | KeyAgreement | raw Z: 32 / 32 / 48 / 66 bytes |
bbs-bls12381-sha256 |
BLS12-381 G2 | BbsSign | 80-byte BBS (draft-10) |
Algorithm and specification conformance
Every primitive is tested against the test vectors of its governing specification.
| Capability | Algorithm(s) | Backend | Specification | Vectors in test suite |
|---|---|---|---|---|
| Signatures | EdDSA (Ed25519) | NSec (libsodium) | RFC 8032 | §7.1 TEST 1–3 |
| Signatures | ECDSA P-256 / P-384 / P-521, DER and IEEE P1363 | .NET BCL | FIPS 186-5 | cross-format + round-trip |
| Signatures | ECDSA secp256k1 (SHA-256 prehash, 64-byte compact; low-S signing, low/high-S verification) | NBitcoin.Secp256k1 | SEC 2 / RFC 8812 ES256K | round-trip + high-S interop |
| Signatures | Recoverable secp256k1 over a caller-supplied digest | NBitcoin.Secp256k1 | SEC 2; raw recovery id (no EVM v-encoding) |
EIP-155 example vector |
| Signatures | BLS12-381 (G1 and G2 variants, hash-to-curve) | Nethermind.Crypto.Bls | RFC 9380 DSTs | round-trip + parity |
| Signatures | BBS (multi-message, selective disclosure) | zkryptium 0.6 via Rust FFI | draft-irtf-cfrg-bbs-signatures-10 (pinned) | §8.4.1 BLS12-381-SHA-256 KeyGen fixture |
| Key agreement | X25519 (+ HKDF-SHA256 convenience) | NSec | RFC 7748 / RFC 5869 | two-party equality |
| Key agreement | Raw ECDH Z: X25519, P-256, P-384, P-521 | BCL | RFC 7518 §4.6 usage | two-party equality |
| KDF | Concat KDF | managed | NIST SP 800-56A §5.8.1 | parity tests |
| KDF | HKDF (SHA-256/384/512) | BCL | RFC 5869 | Appendix A cases 1, 3 |
| Hashing | SHA-256 / SHA-384 / SHA-512 | BCL | FIPS 180-4 | known answers ("abc", "") |
| Hashing | Keccak-256 (original padding 0x01 — not SHA3-256) |
vendored sponge | Keccak submission / Ethereum | KATs, 1000-input differential vs reference, SHA3 negative control, address KAT |
| AEAD | AES-256-GCM (A256GCM) |
BCL AesGcm |
NIST SP 800-38D | NIST CAVP vectors |
| AEAD | AES-256-CBC + HMAC-SHA-512 (A256CBC-HS512) |
composed from BCL | RFC 7518 §5.2.2 | Appendix B.3 |
| AEAD | XChaCha20-Poly1305 (XC20P) |
NSec | draft-irtf-cfrg-xchacha-03 | Appendix A.3 |
| Key wrap | AES Key Wrap (A256KW) |
managed | RFC 3394 | §4.3, §4.6 |
| Key model | KeyType ⇄ multicodec, MultibasePublicKey |
NetCid | multiformats | golden parity values |
| Key repr. | JWK ⇄ raw key bytes (all key types) | Microsoft.IdentityModel.Tokens | RFC 7517 | round-trips |
ECDSA signatures are not unique per message. For every ECDSA key type above — P-256, P-384,
P-521 and secp256k1 — both (R, S) and (R, n − S) are valid signatures over the same key and
message, and Verify accepts both. Neither FIPS 186-5 nor RFC 8812 imposes a low-S rule; that is
a Bitcoin/BIP-62 convention. Two consequences worth designing around:
- Never key a replay cache, dedup set, or idempotency check on signature bytes. Re-submitting
the other encoding defeats it. Bind replay protection to the message — a nonce,
jti, or digest. - If your protocol requires BIP-62/EIP-2 canonical signatures, enforce
S ≤ n/2yourself at your boundary. NetCrypto's ownKeyStoreSignerdoes exactly this for recoverable EVM output.
secp256k1 signing remains deterministic and low-S; only verification is permissive, normalizing a
valid high-S signature to its equivalent low-S form before the Bitcoin-oriented backend check so
RFC 8812 ES256K peers interoperate (#23).
This aligned secp256k1 with the NIST curves' long-standing behavior rather than introducing a new
one. Secp256k1Recoverable is likewise high-S tolerant and documents that recovery hands the
canonicality decision to the caller.
BBS terminology. "BBS" is the CFRG name for the scheme historically called BBS+. Conformance is pinned to draft-10 via zkryptium 0.6; the
BbsCiphersuiteparameter (onlyBls12381Sha256in v1) and the FFI isolation contain future draft churn.BBS header vs presentation header.
Sign/Verify/DeriveProof/VerifyProoftake an optionalheader(default empty): data the signer binds at sign time and that every derived proof commits — the holder cannot drop or alter it (e.g. the W3Cbbs-2023cryptosuite binds its mandatory-disclosure group here). It is distinct from thepresentationHeader(ph) onDeriveProof/VerifyProof, which the holder chooses at derive time (typically the verifier's challenge).
Native BBS library and the supported "BBS-absent" mode
BBS is the only non-managed primitive: the zkryptium-ffi Rust crate
(native/zkryptium-ffi/) is compiled per platform and shipped
inside this single NuGet package under runtimes/{rid}/native/. A consumer that restores
NetCrypto gets the BBS native payload transitively — no per-consumer native assets are needed.
Supported native platform matrix
| RID | Library file | BBS at runtime |
|---|---|---|
osx-arm64 |
libzkryptium_ffi.dylib |
✅ built + smoke-executed in CI |
osx-x64 |
libzkryptium_ffi.dylib |
✅ shipped (cross-compiled; Rosetta-capable hosts load it) |
linux-x64 |
libzkryptium_ffi.so |
✅ built + smoke-executed in CI |
linux-arm64 |
libzkryptium_ffi.so |
✅ shipped (cross-compiled) |
win-x64 |
zkryptium_ffi.dll |
✅ built + smoke-executed in CI |
On any of these RIDs IBbsCryptoProvider.IsAvailable returns true after a plain
dotnet restore. The tag-triggered release workflow fails the build if any RID's native
payload is missing from the produced .nupkg (the "Verify nupkg contains all five RIDs" step),
publishes a SHA-256 checksum per binary, and then smoke-tests the packed package — installing
it into a clean console app and running a BBS sign/verify round-trip — so the guarantee is
verified against the actual published artifact, not the source tree.
All managed primitives work with no native library present (unlisted platforms, or environments that prohibit native code). This is a supported, CI-tested mode:
var bbs = new DefaultBbsCryptoProvider();
if (!bbs.IsAvailable)
{
// Probe never throws. Any BBS operation would throw BbsUnavailableException
// (InnerException carries the original native load error).
}
BBS key generation
The supported way to mint a BBS keypair is the standard key generator — the same one used for every other key type:
var keyPair = new DefaultKeyGenerator().Generate(KeyType.Bls12381G2); // or Bls12381G1
// keyPair.PrivateKey (32-byte BLS12-381 scalar), keyPair.PublicKey (96-byte G2 / 48-byte G1)
byte[] sig = new DefaultBbsCryptoProvider().Sign(keyPair.PrivateKey, messages);
Bls12381G2 (96-byte public key) is the variant the BBS provider signs/verifies with. Raw FFI
keygen (bbs_keygen) is intentionally internal — there is no public raw-keygen helper, by
design, to keep the public surface minimal and backend-agnostic. See
samples/NetCrypto.Samples.Bbs for an end-to-end example.
The repository itself is source-only — every shipped binary is cross-compiled by the tag-triggered release workflow from pinned sources, with SHA-256 checksums published as release assets.
Architecture notes
- Single provider, swappable via DI (Posture 1).
DefaultCryptoProviderimplementsICryptoProviderfor all key types; consumers with special requirements (e.g. a FIPS 140-3-validated module) replace the registration —AddNetCrypto()usesTryAdd, so a registration made before it wins. The designated evolution path is a per-KeyTypeprovider registry behind the unchangedICryptoProviderfacade; no registry exists in v1. - Backend isolation. No type from NSec, NBitcoin, Nethermind, or the FFI appears in
any public signature (enforced by
Microsoft.CodeAnalysis.PublicApiAnalyzerswith a committedPublicAPI.Shipped.txt, plus a reflection test). Sanctioned exceptions:NetCidtypes andMicrosoft.IdentityModel.Tokens.JsonWebKey. - EC point validation.
EcPointValidator.EnsureOnCurve(KeyType, x, y)is the public on-curve validation entry point — the invalid-curve-attack defense (RFC 7518 §6.2.2). Point decompression (DecompressEcPoint/DecompressSecp256k1Point) is an internal implementation detail and is not part of the public surface; validating a key never requires it. - Boundaries. EVM
v-encoding/RLP/transactions, JOSE/JWE/SD-JWT envelopes, Data Integrity proofs, ECDH-ES/1PU assembly, and concrete HSM/KMS stores are deliberately out of scope — they belong to the consumer layers.ICapableKeyStoregives a store the self-description that makes routing possible; it does not add the routing, and no router, profile, or policy machinery ships here. - Security posture. None of the wrapped backends is independently audited or FIPS-validated; this is documented openly rather than implied otherwise.
Building from source
# Managed code + tests (BBS tests require the native library, see below)
dotnet build NetCrypto.sln
dotnet test NetCrypto.sln --filter "Category!=BbsAbsent"
# Native BBS library for your host platform (Rust toolchain pinned in rust-toolchain.toml)
cd native/zkryptium-ffi && cargo build --release
# Rebuild so the test/sample projects pick up the binary, then run everything:
cd ../.. && dotnet build NetCrypto.sln && dotnet test NetCrypto.sln --filter "Category!=BbsAbsent"
# Without the native library (supported BBS-absent mode):
dotnet test NetCrypto.sln --filter "Category!=NativeFFI"
License
Apache-2.0. See LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.8)
- Microsoft.IdentityModel.Tokens (>= 8.19.1)
- NBitcoin.Secp256k1 (>= 4.0.0)
- NetCid (>= 1.6.0)
- Nethermind.Crypto.Bls (>= 1.0.5)
- NSec.Cryptography (>= 26.4.0)
NuGet packages (6)
Showing the top 5 NuGet packages that depend on NetCrypto:
| Package | Downloads |
|---|---|
|
NetDid.Core
Core abstractions, DID document model, and DID-method logic for the NetDid multi-method DID library. Cryptographic primitives are provided by NetCrypto. |
|
|
DataProofsDotnet.Core
Embedded-proof core for W3C VC Data Integrity 1.0: proof model, add/verify proof pipeline, cryptosuite registry, JCS cryptosuites (eddsa-jcs-2022, ecdsa-jcs-2019), and the verification-method resolver abstraction. All cryptography via NetCrypto; multiformats and JCS via NetCid. |
|
|
ZcapLd.Core
.NET implementation of W3C ZCAP-LD for creating, delegating, and verifying authorization capabilities. |
|
|
DidComm.Core
DIDComm Messaging v2.1 for .NET — message model, JWE/JWS envelopes, pack/unpack, routing, rotation. Phase 0: crypto substrate. |
|
|
DataProofsDotnet.Legacy
Legacy Linked-Data-Signature cryptosuites for W3C Data Integrity: Ed25519Signature2020 and EcdsaSecp256r1Signature2019, JCS (default) and RDFC-1.0 variants, byte-compatible with zcap-dotnet's 2020-era JCS-nested wire convention. All cryptography via NetCrypto; multiformats/JCS via NetCid; RDFC via DataProofsDotnet.Rdfc. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.6.0 | 49 | 8/13/2026 |
| 1.5.0 | 139 | 8/6/2026 |
| 1.4.0 | 869 | 7/26/2026 |
| 1.3.0 | 84 | 7/24/2026 |
| 1.2.0 | 757 | 7/11/2026 |
| 1.1.0 | 1,855 | 6/14/2026 |
| 1.0.0 | 761 | 6/13/2026 |
| 1.0.0-preview.2 | 86 | 6/13/2026 |
| 1.0.0-preview.1 | 120 | 6/11/2026 |