From 7e56983fa602cb6601ae63f934624be7aea74f7f Mon Sep 17 00:00:00 2001 From: Brendan McMillion Date: Sat, 21 Nov 2015 09:23:55 -0800 Subject: [PATCH] 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. --- cryptor/cryptor.go | 9 +- msp/README.md | 23 +---- msp/matrix.go | 149 +++++++++++++++++++++++++++++++ msp/matrix_test.go | 44 ++++++++++ msp/msp.go | 214 ++++++++++++--------------------------------- msp/msp_test.go | 8 +- msp/number.go | 116 ++++++++++++++++++++++++ msp/number_test.go | 50 +++++++++++ 8 files changed, 422 insertions(+), 191 deletions(-) create mode 100644 msp/matrix.go create mode 100644 msp/matrix_test.go create mode 100644 msp/number.go create mode 100644 msp/number_test.go diff --git a/cryptor/cryptor.go b/cryptor/cryptor.go index 22acc22..fbdf556 100644 --- a/cryptor/cryptor.go +++ b/cryptor/cryptor.go @@ -351,7 +351,7 @@ func (encrypted *EncryptedData) wrapKey(records *passvault.Records, clearKey []b } db := msp.UserDatabase(UserDatabase{records: records}) - shareSet, err := sss.DistributeShares(clearKey, msp.Modulus(127), &db) + shareSet, err := sss.DistributeShares(clearKey, &db) if err != nil { return err } @@ -450,7 +450,7 @@ func (encrypted *EncryptedData) unwrapKey(cache *keycache.Cache, user string) (u keySet: encrypted.KeySetRSA, shareSet: encrypted.ShareSet, }) - unwrappedKey, err = sss.RecoverSecret(msp.Modulus(127), &db) + unwrappedKey, err = sss.RecoverSecret(&db) return } @@ -476,11 +476,6 @@ func (c *Cryptor) Encrypt(in []byte, labels []string, access AccessStructure) (r if err != nil { return } - if len(access.Predicate) > 0 { - // The first two bits of the key need to be removed to prevent wrapping - // because the modulus is slightly too small. - clearKey[0] &= 63 - } err = encrypted.wrapKey(c.records, clearKey, access) if err != nil { diff --git a/msp/README.md b/msp/README.md index 051ec26..a88c4bc 100644 --- a/msp/README.md +++ b/msp/README.md @@ -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 nk 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. diff --git a/msp/matrix.go b/msp/matrix.go new file mode 100644 index 0000000..6c82141 --- /dev/null +++ b/msp/matrix.go @@ -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() +} diff --git a/msp/matrix_test.go b/msp/matrix_test.go new file mode 100644 index 0000000..f345c16 --- /dev/null +++ b/msp/matrix_test.go @@ -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!") + } + } +} diff --git a/msp/msp.go b/msp/msp.go index 7574a6f..7d4aaff 100644 --- a/msp/msp.go +++ b/msp/msp.go @@ -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 } diff --git a/msp/msp_test.go b/msp/msp_test.go index bf3e460..a8751fa 100644 --- a/msp/msp_test.go +++ b/msp/msp_test.go @@ -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) } diff --git a/msp/number.go b/msp/number.go new file mode 100644 index 0000000..739305d --- /dev/null +++ b/msp/number.go @@ -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 +} diff --git a/msp/number_test.go b/msp/number_test.go new file mode 100644 index 0000000..280cfd7 --- /dev/null +++ b/msp/number_test.go @@ -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) + } +}