PostQuantum.SecretSharing 2.2.0

dotnet add package PostQuantum.SecretSharing --version 2.2.0
                    
NuGet\Install-Package PostQuantum.SecretSharing -Version 2.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="PostQuantum.SecretSharing" Version="2.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PostQuantum.SecretSharing" Version="2.2.0" />
                    
Directory.Packages.props
<PackageReference Include="PostQuantum.SecretSharing" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add PostQuantum.SecretSharing --version 2.2.0
                    
#r "nuget: PostQuantum.SecretSharing, 2.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package PostQuantum.SecretSharing@2.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=PostQuantum.SecretSharing&version=2.2.0
                    
Install as a Cake Addin
#tool nuget:?package=PostQuantum.SecretSharing&version=2.2.0
                    
Install as a Cake Tool

PostQuantum.SecretSharing

CI CodeQL OpenSSF Scorecard License: MIT

Your encrypted system is only as safe as the one key nobody knows where to keep.

Split one high-value secret into N shares so that any K of them rebuild it — and any K−1 reveal mathematically nothing. No single person, machine, or backup is a single point of failure or a single point of compromise.

                                   ┌─ share 1  →  IT Director
                                   ├─ share 2  →  SRE on-call
   master key  ──[ 3-of-5 split ]──┼─ share 3  →  Security lead      any 3 of 5
   (32 bytes)                      ├─ share 4  →  Offsite safe       rebuild it;
                                   └─ share 5  →  Legal/compliance   any 2 reveal
                                                                     nothing
dotnet add package PostQuantum.SecretSharing --version 2.2.0
using PostQuantum.SecretSharing;
using System.Security.Cryptography;

byte[] masterKey = RandomNumberGenerator.GetBytes(32);

// Split 3-of-5 and hand each share to a different custodian.
SecretShare[] shares = ShamirSecretSharing.Split(masterKey, new SharePolicy(Threshold: 3, TotalShares: 5));
foreach (SecretShare s in shares)
    File.WriteAllBytes($"share-{s.ShareIndex}.pqss", s.Export());

// ...later, any three custodians convene and rebuild the key.
SecretShare[] quorum = new[]
{
    SecretShare.Import(File.ReadAllBytes("share-1.pqss")),
    SecretShare.Import(File.ReadAllBytes("share-3.pqss")),
    SecretShare.Import(File.ReadAllBytes("share-5.pqss")),
};
using ZeroizingBuffer recovered = ShamirSecretSharing.Reconstruct(quorum);
// recovered.Span == masterKey, and is wiped from pinned memory on Dispose.

That's the whole idea. Around it ship a pqss CLI, runnable samples, ceremony docs, a threat model, fuzzing, and an audit kit — the rest of this README is about when you need it and how to use it correctly.

Why this over a general-purpose Shamir library?

A textbook Shamir gist — or a general-purpose package like SecretSharingDotNet — splits a secret and hands you the pieces. The math is the easy 5%. This library is built around the other 95% — keeping the share trustworthy and the ceremony operable:

  • Constant-time, table-free GF(2⁸) math. No secret-indexed log/antilog table lookups — the classic cache-timing leak in Shamir implementations.
  • Authenticated shares (ML-DSA-65 / FIPS 204). Detect a tampered or substituted share, pinned to your dealer key. Generic libraries hand back raw field points with nothing to verify.
  • A strict, canonical, self-describing share format (.pqss). Versioned, fail-closed, fuzzed, and spec'd to the byte — not a bare BigInteger or base64 blob you have to frame, version, and validate yourself.
  • Pinned, zeroizing, page-locked secret memory. The reconstructed secret lands in a ZeroizingBuffer the GC can't relocate-and-copy, wiped on dispose — not a string/byte[] left for the collector to scatter.
  • A built-in wrap helper for low-entropy secrets. WrappedSecret splits a random KEK and AES-256-GCM-seals your real secret, closing the offline-guessing oracle that bites naïve splitting of passphrases.
  • Ceremony tooling and operations docs. A real pqss CLI, five runnable samples, and a trustee operations guide — not just a Split() you wire up alone.

If all you need is the polynomial math, a generic library is fine. If the secret is important enough to split, the authentication, format, memory hygiene, and ceremony around it are the actual job — and that is what this package is.


The need: where one key becomes one liability

