Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 73 additions & 55 deletions shamir/shamir.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ package shamir

import (
"crypto/rand"
"crypto/subtle"
"fmt"
mathrand "math/rand"
)

const (
Expand Down Expand Up @@ -101,63 +99,90 @@ func div(a, b uint8) uint8 {
panic("divide by zero")
}

var goodVal, zero uint8
logA := logTable[a]
logB := logTable[b]
diff := (int(logA) - int(logB)) % 255
if diff < 0 {
diff += 255
}

ret := expTable[diff]

// Ensure we return zero if a is zero but aren't subject to timing attacks
goodVal = ret

if subtle.ConstantTimeByteEq(a, 0) == 1 {
ret = zero
} else {
ret = goodVal
}
// a divided by b is the same as a multiplied by the inverse of b:
return mult(a, inverse(b))
}

return ret
// inverse calculates the inverse of a number in GF(2^8)
// Note that a must be non-zero; otherwise 0 is returned
func inverse(a uint8) uint8 {
// This makes use of Fermat's Little Theorem for finite groups:
// If G is a finite group with n elements, and a any element of G,
// then a raised to the power of n equals the neutral element of G.
// (See https://en.wikipedia.org/wiki/Fermat%27s_little_theorem;
// the generalization to finite groups follows from Lagrange's theorem:
// https://en.wikipedia.org/wiki/Lagrange%27s_theorem_(group_theory))
//
// Here we use the multiplicative group of GF(2^8), which has
// n = 2^8 - 1 elements (every element but zero). Thus raising a to
// the (n - 1)th = 254th power gives a number x so that a*x = 1.
//
// If a happens to be 0, which is not part of the multiplicative group,
// then a raised to the power of 254 is still 0.

// (See also https://github.com/openbao/openbao/commit/a209a052024b70bc563d9674cde21a20b5106570)

// In the comments, we use ^ to denote raising to the power:
b := mult(a, a) // b is now a^2
c := mult(a, b) // c is now a^3
b = mult(c, c) // b is now a^6
b = mult(b, b) // b is now a^12
c = mult(b, c) // c is now a^15
b = mult(b, b) // b is now a^24
b = mult(b, b) // b is now a^48
b = mult(b, c) // b is now a^63
b = mult(b, b) // b is now a^126
b = mult(a, b) // b is now a^127
return mult(b, b) // result is a^254
}

