DEV Community

Cover image for How to share a secret
Stefan Alfbo
Stefan Alfbo

Posted on

How to share a secret

I stumbled over this youtube video, Secret Sharing Explained Visually which links to this paper, How to Share a Secret (PDF) written by Adi Shamir.

The paper is going through a method for dividing a secret into n pieces in such a way that the secret can be easily reconstructed from any k pieces, while having complete knowledge of k-1 pieces reveals absolutely no information about the secret. Such a scheme is called a (k, n) threshold scheme.

Example: There are eight person, therefore the secret is divided into eight (n) pieces. For this secret it is decided that it will be enough if only three (k) persons (with their pieces) comes together to reproduce the complete secret. This would mean that two (k-1) persons would not be enough to gain any knowledge about the secret.

k vs n

This technique is aimed at enabling the construction of robust key management schemes for cryptographic systems, allowing for secure sharing and storage of sensitive information.

Let see if we can express this algorithm in a F# script (based on this Python code). For purposes of keeping the code clearer, a prime field is used in the referenced Python code example. We start to define the main function to illustrate the helicopter view of what we want to do in our shamir.fsx script file.

let main () =
    // Pick a prime which is bigger than both the secret and n (12th Mersenne Prime).
    let prime = BigInteger.Pow(2I, 127) - 1I

    // The secret to share which is (or can be made) a number
    let secret = 1234I
    // Minimum number of pieces needed to recreate the secret
    let k = 3
    // Total number of pieces to divide the secret in
    let n = 6

    let pieces = splitSecretIntoPieces secret k n prime

    printfn "The secret is                                  %A" secret

    printfn "Pieces:"
    pieces |> List.iter (fun (x, y) -> printfn "%A" (x, y))

    let recoveredSecret = recoverSecret (pieces |> List.take 3) prime
    printfn "Recovered secret with 3 pieces:                %A" recoveredSecret

    let recoveredSecretOnceMore = recoverSecret (pieces |> List.skip 3) prime
    printfn "Recovered secret with another set of 3 pieces: %A" recoveredSecretOnceMore

main ()
Enter fullscreen mode Exit fullscreen mode

The scheme of Shamir Secret Share (SSS) is based on polynomial interpolation. Therefore we need to be able to generate a random k-1 degree polynomial.

polynomial

Where y on the point (0, y) equals the secret and points on the polynomial are pieces needed to recreate the secret.

The next code snippet is the random generator that we need, it has to be able to generate a number between 0 and less than the prime.

// Generate a random big integer in the range [0, prime)
let randomBigInteger (prime: BigInteger) : BigInteger =
    let rnd = System.Random()
    let bytes = prime.ToByteArray()

    // Generate random bytes
    rnd.NextBytes(bytes)

    // Ensure the number is in the range [0, prime)
    BigInteger.Abs(new BigInteger(bytes)) % prime
Enter fullscreen mode Exit fullscreen mode

With that in place we can create the function splitSecretIntoPieces, which generates the points (pieces).

// Evaluates polynomial at x, used to generate a shamir pool
let evaluatePolynomial (coefficients: BigInteger list) (x: BigInteger) (prime: BigInteger) : BigInteger =
    List.foldBack (fun c acc -> (acc * x + c) % prime) coefficients 0I

// Create random pieces for the secret
let splitSecretIntoPieces (secret: BigInteger) (k: int) (n: int) (prime: BigInteger) =
    if k > n then
        failwith "Pool secret would be irrecoverable."

    // Generate random coefficients for the polynomial
    let coefficients = secret :: [for _ in 1 .. k - 1 -> randomBigInteger prime]

    // Evaluate the polynomial for each piece and return a list of points
    [for x in 1 .. n -> (BigInteger(x), evaluatePolynomial coefficients (BigInteger(x)) prime)]
Enter fullscreen mode Exit fullscreen mode

Now we are halfway through, we have divided the secret in n pieces. Time to implement the process to combine k pieces to get the original secret value. The lagrange interpolation algorithm is used for this, however to implement that we will first need to build some building blocks, canonical modulo operation, extended GCD and modular division.

// Canonical modulo operation
let (%%) a b =
    let c = a % b
    if (c < 0I && b > 0I) || (c > 0I && b < 0I) then
        c + b
    else
        c

