Threshold secret-sharing schemes implemented in pure, safe Rust
directly from their published specifications. The crate is fully
self-contained: Cargo.toml declares no external dependencies, and
its Cargo.lock lists only this crate. Big-integer arithmetic, prime
helpers, the Csprng trait, the ChaCha20Rng CSPRNG, and the
OsRng entropy source are all in-tree.
The crate covers three papers in pubs/ and every constructive
scheme catalogued in bib/references.bib:
| Paper | Year | What it gives us |
|---|---|---|
| Shamir, How to Share a Secret | 1979 | Classical |
| Karnin, Greene, Hellman, On Secret Sharing Systems | 1983 | Trivial n-of-n split, multi-secret extension, the matrix scheme |
| McEliece, Sarwate, On Sharing Secrets and Reed–Solomon Codes | 1981 | Ramp (data-compressed) variant and errors-and-erasures recovery via Berlekamp–Welch |
| Blakley, Safeguarding Cryptographic Keys | 1979 | Geometric |
| Mignotte, How to Share a Secret | 1983 | CRT-based |
| Asmuth, Bloom, A Modular Approach to Key Safeguarding | 1983 | CRT-based |
| Rabin, Efficient Dispersal of Information… | 1989 | Reed–Solomon-style information dispersal (erasure coding, not secret sharing) |
| Yamamoto, *Secret Sharing System Using |
1986 | Generalised ramp scheme spanning Shamir (when |
| Ito, Saito, Nishizeki, Secret Sharing Scheme Realising General Access Structure | 1989 | Cumulative-array realisation of any monotone access structure |
| Benaloh, Leichter, Generalised Secret Sharing and Monotone Functions | 1988 | Recursive distribution along a monotone Boolean formula |
| Rabin, Ben-Or, Verifiable Secret Sharing and Multiparty Protocols | 1989 | Information-theoretic VSS via bivariate polynomials with cross-checks |
| Kothari, Generalized Linear Threshold Scheme | 1984 | Linear |
| Brickell, Some Ideal Secret Sharing Schemes | 1989 | Ideal vector-space SSS — one field-element per player |
| Karchmer, Wigderson, On Span Programs | 1993 | Monotone span programs — captures every linear SSS |
| Massey, Minimal Codewords and Secret Sharing | 1993 | Linear-code SSS — secret = column 0 of a generator matrix |
| Naor, Shamir, Visual Cryptography | 1994 |
|
| Blakley, Meadows, Security of Ramp Schemes | 1984 |
|
| Chor, Goldwasser, Micali, Awerbuch, Verifiable Secret Sharing | 1985 | Computational VSS via discrete-log (Feldman) commitments |
| Herzberg, Jarecki, Krawczyk, Yung, Proactive Secret Sharing | 1995 | Periodic refresh of Shamir shares + lost-share recovery |
For the algebra behind every scheme, see THEORY.md.
For copy-pasteable usage examples, see HOWTO.md. For
performance numbers and kiviat / line / bar charts, see
PERFORMANCE.md. For the current peer-review
status, see PEER-REVIEW.md.
A (k, n) threshold scheme splits a secret
-
Recoverability: any
$k$ shares reconstruct$s$ . -
Secrecy: any
$k - 1$ shares reveal nothing — every candidate value of$s$ remains equally likely (information-theoretic security).
secret_sharing
├── field PrimeField over BigUint; mersenne127 / mersenne521 helpers
├── poly Horner evaluation, Lagrange interpolation
├── trivial KGH §I additive (and XOR) n-of-n split
├── shamir Shamir 1979 (k, n) + KGH §IV multi-secret extension
├── bytes Chunked byte-string Shamir with a versioned wire format
├── kgh KGH §II matrix scheme v_i = u·A_i for vector secrets
├── ramp McEliece–Sarwate ramp / data-compressed Reed–Solomon
├── decode McEliece–Sarwate errors-and-erasures via Berlekamp–Welch
├── blakley Blakley 1979 hyperplane (k, n) threshold
├── mignotte Mignotte 1983 CRT (k, n) — reconstruction-uniqueness
├── asmuth_bloom Asmuth–Bloom 1983 modular CRT (k, n) — statistical secrecy
├── ida Rabin 1989 Information Dispersal (erasure coding, no secrecy)
├── yamamoto Yamamoto 1986 (k, L, n) ramp — generalises Shamir & MS-ramp
├── ito Ito–Saito–Nishizeki 1989 cumulative-array general access
├── benaloh_leichter Benaloh–Leichter 1988 monotone-formula scheme
├── kothari Kothari 1984 generalised linear (k, n)
├── karchmer_wigderson Karchmer–Wigderson 1993 monotone span programs
├── brickell Brickell 1989 ideal vector-space SSS
├── massey Massey 1993 linear-code SSS via minimal codewords
├── visual Naor–Shamir 1994 visual cryptography (n, n)
├── blakley_meadows Blakley–Meadows 1984 (k, L, n) hyperplane ramp
├── vss Rabin–Ben-Or 1989 bivariate-polynomial VSS
├── cgma_vss Chor–Goldwasser–Micali–Awerbuch 1985 Feldman-style VSS
├── proactive Herzberg et al. 1995 share refresh + lost-share recovery
├── bigint Self-contained BigUint + BigInt + MontgomeryCtx
├── csprng Csprng trait + ChaCha20Rng (RFC 7539) + OsRng
├── primes gcd, mod_inverse, mod_pow, random_below, is_probable_prime{,_random}
├── secure Zeroize + Zeroizing<T> + ct_eq_biguint + ct_eq_biguint_padded
└── poly Horner + Lagrange (mod-p label discipline)
Pick
so that
Choose a random degree
over
Pack
with
Generalise the secret to a length
Real secrets are byte strings (AES keys, passphrases, files), not
single field elements. The bytes module chunks the secret into
block_len =
version : u8 = 0x01
label : u8 = trustee index 1..=255
length : u32 (BE) = byte-length of the original secret
blocks : [u8; ...] = concatenated big-endian field-element blocks
share_elem_len =
The secret is now
McEliece–Sarwate observed that Shamir's scheme is a Reed–Solomon
code, so the standard errors-and-erasures decoders apply. Given
This crate implements Berlekamp–Welch: find polynomials
Pick a random point
with
A Mignotte sequence is
The secret
Strengthens Mignotte with a public secret-modulus
Not a secret-sharing scheme. Encode a file
The secret is
Realise any monotone access structure
Distribute the secret along a monotone Boolean formula tree:
- AND nodes additively split the value among children (random shares summing to the value).
- OR nodes hand each child the same value.
- Leaves go to the labelled party.
Reconstruction walks the formula bottom-up: AND requires every child
to recover (sum); OR succeeds as soon as any child recovers.
Per-party share size is the number of leaves labelled with that
party — small when the formula is succinct. Each fragment carries the
path to its source leaf, and reconstruct rejects fragments whose
path resolves to a leaf labelled with a different player.
Public
The most general linear SSS framework. A labelled matrix
One vector
A
Black-and-white image as Vec<Vec<bool>>. Each pixel expands to
Place the length
Information-theoretic verifiable secret sharing. Sample a bivariate
polynomial deal_validated constructor.
Public Schnorr group
Group construction validates primality of is_probable_prime_random for larger), exact divisibility verify_share time.
refresh produces a fresh share vector for the same secret by
having each player contribute a degree recover_share
reconstructs a missing share via Lagrange evaluation, with the same
extras-validation discipline as shamir::reconstruct.
Beyond the per-scheme math, the crate enforces a uniform defensive- security layer:
-
Volatile-zero on drop. Every secret-bearing intermediate buffer
(polynomial coefficient vectors, bivariate matrices, refresh
contributions, rng buffers) is wrapped in
Zeroizing<T>so its contents are volatile-zeroed on every exit path, including panic unwind.BigUint::Dropitself volatile-zeros the entire allocated capacity of its limb buffer (not just $[0..\mathrm{len})$). -
Constant-time secret-derived equality.
BigUint::eqcompares all limbs in an OR-fold without short-circuit. Thesecure::ct_eq_biguint{,_padded}helpers do the same at the byte level; every reconstruct / extras-validate site that compares secret-derived values uses one of them, never==directly. -
Non-leaking Debug.
BigUint's manualDebugprintsBigUint(<elided>)so panic backtraces,dbg!, andassert_eq!failure messages cannot accidentally print a secret limb. Every share-bearing struct elides its inner value through the same pattern. -
CSPRNG memory hygiene.
ChaCha20Rnghas noClone, scrubs its key / nonce / counter / keystream-buffer inDrop, scrubs thestateandinitstack arrays insiderefillthrough their live mutable bindings, and treats$(\mathrm{counter},\mathrm{nonce})$ as a single 128-bit block index so the period is$2^{128}$ blocks.
What is not addressed:
- Variable-time arithmetic.
BigUintmul/add/sub/invare not constant-time. Co-located timing observers can recover bit-length information from secret arithmetic. Side-channel resistance is explicitly out of scope. mlockagainst swap. Platform-specific; do at the application layer if needed.
[dependencies]
secret-sharing = { path = "path/to/secret-sharing" }The crate has no other dependencies.
use secret_sharing::{
csprng::OsRng,
field::{mersenne127, PrimeField},
shamir, BigUint, ChaCha20Rng,
};
let field = PrimeField::new_unchecked(mersenne127()); // bundled prime
let mut rng = ChaCha20Rng::from_os_entropy(&mut OsRng::new().unwrap());
let secret = BigUint::from_u64(0xC0FFEE);
let shares = shamir::split(&field, &mut rng, &secret, /*k=*/3, /*n=*/5);
let recovered = shamir::reconstruct(&field, &shares[..3], 3).unwrap();
assert_eq!(recovered, secret);use secret_sharing::{
csprng::OsRng, bytes,
field::{mersenne127, PrimeField},
ChaCha20Rng,
};
let field = PrimeField::new_unchecked(mersenne127());
let mut rng = ChaCha20Rng::from_os_entropy(&mut OsRng::new().unwrap());
let aes_key = b"32-byte AES-256 key payload!!!!!";
let shares = bytes::split(&field, &mut rng, aes_key, /*k=*/3, /*n=*/5);
let refs: Vec<&[u8]> = shares.iter().map(Vec::as_slice).collect();
let recovered = bytes::reconstruct(&field, &refs[..3], 3).unwrap();
assert_eq!(recovered, aes_key.to_vec());use secret_sharing::{
csprng::OsRng,
decode::reconstruct_with_errors,
field::{mersenne127, PrimeField},
shamir, BigUint, ChaCha20Rng,
};
let field = PrimeField::new_unchecked(mersenne127());
let mut rng = ChaCha20Rng::from_os_entropy(&mut OsRng::new().unwrap());
let secret = BigUint::from_u64(0xDECAF);
// k = 4, n = 11 — survives up to t = 3 tampered shares (m − 2t = 5 ≥ k).
let mut shares = shamir::split(&field, &mut rng, &secret, 4, 11);
shares[2].y = field.add(&shares[2].y, &BigUint::from_u64(1));
shares[5].y = BigUint::zero();
shares[9].y = field.add(&shares[9].y, &BigUint::from_u64(99));
let recovered = reconstruct_with_errors(&field, &shares, 4, 3).unwrap();
assert_eq!(recovered, secret);use secret_sharing::{
csprng::OsRng,
field::{mersenne127, PrimeField},
shamir, BigUint, ChaCha20Rng,
};
let field = PrimeField::new_unchecked(mersenne127());
let mut rng = ChaCha20Rng::from_os_entropy(&mut OsRng::new().unwrap());
let secrets: Vec<BigUint> = (1..=3).map(|i| BigUint::from_u64(900 + i)).collect();
let shares = shamir::split_multi(&field, &mut rng, &secrets, /*k=*/4, /*n=*/7);
let got = shamir::reconstruct_multi(&field, &shares[..4], 4, secrets.len()).unwrap();
assert_eq!(got, secrets);use secret_sharing::{
field::{mersenne127, PrimeField},
ramp, BigUint,
};
let field = PrimeField::new_unchecked(mersenne127());
let secret: Vec<BigUint> = (0..5).map(|i| BigUint::from_u64(0x1000 + i)).collect();
let shares = ramp::split(&field, &secret, /*n=*/8);
let recovered = ramp::reconstruct(&field, &shares[..5], secret.len()).unwrap();
assert_eq!(recovered, secret);use secret_sharing::{
csprng::OsRng,
field::{mersenne127, PrimeField},
kgh, BigUint, ChaCha20Rng,
};
let field = PrimeField::new_unchecked(mersenne127());
let mut rng = ChaCha20Rng::from_os_entropy(&mut OsRng::new().unwrap());
let secret: Vec<BigUint> = (1..=4).map(|i| BigUint::from_u64(0x100 + i)).collect();
let shares = kgh::split(&field, &mut rng, &secret, /*k=*/3, /*n=*/6);
let recovered = kgh::reconstruct(&field, &shares[..3], 3).unwrap();
assert_eq!(recovered, secret);For copy-pasteable examples covering every scheme, see
HOWTO.md.
cargo test # 236 unit + 7 integration + 2 doc
cargo clippy --all-targets -- -D warnings # clean
bash scripts/bench_pilot.sh # CI-backed pilot-bench numbers — see PERFORMANCE.mdThe crate is generic over the prime modulus. Two convenience fields:
| Function | Modulus | Plaintext block | Share-element width |
|---|---|---|---|
field::mersenne127() |
15 bytes | 16 bytes | |
field::mersenne521() |
65 bytes | 66 bytes |
Any user-supplied prime works as well — PrimeField::new(p) runs
Miller–Rabin (deterministic for primes::is_probable_prime_random yourself first and then construct
via PrimeField::new_unchecked once you have witnesses.
-
No external dependencies.
Cargo.toml's[dependencies]is empty. Big-integer code is in-tree (src/bigint.rs); the CSPRNG is ChaCha20 (src/csprng.rs); OS entropy is read directly from/dev/urandom(src/csprng.rs::OsRng). -
Variable-time arithmetic.
BigUintis documented as variable-time; do not deploy in side-channel-exposed environments. -
No allocation-free path. Polynomials and matrices use plain
Vec<BigUint>. Performance is dominated by big-integer modular multiplication, not by allocator overhead. -
Wire format only in
bytesandida. Other modules return nativeBigUintshares; serialise viaBigUint::to_be_bytes()if needed. -
Option-returning reconstructors. Everyreconstructvalidates its inputs and returnsNoneon contract violations the caller may not have controlled (corrupt shares, malformed wire format, duplicate / mod-p-aliased$x$ coordinates, non-canonical encoded field elements). Static contract violations (e.g.$k < 2$ ) panic.
BSD 2-Clause. See LICENSE.