Almost every secure system eventually has a key that is too important to lose and too dangerous to concentrate. Put it on one machine and that machine is a single point of failure. Protect it with one passphrase and the passphrase is a single point of guessing. Hand it to one administrator and that administrator is a single point of trust.

Secret sharing dissolves that single point. You decide the quorum: "any 3 of these 5 people," "both of these 2 plus one backup," "5 of 9 board members." Below the quorum, the shares are useless — not "hard to crack," but provably empty of information.

Concrete problems this solves

You have… The pain What this gives you
A code / release signing key One developer can sign unilaterally — or lose the key and brick releases M-of-N custody: no lone signer, no lone loss
A root CA / trust-anchor private key The whole PKI hinges on one file in one HSM/safe Quorum custody + dealer-authenticated shares (ML-DSA-65)
A database / disk master key (KEK) Bus factor of 1; if the one holder is gone, data is gone Recoverable by any quorum, survivable to lost shares
A cloud KMS / vault unseal/root key Break-glass is a sticky note or a shared passphrase Real K-of-N break-glass instead of a guessable secret
A crypto wallet seed / treasury key Single seed phrase = single theft or single loss Threshold custody across people and locations
"God-mode" admin / root credentials One person silently holds total access Require a quorum to assemble the credential
Backup-encryption keys Keys escrowed next to the backups they protect Distribute key shares across departments/sites

What you get that a passphrase or a single file does not

  • No single point of compromise. Stealing one (or any K−1) shares yields zero information about the secret. This is provable, not "computationally expensive."
  • No single point of failure. Lose up to N−K shares and you still recover.
  • No guessability. Unlike a passphrase, shares are full-entropy data; there is nothing to brute-force below the quorum.
  • Survives quantum computers. The secrecy guarantee is information-theoretic — it does not rest on any problem a quantum computer could solve (see below).
  • Tamper-evident. With authenticated mode, a swapped or corrupted share is detected and rejected, not silently used.

How it works in 30 seconds

Each byte of your secret becomes the constant term of a random polynomial of degree K−1 over the finite field GF(2⁸). A "share" is that polynomial evaluated at a distinct point x. K points uniquely determine a degree-K−1 polynomial (so K shares rebuild the secret); K−1 points are consistent with every possible constant term equally (so they reveal nothing). That second fact is the information-theoretic guarantee.

You never need to know the math to use the library — but you should know the one honest caveat: the scheme is unconditionally secure; the implementation (authentication, memory hygiene, the integrity check) is ordinary engineering, documented plainly here and in docs/THREAT-MODEL.md.


Quick start

1. Unauthenticated split (integrity check only)

Good when shares live in trusted storage and you only need to detect accidental corruption or mixed-up shares.

using PostQuantum.SecretSharing;
using System.Security.Cryptography;

byte[] secret = RandomNumberGenerator.GetBytes(32);              // a 256-bit key

SecretShare[] shares = ShamirSecretSharing.Split(secret, new SharePolicy(3, 5));
byte[][] files = shares.Select(s => s.Export()).ToArray();       // canonical .pqss bytes
CryptographicOperations.ZeroMemory(secret);                      // done with the plaintext

// Reconstruct from EXACTLY 3 shares (passing more is rejected — see FAQ).
SecretShare[] quorum = files.Take(3).Select(SecretShare.Import).ToArray();
using ZeroizingBuffer recovered = ShamirSecretSharing.Reconstruct(quorum);
Console.WriteLine(recovered.Span.SequenceEqual(secret));         // (if you kept a copy)

2. Authenticated split (dealer-signed shares — net10.0)

Use this when shares travel through untrusted hands and you must detect a tampered or substituted share. The dealer signs every share with ML-DSA-65 (FIPS 204); reconstruction verifies against a public key you pin out-of-band.

using PostQuantum.SecretSharing;
using System.Security.Cryptography;

byte[] secret = RandomNumberGenerator.GetBytes(32);

using MlDsa65ShareAuthenticator dealer = MlDsa65ShareAuthenticator.Generate();
ReadOnlyMemory<byte> dealerPublicKey = dealer.PublicKey;         // PIN this (print it, store in config, read it aloud)

SecretShare[] shares = ShamirSecretSharing.Split(secret, new SharePolicy(3, 5), dealer);
CryptographicOperations.ZeroMemory(secret);

// Every share must be signed by the pinned dealer key, or reconstruction throws.
SecretShare[] quorum = /* import any 3 shares */ Array.Empty<SecretShare>();
using ZeroizingBuffer recovered = ShamirSecretSharing.Reconstruct(quorum, dealerPublicKey);