//  Extended GCD algorithm
let rec extendedGCD a b =
    let rec extendedGCD' a b x x' y y' =
        match b with
        | b' when b' = 0I -> x', y'
        | _ ->
            let quot = a / b

            extendedGCD' b (a %% b) (x' - quot * x) x (y' - quot * y) y

    extendedGCD' a b 0I 1I 1I 0I

// Modular division (num / den) % p
let modularDivision num den p =
    let (inv, _) = extendedGCD den p

    (num * inv)
Enter fullscreen mode Exit fullscreen mode

Next step is to implement the algorithm for Lagrange's Interpolation. As you can see it got pretty messy, but it works though.

// Find the y-value for the given x, given n (x, y) points;
// k points will define a polynomial of up to kth order
let lagrangeInterpolate x (pieces : (BigInteger * BigInteger) list) prime =
    let indexes = [0..pieces.Length-1]

    let calculateNum k =
        indexes
        |> List.map (fun j ->
            if k <> j then x - fst pieces.[j]
            else 1I)
        |> List.fold (*) 1I

    let calculateDen k =
        indexes
        |> List.map (fun j ->
            if k <> j then fst pieces.[k] - fst pieces.[j]
            else 1I)
        |> List.fold (*) 1I

    let nums = indexes |> List.map calculateNum
    let dens = indexes |> List.map calculateDen

    let den = List.fold (*) 1I dens

    let calculateTerm i =
        modularDivision ((nums.[i] * den * (snd pieces.[i])) %% prime) dens.[i] prime

    let num = indexes |> List.map calculateTerm |> List.fold (+) 0I

    (modularDivision num den prime) %% prime
Enter fullscreen mode Exit fullscreen mode

Finally we can create the last function, recoverSecret, which is very simple in it's nature.

// Recover the secret from pieces
let recoverSecret (pieces: (BigInteger * BigInteger) list) (prime: BigInteger) : BigInteger =
    if List.length pieces < 3 then
        failwith "Need at least three pieces"

    lagrangeInterpolate 0I pieces prime
Enter fullscreen mode Exit fullscreen mode

Now we have everything in place and can run the script.

run fsharp script

As you can see, it would be very hard to say anything about the secret if you just saw one piece.

The complete script looks like this in my shamir.fsx file.

open System
open System.Numerics

// Generate a random big integer in the range [0, prime)
let randomBigInteger (prime: BigInteger) : BigInteger =
    let rnd = System.Random()
    let bytes = prime.ToByteArray()

    // Generate random bytes
    rnd.NextBytes(bytes)

    // Ensure the number is in the range [0, prime)
    BigInteger.Abs(new BigInteger(bytes)) % prime

// Evaluates polynomial at x, used to generate a shamir pool
let evaluatePolynomial (coefficients: BigInteger list) (x: BigInteger) (prime: BigInteger) : BigInteger =
    List.foldBack (fun c acc -> (acc * x + c) % prime) coefficients 0I

// Create random pieces for the secret
let splitSecretIntoPieces (secret: BigInteger) (k: int) (n: int) (prime: BigInteger) =
    if k > n then
        failwith "Pool secret would be irrecoverable."

    // Generate random coefficients for the polynomial
    let coefficients = secret :: [for _ in 1 .. k - 1 -> randomBigInteger prime]

    // Evaluate the polynomial for each piece and return a list of points
    [for x in 1 .. n -> (BigInteger(x), evaluatePolynomial coefficients (BigInteger(x)) prime)]

// Canonical modulo operation
let (%%) a b =
    let c = a % b
    if (c < 0I && b > 0I) || (c > 0I && b < 0I) then
        c + b
    else
        c

//  Extended GCD algorithm
let rec extendedGCD a b =
    let rec extendedGCD' a b x x' y y' =
        match b with
        | b' when b' = 0I -> x', y'
        | _ ->
            let quot = a / b

            extendedGCD' b (a %% b) (x' - quot * x) x (y' - quot * y) y

    extendedGCD' a b 0I 1I 1I 0I

// Modular division (num / den) % p
let modularDivision num den p =
    let (inv, _) = extendedGCD den p

    (num * inv)

// Find the y-value for the given x, given n (x, y) points;
// k points will define a polynomial of up to kth order.
let lagrangeInterpolate x (pieces : (BigInteger * BigInteger) list) prime =
    let indexes = [0..pieces.Length-1]

    let calculateNum k =
        indexes
        |> List.map (fun j ->
            if k <> j then x - fst pieces.[j]
            else 1I)
        |> List.fold (*) 1I

    let calculateDen k =
        indexes
        |> List.map (fun j ->
            if k <> j then fst pieces.[k] - fst pieces.[j]
            else 1I)
        |> List.fold (*) 1I

    let nums = indexes |> List.map calculateNum
    let dens = indexes |> List.map calculateDen

    let den = List.fold (*) 1I dens

    let calculateTerm i =
        modularDivision ((nums.[i] * den * (snd pieces.[i])) %% prime) dens.[i] prime

    let num = indexes |> List.map calculateTerm |> List.fold (+) 0I

    (modularDivision num den prime) %% prime

// Recover the secret from pieces
let recoverSecret (pieces: (BigInteger * BigInteger) list) (prime: BigInteger) : BigInteger =
    if List.length pieces < 3 then
        failwith "Need at least three pieces"

    lagrangeInterpolate 0I pieces prime

let main () =
    // Pick a prime which is bigger than both the secret and n (12th Mersenne Prime).
    let prime = BigInteger.Pow(2I, 127) - 1I

    // The secret to share which is (or can be made) a number
    let secret = 1234I
    // Minimum number of pieces needed to recreate the secret
    let k = 3
    // Total number of pieces to divide the secret in
    let n = 6

    let pieces = splitSecretIntoPieces secret k n prime

    printfn "The secret is                                  %A" secret

    printfn "Pieces:"
    pieces |> List.iter (fun (x, y) -> printfn "%A" (x, y))

    let recoveredSecret = recoverSecret (pieces |> List.take 3) prime
    printfn "Recovered secret with 3 pieces:                %A" recoveredSecret

    let recoveredSecretOnceMore = recoverSecret (pieces |> List.skip 3) prime
    printfn "Recovered secret with another set of 3 pieces: %A" recoveredSecretOnceMore

main ()
Enter fullscreen mode Exit fullscreen mode

Happy secret sharing!

Top comments (3)

Collapse
 
nigel447 profile image
nigel447

good use of time to read this excellent work

Collapse
 
xet7 profile image
Lauri Ojansivu

If you would like to share 2FA, you could use 2FA app like ente, export it to file, and share via ProtonMail.

Collapse
 
donfour profile image
Donovan So

really interesting, thanks for sharing