Shamir Secret Sharing

#cryptography #algorithm #math #golang

Original presentation by Jinwoo Lee on Oct 24, 2024

SSS란?


Principle

다항식(Polynomial)과 그 해의 기본 성질을 이용합니다.

다항식(Polynomial) 및 (k1)(k-1)차 방정식 해의 성질

Q: 그러면 kk개 이상의 점이 있어도 되나? A: 네. 위 조건들은 필요한 최소 개수입니다. (knk \le n)

1. Polynomial

이제 위 성질을 알았으니 랜덤한 aia_i 값을 만들고 다항식을 생성합시다.

a0,a1,a2,...,ak1(randomly generated numbers)a_0, a_1, a_2, ... , a_{k-1} \quad (\text{randomly generated numbers}) y=f(x)=ak1xk1+...+a1x+a0y = f(x) = a_{k-1}x^{k-1} + ... + a_1x + a_0

2. Secret Key

임의의 a0ak1a_0 \sim a_{k-1}을 설정하고, 이때 a0a_0가 비밀키(Secret Key)가 됩니다.

Secret Key=a0\text{Secret Key} = a_0

3. Sharing

4. Recovery

How?

  1. 가우스-조던 소거법 (Gaussian Elimination)
    • 시간이 많이 걸리고 연산량도 많습니다.
  2. 다른 접근: 라그랑주 보간법 (Lagrange Interpolation)
    • 다항식의 선형대수적인 특성으로 인해 보간 알고리즘으로 기존 다항식을 복구하는 것이 가능합니다.
f(x)=j=0k1yjijk1xxixjxif(x) = \sum_{j=0}^{k-1} y_j \prod_{i\neq j}^{k-1} \frac{x-x_i}{x_j-x_i} f(x)=j=0k1yj(xx0xjx0)(xx1xjx1)...f(x) = \sum_{j=0}^{k-1} y_j \left(\frac{x-x_0}{x_j-x_0}\right) \left(\frac{x-x_1}{x_j-x_1}\right) ... f(0)=a0f(0) = a_0

유한체 (Finite Field)

위의 다항식 원리는 유한체 위에서도 잘 정의됩니다. 왜냐하면 (k1)(k-1)차 다항식은 유한체 FF에 대한 차원이 kk벡터 공간 VV (Finite Dimension Vector Space)로 정의할 수 있기 때문입니다.

1. 성질

유한체는 다음과 같은 성질을 가지고 있습니다:

2. 생성 방법

유한체는 보통 정수 중 다음과 같은 집합을 사용하여 쉽게 얻을 수 있습니다.

Example (p=5p=5):

  • 3+4=7(mod5)=23 + 4 = 7 \pmod 5 = 2
  • 3×4=12(mod5)=23 \times 4 = 12 \pmod 5 = 2

3. 빼기/나누기 연산

역원 연산으로 뺄셈, 나눗셈 연산을 정의합니다.


Code Example

전체 코드: GitHub Link

package main

import (
	"crypto/rand"
	"errors"
	"math/big"
)

// 다항식 (x_i, y_i) 총 n개의 서로다른 점 -> n명에게 나눠준다.
type SecretShare struct {
	X *big.Int
	Y *big.Int
}

// 32bytes Random Generation
func generateRand32Bytes() (*big.Int, error) {
	b := make([]byte, 32)
	_, err := rand.Read(b)
	if err != nil {
		return nil, err
	}
	return new(big.Int).SetBytes(b), nil
}

// (x, y) 서로 다른 점을 생성
func generateDistinctRandomInt64s(n int) ([]int64, error) {
	randomInt64s := make([]int64, n)
	randMap := make(map[int64]bool)
	for i := 0; i < n; i++ {
		b := make([]byte, 8)
		_, err := rand.Read(b)
		if err != nil {
			return nil, err
		}
        // ... (bit manipulation omitted for brevity) ...
		randomInt64s[i] = int64(b[0]) // Simplified
		if _, ok := randMap[randomInt64s[i]]; ok {
			i--
		} else {
			randMap[randomInt64s[i]] = true
		}
	}
	return randomInt64s, nil
}

