Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Changelog

All notable changes to **NetCrypto** are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.0] - 2026-06-13

First stable (GA) release. The public API is frozen: `PublicAPI.Shipped.txt` is the
authoritative contract and `PublicAPI.Unshipped.txt` is empty. From here, additive changes
bump the minor version and breaking changes the major. No `--prerelease` flag is required.

This release also includes the malformed-input security hardening developed as `1.0.0-preview.3`,
which was never published as a standalone release.

### Security
- Malformed key inputs now surface as a **parameter-named `ArgumentException`** instead of leaking
a backend exception type (`System.FormatException` from NSec, `Nethermind.Crypto.Bls+BlsException`,
or a platform `CryptographicException`). Up-front validation was added at every backend hand-off:
Ed25519 / X25519 (32-byte length), NIST EC private keys (per-curve scalar length **and**
`0 < D < n` range), and BLS12-381 (32-byte length + invalid-scalar mapping).
- `DefaultKeyGenerator.DeriveX25519PublicKeyFromEd25519` now rejects inputs that map to a
**low-order Curve25519 point** (e.g. the all-zero Ed25519 key), instead of minting a degenerate,
small-subgroup X25519 `PublicKeyReference`.
- The NFR-3 fuzz suite no longer carries a "known backend deviations" allow-list; any non-contract
exception fails the suite.

### Changed
- Stabilized to GA `1.0.0` — the package is now a non-prerelease NuGet release.

