Move field and matrix logic into their own files and abstractions.

- Instead of using GF(2^127-1) as one of many options, move to GF(2^128) exclusively.
- Don't clear the first two bits of every secret key.
This commit is contained in:
Brendan McMillion
2015-11-21 09:23:55 -08:00
parent 07b99b15f3
commit 7e56983fa6
8 changed files with 422 additions and 191 deletions

View File

@@ -116,9 +116,8 @@ fmt.Printf("%v\n", r2.Formatted()) // (3, Alice, Bob, Carl)
```go
type MSP Formatted
func Modulus(n int) *big.Int {}
func (m MSP) DistributeShares(sec []byte, modulus *big.Int, db *UserDatabase) (map[string][][]byte, error) {}
func (m MSP) RecoverSecret(modulus *big.Int, db *UserDatabase) ([]byte, error) {}
func (m MSP) DistributeShares(sec []byte, db *UserDatabase) (map[string][][]byte, error) {}
func (m MSP) RecoverSecret(db *UserDatabase) ([]byte, error) {}
```
To switch from predicate-mode to secret-sharing-mode, just cast your formatted
@@ -133,21 +132,3 @@ of shares which should be given to that party and integrated into the
`UserDatabase` somehow. When you're ready to reconstruct the secret, you call
`RecoverSecret`, which does some prodding about and hopefully gives you back
what you put in.
The modulus determines the size of the secret shares. It must be prime, larger
than the secret, and larger than n<sup>k</sup> where `n` is the number of
parties and `k` is the depth of the circuit. (For each path from the root to a
gate with only leaf nodes, sum the thresholds of all the gates along that path.
`k` is the maximum of these values--a safe overestimation is to sum the
thresholds of all gates in the circuit) Because this form of secret sharing
is *information-theoretically secure*, as long as the modulus satisfies those
requirements, it can be as small or as large as you'd like (there's no
bonus/reduction in security). An excessively small modulus is restrictive, but
saves a *lot* of bandwidth. However, there's no sensible reason for a modulus
over about 256 bits--by that point, it's easier to generate a random AES key,
encrypt your secret with *that*, and distribute shares of the decryption key
instead.
For convenience, the `Modulus` function has some hard coded moduli that are
useful. The choices are currently: `127`, `224`, and `256` for a 127-bit,
224-bit, or 256-bit modulus, respectively.

149
msp/matrix.go Normal file
View File

@@ -0,0 +1,149 @@
// Matrix operations for elements in GF(2^128).
package msp
type Row []FieldElem
// NewRow returns a row of length s with all zero entries.
func NewRow(s int) Row {
out := Row(make([]FieldElem, s))
for i := 0; i < s; i++ {
out[i] = NewFieldElem()
}
return out
}
// AddM adds two vectors.
func (e Row) AddM(f Row) {
le, lf := e.Size(), f.Size()
if le != lf {
panic("Can't add rows that are different sizes!")
}
for i, f_i := range f {
e[i].AddM(f_i)
}
return
}
// MulM multiplies the row by a scalar.
func (e Row) MulM(f FieldElem) {
for i, _ := range e {
e[i] = e[i].Mul(f)
}
}
func (e Row) Mul(f FieldElem) Row {
out := NewRow(e.Size())
for i := 0; i < e.Size(); i++ {
out[i] = e[i].Mul(f)
}
return out
}
// DotProduct computes the dot product of two vectors.
func (e Row) DotProduct(f Row) FieldElem {
if e.Size() != f.Size() {
panic("Can't get dot product of rows of different length!")
}
out := NewFieldElem()
for i := 0; i < e.Size(); i++ {
out.AddM(e[i].Mul(f[i]))
}
return out
}
func (e Row) Size() int {
return len(e)
}
type Matrix []Row
// Mul right-multiplies a matrix by a row.
func (e Matrix) Mul(f Row) Row {
out, in := e.Size()
if in != f.Size() {
panic("Can't multiply by row that is wrong size!")
}
res := NewRow(out)
for i := 0; i < out; i++ {
res[i] = e[i].DotProduct(f)
}
return res
}
// Recovery returns the row vector that takes this matrix to the target vector [1 0 0 ... 0].
func (e Matrix) Recovery() (Row, bool) {
a, b := e.Size()
// aug is the target vector.
aug := NewRow(a)
aug[0] = One.Dup()
// Duplicate e away so we don't mutate it; transpose it at the same time.
f := make([]Row, b)
for i, _ := range f {
f[i] = NewRow(a)
}
for i := 0; i < a; i++ {
for j := 0; j < b; j++ {
f[j][i] = e[i][j].Dup()
}
}
for row, _ := range f {
if row >= b { // The matrix is tall and thin--we've finished before exhausting all the rows.
break
}
// Find a row with a non-zero entry in the (row)th position
candId := -1
for j, f_j := range f[row:] {
if !f_j[row].IsZero() {
candId = j + row
break
}
}
if candId == -1 { // If we can't find one, fail and return our partial work.
return aug, false
}
// Move it to the top
f[row], f[candId] = f[candId], f[row]
aug[row], aug[candId] = aug[candId], aug[row]
// Make the pivot 1.
fInv := f[row][row].Invert()
f[row].MulM(fInv)
aug[row] = aug[row].Mul(fInv)
// Cancel out the (row)th position for every row above and below it.
for i, _ := range f {
if i != row && !f[i][row].IsZero() {
c := f[i][row].Dup()
f[i].AddM(f[row].Mul(c))
aug[i].AddM(aug[row].Mul(c))
}
}
}
return aug, true
}
func (e Matrix) Size() (int, int) {
return len(e), e[0].Size()
}