// mult multiplies two numbers in GF(2^8)
// GF(2^8) multiplication using log/exp tables
func mult(a, b uint8) (out uint8) {
var goodVal, zero uint8
log_a := logTable[a]
log_b := logTable[b]
sum := (int(log_a) + int(log_b)) % 255

ret := expTable[sum]

// Ensure we return zero if either a or b are zero but aren't subject to
// timing attacks
goodVal = ret

if subtle.ConstantTimeByteEq(a, 0) == 1 {
ret = zero
} else {
ret = goodVal
}

if subtle.ConstantTimeByteEq(b, 0) == 1 {
ret = zero
} else {
// This operation does not do anything logically useful. It
// only ensures a constant number of assignments to thwart
// timing attacks.
goodVal = zero
// This computes a * b in GF(2^8), which is defined as GF(2)[X] / <X^8 + X^4 + X^3 + X + 1>.
// This finite field is known as Rijndael's finite field. (Rijndael is the algorithm that
// was standardized as AES.)
// (See https://en.wikipedia.org/wiki/Finite_field_arithmetic#Rijndael's_(AES)_finite_field)
//
// We identify elements in GF(2^8) with polynomials of degree < 8. The i-th bit of a field
// element is the coefficient of X^i in that polynomial.
//
// To multiply a and b in this finite field, we use something similar to Russian peasant
// multiplication. We iterate over b's bits, starting from the highest to the lowest.
// i denotes the bit we're currently processing (7, 6, 5, 4, 3, 2, 1, 0).
// The accumulator is set to 0; every iteration, we multiply the accumulator
// by X modulo X^8+X^4+X^3+X+1, and then add a to the accumulator in case b's i-th bit is 1.
var accumulator uint8 = 0
var i uint8 = 8

for i > 0 {
i--
// Get the i-th bit of b; bitOfB is either 0 or 1.
bitOfB := b >> i & 1
// aOrZero is 0 if the i-th bit of b is 0, and a if the i-th bit of b is 1. This is
// what we later add to the accumulator.
aOrZero := -bitOfB & a
// zeroOr1B is 0 if the 7th bit of the accumulator is 0, and 0x1B = 11011_2 if the
// 7th bit of accumulator is 1
zeroOr1B := -(accumulator >> 7) & 0x1B
// accumulatorMultipliedByX equals accumulator multiplied by X modulo X^8+X^4+X^3+X+1
// In the expression, accumulator + accumulator equals accumulator << 1, which would be
// the accumulator multiplied by X modulo X^8.
// By XORing (addition and subtraction in GF(2^8)) with zeroOr1B, we turn this into
// accumulator multiplied by X modulo X^8 + X^4 + X^3 + X + 1.
accumulatorMultipliedByX := zeroOr1B ^ (accumulator + accumulator)
// We can now compute the next value of the accumulator as the sum (in GF(2^8)) of aOrZero
// and accumulatorMultipliedByX.
accumulator = aOrZero ^ accumulatorMultipliedByX
}

return ret
return accumulator
}

// add combines two numbers in GF(2^8)
// This can also be used for subtraction since it is symmetric.
func add(a, b uint8) uint8 {
// Addition in GF(2^8) equals XOR:
return a ^ b
}

Expand All @@ -184,13 +209,6 @@ func Split(secret []byte, parts, threshold int) ([][]byte, error) {
return nil, fmt.Errorf("cannot split an empty secret")
}

// Generate random x coordinates for computing points. I don't know
// why random x coordinates are used, and I also don't know why
// a non-cryptographically secure source of randomness is used.
// As far as I know the x coordinates do not need to be random.

xCoordinates := mathrand.Perm(255)

// Allocate the output array, initialize the final byte
// of the output with the offset. The representation of each
// output is {y1, y2, .., yN, x}.
Expand All @@ -201,7 +219,7 @@ func Split(secret []byte, parts, threshold int) ([][]byte, error) {
// then the result of evaluating the polynomial at that point
// will be our secret
out[idx] = make([]byte, len(secret)+1)
out[idx][len(secret)] = uint8(xCoordinates[idx]) + 1
out[idx][len(secret)] = uint8(idx) + 1
}

// Construct a random polynomial for each byte of the secret.
Expand All @@ -222,7 +240,7 @@ func Split(secret []byte, parts, threshold int) ([][]byte, error) {
for i := 0; i < parts; i++ {
// Add 1 to the xCoordinate because if it's 0,
// then the result of p.evaluate(x) will be our secret
x := uint8(xCoordinates[i]) + 1
x := uint8(i) + 1
// Evaluate the polynomial at x
y := p.evaluate(x)
out[i][idx] = y
Expand Down
16 changes: 16 additions & 0 deletions shamir/shamir_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,22 @@ func TestCombine(t *testing.T) {
}
}

func TestField_MulDivSmoke(t *testing.T) {
for a := range 256 {
for b := range 256 {
if b == 0 {
if out := mult(uint8(a), uint8(b)); out != 0 {
t.Fatalf("Bad: %v * %v = %v 0", a, b, out)
}
} else {
if out := div(mult(uint8(a), uint8(b)), uint8(b)); out != uint8(a) {
t.Fatalf("Bad: (%v * %v) / %v = %v %v", a, b, b, out, a)
}
}
}
}
}

func TestField_Add(t *testing.T) {
if out := add(16, 16); out != 0 {
t.Fatalf("Bad: %v 16", out)
Expand Down
77 changes: 0 additions & 77 deletions shamir/tables.go

This file was deleted.

13 changes: 0 additions & 13 deletions shamir/tables_test.go

This file was deleted.

Loading