// a_0 ~ a_k-1 계수 생성
func GenerateCoefficients(k int) ([]*big.Int, error) {
	coefficients := make([]*big.Int, k)
	for i := 0; i < k; i++ {
		coeff, err := generateRand32Bytes()
		if err != nil {
			return nil, err
		}
		coefficients[i] = coeff
	}
	return coefficients, nil
}

// Shamir Secret Share 생성
func ShamirSecretShare(coefficients []*big.Int, k, n int) ([]SecretShare, error) {
	if k > n {
		return nil, errors.New("k must be less than n")
	}

	shares := make([]SecretShare, n)
	distinctPointXs, err := generateDistinctRandomInt64s(n)
	if err != nil {
		return nil, err
	}

	for i := 0; i < n; i++ {
		x := big.NewInt(distinctPointXs[i])
		y := new(big.Int)

		for j := 0; j < k; j++ {
			// term = a_j * x^j
			term := new(big.Int).Exp(x, big.NewInt(int64(j)), nil)
			term.Mul(term, coefficients[j])
			y.Add(y, term)
		}
		shares[i] = SecretShare{X: x, Y: y}
	}
	return shares, nil
}

// Lagrange Interpolation (복구)
func LagrangeInterpolation(shares []SecretShare, prime *big.Int) *big.Int {
	result := new(big.Int)

	for i := 0; i < len(shares); i++ {
		numerator := new(big.Int)
		denominator := new(big.Int)

		numerator.SetInt64(1)
		denominator.SetInt64(1)

		for j := 0; j < len(shares); j++ {
			if i == j { continue }

			// numerator *= -shares[j].X
			numerator.Mul(numerator, shares[j].X)
			numerator.Neg(numerator)

			// denominator *= shares[i].X - shares[j].X
			denominator.Mul(denominator, new(big.Int).Sub(shares[i].X, shares[j].X))
		}

		// denominator = 1 / denominator (Inverse)
		denominator.ModInverse(denominator, prime)

        // Term calc
		term := new(big.Int).Mul(new(big.Int).Mul(shares[i].Y, numerator), denominator)
		result.Add(result, term)
	}

	result.Mod(result, prime)
	return result
}

Appendix: 라그랑주 보간법의 수학적 증명

  1. 주어진 다항식의 서로 다른 점 kk개를 취합니다.

    {(x0,y0),(x1,y1),...,(xk1,yk1)}\{(x_0, y_0), (x_1, y_1), ..., (x_{k-1}, y_{k-1})\}
  2. 라그랑주 기본 다항식 (Lagrange Basis Polynomial)을 다음과 같이 정의합시다.

    pj(x)=ijk1xxixjxip_j(x) = \prod_{i\neq j}^{k-1} \frac{x-x_i}{x_j-x_i}
  3. 이 다항식들은 다음과 같은 성질을 가집니다.

    • Linearly Independent합니다.
    • Span합니다.
    • 따라서 Pk1P_{k-1} ((k1)(k-1)차 이하 다항식의 벡터 공간)의 Basis가 됩니다.
  4. x=xix = x_i를 대입하는 것은 Linear Functional LiL_i에 대응되며, 다음과 같은 성질을 지닙니다 (Kronecker Delta).

    Li(pj(x))=pj(xi)=δjiL_i(p_j(x)) = p_j(x_i) = \delta_{ji}

    (즉, i=ji=j이면 1, 아니면 0)

  5. 임의의 다항식 p(x)p(x)를 Basis의 선형 결합(Linear Combination)으로 표현할 수 있습니다.

    p(x)=j=0k1yjpj(x)p(x) = \sum_{j=0}^{k-1} y_j p_j(x)
  6. 결론적으로, 서로 다른 점 kk개를 통해 유일한 다항식을 복구할 수 있음을 증명할 수 있습니다.