44
msp/matrix_test.go Normal file
View File

@@ -0,0 +1,44 @@
package msp
import (
"testing"
)
func TestRecovery(t *testing.T) {
// Generate the matrix.
height, width := 10, 10
M := Matrix(make([]Row, height))
for i := 0; i < height; i++ {
M[i] = NewRow(width)
for j := 0; j < width; j++ {
M[i][j][0] = byte(i + 1)
M[i][j] = M[i][j].Exp(j)
}
}
// Find the recovery vector.
r, ok := M.Recovery()
if !ok {
t.Fatalf("Failed to find the recovery vector!")
}
// Find the output vector.
out := NewRow(width)
for i := 0; i < height; i++ {
out.AddM(M[i].Mul(r[i]))
}
// Check that it is the target vector.
if !out[0].IsOne() {
t.Fatalf("Output is not the target vector!")
}
for i := 1; i < width; i++ {
if !out[i].IsZero() {
t.Fatalf("Output is not the target vector!")
}
}
}

View File

@@ -4,7 +4,6 @@ import (
"container/heap"
"crypto/rand"
"errors"
"math/big"
"strings"
)
@@ -82,34 +81,6 @@ TopLoop:
type MSP Formatted
func Modulus(n int) (modulus *big.Int) {
switch n {
case 256:
modulus = big.NewInt(1) // 2^256 - 2^224 + 2^192 + 2^96 - 1
modulus.Lsh(modulus, 256)
modulus.Sub(modulus, big.NewInt(0).Lsh(big.NewInt(1), 224))
modulus.Add(modulus, big.NewInt(0).Lsh(big.NewInt(1), 192))
modulus.Add(modulus, big.NewInt(0).Lsh(big.NewInt(1), 96))
modulus.Sub(modulus, big.NewInt(1))
case 224:
modulus = big.NewInt(1) // 2^224 - 2^96 + 1
modulus.Lsh(modulus, 224)
modulus.Sub(modulus, big.NewInt(0).Lsh(big.NewInt(1), 96))
modulus.Add(modulus, big.NewInt(1))
case 127:
modulus = big.NewInt(1) // 2^127 - 1
modulus.Lsh(modulus, 127)
modulus.Sub(modulus, big.NewInt(1))
default:
panic("Invalid modulus size chosen!")
}
return
}
func StringToMSP(pred string) (m MSP, err error) {
var f Formatted
@@ -164,53 +135,56 @@ func (m MSP) DerivePath(db *UserDatabase) (ok bool, names []string, locs []int,
return
}
func (m MSP) DistributeShares(sec []byte, modulus *big.Int, db *UserDatabase) (map[string][][]byte, error) {
// DistributeShares takes as input a secret and a user database and returns secret shares according to access structure
// described by the MSP.
func (m MSP) DistributeShares(sec []byte, db *UserDatabase) (map[string][][]byte, error) {
out := make(map[string][][]byte)
// Math to distribute shares.
secInt := big.NewInt(0).SetBytes(sec) // Convert secret to number.
secInt.Mod(secInt, modulus)
// Generate a Vandermonde matrix.
height, width := len(m.Conds), m.Min
M := Matrix(make([]Row, height))
var junk []*big.Int // Generate junk numbers.
for i := 1; i < m.Min; i++ {
r := make([]byte, (modulus.BitLen()/8)+1)
_, err := rand.Read(r)
if err != nil {
return out, err
for i := 0; i < height; i++ {
M[i] = NewRow(width)
for j := 0; j < width; j++ {
M[i][j][0] = byte(i + 1)
M[i][j] = M[i][j].Exp(j)
}
s := big.NewInt(0).SetBytes(r)
s.Mod(s, modulus)
junk = append(junk, s)
}
for i, cond := range m.Conds { // Calculate shares.
share := big.NewInt(1)
share.Mul(share, secInt)
// Convert secret vector.
s := NewRow(width)
s[0] = FieldElem(sec)
for j := 2; j <= m.Min; j++ {
cell := big.NewInt(int64(i + 1))
cell.Exp(cell, big.NewInt(int64(j-1)), modulus)
// CELL SHOULD ALWAYS BE LESS THAN MODULUS
for i := 1; i < width; i++ {
r := NewFieldElem()
rand.Read(r)
share.Add(share, cell.Mul(cell, junk[j-2])).Mod(share, modulus)
}
s[i] = FieldElem(r)
}
// Calculate shares.
shares := M.Mul(s)
// Distribute the shares.
for i, cond := range m.Conds {
share := shares[i]
switch cond.(type) {
case Name:
name := cond.(Name).string
if _, ok := out[name]; ok {
out[name] = append(out[name], m.encode(share, modulus))
out[name] = append(out[name], share)
} else if (*db).ValidUser(name) {
out[name] = [][]byte{m.encode(share, modulus)}
out[name] = [][]byte{share}
} else {
return out, errors.New("Unknown user in predicate.")
}
default:
below := MSP(cond.(Formatted))
subOut, err := below.DistributeShares(m.encode(share, modulus), modulus, db)
subOut, err := below.DistributeShares(share, db)
if err != nil {
return out, err
}
@@ -229,15 +203,16 @@ func (m MSP) DistributeShares(sec []byte, modulus *big.Int, db *UserDatabase) (m
return out, nil
}
func (m MSP) RecoverSecret(modulus *big.Int, db *UserDatabase) ([]byte, error) {
// RecoverSecret takes a user database storing secret shares as input and returns the original secret.
func (m MSP) RecoverSecret(db *UserDatabase) ([]byte, error) {
cache := make(map[string][][]byte, 0) // Caches un-used shares for a user.
return m.recoverSecret(modulus, db, cache)
return m.recoverSecret(db, cache)
}
func (m MSP) recoverSecret(modulus *big.Int, db *UserDatabase, cache map[string][][]byte) ([]byte, error) {
func (m MSP) recoverSecret(db *UserDatabase, cache map[string][][]byte) ([]byte, error) {
var (
index = []int{} // Indexes where given shares were in the matrix.
shares = [][]byte{} // Contains shares that will be used in reconstruction.
index = []int{} // Indexes where given shares were in the matrix.
shares = []FieldElem{} // Contains shares that will be used in reconstruction.
)
ok, names, locs, _ := m.DerivePath(db)
@@ -266,118 +241,39 @@ func (m MSP) recoverSecret(modulus *big.Int, db *UserDatabase, cache map[string]
return nil, errors.New("Predicate / database mismatch!")
}
shares = append(shares, cache[gate.(Name).string][gate.(Name).index])
shares = append(shares, FieldElem(cache[gate.(Name).string][gate.(Name).index]))
case Formatted:
share, err := MSP(gate.(Formatted)).recoverSecret(modulus, db, cache)
share, err := MSP(gate.(Formatted)).recoverSecret(db, cache)
if err != nil {
return nil, err
}
shares = append(shares, share)
shares = append(shares, FieldElem(share))
}
}
// Calculate the reconstruction vector. We only need the top row of the
// matrix's inverse, so we augment M transposed with u1 transposed and
// eliminate Gauss-Jordan style.
matrix := [][][2]int{} // 2d grid of (numerator, denominator)
matrix = append(matrix, [][2]int{}) // Create first row of all 1s
// Generate the Vandermonde matrix specific to whichever users' shares we're using.
MSub := Matrix(make([]Row, m.Min))
for j := 0; j < m.Min; j++ {
matrix[0] = append(matrix[0], [2]int{1, 1})
for i := 0; i < m.Min; i++ {
MSub[i] = NewRow(m.Min)
for j := 0; j < m.Min; j++ {
MSub[i][j][0] = byte(index[i])
MSub[i][j] = MSub[i][j].Exp(j)
}
}
for j := 1; j < m.Min; j++ { // Fill in rest of matrix.
row := [][2]int{}
for k, idx := range index {
row = append(row, [2]int{int(idx) * matrix[j-1][k][0], matrix[j-1][k][1]})
}
matrix = append(matrix, row)
}
matrix[0] = append(matrix[0], [2]int{1, 1}) // Stick on last column.
for j := 1; j < m.Min; j++ {
matrix[j] = append(matrix[j], [2]int{0, 1})
}
// Reduce matrix.
for i := 0; i < len(matrix); i++ {
for j := 0; j < len(matrix[i]); j++ { // Make row unary.
if i == j {
continue
}
matrix[i][j][0] *= matrix[i][i][1]
matrix[i][j][1] *= matrix[i][i][0]
}
matrix[i][i] = [2]int{1, 1}
for j := 0; j < len(matrix); j++ { // Remove this row from the others.
if i == j {
continue
}
top := matrix[j][i][0]
bot := matrix[j][i][1]
for k := 0; k < len(matrix[j]); k++ {
// matrix[j][k] = matrix[j][k] - matrix[j][i] * matrix[i][k]
temp := [2]int{0, 0}
temp[0] = top * matrix[i][k][0]
temp[1] = bot * matrix[i][k][1]
if matrix[j][k][0] == 0 {
matrix[j][k][0] = -temp[0]
matrix[j][k][1] = temp[1]
} else {
matrix[j][k][0] = (matrix[j][k][0] * temp[1]) - (temp[0] * matrix[j][k][1])
matrix[j][k][1] *= temp[1]
}
if matrix[j][k][0] == 0 {
matrix[j][k][1] = 1
}
}
}
// Calculate the reconstruction vector and use it to recover the secret.
r, ok := MSub.Recovery()
if !ok {
return nil, errors.New("Unable to find a reconstruction vector!")
}
// Compute dot product of the shares vector and the reconstruction vector to
// reconstruct the secret.
secInt := big.NewInt(0)
// recover the secret.
s := Row(shares).DotProduct(r)
for i, share := range shares {
lst := len(matrix[i]) - 1
num := big.NewInt(int64(matrix[i][lst][0]))
den := big.NewInt(int64(matrix[i][lst][1]))
num.Mod(num, modulus)
den.Mod(den, modulus)
coeff := big.NewInt(0).ModInverse(den, modulus)
coeff.Mul(coeff, num).Mod(coeff, modulus)
shareInt := big.NewInt(0).SetBytes(share)
shareInt.Mul(shareInt, coeff).Mod(shareInt, modulus)
secInt.Add(secInt, shareInt).Mod(secInt, modulus)
}
return m.encode(secInt, modulus), nil
}
func (m MSP) encode(x *big.Int, modulus *big.Int) (out []byte) {
tmp := x.Bytes()
tmpSize := len(tmp)
modSize := len(modulus.Bytes())
out = make([]byte, modSize)
for pos := 0; pos < tmpSize; pos++ {
out[modSize-pos-1] = tmp[tmpSize-pos-1]
}
return out
return []byte(s), nil
}

View File

@@ -42,8 +42,8 @@ func TestMSP(t *testing.T) {
predicate, _ := StringToMSP("(2, (1, Alice, Bob), Carl)")
shares1, _ := predicate.DistributeShares(sec, Modulus(127), &db)
shares2, _ := predicate.DistributeShares(sec, Modulus(127), &db)
shares1, _ := predicate.DistributeShares(sec, &db)
shares2, _ := predicate.DistributeShares(sec, &db)
alice := bytes.Compare(shares1["Alice"][0], shares2["Alice"][0])
bob := bytes.Compare(shares1["Bob"][0], shares2["Bob"][0])
@@ -56,12 +56,12 @@ func TestMSP(t *testing.T) {
db1 := UserDatabase(Database(shares1))
db2 := UserDatabase(Database(shares2))
sec1, err := predicate.RecoverSecret(Modulus(127), &db1)
sec1, err := predicate.RecoverSecret(&db1)
if err != nil {
t.Fatalf("#1: %v", err)
}
sec2, err := predicate.RecoverSecret(Modulus(127), &db2)
sec2, err := predicate.RecoverSecret(&db2)
if err != nil {
t.Fatalf("#2: %v", err)
}

116
msp/number.go Normal file
View File

@@ -0,0 +1,116 @@
// Polynomial fields with coefficients in GF(2)
package msp
import (
"bytes"
)
type FieldElem []byte
var (
Modulus FieldElem = []byte{135, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // x^128 + x^7 + x^2 + x + 1
ModulusSize int = 16
ModulusBitSize int = 128
Zero FieldElem = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
One FieldElem = []byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
)
// NewFieldElem returns a new zero element.
func NewFieldElem() FieldElem {
return FieldElem(make([]byte, ModulusSize))
}
// AddM mutates e into e+f.
func (e FieldElem) AddM(f FieldElem) {
for i := 0; i < ModulusSize; i++ {
e[i] ^= f[i]
}
}
// Add returns e+f.
func (e FieldElem) Add(f FieldElem) FieldElem {
out := e.Dup()
out.AddM(f)
return out
}
// Mul returns e*f.
func (e FieldElem) Mul(f FieldElem) FieldElem {
out := NewFieldElem()
for i := 0; i < ModulusBitSize; i++ { // Foreach bit e_i in e:
if e.getCoeff(i) == 1 { // where e_i equals 1:
temp := f.Dup() // Multiply f * x^i mod M(x):
for j := 0; j < i; j++ { // Multiply f by x mod M(x), i times.
carry := temp.shift()
if carry {
temp[0] ^= Modulus[0]
}
}
out.AddM(temp) // Add f * x^i to the output
}
}
return out
}
// Exp returns e^i.
func (e FieldElem) Exp(i int) FieldElem {
out := One.Dup()
for j := 0; j < i; j++ {
out = out.Mul(e)
}
return out
}
// Invert returns the multiplicative inverse of e.
func (e FieldElem) Invert() FieldElem {
out, temp := e.Dup(), e.Dup()
for i := 0; i < 126; i++ {
temp = temp.Mul(temp)
out = out.Mul(temp)
}
return out.Mul(out)
}
// getCoeff returns the ith coefficient of the field element: either 0 or 1.
func (e FieldElem) getCoeff(i int) byte {
return (e[i/8] >> (uint(i) % 8)) & 1
}
// shift multiplies e by 2 and returns true if there was overflow and false if there wasn't.
func (e FieldElem) shift() bool {
carry := false
for i := 0; i < ModulusSize; i++ {
nextCarry := e[i] >= 128
e[i] = e[i] << 1
if carry {
e[i]++
}
carry = nextCarry
}
return carry
}
func (e FieldElem) IsZero() bool { return bytes.Compare(e, Zero) == 0 }
func (e FieldElem) IsOne() bool { return bytes.Compare(e, One) == 0 }
// Dup returns a duplicate of e.
func (e FieldElem) Dup() FieldElem {
out := FieldElem(make([]byte, ModulusSize))
copy(out, e)
return out
}

50
msp/number_test.go Normal file
View File

@@ -0,0 +1,50 @@
package msp
import (
"bytes"
"crypto/rand"
"testing"
)
func TestFieldElemMultiplicationOne(t *testing.T) {
x := FieldElem(make([]byte, ModulusSize))
rand.Read(x)
xy, yx := x.Mul(One), One.Mul(x)
if !One.IsOne() {
t.Fatalf("One is not one?")
}
if bytes.Compare(xy, x) != 0 || bytes.Compare(yx, x) != 0 {
t.Fatalf("Multiplication by 1 failed!\nx = %x\n1*x = %x\nx*1 = %x", x, yx, xy)
}
}
func TestFieldElemMultiplicationZero(t *testing.T) {
x := FieldElem(make([]byte, ModulusSize))
rand.Read(x)
xy, yx := x.Mul(Zero), Zero.Mul(x)
if !Zero.IsZero() {
t.Fatalf("Zero is not zero?")
}
if !xy.IsZero() || !yx.IsZero() {
t.Fatalf("Multiplication by 0 failed!\nx = %x\n0*x = %x\nx*0 = %x", x, yx, xy)
}
}
func TestFieldElemInvert(t *testing.T) {
x := FieldElem(make([]byte, ModulusSize))
rand.Read(x)
xInv := x.Invert()
xy, yx := x.Mul(xInv), xInv.Mul(x)
if !xy.IsOne() || !yx.IsOne() {
t.Fatalf("Multiplication by inverse failed!\nx = %x\nxInv = %x\nxInv*x = %x\nx*xInv = %x", x, yx, xy)
}
}