Skip to content

Commit 24ceb08

Browse files
authored
Merge pull request #1813 from felixfontein/shamir
Improve Shamir Secret Sharing code
2 parents e439a99 + bae6742 commit 24ceb08

File tree

4 files changed

+89
-145
lines changed

4 files changed

+89
-145
lines changed

shamir/shamir.go

Lines changed: 73 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ package shamir
1212

1313
import (
1414
"crypto/rand"
15-
"crypto/subtle"
1615
"fmt"
17-
mathrand "math/rand"
1816
)
1917

2018
const (
@@ -101,63 +99,90 @@ func div(a, b uint8) uint8 {
10199
panic("divide by zero")
102100
}
103101

104-
var goodVal, zero uint8
105-
logA := logTable[a]
106-
logB := logTable[b]
107-
diff := (int(logA) - int(logB)) % 255
108-
if diff < 0 {
109-
diff += 255
110-
}
111-
112-
ret := expTable[diff]
113-
114-
// Ensure we return zero if a is zero but aren't subject to timing attacks
115-
goodVal = ret
116-
117-
if subtle.ConstantTimeByteEq(a, 0) == 1 {
118-
ret = zero
119-
} else {
120-
ret = goodVal
121-
}
102+
// a divided by b is the same as a multiplied by the inverse of b:
103+
return mult(a, inverse(b))
104+
}
122105

123-
return ret
106+
// inverse calculates the inverse of a number in GF(2^8)
107+
// Note that a must be non-zero; otherwise 0 is returned
108+
func inverse(a uint8) uint8 {
109+
// This makes use of Fermat's Little Theorem for finite groups:
110+
// If G is a finite group with n elements, and a any element of G,
111+
// then a raised to the power of n equals the neutral element of G.
112+
// (See https://en.wikipedia.org/wiki/Fermat%27s_little_theorem;
113+
// the generalization to finite groups follows from Lagrange's theorem:
114+
// https://en.wikipedia.org/wiki/Lagrange%27s_theorem_(group_theory))
115+
//
116+
// Here we use the multiplicative group of GF(2^8), which has
117+
// n = 2^8 - 1 elements (every element but zero). Thus raising a to
118+
// the (n - 1)th = 254th power gives a number x so that a*x = 1.
119+
//
120+
// If a happens to be 0, which is not part of the multiplicative group,
121+
// then a raised to the power of 254 is still 0.
122+
123+
// (See also https://github.com/openbao/openbao/commit/a209a052024b70bc563d9674cde21a20b5106570)
124+
125+
// In the comments, we use ^ to denote raising to the power:
126+
b := mult(a, a) // b is now a^2
127+
c := mult(a, b) // c is now a^3
128+
b = mult(c, c) // b is now a^6
129+
b = mult(b, b) // b is now a^12
130+
c = mult(b, c) // c is now a^15
131+
b = mult(b, b) // b is now a^24
132+
b = mult(b, b) // b is now a^48
133+
b = mult(b, c) // b is now a^63
134+
b = mult(b, b) // b is now a^126
135+
b = mult(a, b) // b is now a^127
136+
return mult(b, b) // result is a^254
124137
}
125138

