# HOWTO — Using `secret-sharing`
Working, copy-pasteable examples for every scheme in the crate. Every
snippet compiles against `secret-sharing = { path = "..." }` with no
other dependencies.
For randomness the crate uses a ChaCha20 CSPRNG (RFC 7539), seeded
from operating-system entropy via [`OsRng`](src/csprng.rs):
```rust
use secret_sharing::{
BigUint, ChaCha20Rng, PrimeField,
csprng::OsRng,
field::mersenne127,
};
fn fresh_rng() -> ChaCha20Rng {
// Production: seed ChaCha20 from /dev/urandom via OsRng.
let mut os = OsRng::new().expect("operating-system entropy unavailable");
ChaCha20Rng::from_os_entropy(&mut os)
}
fn small_field() -> PrimeField {
PrimeField::new(mersenne127())
}
```
**Tests and reproducible benches** may seed from a fixed byte array
via `ChaCha20Rng::from_seed(&[0x42u8; 32])` — the snippets below in
this document use the OS-entropy form to model real usage. If you only
have a hardware RNG or a non-`/dev/urandom` platform, implement the
[`Csprng`](src/csprng.rs) trait yourself; that trait is the boundary,
not the bundled `ChaCha20Rng`.
## Pick the right scheme
| Threshold `(k, n)` over field elements | `shamir` |
| Threshold `(k, n)` for byte strings | `bytes` |
| Geometric variant (hyperplanes) | `blakley` |
| `(k, n)` without polynomials, using CRT | `mignotte` (uniqueness) or `asmuth_bloom` (statistical secrecy) |
| Erasure coding of a file (no secrecy) | `ida` |
| Vector secret with data compression | `ramp`, `kgh`, `yamamoto`, `blakley_meadows` |
| Reed–Solomon-style robust recovery | `decode` (Berlekamp–Welch) |
| Arbitrary monotone access structure | `ito` (cumulative) or `benaloh_leichter` (formula tree) |
| Linear scheme over user-supplied matrix | `kothari`, `karchmer_wigderson`, `brickell`, `massey` |
| Verifiable shares (info-theoretic) | `vss` |
| Verifiable shares (computational, smaller) | `cgma_vss` |
| Long-lived secret refreshed periodically | `proactive` |
| Secret picture you can decode by stacking | `visual` |
| Trivial `n`-of-`n` for testing | `trivial` |
---
## `shamir` — Shamir 1979 `(k, n)` polynomial threshold
```rust
use secret_sharing::{csprng::OsRng, shamir, BigUint, ChaCha20Rng, PrimeField};
use secret_sharing::field::mersenne127;
let field = PrimeField::new(mersenne127());
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);
```
Multi-secret pack (`ℓ ≤ k` secrets share the same polynomial; KGH §IV):
```rust
use secret_sharing::{csprng::OsRng, shamir, BigUint, ChaCha20Rng, PrimeField};
use secret_sharing::field::mersenne127;
let field = PrimeField::new(mersenne127());
let mut rng = ChaCha20Rng::from_os_entropy(&mut OsRng::new().unwrap());
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);
```
## `bytes` — byte-string Shamir
Real secrets are byte arrays (AES keys, passphrases, files). The
`bytes` module chunks a secret into field-sized blocks, runs Shamir per
block, and serialises each share with a self-describing header.
```rust
use secret_sharing::{csprng::OsRng, bytes, ChaCha20Rng, PrimeField};
use secret_sharing::field::mersenne127;
let field = PrimeField::new(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());
```
## `blakley` — geometric `(k, n)`
```rust
use secret_sharing::{csprng::OsRng, blakley, BigUint, ChaCha20Rng, PrimeField};
use secret_sharing::field::mersenne127;
let field = PrimeField::new(mersenne127());
let mut rng = ChaCha20Rng::from_os_entropy(&mut OsRng::new().unwrap());
let secret = BigUint::from_u64(0xC0FFEE);
let shares = blakley::split(&field, &mut rng, &secret, /*k=*/3, /*n=*/5);
let recovered = blakley::reconstruct(&field, &shares[..3], 3).unwrap();
assert_eq!(recovered, secret);
```
## `mignotte` — CRT `(k, n)` with uniqueness recovery
```rust
use secret_sharing::{mignotte, BigUint};
// Bundled (3, 5) sequence over small primes; see source for the full
// validation rules.
let seq = mignotte::small_example_3_of_5();
// Secret must lie strictly inside (alpha, beta) = (437, 2431).
let secret = BigUint::from_u64(1234);
let shares = mignotte::split(&seq, &secret);
let recovered = mignotte::reconstruct(&seq, &shares[..3]).unwrap();
assert_eq!(recovered, secret);
```
To build your own sequence, use `MignotteSequence::new(moduli, k)` with
pairwise-coprime, strictly-increasing `moduli` satisfying $\alpha < \beta$.
## `asmuth_bloom` — modular CRT `(k, n)`
```rust
use secret_sharing::{csprng::OsRng, asmuth_bloom, BigUint, ChaCha20Rng};
let params = asmuth_bloom::small_example_3_of_5();
let mut rng = ChaCha20Rng::from_os_entropy(&mut OsRng::new().unwrap());
let secret = BigUint::from_u64(2); // < m_0 = 5
let shares = asmuth_bloom::split(¶ms, &mut rng, &secret);
let recovered = asmuth_bloom::reconstruct(¶ms, &shares[..3]).unwrap();
assert_eq!(recovered, secret);
```
## `ida` — Rabin Information Dispersal
Erasure coding, **not** secret sharing — per-share is `|F|/k` bytes,
any `k` shares reconstruct, but `k` of any provenance suffice.
```rust
use secret_sharing::{ida, PrimeField};
use secret_sharing::field::mersenne127;
let field = PrimeField::new(mersenne127());
let data = b"the quick brown fox jumps over the lazy dog".to_vec();
let shares = ida::split(&field, &data, /*k=*/3, /*n=*/5);
let refs: Vec<&[u8]> = shares.iter().map(Vec::as_slice).collect();
let recovered = ida::reconstruct(&field, &refs[..3], 3).unwrap();
assert_eq!(recovered, data);
```
## `ramp` — McEliece–Sarwate ramp
Vector secret of `k` field elements; per-share payload is one field
element regardless of secret length.
```rust
use secret_sharing::{ramp, BigUint, PrimeField};
use secret_sharing::field::mersenne127;
let field = PrimeField::new(mersenne127());
let shares = ramp::split(&field, &secret, /*n=*/8);
let recovered = ramp::reconstruct(&field, &shares[..5], secret.len()).unwrap();
assert_eq!(recovered, secret);
```
## `decode` — Berlekamp–Welch errors-and-erasures recovery
```rust
use secret_sharing::decode::reconstruct_with_errors;
use secret_sharing::{csprng::OsRng, shamir, BigUint, ChaCha20Rng, PrimeField};
use secret_sharing::field::mersenne127;
let field = PrimeField::new(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);
```
## `kgh` — KGH §II matrix scheme for vector secrets
```rust
use secret_sharing::{csprng::OsRng, kgh, BigUint, ChaCha20Rng, PrimeField};
use secret_sharing::field::mersenne127;
let field = PrimeField::new(mersenne127());
let mut rng = ChaCha20Rng::from_os_entropy(&mut OsRng::new().unwrap());
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);
```
## `yamamoto` — `(k, L, n)` ramp
```rust
use secret_sharing::{csprng::OsRng, yamamoto, BigUint, ChaCha20Rng, PrimeField};
use secret_sharing::field::mersenne127;
let field = PrimeField::new(mersenne127());
let mut rng = ChaCha20Rng::from_os_entropy(&mut OsRng::new().unwrap());
// k=5, L=3, n=8: any 5 shares recover all 3 secrets; ≤ 2 reveal nothing.
let shares = yamamoto::split(&field, &mut rng, &secret, /*k=*/5, /*n=*/8);
let recovered = yamamoto::reconstruct(&field, &shares[..5], 5, secret.len()).unwrap();
assert_eq!(recovered, secret);
```
## `blakley_meadows` — `(k, L, n)` Blakley ramp
```rust
use secret_sharing::{csprng::OsRng, blakley_meadows, BigUint, ChaCha20Rng, PrimeField};
use secret_sharing::field::mersenne127;
let field = PrimeField::new(mersenne127());
let mut rng = ChaCha20Rng::from_os_entropy(&mut OsRng::new().unwrap());
// k=5, L=3, n=8: same threshold structure as yamamoto, geometric form.
let shares = blakley_meadows::split(&field, &mut rng, &secret, /*k=*/5, /*n=*/8);
let recovered = blakley_meadows::reconstruct(&field, &shares[..5], 5, secret.len()).unwrap();
assert_eq!(recovered, secret);
```
## `ito` — Ito–Saito–Nishizeki cumulative array
Realises any monotone access structure described by its maximal
forbidden coalitions.
```rust
use secret_sharing::{csprng::OsRng, ito, BigUint, ChaCha20Rng, PrimeField};
let field = PrimeField::new(BigUint::from_u64(65_537));
let mut rng = ChaCha20Rng::from_os_entropy(&mut OsRng::new().unwrap());
// Custom access structure: {1,2}, {3,4}, {1,3} qualify.
// Maximal forbidden coalitions: {1,4}, {2,3}, {2,4}.
let structure = ito::AccessStructure::new(
/*n=*/4,
vec![vec![1, 4], vec![2, 3], vec![2, 4]],
).unwrap();
let secret = BigUint::from_u64(0xC0DE);
let shares = ito::split(&field, &mut rng, &secret, &structure);
// {1, 2} qualifies and recovers.
let coalition: Vec<_> = shares.iter()
.filter(|s| matches!(s.player, 1 | 2))
.cloned()
.collect();
let recovered = ito::reconstruct(&field, &structure, &coalition).unwrap();
assert_eq!(recovered, secret);
```
For an `(k, n)` threshold rendered as ISN, use the helper:
```rust
use secret_sharing::ito::threshold_access_structure;
let structure = threshold_access_structure(/*n=*/5, /*k=*/3);
```
## `benaloh_leichter` — monotone formula tree
```rust
use secret_sharing::{csprng::OsRng, benaloh_leichter as bl, BigUint, ChaCha20Rng, PrimeField};
let field = PrimeField::new(BigUint::from_u64(65_537));
let mut rng = ChaCha20Rng::from_os_entropy(&mut OsRng::new().unwrap());
// T = (P1 AND P2) OR (P1 AND P3) OR (P2 AND P3) — a 2-of-3 threshold.
let formula = bl::Formula::or(vec![
bl::Formula::and(vec![bl::Formula::party(1), bl::Formula::party(2)]),
bl::Formula::and(vec![bl::Formula::party(1), bl::Formula::party(3)]),
bl::Formula::and(vec![bl::Formula::party(2), bl::Formula::party(3)]),
]);
let secret = BigUint::from_u64(0xBEEF);
let shares = bl::split(&field, &mut rng, &secret, &formula);
let pair: Vec<_> = shares.iter()
.filter(|s| matches!(s.player, 1 | 2))
.cloned()
.collect();
let recovered = bl::reconstruct(&field, &formula, &pair).unwrap();
assert_eq!(recovered, secret);
```
## `kothari` — generalised linear `(k, n)`
User-supplied public matrix. Vandermonde convenience matches Shamir.
```rust
use secret_sharing::{csprng::OsRng, kothari, BigUint, ChaCha20Rng, PrimeField};
let field = PrimeField::new(BigUint::from_u64((1u64 << 61) - 1));
let scheme = kothari::vandermonde(field, /*k=*/3, /*n=*/5);
let mut rng = ChaCha20Rng::from_os_entropy(&mut OsRng::new().unwrap());
let secret = BigUint::from_u64(0xCAFE);
let shares = kothari::split(&scheme, &mut rng, &secret);
let pairs: Vec<_> = (0..3).map(|c| (c, shares[c].clone())).collect();
let recovered = kothari::reconstruct(&scheme, &pairs).unwrap();
assert_eq!(recovered, secret);
```
## `karchmer_wigderson` — monotone span programs
The most general linear-SSS framework. Threshold convenience:
```rust
use secret_sharing::{csprng::OsRng, karchmer_wigderson as kw, BigUint, ChaCha20Rng, PrimeField};
let field = PrimeField::new(BigUint::from_u64((1u64 << 61) - 1));
let prog = kw::threshold_msp(field, /*k=*/3, /*n=*/5);
let mut rng = ChaCha20Rng::from_os_entropy(&mut OsRng::new().unwrap());
let secret = BigUint::from_u64(0xC0FFEE);
let shares = kw::split(&prog, &mut rng, &secret);
let coalition: Vec<_> = shares.iter()
.filter(|s| matches!(s.player, 1 | 2 | 3))
.cloned()
.collect();
let recovered = kw::reconstruct(&prog, &coalition).unwrap();
assert_eq!(recovered, secret);
```
For a custom MSP, hand-build the `(rows, labels)` so that the row-set
of every qualified coalition spans the canonical target `e_1`.
## `brickell` — ideal vector-space SSS
One vector per player; per-player share is a single field element.
```rust
use secret_sharing::{csprng::OsRng, brickell, BigUint, ChaCha20Rng, PrimeField};
let field = PrimeField::new(BigUint::from_u64((1u64 << 61) - 1));
// Brickell's flagship example: realises an access structure where
// {1} qualifies alone, {2,3} qualifies, but {2} and {3} do not.
let v = vec![
vec![BigUint::one(), BigUint::zero()], // v_1 = (1, 0)
vec![BigUint::one(), BigUint::one()], // v_2 = (1, 1)
vec![BigUint::one(), BigUint::from_u64(2)],// v_3 = (1, 2)
vec![BigUint::zero(), BigUint::one()], // v_4 = (0, 1)
];
let scheme = brickell::Scheme::new(field, v);
let mut rng = ChaCha20Rng::from_os_entropy(&mut OsRng::new().unwrap());
let secret = BigUint::from_u64(33);
let shares = brickell::split(&scheme, &mut rng, &secret);
let solo_p1: Vec<_> = shares.iter()
.filter(|s| s.player == 1)
.cloned()
.collect();
let recovered = brickell::reconstruct(&scheme, &solo_p1).unwrap();
assert_eq!(recovered, secret);
```
## `massey` — linear-code SSS via minimal codewords
```rust
use secret_sharing::{csprng::OsRng, massey, BigUint, ChaCha20Rng, PrimeField};
let field = PrimeField::new(BigUint::from_u64((1u64 << 61) - 1));
// Reed–Solomon-style (2, 3) Shamir as a Massey code:
// G is 2 × 4, column 0 is the secret slot, columns 1..3 are players.
let g = vec![
vec![BigUint::one(), BigUint::one(), BigUint::one(), BigUint::one()],
vec![BigUint::zero(), BigUint::one(), BigUint::from_u64(2), BigUint::from_u64(3)],
];
let scheme = massey::CodeScheme::new(field, g);
let mut rng = ChaCha20Rng::from_os_entropy(&mut OsRng::new().unwrap());
let secret = BigUint::from_u64(0xC0FFEE);
let shares = massey::split(&scheme, &mut rng, &secret);
let pair: Vec<_> = shares.iter()
.filter(|s| matches!(s.player, 1 | 2))
.cloned()
.collect();
let recovered = massey::reconstruct(&scheme, &pair).unwrap();
assert_eq!(recovered, secret);
```
## `vss` — Rabin–Ben-Or bivariate VSS (information-theoretic)
```rust
use secret_sharing::{csprng::OsRng, vss, BigUint, ChaCha20Rng, PrimeField};
let field = PrimeField::new(BigUint::from_u64((1u64 << 61) - 1));
let mut rng = ChaCha20Rng::from_os_entropy(&mut OsRng::new().unwrap());
let secret = BigUint::from_u64(0xC0FFEE);
// Honest-majority: 2(k − 1) < n. Helper:
assert!(vss::is_honest_majority(/*k=*/3, /*n=*/5));
let shares = vss::deal(&field, &mut rng, &secret, 3, 5);
// Pairwise cross-checks must pass before any share is used.
assert!(vss::verify_consistent(&field, &shares));
let recovered = vss::reconstruct(&field, &shares[..3], 3).unwrap();
assert_eq!(recovered, secret);
```
## `cgma_vss` — Chor-GMA computational VSS (Feldman commitments)
```rust
use secret_sharing::{csprng::OsRng, cgma_vss, BigUint, ChaCha20Rng};
// RFC 5114 §2.3 — 2048-bit p, 256-bit prime-order subgroup, generator
// g of that subgroup. Validated cryptographer-grade Schnorr group.
let group = cgma_vss::rfc5114_modp_2048_256();
let mut rng = ChaCha20Rng::from_os_entropy(&mut OsRng::new().unwrap());
let secret = BigUint::from_u64(0x1234_5678_9abc_def0); // < q (256-bit)
let (shares, commits) = cgma_vss::deal(&group, &mut rng, &secret, /*k=*/3, /*n=*/5);
// Each receiver must verify before using their share.
for s in &shares {
assert!(cgma_vss::verify_share(&group, &commits, s), "share {} valid", s.player);
}
let recovered = cgma_vss::reconstruct(&group, &shares[..3], 3).unwrap();
assert_eq!(recovered, secret);
```
## `proactive` — Herzberg et al. share refresh
Refresh a Shamir share set without changing the underlying secret.
Old shares are no longer compatible with new shares — discard them.
```rust
use secret_sharing::{csprng::OsRng, proactive, shamir, BigUint, ChaCha20Rng, PrimeField};
use secret_sharing::field::mersenne127;
let field = PrimeField::new(mersenne127());
let mut rng = ChaCha20Rng::from_os_entropy(&mut OsRng::new().unwrap());
let secret = BigUint::from_u64(0xC0FFEE);
let mut shares = shamir::split(&field, &mut rng, &secret, /*k=*/3, /*n=*/5);
// Epoch boundary: refresh.
shares = proactive::refresh(&field, &mut rng, &shares, 3);
// Same secret, different polynomial.
let recovered = shamir::reconstruct(&field, &shares[..3], 3).unwrap();
assert_eq!(recovered, secret);
// Lost-share recovery: simulate losing player 3's share.
let lost_x = shares[2].x.clone();
let live = vec![shares[0].clone(), shares[1].clone(), shares[3].clone()];
let recovered_share = proactive::recover_share(&field, &live, 3, &lost_x).unwrap();
assert_eq!(recovered_share, shares[2]);
```
## `visual` — Naor–Shamir visual cryptography (n, n)
Black-and-white image as `Vec<Vec<bool>>`; reconstruction by stacking
(bitwise OR) all `n` shares. Per-pixel expansion is $2^{n-1}$.
```rust
use secret_sharing::{csprng::OsRng, visual, ChaCha20Rng};
let mut rng = ChaCha20Rng::from_os_entropy(&mut OsRng::new().unwrap());
// 4×4 checkerboard.
let secret: Vec<Vec<bool>> = (0..4)
.map(|y| (0..4).map(|x| (x + y) % 2 == 0).collect())
.collect();
let shares = visual::split_n_of_n(&mut rng, &secret, /*n=*/2);
// Stack — physically, lay the transparencies on top of each other.
let stacked = visual::stack(&shares).unwrap();
let decoded = visual::decode(&stacked, /*n=*/2).unwrap();
assert_eq!(decoded, secret);
```
`n` is not stored alongside the shares — caller must persist it.
## `trivial` — `n`-of-`n` additive split
```rust
use secret_sharing::{csprng::OsRng, trivial, BigUint, ChaCha20Rng, PrimeField};
let field = PrimeField::new(BigUint::from_u64(65_537));
let mut rng = ChaCha20Rng::from_seed(&[7u8; 32]);
let secret = BigUint::from_u64(12_345);
let shares = trivial::split(&field, &mut rng, &secret, /*n=*/3);
let recovered = trivial::reconstruct(&field, &shares);
assert_eq!(recovered, secret);
// Byte-XOR variant (q = 2 special case):
let xor_shares = trivial::split_xor(&mut rng, b"my super secret payload", 3);
let recovered = trivial::reconstruct_xor(&xor_shares);
assert_eq!(recovered, b"my super secret payload".to_vec());
```
---
## Failure modes that return `None`
Every reconstruction function in this crate uses `Option<...>` rather
than panicking on caller-supplied bad input. A `None` means **one of**:
- fewer than `k` shares,
- duplicate / out-of-range share index or player,
- malformed wire bytes (for `bytes` and `ida`),
- extra shares (beyond the first `k`) inconsistent with the
polynomial or matrix fitted to the first `k`,
- (CRT schemes) recovered value outside the legal secret range,
- (`vss`) any pairwise cross-check fails,
- (`cgma_vss`) commitment fails the discrete-log verification or lies
outside the order-`q` subgroup.
Static contract violations (e.g. `k < 2`, `n < k`, secret ≥ field
modulus) panic via `assert!` — those are programmer errors, not
runtime failures.
## Picking a field
| `field::mersenne127()` | $2^{127} - 1$ | 15 bytes | 16 bytes |
| `field::mersenne521()` | $2^{521} - 1$ | 65 bytes | 66 bytes |
Any user-supplied prime works — `PrimeField::new(p)` accepts an
arbitrary `BigUint`. The caller is responsible for choosing a value
that is genuinely prime and large enough to comfortably exceed `n`.
## Threat model recap
- **Variable-time arithmetic.** `BigUint` is documented as variable-
time; do not deploy in side-channel-exposed environments.
- **Honest dealer.** Most schemes (everything except `vss` and
`cgma_vss`) trust the dealer. Use a VSS module if dealer integrity
is in scope.
- **Tampering.** Naive Lagrange / linear-system reconstruction returns
a wrong-but-plausible secret if exactly `k` shares are supplied and
one is corrupted. Provide extras (`shares.len() > k`) so the
validator can cross-check, or use `decode` for Reed–Solomon error
correction up to `(m − k) / 2` shares.
- **Zeroization.** `BigUint` zeroes its limb buffer on `Drop`. Other
intermediate `Vec`s and byte buffers are not scrubbed beyond that.