PostQuantum.SecretSharing
2.2.0
dotnet add package PostQuantum.SecretSharing --version 2.2.0
NuGet\Install-Package PostQuantum.SecretSharing -Version 2.2.0
<PackageReference Include="PostQuantum.SecretSharing" Version="2.2.0" />
<PackageVersion Include="PostQuantum.SecretSharing" Version="2.2.0" />
<PackageReference Include="PostQuantum.SecretSharing" />
paket add PostQuantum.SecretSharing --version 2.2.0
#r "nuget: PostQuantum.SecretSharing, 2.2.0"
#:package PostQuantum.SecretSharing@2.2.0
#addin nuget:?package=PostQuantum.SecretSharing&version=2.2.0
#tool nuget:?package=PostQuantum.SecretSharing&version=2.2.0
PostQuantum.SecretSharing
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 bareBigIntegeror base64 blob you have to frame, version, and validate yourself. - Pinned, zeroizing, page-locked secret memory. The reconstructed secret lands
in a
ZeroizingBufferthe GC can't relocate-and-copy, wiped on dispose — not astring/byte[]left for the collector to scatter. - A built-in wrap helper for low-entropy secrets.
WrappedSecretsplits 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
pqssCLI, five runnable samples, and a trustee operations guide — not just aSplit()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−Kshares 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 withMlDsa65ShareAuthenticator.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−1shares = 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":
- Its core security claim survives quantum computers as a mathematical fact.
- 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
WrappedSecrethelper, 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-inPostQuantum.SecretSharing.Vsspackage (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
Refreshrotates shares (re-split, newsplitId) 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-inPostQuantum.SecretSharing.Extensionspackage (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
srcmulti-targetsnet8.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
.pqssparser 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
Kshares, 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
ZeroizingBufferon 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.xrelease: 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…Extensionspackage 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.Vsspackage — 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. Seedocs/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.Extensionspackage (ProactiveRefresh), dependency-free like the core. Honest-but-curious construction (secrecy preserved; malicious-contributor corruption is detected, not a leak). Seedocs/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.pqssformat 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, withdocs/VSS-AUDIT-GUIDE.md(reviewer kit) anddocs/test-vectors-vss.md.docs/PROACTIVE-REFRESH.md— design + threat model of the opt-in…Extensionsdistributed 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), andpqss(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.pqssparser; runs in CI.docs/COMPATIBILITY.md—.pqssformat-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 | Versions 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. |
-
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 |