Shamir's secret sharing

Exploring a very simple and elegant cryptographic scheme with a step by step typescript implementation

Shamir’s secret sharing is a cryptographic scheme that allows you to break a secret into shares (sometimes also called “shards”) in such a way that a configurable amount of them is required to reconstruct the original.

It was created by Adi Shamir (the “S” in “RSA”!) and the beauty of it is that the high level concepts are understandable with high school level math and fairly easy to implement for demonstrative purposes.

It has numerous use cases: it is used as a key recovery mechanism for crypto wallets, and it can also function as a way to ensure cooperation between parties to access certain resources or to create asymmetric access structures (for example, using 4 shares with a treshold of 2 and giving 2 shares to a company’s CEO and one each to his assistants allows him to access the resource alone but requires his assistants to both agree.)

This post will explain the high level ideas behind the scheme with a follow along toy Typescript implementation.

Overview

Shamir’s scheme leverages the fact that nn points P(x,y)P(x,y) on a plane with different xx uniquely detetermine a polynomial of degree n1n - 1, while less than nn points allow an infinite number of polynomial to pass through them.

The uniquely determined polynomial intercepts the yy axis in one point, P(0,y0)P(0, y_0).

Let’t say that you want to split you secret into NN shares and you want KK of them to be able to reconstruct it.

You must:

  • choose the point P(0,y0)P(0,y_0) so that y0y_0 is your secret;
  • generate K1K-1 random coefficients aa and build the polynomial y0+a1x1+a2x2+...+ak1xk1y_0 + a_1x^1 + a_2x^2 + ... + a_{k-1}x^{k-1}
  • generate NN random points P(x1,y1)...P(xn,yn)P(x_1, y_1) ... P(x_n, y_n): these are your shares!

By having at least KK points from the third steps, anyone is able to reconstruct the polynomial from the second step (for example with Lagrange interpolation) and thus determine the secret y0y_0.

This is easier to show in code than with formulas, so let’s proceed with a step by step Typescript implementation for the following example:


The code to my safe is 6174 (Kaprekar’s constant) and I want to share it with 10 of my friends and family so that it takes at least 4 of them to open it.


Step 1: representing a point and a polynomial

Let’s start with the basic building block: a point on the plane. We’ll represent a polynomial simply as an array of its coefficients, where the index of the array is the power of xx that coefficient multiplies:

type Point = {
  x: number;
  y: number;
};

// coefficients[i] is the coefficient of x^i
// coefficients[0] is always the secret itself, since P(0) = coefficients[0]
type Polynomial = number[];

function evaluate(polynomial: Polynomial, x: number): number {
  return polynomial.reduce((sum, coefficient, power) => {
    return sum + coefficient * Math.pow(x, power);
  }, 0);
}

Step 2: building the polynomial

To split our secret into shares requiring a threshold of KK, we need a polynomial of degree K1K - 1. Its constant term is our secret, and every other coefficient is chosen at random:

UPPER_BOUND = 9999 // in practice this could be unbounded, it's just to keep things readable

function randomCoefficient(): number {
  // NOT cryptographically secure, this is a toy implementation
  return Math.floor(Math.random() * UPPER_BOUND) + 1;
}

function buildPolynomial(secret: number, threshold: number): Polynomial {
  const polynomial: Polynomial = [secret];
  for (let power = 1; power < threshold; power++) {
    polynomial.push(randomCoefficient());
  }
  return polynomial;
}

For our safe, threshold is 4, so buildPolynomial(6174, 4) gives us a degree-3 polynomial, something like:

6174+4821x+118x2+7350x36174 + 4821x + 118x^2 + 7350x^3

Step 3: generating the shares

Now we just need to evaluate the polynomial at NN different, non-zero values of xx. We never hand out the point at x=0x = 0, since that’s the secret itself:

