---
title: Secret Sharing
description: Shamir, Feldman, and Pedersen verifiable secret sharing over any supported curve — plus the legacy sharing/v1 layer and its known defects.
sidebar:
  order: 2
  icon: split
---

The `sharing` package splits a curve scalar into `n` shares such that any `t` of them reconstruct
it, and fewer than `t` reveal nothing. All three schemes share one share type and one
reconstruction routine; they differ only in what a shareholder can *verify* about the share it was
handed.

This is dealer-based sharing: one process holds the secret while `Split` runs. If you need a key
that never exists in one place, you want [DKG](/threshold/dkg) instead.

## Picking a scheme

<CardGroup cols={3}>
<Card title="Shamir" icon="split">
No verification. Fastest, smallest. Use only when every shareholder is trusted, or when a higher
layer verifies for you.
</Card>
<Card title="Feldman" icon="eye">
Adds polynomial commitments `a_j · G`. A holder can check its own share against them. The
commitments leak `a_0 · G` — i.e. the public key of the secret.
</Card>
<Card title="Pedersen" icon="eye-off">
Feldman plus a blinding polynomial under a second generator `H`. The commitments are
information-theoretically hiding, so nothing about the secret leaks before reconstruction.
</Card>
</CardGroup>

## Shamir

```go
import (
	crand "crypto/rand"

	"github.com/sonr-io/crypto/core/curves"
	"github.com/sonr-io/crypto/sharing"
)

func shamirRoundTrip() error {
	curve := curves.ED25519()

	scheme, err := sharing.NewShamir(3, 5, curve) // 3-of-5
	if err != nil {
		return err
	}

	secret := curve.Scalar.Hash([]byte("test"))
	shares, err := scheme.Split(secret, crand.Reader)
	if err != nil {
		return err
	}

	// Any 3 of the 5 shares reconstruct the secret.
	recovered, err := scheme.Combine(shares[0], shares[2], shares[4])
	if err != nil {
		return err
	}
	_ = recovered.Cmp(secret) // == 0
	return nil
}
```

`Combine` reconstructs the scalar. `CombinePoints` does the same interpolation in the group,
returning `secret · G` — useful when you want to check that a share set corresponds to a known
public key without materialising the key. `Shamir.LagrangeCoeffs(identities []uint32)` returns the
interpolation coefficients on their own, keyed by identifier, so a caller can compute
`Σ λ_i · share_i` itself; this is exactly what [`ted25519/frost`](/threshold/threshold-ed25519)
needs.

:::info[LagrangeCoeffs has two different signatures]
`Shamir.LagrangeCoeffs` takes `identities []uint32`. `Feldman.LagrangeCoeffs` and
`Pedersen.LagrangeCoeffs` take `shares map[uint32]*ShamirShare` and then delegate to the Shamir
implementation — same computation, different argument shape. Note the subtlety: they read the id
from each map *value* (`share.Id`), never from the map key, so a mismatched key is silently
ignored and a nil value panics. `Combine` and `CombinePoints` likewise delegate, so all three
types behave identically there.
:::

:::danger[Shamir has no integrity check whatsoever]
`Combine` validates that each share's id is nonzero and within `limit`, that ids are not duplicated,
and that the value is a nonzero scalar. It does **not** and cannot check that a share lies on the
dealer's polynomial. One malicious shareholder submitting a well-formed but wrong `Value` silently
produces a wrong secret, with no error. If shareholders are not mutually trusted, use Feldman or
Pedersen and verify every share before combining.
:::

## Feldman

`Feldman.Split` returns a `*FeldmanVerifier` alongside the shares. The verifier holds `Threshold`
points — `a_j · G` for each polynomial coefficient — and `Verify` recomputes
`Σ a_j · id^j` and compares it to `share · G`.

```go
scheme, err := sharing.NewFeldman(3, 5, curves.ED25519())
if err != nil {
	return err
}

verifier, shares, err := scheme.Split(secret, crand.Reader)
if err != nil {
	return err
}

for _, s := range shares {
	if err := verifier.Verify(s); err != nil { // nil == valid
		return err
	}
}

recovered, err := scheme.Combine(shares[0], shares[1], shares[2])
```

`Verify` returns `fmt.Errorf("not equal")` on a mismatch — there is no typed sentinel error, so
compare against `nil` rather than matching the message.

:::info[The verifier is public data, and it publishes the public key]
`Commitments[0]` *is* `secret · G`. Distributing a `FeldmanVerifier` therefore discloses the public
key of the shared secret. That is normally what you want for a signing key. It is not what you want
if the shared secret must stay hidden even in the exponent — use Pedersen.
:::