### Documentation
- Documented the supported **native platform RID matrix** (`osx-arm64`, `osx-x64`, `linux-x64`,
`linux-arm64`, `win-x64`) and that the published `.nupkg` ships `runtimes/{rid}/native/`
transitively, verified by release CI against the packed artifact and a BBS smoke test. (#4)
- Documented that the supported BBS keygen path is `DefaultKeyGenerator.Generate(KeyType.Bls12381G2)`
/ `Bls12381G1`, and that raw FFI keygen is intentionally internal. (#6)
- Confirmed `EcPointValidator.EnsureOnCurve` as the public EC on-curve validation entry point, with
point decompression intentionally internal. (#7)

## [1.0.0-preview.2] - 2026-06-13

### Added
- Exposed the BBS signature **`header`** parameter on `IBbsCryptoProvider` /
`DefaultBbsCryptoProvider` (`Sign`, `Verify`, `DeriveProof`, `VerifyProof`) as an optional
`ReadOnlySpan<byte>` (default empty). The header is fixed by the signer and committed by both
verification and any derived proof — letting a consumer bind application data (e.g. the W3C
`bbs-2023` mandatory-disclosure group) that a holder cannot drop or alter. (#2)

### Changed
- **Breaking (pre-GA):** renamed the `nonce` parameter on `DeriveProof` / `VerifyProof` to
`presentationHeader` — it is the BBS presentation header (`ph`), distinct from the new signature
`header`. Positional callers are unaffected; named-argument (`nonce:`) callers must update.

## [1.0.0-preview.1] - 2026-06-11

Initial preview. NetCrypto consolidates every cryptographic primitive for the NetCid/NetDid
library stack behind stable interfaces, so no domain library binds directly to a specific backend.

### Added
- **Key model:** `KeyType` (Ed25519, X25519, P-256/384/521, secp256k1, BLS12-381 G1/G2), `KeyPair`,
`PublicKeyReference`, and multibase/multicodec encoding (`MultibasePublicKey`) via NetCid.
- **Signing & verification** (`ICryptoProvider` / `DefaultCryptoProvider`): EdDSA (Ed25519), ECDSA
on the NIST curves (DER and IEEE P1363), secp256k1 (64-byte compact, low-S), and BLS12-381
(G1/G2 variants, hash-to-curve).
- **Recoverable secp256k1 ECDSA** (`Secp256k1Recoverable`) over a caller-supplied 32-byte digest,
returning the raw recovery id (no EVM `v`-encoding).
- **BBS selective-disclosure signatures** (`IBbsCryptoProvider` / `DefaultBbsCryptoProvider`,
BLS12-381-SHA-256, draft-irtf-cfrg-bbs-signatures-10): sign, verify, derive-proof, verify-proof;
`BbsCiphersuite`; `BbsUnavailableException`; and the supported **BBS-absent mode** (`IsAvailable`).
- **Key generation** (`IKeyGenerator` / `DefaultKeyGenerator`) for all key types, including
Ed25519→X25519 derivation.
- **Key agreement:** X25519 (with an HKDF-SHA256 convenience) and raw ECDH "Z" for X25519 and
P-256/384/521.
- **KDFs:** HKDF (SHA-256/384/512) and Concat KDF (NIST SP 800-56A).
- **AEADs:** AES-256-GCM (`A256GCM`), AES-256-CBC + HMAC-SHA-512 (`A256CBC-HS512`),
XChaCha20-Poly1305 (`XC20P`); and AES Key Wrap (`A256KW`).
- **Hashing:** SHA-256/384/512 and Keccak-256 (original padding, not SHA3-256).
- **JWK conversion** (`JwkConverter`) for all key types.
- **Signing & key-store abstractions:** `ISigner`, `KeyPairSigner`, `KeyStoreSigner`, `IKeyStore`,
`InMemoryKeyStore`.
- **EC point validation** (`EcPointValidator.EnsureOnCurve`) — the invalid-curve-attack defense.
- **Dependency injection:** `AddNetCrypto()` (swap-seam via `TryAdd`).
- **Native BBS distribution** for five RIDs (`osx-arm64`, `osx-x64`, `linux-x64`, `linux-arm64`,
`win-x64`), packed into the single NuGet package; the repository stays source-only.

[1.0.0]: https://github.com/moisesja/crypto-dotnet/compare/v1.0.0-preview.2...v1.0.0
[1.0.0-preview.2]: https://github.com/moisesja/crypto-dotnet/compare/v1.0.0-preview.1...v1.0.0-preview.2
[1.0.0-preview.1]: https://github.com/moisesja/crypto-dotnet/releases/tag/v1.0.0-preview.1
7 changes: 4 additions & 3 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- Preview line: hyphenated SemVer suffix marks every build as a NuGet prerelease.
Release CI overrides this from the git tag (e.g. v1.0.0-preview.1 -> 1.0.0-preview.1). -->
<NetCryptoVersion Condition="'$(NetCryptoVersion)' == ''">1.0.0-preview.3</NetCryptoVersion>
<!-- GA: a plain SemVer (no hyphenated suffix) is a stable, non-prerelease NuGet package.
Release CI overrides this from the git tag (e.g. v1.0.0 -> 1.0.0; a v1.0.1-rc.1 tag would
still produce a prerelease). -->
<NetCryptoVersion Condition="'$(NetCryptoVersion)' == ''">1.0.0</NetCryptoVersion>
<Deterministic>true</Deterministic>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSourceRevisionInInformationalVersion>true</IncludeSourceRevisionInInformationalVersion>
Expand Down
50 changes: 45 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ 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 --prerelease
dotnet add package NetCrypto
```

> NetCrypto is currently published as a **preview** (`1.0.0-preview.*`); the `--prerelease`
> flag (or an explicit prerelease version) is required until a stable `1.0.0` is cut.
> **Stable release.** NetCrypto is published as a stable **`1.0.0`** (GA) — no `--prerelease`
> flag is required. The public API is frozen: `PublicAPI.Shipped.txt` is the authoritative
> contract and `PublicAPI.Unshipped.txt` is empty. Semantic versioning applies — additive
> changes bump the minor version, breaking changes the major; any pre-GA `1.0.0-preview.*`
> packages are superseded by `1.0.0`. See [CHANGELOG.md](CHANGELOG.md) for the release history.

Target framework: **net10.0**. Depends on [`NetCid`](https://www.nuget.org/packages/NetCid)
for multibase/multicodec encoding.
Expand Down Expand Up @@ -88,8 +91,25 @@ Every primitive is tested against the test vectors of its governing specificatio

BBS is the only non-managed primitive: the `zkryptium-ffi` Rust crate
([`native/zkryptium-ffi/`](native/zkryptium-ffi/)) is compiled per platform and shipped
inside this single NuGet package under `runtimes/{rid}/native/` for:
`linux-x64`, `linux-arm64`, `osx-arm64`, `osx-x64`, `win-x64`.
**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:
Expand All @@ -103,6 +123,22 @@ if (!bbs.IsAvailable)
}
```

### 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:

```csharp
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`](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.
Expand All @@ -118,6 +154,10 @@ as release assets.
any public signature (enforced by `Microsoft.CodeAnalysis.PublicApiAnalyzers` with a
committed `PublicAPI.Shipped.txt`, plus a reflection test). Sanctioned exceptions:
`NetCid` types and `Microsoft.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.
Expand Down
6 changes: 6 additions & 0 deletions src/NetCrypto/DefaultBbsCryptoProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ namespace NetCrypto;
/// BBS signature operations using BLS12-381-SHA-256 (IETF draft-irtf-cfrg-bbs-signatures-10).
/// Delegates to the zkryptium-ffi native library via P/Invoke.
/// </summary>
/// <remarks>
/// To mint a BBS keypair, use <see cref="DefaultKeyGenerator.Generate"/> with
/// <see cref="KeyType.Bls12381G2"/> (96-byte public key — the variant signed/verified here) or
/// <see cref="KeyType.Bls12381G1"/>. Raw FFI keygen is intentionally internal: there is no public
/// raw secret/public-key minting helper, keeping the surface minimal and backend-agnostic.
/// </remarks>
public sealed class DefaultBbsCryptoProvider : IBbsCryptoProvider
{
private const int SecretKeySize = 32;
Expand Down
6 changes: 6 additions & 0 deletions src/NetCrypto/EcPointValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ namespace NetCrypto;
/// key agreement. Curve25519/Ed25519 do not need this — all encoded points are on-curve — so
/// this validator only applies to NIST curves (P-256, P-384, P-521) and secp256k1. All four have
/// cofactor 1, so a point on the curve is automatically in the prime-order subgroup.
/// <para>
/// <see cref="EnsureOnCurve"/> is the public EC point-validation entry point. Point
/// <em>decompression</em> (recovering Y from a compressed SEC1 point) is an internal
/// implementation detail of the provider, not part of the public surface — on-curve validation
/// does not require it.
/// </para>
/// </remarks>
public static class EcPointValidator
{
Expand Down
16 changes: 8 additions & 8 deletions src/NetCrypto/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ NetCrypto.ConcatKdf
NetCrypto.DefaultBbsCryptoProvider
NetCrypto.DefaultBbsCryptoProvider.Ciphersuite.get -> NetCrypto.BbsCiphersuite
NetCrypto.DefaultBbsCryptoProvider.DefaultBbsCryptoProvider(NetCrypto.BbsCiphersuite ciphersuite = NetCrypto.BbsCiphersuite.Bls12381Sha256) -> void
NetCrypto.DefaultBbsCryptoProvider.DeriveProof(System.ReadOnlySpan<byte> publicKey, byte[]! signature, System.Collections.Generic.IReadOnlyList<byte[]!>! messages, System.Collections.Generic.IReadOnlyList<int>! revealedIndices, System.ReadOnlySpan<byte> nonce) -> byte[]!
NetCrypto.DefaultBbsCryptoProvider.DeriveProof(System.ReadOnlySpan<byte> publicKey, byte[]! signature, System.Collections.Generic.IReadOnlyList<byte[]!>! messages, System.Collections.Generic.IReadOnlyList<int>! revealedIndices, System.ReadOnlySpan<byte> presentationHeader, System.ReadOnlySpan<byte> header = default(System.ReadOnlySpan<byte>)) -> byte[]!
NetCrypto.DefaultBbsCryptoProvider.IsAvailable.get -> bool
NetCrypto.DefaultBbsCryptoProvider.Sign(System.ReadOnlySpan<byte> privateKey, System.Collections.Generic.IReadOnlyList<byte[]!>! messages) -> byte[]!
NetCrypto.DefaultBbsCryptoProvider.Verify(System.ReadOnlySpan<byte> publicKey, System.ReadOnlySpan<byte> signature, System.Collections.Generic.IReadOnlyList<byte[]!>! messages) -> bool
NetCrypto.DefaultBbsCryptoProvider.VerifyProof(System.ReadOnlySpan<byte> publicKey, byte[]! proof, System.Collections.Generic.IReadOnlyList<byte[]!>! revealedMessages, System.Collections.Generic.IReadOnlyList<int>! revealedIndices, System.ReadOnlySpan<byte> nonce) -> bool
NetCrypto.DefaultBbsCryptoProvider.Sign(System.ReadOnlySpan<byte> privateKey, System.Collections.Generic.IReadOnlyList<byte[]!>! messages, System.ReadOnlySpan<byte> header = default(System.ReadOnlySpan<byte>)) -> byte[]!
NetCrypto.DefaultBbsCryptoProvider.Verify(System.ReadOnlySpan<byte> publicKey, System.ReadOnlySpan<byte> signature, System.Collections.Generic.IReadOnlyList<byte[]!>! messages, System.ReadOnlySpan<byte> header = default(System.ReadOnlySpan<byte>)) -> bool
NetCrypto.DefaultBbsCryptoProvider.VerifyProof(System.ReadOnlySpan<byte> publicKey, byte[]! proof, System.Collections.Generic.IReadOnlyList<byte[]!>! revealedMessages, System.Collections.Generic.IReadOnlyList<int>! revealedIndices, System.ReadOnlySpan<byte> presentationHeader, System.ReadOnlySpan<byte> header = default(System.ReadOnlySpan<byte>)) -> bool
NetCrypto.DefaultCryptoProvider
NetCrypto.DefaultCryptoProvider.DefaultCryptoProvider() -> void
NetCrypto.DefaultCryptoProvider.DeriveSharedSecret(NetCrypto.KeyType keyType, System.ReadOnlySpan<byte> privateKey, System.ReadOnlySpan<byte> publicKey) -> byte[]!
Expand All @@ -39,11 +39,11 @@ NetCrypto.Hash
NetCrypto.Hkdf
NetCrypto.IBbsCryptoProvider
NetCrypto.IBbsCryptoProvider.Ciphersuite.get -> NetCrypto.BbsCiphersuite
NetCrypto.IBbsCryptoProvider.DeriveProof(System.ReadOnlySpan<byte> publicKey, byte[]! signature, System.Collections.Generic.IReadOnlyList<byte[]!>! messages, System.Collections.Generic.IReadOnlyList<int>! revealedIndices, System.ReadOnlySpan<byte> nonce) -> byte[]!
NetCrypto.IBbsCryptoProvider.DeriveProof(System.ReadOnlySpan<byte> publicKey, byte[]! signature, System.Collections.Generic.IReadOnlyList<byte[]!>! messages, System.Collections.Generic.IReadOnlyList<int>! revealedIndices, System.ReadOnlySpan<byte> presentationHeader, System.ReadOnlySpan<byte> header = default(System.ReadOnlySpan<byte>)) -> byte[]!
NetCrypto.IBbsCryptoProvider.IsAvailable.get -> bool
NetCrypto.IBbsCryptoProvider.Sign(System.ReadOnlySpan<byte> privateKey, System.Collections.Generic.IReadOnlyList<byte[]!>! messages) -> byte[]!
NetCrypto.IBbsCryptoProvider.Verify(System.ReadOnlySpan<byte> publicKey, System.ReadOnlySpan<byte> signature, System.Collections.Generic.IReadOnlyList<byte[]!>! messages) -> bool
NetCrypto.IBbsCryptoProvider.VerifyProof(System.ReadOnlySpan<byte> publicKey, byte[]! proof, System.Collections.Generic.IReadOnlyList<byte[]!>! revealedMessages, System.Collections.Generic.IReadOnlyList<int>! revealedIndices, System.ReadOnlySpan<byte> nonce) -> bool
NetCrypto.IBbsCryptoProvider.Sign(System.ReadOnlySpan<byte> privateKey, System.Collections.Generic.IReadOnlyList<byte[]!>! messages, System.ReadOnlySpan<byte> header = default(System.ReadOnlySpan<byte>)) -> byte[]!
NetCrypto.IBbsCryptoProvider.Verify(System.ReadOnlySpan<byte> publicKey, System.ReadOnlySpan<byte> signature, System.Collections.Generic.IReadOnlyList<byte[]!>! messages, System.ReadOnlySpan<byte> header = default(System.ReadOnlySpan<byte>)) -> bool
NetCrypto.IBbsCryptoProvider.VerifyProof(System.ReadOnlySpan<byte> publicKey, byte[]! proof, System.Collections.Generic.IReadOnlyList<byte[]!>! revealedMessages, System.Collections.Generic.IReadOnlyList<int>! revealedIndices, System.ReadOnlySpan<byte> presentationHeader, System.ReadOnlySpan<byte> header = default(System.ReadOnlySpan<byte>)) -> bool
NetCrypto.ICryptoProvider
NetCrypto.ICryptoProvider.DeriveSharedSecret(NetCrypto.KeyType keyType, System.ReadOnlySpan<byte> privateKey, System.ReadOnlySpan<byte> publicKey) -> byte[]!
NetCrypto.ICryptoProvider.KeyAgreement(System.ReadOnlySpan<byte> privateKey, System.ReadOnlySpan<byte> publicKey) -> byte[]!
Expand Down
Loading
Loading