function generateShares(secret: number, shareCount: number, threshold: number): Point[] {
  const polynomial = buildPolynomial(secret, threshold);
  const shares: Point[] = [];

  for (let x = 1; x <= shareCount; x++) {
    shares.push({ x, y: evaluate(polynomial, x) });
  }

  return shares;
}

const SECRET = 6174;
const N = 10; // number of shares
const K = 4; // threshold

const shares = generateShares(SECRET, N, K);

Each of my 10 friends and family members gets one Point. On their own, a single point (or even three of them) tells you nothing about the secret: infinitely many degree-3 polynomials pass through any three points, and each one has a different yy-intercept.

Step 4: reconstructing the secret

This is where Lagrange interpolation comes in. Given KK points, there’s exactly one polynomial of degree K1K - 1 passing through all of them. We don’t actually need to rebuild the whole polynomial though: we only care about its value at x=0x = 0, which simplifies the general Lagrange formula quite a bit:

y0=i=1kyijixjxixjy_0 = \sum_{i=1}^{k} y_i \prod_{j \neq i} \frac{-x_j}{x_i - x_j}

In code:

function reconstructSecret(points: Point[]): number {
  let secret = 0;

  for (let i = 0; i < points.length; i++) {
    const { x: xi, y: yi } = points[i];

    let numerator = 1;
    let denominator = 1;

    for (let j = 0; j < points.length; j++) {
      if (i === j) continue;
      const { x: xj } = points[j];
      numerator *= -xj;
      denominator *= xi - xj;
    }

    secret += yi * (numerator / denominator);
  }

  return Math.round(secret);
}

Let’s try it with any 4 of the 10 shares:

const someShares = [shares[1], shares[3], shares[6], shares[9]];
console.log(reconstructSecret(someShares)); // 6174

It doesn’t matter which 4 shares I pick - any subset of 4 (or more) will reconstruct the exact same polynomial, and therefore the exact same secret. But if I only gather 3:

const tooFewShares = [shares[1], shares[3], shares[6]];
console.log(reconstructSecret(tooFewShares)); // some other number, NOT 6174

reconstructSecret happily returns a number, it just isn’t the right one. There are infinitely many degree-3 polynomials through those 3 points, and the formula above silently picks the one that assumes the missing point doesn’t exist, landing on the wrong yy-intercept. This “confidently wrong” behavior is the seed of the first drawback we’ll cover below.

Drawbacks

Shamir’s scheme is elegant, but it’s not a complete solution on its own. Two limitations show up almost immediately once you try to use it for anything beyond a toy example:

Share size

Every share is a full point on the polynomial, meaning its size is comparable to the secret itself (with some overhead for the xx coordinate and, in a finite field implementation, padding to the field size). Splitting a large secret such as a video file into 10 shares means storing roughly 10x the original data across all shareholders combined.

No authentication

As we saw above, the scheme cannot tell the difference between “not enough shares” and “wrong shares.” Any KK points, whether genuine or tampered with, produce some polynomial and some yy-intercept. There’s no built-in way to verify that a reconstructed secret is the real one, or that a given share hasn’t been corrupted or maliciously substituted. Extensions like Verifiable Secret Sharing address this, at the cost of extra complexity and additional data attached to each share.

Wrapping up

With about sixty lines of TypeScript, we can go from a hand-drawn intuition about lines and points to a working (albeit non-production) implementation of Shamir’s secret sharing: splitting a secret into shares, and reconstructing it from any large-enough subset. The core idea that KK points pin down a degree K1K-1 polynomial, while fewer than KK leave it wide open is the entire scheme. Everything else, from finite fields to authentication, is about closing the gaps between “mathematically elegant” and “practical and safe to use with a real secret.”

This is why YASSS encrypts the actual secret with AES-GCM (using the shares as key material, with additional authenticated data to bind each share to its context), and signs the shares with Ed25519 so that tampering or forged shares can be detected before they ever reach the reconstruction step. Shamir’s scheme handles the “how many parties need to agree” part, the rest of the implementation does logistics and the “can we trust what they’re giving us” part.

Thank you for reading and see you next time!