## Pedersen

`NewPedersen` takes a **generator point** rather than a curve; the curve is derived from
`generator.CurveName()`. `Split` returns a single struct carrying both verifiers and both share
sets.

```go
curve := curves.ED25519()

// H must have unknown discrete log with respect to G.
h := curve.Point.Generator().Hash([]byte("sonr/pedersen/H/v1"))

scheme, err := sharing.NewPedersen(3, 5, h)
if err != nil {
	return err
}

result, err := scheme.Split(secret, crand.Reader)
if err != nil {
	return err
}

for i := range result.SecretShares {
	// Pedersen verification needs BOTH the secret share and its blinding share.
	err = result.PedersenVerifier.Verify(result.SecretShares[i], result.BlindingShares[i])
	if err != nil {
		return err
	}
	// The Feldman verifier is also returned and checks the secret share alone.
	if err = result.FeldmanVerifier.Verify(result.SecretShares[i]); err != nil {
		return err
	}
}

recovered, err := scheme.Combine(result.SecretShares[0], result.SecretShares[1], result.SecretShares[2])
```

| Prop | Type | Default | Description |
| - | - | - | - |
| `Blinding` | `curves.Scalar` | - | The blinding factor's intercept. Secret — leaking it collapses Pedersen to Feldman. |
| `SecretShares` | `[]*ShamirShare` | - | Shares of the secret. Length == limit. |
| `BlindingShares` | `[]*ShamirShare` | - | Shares of the blinding polynomial, index-aligned with SecretShares. |
| `FeldmanVerifier` | `*FeldmanVerifier` | - | Unblinded commitments a_j · G. Reveals the public key. |
| `PedersenVerifier` | `*PedersenVerifier` | - | Blinded commitments a_j · G + b_j · H, plus the generator H. |

:::danger[The generator must have unknown discrete log relative to the base point]
Pedersen's hiding property rests on nobody knowing `x` with `H = x · G`. If the dealer picks
`H = x · G` for a known `x`, it can open the commitment `a_j · G + b_j · H` to any value it likes:
the commitment stops being binding, so the dealer can hand out shares of one secret and later prove
they were shares of another. `NewPedersen` cannot detect this — it only checks that the generator is
on the curve and is not the identity. Derive `H` by hashing to the curve (as above), or use a
published nothing-up-my-sleeve constant. Never derive it as a scalar multiple of `G`.
:::

## ShamirShare

All three schemes emit the same share type.

| Prop | Type | Default | Description |
| - | - | - | - |
| `Id` | `uint32` | - | The x-coordinate. 1-indexed; 0 is rejected. Must be ≤ limit. |
| `Value` | `[]byte` | - | The y-coordinate, as the curve's canonical scalar encoding. |

`Bytes()` returns the id as 4 big-endian bytes followed by `Value` — a stable wire form.
`Validate(curve)` rejects a zero id, a `Value` that does not decode as a scalar on `curve`, and a
zero scalar. The struct carries `json` tags (`identifier`, `value`) and round-trips through
`encoding/json`.

## Constructor constraints

Identical across `NewShamir`, `NewFeldman`, and `NewPedersen`, checked in this order:

| Check | Error |
| --- | --- |
| `limit >= threshold` | `limit cannot be less than threshold` |
| `threshold >= 2` | `threshold cannot be less than 2` |
| `limit <= 255` | `cannot exceed 255 shares` |
| curve resolvable / non-nil | `invalid curve` |

`Shamir.Split` and `Feldman.Split` additionally reject a zero secret with `invalid secret`.

:::warning[Two rough edges in the constructors]
`Pedersen.Split` reaches the shared polynomial helper directly and **skips the zero-secret check**,
so `NewPedersen(...).Split(curve.Scalar.Zero(), rand)` succeeds and produces shares of zero. Check
`secret.IsZero()` yourself.

`NewPedersen` calls `generator.CurveName()` *before* its `generator == nil` guard, so passing a nil
generator panics with a nil-pointer dereference instead of returning `invalid generator`.
:::

## The Polynomial degree gotcha

`sharing.Polynomial` is exported, and its `Init` signature reads as if it takes a degree:

```go
func (p *Polynomial) Init(intercept curves.Scalar, degree uint32, reader io.Reader) *Polynomial
```

It does not. The implementation allocates `degree` coefficients — `Coefficients[0] = intercept` plus
`degree - 1` random ones — so the resulting polynomial has algebraic degree `degree - 1`. The
parameter is really a *coefficient count*.