Keep the dealer private key with the same care as any signing key — anyone who has it can mint shares that pass your pin. Persist it with dealer.ExportPrivateKey() and reload it later with MlDsa65ShareAuthenticator.ImportPrivateKey(...), or destroy it after the ceremony if you will never re-split.

3. Handling the secret safely

Reconstruction returns a ZeroizingBuffer, not a byte[]. It is allocated on the pinned object heap (the GC can't relocate and silently copy it) and is zeroed when disposed. Always using it, and don't copy Span into a long-lived array.

using (ZeroizingBuffer key = ShamirSecretSharing.Reconstruct(quorum))
{
    using var aes = new AesGcm(key.Span, tagSizeInBytes: 16);
    // ...use key.Span for the minimum time needed...
}   // key is wiped here

Choosing K and N

Goal Suggested policy Why
Sensible default 3-of-5 Survive losing 2 shares; resist any 2 colluding
Two-person rule + a backup 2-of-3 Either pair (or the backup) can act
High assurance 5-of-9 Larger collusion barrier, still tolerant of loss
Avoid 2-of-2 Lose either share → secret gone forever; no redundancy
Forbidden 1-of-N Every share is the secret — the library rejects K=1

Rules of thumb: pick K so no realistically-collusion-prone subset reaches it, and pick N − K ≥ 2 so you can lose two shares and still recover. Limits: 2 ≤ K ≤ N ≤ 255, secret length 1..65536 bytes. See docs/OPERATIONS.md for running an actual ceremony.


Splitting low-entropy secrets safely (the wrap pattern)

Do not split a passphrase, PIN, or short password directly. Every share carries an HKDF check value that lets a single shareholder brute-force a guessable secret offline (see "When NOT to use this"). Instead, split a random key and let that key wrap your real secret.

The library does this for you with WrappedSecret:

using PostQuantum.SecretSharing;
using System.Text;

byte[] realSecret = Encoding.UTF8.GetBytes("correct horse battery staple"); // low entropy!

// Generates a random KEK, AES-256-GCM-seals your secret, and splits the KEK.
WrappedSplit w = WrappedSecret.Split(realSecret, new SharePolicy(3, 5));
//   w.Shares   → give to trustees
//   w.Envelope → store anywhere; it is NOT secret

// Recover: any 3 KEK shares + the envelope.
using ZeroizingBuffer recovered = WrappedSecret.Reconstruct(
    new[] { w.Shares[0], w.Shares[2], w.Shares[4] }, w.Envelope);

Under the hood that is exactly the pattern below — shown explicitly in case you want to manage the envelope yourself:

using PostQuantum.SecretSharing;
using System.Security.Cryptography;

byte[] realSecret = Encoding.UTF8.GetBytes("correct horse battery staple"); // low entropy!

// 1. Generate a full-entropy key-encryption key and encrypt the real secret.
byte[] kek = RandomNumberGenerator.GetBytes(32);
byte[] nonce = RandomNumberGenerator.GetBytes(AesGcm.NonceByteSizes.MaxSize);
byte[] ciphertext = new byte[realSecret.Length];
byte[] tag = new byte[AesGcm.TagByteSizes.MaxSize];
using (var aes = new AesGcm(kek, tag.Length))
    aes.Encrypt(nonce, realSecret, ciphertext, tag);

// 2. Split the KEK (high entropy ⇒ the check-value oracle is harmless), and store
//    nonce + ciphertext + tag alongside the shares (they are not secret).
SecretShare[] shares = ShamirSecretSharing.Split(kek, new SharePolicy(3, 5));
CryptographicOperations.ZeroMemory(kek);

// 3. To recover: reconstruct the KEK, then decrypt.
using ZeroizingBuffer recoveredKek = ShamirSecretSharing.Reconstruct(/* 3 shares */ shares.Take(3).ToList());
byte[] plaintext = new byte[ciphertext.Length];
using (var aes = new AesGcm(recoveredKek.Span, tag.Length))
    aes.Decrypt(nonce, ciphertext, tag, plaintext);

API reference

Member Purpose
SharePolicy(int Threshold, int TotalShares) The K-of-N policy. 2 ≤ K ≤ N ≤ 255.
ShamirSecretSharing.Split(secret, policy) Split into N unauthenticated shares.
ShamirSecretSharing.Split(secret, policy, dealer) Split into N dealer-signed shares.
ShamirSecretSharing.Reconstruct(shares) Rebuild from exactly K shares; returns a ZeroizingBuffer.
ShamirSecretSharing.Reconstruct(shares, expectedDealerPublicKey) As above, but require every share to verify against the pinned key.
ShamirSecretSharing.Refresh(shares, newPolicy?, expectedDealerPublicKey?, newDealer?) Rotate custody: re-split the same secret into fresh shares with a new splitId (old shares stop interoperating).
WrappedSecret.Split(secret, policy[, dealer]) Safe path for low-entropy/large secrets: random KEK + AES-256-GCM envelope; splits the KEK. Returns WrappedSplit { Shares, Envelope }.
WrappedSecret.Reconstruct(shares, envelope[, expectedDealerPublicKey]) Reconstruct the KEK and decrypt the envelope; returns a ZeroizingBuffer.
DealerCommitment.Compute(secret) / .Verify(secret, commitment) Publish a one-time commitment to the intended secret; quorums confirm they recovered that value (not full VSS — see below).
SecretShare.Export() Canonical .pqss bytes for distribution/storage.
SecretShare.Import(bytes) Strict, fail-closed parse of .pqss bytes.
SecretShare.{Threshold, TotalShares, ShareIndex, SecretLength, SplitId, Authentication, DealerPublicKey} Public metadata. (Raw y data is intentionally not exposed.)
ZeroizingBuffer.Span / .Length / .IsMemoryLocked / .Dispose() Pinned, page-locked (best-effort), zeroizing access to the recovered secret.
IShareAuthenticator Dealer-signer abstraction (Kind, PublicKey, Sign).
MlDsa65ShareAuthenticator (net10.0) Generate(), ImportPrivateKey(), ExportPrivateKey(), PublicKey, Sign().
ShareAuthenticationKind None (0) or MlDsa65 (1).

The one unconditional claim — and its precise limit

Shamir's scheme is information-theoretically secure: K−1 shares reveal nothing about the secret against any adversary — classical or quantum, with unlimited compute. This is a mathematical fact about the scheme, not a computational-hardness assumption. No future algorithm or machine weakens it.

That guarantee is about the scheme. Every real risk lives in the implementation — share authentication, side channels, memory hygiene, and the check-value oracle. We never let marketing language blur the two:

  • The scheme is unconditional. K−1 shares = zero information. Full stop.
  • The implementation is where you must trust engineering. We document every one of those trust points honestly, including the unflattering ones.

This package is called post-quantum for two concrete reasons, neither of which is "we hardened Shamir":

  1. Its core security claim survives quantum computers as a mathematical fact.
  2. Its authentication layer uses ML-DSA-65 (FIPS 204) — a post-quantum signature scheme — to authenticate shares against the dealer.

Security layers

Layer Mechanism Guarantee
Secrecy of the secret Shamir over GF(2⁸) Information-theoretic. K−1 shares reveal nothing, against any adversary, ever.
Share authenticity ML-DSA-65 / FIPS 204 (optional) Computational (post-quantum). Detects tampered/substituted shares when you pin the dealer key.
Share integrity HKDF-SHA256 check value Detects accidental corruption / mismatched shares at reconstruction. Caveat: also an offline guessing oracle for low-entropy secrets.

Trust model in one paragraph

expectedDealerPublicKey is your pin. When you supply it, every share must be authenticated, carry exactly that key, and verify — otherwise reconstruction throws. When you omit it but the shares carry signatures anyway, the signatures are still verified against the embedded key as defense in depth — but understand what that is: embedded-key-only verification is self-attestation, not authority. A forged set of shares can embed any key and sign with it. Only a pin you obtained out-of-band proves the shares came from your dealer.


When NOT to use this

  • You are splitting a low-entropy secret (passphrase, PIN, short password). The integrity check value travels inside every share and is an offline brute-force oracle: a single shareholder can test guesses without any quorum. For a 32-byte random key this is irrelevant (2²⁵⁶ search). Split keys, not passwords — or use the built-in WrappedSecret helper, which splits a random key and wraps your real secret for you.
  • You have a single custodian. Sharing among one person is pointless ceremony; just encrypt the secret.
  • You need verifiable secret sharing (VSS) in the dependency-free core. A malicious dealer can hand inconsistent shares to different trustees. The core authenticates shares against the dealer and offers DealerCommitment (a one-time published commitment to the intended secret), but it cannot prove shares are mutually consistent before reconstruction. Full VSS needs a prime/EC group rather than GF(2⁸), so it ships separately in the opt-in PostQuantum.SecretSharing.Vss package (Pedersen VSS over P-256, with optional ML-DSA-65 broadcast signing) — kept out of the core so the core stays dependency-free.
  • You need distributed proactive secret sharing in the core. The core's Refresh rotates shares (re-split, new splitId) but is quorum-mediated — it briefly reconstructs the secret. A protocol that re-randomizes shares across parties (or a co-located set) without any reconstruction ships separately in the opt-in PostQuantum.SecretSharing.Extensions package (ProactiveRefresh) — kept out of the core so the core stays minimal.
  • You need a KMS. This is a primitive, not a key-management service.

How it compares

Honest positioning. "✅/❌" describe this library's choices, not a value judgment of the alternatives — each tool solves a different problem.

Capability This library Passphrase-encrypt a file Naive XOR split Vault's Shamir (unseal) Typical generic Shamir lib
True K-of-N threshold (K < N) ❌ (needs all parts)
Information-theoretic secrecy below quorum ❌ (brute-forceable) usually ✅
Survives quantum adversaries (secrecy) ❌ (KDF/cipher assumptions) usually ✅
Constant-time, table-free field math n/a n/a often ❌ (log tables)
Authenticated shares (tamper/substitution) ✅ ML-DSA-65 n/a usually ❌
Strict, canonical, self-describing share format .pqss n/a partial varies
Pinned + zeroized + page-locked secret memory rarely
Built-in low-entropy wrap helper n/a n/a
Ceremony tooling + operations guide ✅ CLI + docs
Verifiable Secret Sharing (malicious dealer) (opt-in pkg) n/a rarely
Independently audited (honest) varies n/a varies

Where we deliberately win: memory hygiene, constant-time field math, a strict format we control, post-quantum share authentication, and first-class ceremony support. VSS (malicious-dealer detection) ships as the opt-in PostQuantum.SecretSharing.Vss package — Pedersen VSS over P-256 with optional ML-DSA-65 broadcast signing, kept out of the dependency-free core. Where we don't (yet): no independent audit — stated plainly rather than glossed over (both packages are built to make one cheap; see the VSS audit guide).


Platform matrix

The core has no platform blockers. The Shamir engine, the CBOR codec, the HKDF check value, and ZeroizingBuffer are pure managed code plus SHA-256/HKDF from the BCL — so they run on net8.0 everywhere, including macOS, iOS, and Android.

Component Windows Linux macOS iOS / Android
Core (split, reconstruct, .pqss, check value, ZeroizingBuffer)
ML-DSA-65 authenticator (MlDsa65ShareAuthenticator, net10.0) ✅ (OpenSSL ≥ 3.5) ❌ (upstream)

The ML-DSA-65 authenticator compiles only under net10.0 and additionally guards at runtime on MLDsa.IsSupported, throwing PlatformNotSupportedException (with a pointer back to this matrix) where FIPS 204 is unavailable. macOS lacks ML-DSA support upstream — the core still runs there fully; only the optional signing layer does not. CI proves this by running the full net8.0 suite on macOS and letting the ML-DSA tests skip.


Targets

  • src multi-targets net8.0;net10.0.
  • Tests multi-target the same pair; ML-DSA test classes skip at runtime on platforms without FIPS 204.
  • LangVersion latest, nullable enable, TreatWarningsAsErrors true, deterministic build, SourceLink, embedded untracked sources, CI build flag.

Fail-closed guarantees

Every parse error, length mismatch, policy violation, or signature failure throws a specific exception before any secret-dependent computation runs:

Exception Meaning
SecretSharingException Abstract base for all of the below.
ShareFormatException Malformed / non-canonical .pqss, wrong type, unknown field, trailing bytes, presence contradicting the declared mode.
SharePolicyException K, N, secret length, or share index out of range; wrong number of shares at reconstruct.
ShareAuthenticationException Signature does not verify, or pinned dealer key mismatch.
ShareConsistencyException Well-formed shares that cannot belong to one split (mixed split IDs, metadata, duplicate indices), or a check-value mismatch after interpolation.

Design decisions

  • No log/antilog tables in the field math. Table lookups indexed by secret-dependent values are the classic cache-timing leak in Shamir libraries. All GF(2⁸) multiplication is branchless, fixed-iteration, table-free.
  • K=1 is banned. With a threshold of one, every share is the secret — security theater, not sharing. The library refuses it.
  • Strict canonical CBOR we own. The .pqss parser accepts only a tiny, fully-canonical subset (definite lengths, shortest-form integers, ascending unique integer keys, no trailing bytes, exact type per field). Hand-rolled (~150 lines each way) rather than taking a dependency.
  • Exactly-K reconstruction. Reconstruct requires exactly K shares, not "at least K." Silently using a subset would hide operator errors.
  • No RNG injection in the public API. An injectable RNG in a secret-sharing library is a foot-gun. Test determinism comes from published reference vectors.
  • Pinned, zeroizing secret buffers. Reconstructed secrets land in a ZeroizingBuffer on the pinned object heap, so the GC cannot relocate (and thus silently copy) the secret, and it is zeroed on dispose.

FAQ

Is this just XOR-style splitting? No. Naive XOR splitting needs all shares to recover. This is true threshold sharing: any K of N, with K < N.

How is this "post-quantum" if Shamir is from 1979? The secrecy guarantee is information-theoretic, so it already survives quantum adversaries — and the optional authentication layer uses ML-DSA-65 (FIPS 204), a post-quantum signature.

Why exactly K shares, not "at least K"? Passing extra shares usually means an operator mistake (wrong pile, duplicates). Requiring exactly K surfaces that instead of quietly succeeding from a subset.

Why a ZeroizingBuffer instead of a byte[]? So the secret lives in pinned memory the GC can't copy, and is wiped deterministically on Dispose.

Can I lose a share? Yes — up to N − K of them. Plan N − K ≥ 2.

Can I revoke a share? Not really. A printed share exists forever. To remove a trustee, rotate the secret and re-split; the old share then unlocks only a retired secret. See docs/OPERATIONS.md.

What does a share look like on disk? A compact, strictly-canonical CBOR map (.pqss). The byte-level format is fully specified in docs/SPEC.md.

Is it fast? A 3-of-5 split of a 32-byte key is well under a millisecond; a 64 KiB split + reconstruct is tens of milliseconds. It is a primitive, not a bottleneck.


Maturity

This package is not audited. It is carefully engineered — constant-time field math, a strict parser, fail-closed validation, honest documentation — but carefully engineered and audited are different claims, and we will not conflate them. Treat it as a well-built primitive pending independent review. See docs/KNOWN-GAPS.md and docs/THREAT-MODEL.md for the unvarnished limitations.


Status & roadmap

Current release: 2.2.0. The information-theoretic core and the engineering around it are feature-complete; the API and the .pqss format are stable and will not change without a SemVer signal. The core, the opt-in VSS package, and the opt-in Extensions package share one version line (see CHANGELOG.md); the on-disk .pqss core format remains v1 (the VSS package adds v2 records). The one item still open is an independent audit — stated plainly, not implied.

Area Status
Shamir split/reconstruct over GF(2⁸), constant-time field math ✅ Stable
Strict canonical .pqss format (spec'd, fuzzed, property-tested) ✅ Stable
HKDF-SHA256 integrity check value ✅ Stable
ZeroizingBuffer (pinned, zeroizing, best-effort page-lock) ✅ Stable
ML-DSA-65 dealer authentication with key pinning (net10.0) ✅ Stable
WrappedSecret, Refresh, DealerCommitment, per-share verify ✅ Stable
Verifiable Secret Sharing — Pedersen, opt-in …Vss (net10.0 signing) ✅ Shipped (unaudited)
Distributed proactive refresh — opt-in …Extensions ✅ Shipped (honest-but-curious)
pqss CLI, six samples, full docs ✅ Stable
Independent security audit ⏳ Not yet — honestly stated, not implied

What you can expect next (intent, not a promise — full detail in ROADMAP.md):

  • Toward a stable 2.x release: independent review of the GF(2⁸) math and the CBOR codec, a written-up real-world dogfooding deployment, and a quiet preview period with no format/API churn.
  • 2.x (additive, non-breaking): more ecosystem samples (EF Core master key, cloud-KMS hybrid) and more published cross-implementation test vectors. The optional …Extensions package for higher-level ceremony helpers has shipped (first helper: distributed proactive refresh).
  • Verifiable Secret Sharing (opt-in, shipped): detect a malicious dealer — ships as the separate PostQuantum.SecretSharing.Vss package — Pedersen VSS over P-256 with optional ML-DSA-65 broadcast signing, kept out of the dependency-free core. Secrecy stays information-theoretic; only the dealer-fraud detection is computational (the honest tradeoff, documented). The wire format is pinned (SPEC §v2), vectors are published, and a dedicated audit guide ships with it; the one remaining step to stable is an independent audit. See docs/KNOWN-GAPS.md §1.
  • Distributed proactive refresh (opt-in, shipped): re-randomize shares across parties — or a co-located set — without ever reconstructing the secret, defeating a mobile adversary. Ships as the separate PostQuantum.SecretSharing.Extensions package (ProactiveRefresh), dependency-free like the core. Honest-but-curious construction (secrecy preserved; malicious-contributor corruption is detected, not a leak). See docs/KNOWN-GAPS.md §5.

What this deliberately is not, now or planned: a KMS, a way to safely split low-entropy secrets directly (use WrappedSecret), or a defense against power/EM side channels and process memory dumps.


Documentation

  • docs/SPEC.md — byte-level .pqss format specification (with a worked, test-pinned hex example).
  • docs/THREAT-MODEL.md — in/out of scope, plainly stated.
  • docs/KNOWN-GAPS.md — real limitations, including the unflattering ones.
  • docs/AUDIT.md — reviewer's audit kit: scope, repro, ranked risk areas, and a checklist (we want this cheap to audit).
  • docs/VSS-DESIGN.md — design + tradeoffs of the opt-in Verifiable Secret Sharing (Pedersen) package, with docs/VSS-AUDIT-GUIDE.md (reviewer kit) and docs/test-vectors-vss.md.
  • docs/PROACTIVE-REFRESH.md — design + threat model of the opt-in …Extensions distributed proactive refresh (re-randomize shares without reconstructing).
  • docs/OPERATIONS.md — trustee ceremony guide.
  • docs/CASE-STUDY-signing-key.md — a verified, reproducible ceremony protecting a code-signing key (with byte-identical + signature proofs).
  • docs/test-vectors.md — cross-implementation test vectors.
  • samples/ — six runnable samples: SignerCustody (authenticated 3-of-5 custody), EnvelopeRecovery (the wrap pattern, net8.0), VaultUnseal (Vault-style sealed service), AspNetCoreDataProtection (encrypt the DP key ring behind a quorum), MaliciousDealerDetected (Verifiable Secret Sharing catching an inconsistent dealer, net8.0), and pqss (a real split/inspect/verify/combine/refresh CLI).
  • docs/BENCHMARKS.md — throughput numbers and constant-time evidence (and how to reproduce).
  • fuzz/ — coverage-guided (SharpFuzz + libFuzzer) fuzzing of the .pqss parser; runs in CI.
  • docs/COMPATIBILITY.md.pqss format-stability and SemVer policy.
  • docs/SUPPLY-CHAIN.md — build provenance, SBOM, reproducible builds, and how to verify a release yourself.
  • CONTRIBUTING.md — build/test, the API-lock and banned-API gates, and the release ritual.
  • ROADMAP.md — v1 / v1.x / v2 plan. CHANGELOG.md — release history.
  • SECURITY.md — how to report vulnerabilities.

License

MIT. See LICENSE.


Soli Deo Gloria — 1 Corinthians 10:31

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on PostQuantum.SecretSharing:

Package Downloads
PostQuantum.SecretSharing.Vss

Opt-in Verifiable Secret Sharing (Pedersen VSS) for PostQuantum.SecretSharing: detect a malicious dealer who issues inconsistent shares, with optional ML-DSA-65 signing of the commitment broadcast. Secrecy stays information-theoretic; dealer-fraud detection is computational (discrete-log) and documented as such. Unaudited new crypto, built to be cheap to audit.

PostQuantum.SecretSharing.Extensions

Opt-in higher-level ceremony helpers for PostQuantum.SecretSharing. Ships distributed proactive secret sharing (Herzberg-style): re-randomize K-of-N shares across parties — or a co-located set — WITHOUT ever reconstructing the secret, so a mobile adversary holding old shares cannot use them after a refresh. Dependency-free, like the core.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.2.0 128 6/14/2026
2.1.0 111 6/14/2026
2.0.1-preview.1 55 6/13/2026
1.0.0-rc.2 60 6/13/2026
1.0.0-rc.1 56 6/13/2026