126139
// mult multiplies two numbers in GF(2^8)
127140
// GF(2^8) multiplication using log/exp tables
128141
func mult(a, b uint8) (out uint8) {
129-
var goodVal, zero uint8
130-
log_a := logTable[a]
131-
log_b := logTable[b]
132-
sum := (int(log_a) + int(log_b)) % 255
133-
134-
ret := expTable[sum]
135-
136-
// Ensure we return zero if either a or b are zero but aren't subject to
137-
// timing attacks
138-
goodVal = ret
139-
140-
if subtle.ConstantTimeByteEq(a, 0) == 1 {
141-
ret = zero
142-
} else {
143-
ret = goodVal
144-
}
145-
146-
if subtle.ConstantTimeByteEq(b, 0) == 1 {
147-
ret = zero
148-
} else {
149-
// This operation does not do anything logically useful. It
150-
// only ensures a constant number of assignments to thwart
151-
// timing attacks.
152-
goodVal = zero
142+
// This computes a * b in GF(2^8), which is defined as GF(2)[X] / <X^8 + X^4 + X^3 + X + 1>.
143+
// This finite field is known as Rijndael's finite field. (Rijndael is the algorithm that
144+
// was standardized as AES.)
145+
// (See https://en.wikipedia.org/wiki/Finite_field_arithmetic#Rijndael's_(AES)_finite_field)
146+
//
147+
// We identify elements in GF(2^8) with polynomials of degree < 8. The i-th bit of a field
148+
// element is the coefficient of X^i in that polynomial.
149+
//
150+
// To multiply a and b in this finite field, we use something similar to Russian peasant
151+
// multiplication. We iterate over b's bits, starting from the highest to the lowest.
152+
// i denotes the bit we're currently processing (7, 6, 5, 4, 3, 2, 1, 0).
153+
// The accumulator is set to 0; every iteration, we multiply the accumulator
154+
// 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.
155+
var accumulator uint8 = 0
156+
var i uint8 = 8
157+
158+
for i > 0 {
159+
i--
160+
// Get the i-th bit of b; bitOfB is either 0 or 1.
161+
bitOfB := b >> i & 1
162+
// 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
163+
// what we later add to the accumulator.
164+
aOrZero := -bitOfB & a
165+
// zeroOr1B is 0 if the 7th bit of the accumulator is 0, and 0x1B = 11011_2 if the
166+
// 7th bit of accumulator is 1
167+
zeroOr1B := -(accumulator >> 7) & 0x1B
168+
// accumulatorMultipliedByX equals accumulator multiplied by X modulo X^8+X^4+X^3+X+1
169+
// In the expression, accumulator + accumulator equals accumulator << 1, which would be
170+
// the accumulator multiplied by X modulo X^8.
171+
// By XORing (addition and subtraction in GF(2^8)) with zeroOr1B, we turn this into
172+
// accumulator multiplied by X modulo X^8 + X^4 + X^3 + X + 1.
173+
accumulatorMultipliedByX := zeroOr1B ^ (accumulator + accumulator)
174+
// We can now compute the next value of the accumulator as the sum (in GF(2^8)) of aOrZero
175+
// and accumulatorMultipliedByX.
176+
accumulator = aOrZero ^ accumulatorMultipliedByX
153177
}
154178

155-
return ret
179+
return accumulator
156180
}
157181

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

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

187-
// Generate random x coordinates for computing points. I don't know
188-
// why random x coordinates are used, and I also don't know why
189-
// a non-cryptographically secure source of randomness is used.
190-
// As far as I know the x coordinates do not need to be random.
191-
192-
xCoordinates := mathrand.Perm(255)
193-
194212
// Allocate the output array, initialize the final byte
195213
// of the output with the offset. The representation of each
196214
// output is {y1, y2, .., yN, x}.
@@ -201,7 +219,7 @@ func Split(secret []byte, parts, threshold int) ([][]byte, error) {
201219
// then the result of evaluating the polynomial at that point
202220
// will be our secret
203221
out[idx] = make([]byte, len(secret)+1)
204-
out[idx][len(secret)] = uint8(xCoordinates[idx]) + 1
222+
out[idx][len(secret)] = uint8(idx) + 1
205223
}
206224

207225
// Construct a random polynomial for each byte of the secret.
@@ -222,7 +240,7 @@ func Split(secret []byte, parts, threshold int) ([][]byte, error) {
222240
for i := 0; i < parts; i++ {
223241
// Add 1 to the xCoordinate because if it's 0,
224242
// then the result of p.evaluate(x) will be our secret
225-
x := uint8(xCoordinates[i]) + 1
243+
x := uint8(i) + 1
226244
// Evaluate the polynomial at x
227245
y := p.evaluate(x)
228246
out[i][idx] = y

shamir/shamir_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,22 @@ func TestCombine(t *testing.T) {
115115
}
116116
}
117117

118+
func TestField_MulDivSmoke(t *testing.T) {
119+
for a := range 256 {
120+
for b := range 256 {
121+
if b == 0 {
122+
if out := mult(uint8(a), uint8(b)); out != 0 {
123+
t.Fatalf("Bad: %v * %v = %v 0", a, b, out)
124+
}
125+
} else {
126+
if out := div(mult(uint8(a), uint8(b)), uint8(b)); out != uint8(a) {
127+
t.Fatalf("Bad: (%v * %v) / %v = %v %v", a, b, b, out, a)
128+
}
129+
}
130+
}
131+
}
132+
}
133+
118134
func TestField_Add(t *testing.T) {
119135
if out := add(16, 16); out != 0 {
120136
t.Fatalf("Bad: %v 16", out)

shamir/tables.go

Lines changed: 0 additions & 77 deletions
This file was deleted.

shamir/tables_test.go

Lines changed: 0 additions & 13 deletions
This file was deleted.

0 commit comments

Comments
 (0)