Inside the package this is consistent: every scheme calls `Init(secret, threshold, reader)`, which
yields `threshold` coefficients and hence degree `threshold - 1` — precisely what a `t`-of-`n`
scheme requires. But if you call `Init` yourself expecting the named semantics you will get a
polynomial one degree lower than you asked for, and `Init(x, 0, r)` panics on
`Coefficients[0]` before it can return an error.

:::tip
Use `NewShamir`/`NewFeldman`/`NewPedersen`. `Polynomial` is exported incidentally, not as a
supported API.
:::

## Legacy: sharing/v1

<Badge variant="warning">Legacy — do not use for new code</Badge>

`sharing/v1` is the pre-`curves.Curve` generation of the same three schemes. It operates on
`[]byte` secrets over `curves.Field`/`curves.Element` and uses `curves.EcPoint` (aliased locally as
`ShareVerifier`) rather than `curves.Point`. It survives because [`dkg/gennaro`](/threshold/dkg) and
[`ted25519/ted25519`](/threshold/threshold-ed25519) are built on it and have never been ported.

Differences that matter if you must read it:

- `v1.NewShamir(threshold, limit int, field *curves.Field)` takes plain `int`s and a *field*, not a
  curve. It enforces only `limit >= threshold` and `threshold >= 2` — **no 255-share ceiling**.
- `v1.NewFeldman(threshold, limit uint32, curve elliptic.Curve)` and
  `v1.NewPedersen(threshold, limit uint32, generator *curves.EcPoint)` take standard-library
  curves. Tests drive them with `btcec.S256()` and `elliptic.P256()`.
- `Split` takes `[]byte` and reads randomness from an internal source — there is no `io.Reader`
  parameter, so you cannot inject a deterministic RNG.
- `Verify` returns `(bool, error)` rather than a bare `error`, and the verifier list is a plain
  slice you pass in, not a struct.
- `ShamirShare` here has fields `Identifier uint32` and `Value *curves.Element`, and gains an
  `Add` method that panics if the two identifiers differ.
- `ComputeL` is the `LagrangeCoeffs` equivalent, returning an ordered `[]*curves.Element`.

Curve helpers provided by the package: `Ed25519()`, `Bls12381G1()`, `Bls12381G2()`, and
`K256GeneratorFromHashedBytes(bytes []byte) (x, y *big.Int, err error)` — which derives a generator
with unknown discrete log from a byte string, exactly the Pedersen requirement above. There is no
k256 or p256 curve constructor in `v1`; use `btcec.S256()` and `elliptic.P256()` directly, as the
tests do.

:::danger[v1.Bls12381G2 does not return a G2 curve]
Despite its name and its `*Bls12381G1Curve` return type, `Bls12381G2()` initialises a
`Bls12381G2Curve` singleton and then returns `&bls12381g1` — the **G1** curve. Its initialiser also
sets `Name = "Bls12381G1"`, and its `Gy` is a verbatim copy of `B`. The `Bls12381G2Curve` methods
(`Add`, `Double`, `ScalarMult`, `IsOnCurve`, `Hash`) do operate on real G2 points, but there is no
exported constructor that hands you a value of that type. Do not use `Bls12381G2()`.
:::

:::warning[v1.Shamir.Combine ignores shares past the threshold]
`Combine` loops `for i := 0; i < int(s.threshold); i++` over the variadic slice, so passing five
shares to a 3-of-5 scheme interpolates the **first three** and discards the rest. Combined with
`v1`'s lack of Feldman checking inside `Combine`, a corrupt share in one of the leading positions
silently poisons the result even when enough good shares were supplied to notice. The same slicing
applies to `ComputeL`. Note the contrast: the modern `sharing.Shamir.Combine` interpolates over
*all* shares you pass.
:::

## Caveats

:::warning[Not constant time]
Reconstruction goes through `curves.Scalar` arithmetic and, in `v1`, `big.Int`-backed
`curves.Element` arithmetic. Neither is written for constant-time operation on secret data. Treat
these routines as unsafe against local timing adversaries.
:::

:::note[Reconstruction is the dangerous moment]
`Combine` materialises the secret in memory. Any design where `Combine` runs in production has a
window where the key exists in one place — the thing threshold cryptography is meant to eliminate.
Prefer schemes where shares are consumed *as shares* (`LagrangeCoeffs` plus threshold signing) over
schemes that reassemble.
:::

## Next

<CardGroup cols={2}>
<Card title="Distributed Key Generation" href="/threshold/dkg" icon="git-branch">
Same VSS machinery, but nobody ever holds the secret.
</Card>
<Card title="Curves & Scalars" href="/foundations/curves" icon="binary">
The `Curve`, `Point`, and `Scalar` abstractions every scheme here is generic over.
</Card>
</CardGroup>
