mirror of
https://github.com/cloudflare/redoctober.git
synced 2026-07-31 04:23:02 +00:00
Vendored in Bren2010/MSP
This commit is contained in:
+9
-9
@@ -15,8 +15,8 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/Bren2010/msp"
|
||||
"github.com/cloudflare/redoctober/keycache"
|
||||
"github.com/cloudflare/redoctober/msp"
|
||||
"github.com/cloudflare/redoctober/padding"
|
||||
"github.com/cloudflare/redoctober/passvault"
|
||||
"github.com/cloudflare/redoctober/symcrypt"
|
||||
@@ -52,11 +52,11 @@ type AccessStructure struct {
|
||||
// Implements msp.UserDatabase
|
||||
type UserDatabase struct {
|
||||
records *passvault.Records
|
||||
cache *keycache.Cache
|
||||
cache *keycache.Cache
|
||||
|
||||
user string
|
||||
labels []string
|
||||
keySet map[string]SingleWrappedKey
|
||||
user string
|
||||
labels []string
|
||||
keySet map[string]SingleWrappedKey
|
||||
shareSet map[string][][]byte
|
||||
}
|
||||
|
||||
@@ -433,10 +433,10 @@ func (encrypted *EncryptedData) unwrapKey(cache *keycache.Cache, user string) (u
|
||||
}
|
||||
|
||||
db := msp.UserDatabase(UserDatabase{
|
||||
cache: cache,
|
||||
user: user,
|
||||
labels: encrypted.Labels,
|
||||
keySet: encrypted.KeySetRSA,
|
||||
cache: cache,
|
||||
user: user,
|
||||
labels: encrypted.Labels,
|
||||
keySet: encrypted.KeySetRSA,
|
||||
shareSet: encrypted.ShareSet,
|
||||
})
|
||||
unwrappedKey, err = sss.RecoverSecret(msp.Modulus(127), &db)
|
||||
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
Monotone Span Programs
|
||||
======================
|
||||
|
||||
- [Introduction](#monotone-span-programs)
|
||||
- [Types of Predicates](#types-of-predicates)
|
||||
- [Documentation](#documentation)
|
||||
- [User Databases](#user-databases)
|
||||
- [Building Predicates](#building-predicates)
|
||||
- [Splitting & Reconstructing Secrets](#splitting--reconstructing-secrets)
|
||||
|
||||
A *Monotone Span Program* (or *MSP*) is a cryptographic technique for splitting
|
||||
a secret into several *shares* that are then distributed to *parties* or
|
||||
*users*. (Have you heard of [Shamir's Secret Sharing](http://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing)? It's like that.)
|
||||
|
||||
Unlike Shamir's Secret Sharing, MSPs allow *arbitrary monotone access
|
||||
structures*. An access structure is just a boolean predicate on a set of users
|
||||
that tells us whether or not that set is allowed to recover the secret. A
|
||||
monotone access structure is the same thing, but with the invariant that adding
|
||||
a user to a set will never turn the predicate's output from `true` to
|
||||
`false`--negations or boolean `nots` are disallowed.
|
||||
|
||||
**Example:** `(Alice or Bob) and Carl` is good, but `(Alice or Bob) and !Carl`
|
||||
is not because excluding people is rude.
|
||||
|
||||
MSPs are fundamental and powerful primitives. They're well-suited for
|
||||
distributed commitments (DC), verifiable secret sharing (VSS) and multi-party
|
||||
computation (MPC).
|
||||
|
||||
|
||||
#### Types of Predicates
|
||||
|
||||
An MSP itself is a type of predicate and the reader is probably familiar with
|
||||
raw boolean predicates like in the example above, but another important type is
|
||||
a *formatted boolean predicate*.
|
||||
|
||||
Formatted boolean predicates are isomorphic to all MSPs and therefore all
|
||||
monotone raw boolean predicates. They're built by nesting threshold gates.
|
||||
|
||||
**Example:** Let `(2, Alice, Bob, Carl)` denote that at least 2 members of the
|
||||
set `{Alice, Bob, Carl}` must be present to recover the secret. Then,
|
||||
`(2, (1, Alice, Bob), Carl)` is the formatted version of
|
||||
`(Alice or Bob) and Carl`.
|
||||
|
||||
It is possible to convert between different types of predicates (and its one of
|
||||
the fundamental operations of splitting secrets with an MSP), but circuit
|
||||
minimization is a non-trivial and computationally complex problem. The code can
|
||||
do a small amount of compression, but the onus is on the user to design
|
||||
efficiently computable predicates.
|
||||
|
||||
|
||||
#### To Do
|
||||
|
||||
1. Anonymous secret generation / secret homomorphisms
|
||||
2. Non-interactive verifiable secret sharing / distributed commitments
|
||||
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
### User Databases
|
||||
|
||||
```go
|
||||
type UserDatabase interface {
|
||||
ValidUser(string) bool // Is this the name of an existing user?
|
||||
CanGetShare(string) bool // Can I get this user's share?
|
||||
GetShare(string) ([][]byte, error) // Retrieves a user's shares.
|
||||
}
|
||||
```
|
||||
|
||||
User databases are an abstraction over the primitive name -> share map that
|
||||
hopefully offer a bit more flexibility in implementing secret sharing schemes.
|
||||
`CanGetShare(name)` should be faster and less binding than `GetShare(name)`.
|
||||
`CanGetShare(name)` may be called a large number of times, but `GetShare(name)`
|
||||
will be called the absolute minimum number of times possible.
|
||||
|
||||
Depending on the predicate used, a name may be associated to multiple shares of
|
||||
a secret, hence `[][]byte` as opposed to one share (`[]byte`).
|
||||
|
||||
For test/play purposes there's a cheaty implementation of `UserDatabase` in
|
||||
`msp_test.go` that just wraps the `map[string][][]byte` returned by
|
||||
`DistributeShares(...)`
|
||||
|
||||
### Building Predicates
|
||||
|
||||
```go
|
||||
type Raw struct { ... }
|
||||
|
||||
func StringToRaw(string) (Raw, error) { ... }
|
||||
func (r Raw) String() string { .. .}
|
||||
func (r Raw) Formatted() Formatted { ... }
|
||||
|
||||
|
||||
type Formatted struct { ... }
|
||||
|
||||
func StringToFormatted(string) (Formatted, error)
|
||||
func (f Formatted) String() string
|
||||
```
|
||||
|
||||
Building predicates is extremely easy--just write it out in a string and have
|
||||
one of the package methods parse it.
|
||||
|
||||
Raw predicates take the `&` (logical AND) and `|` (logical OR) operators, but
|
||||
are otherwise the same as discussed above. Formatted predicates are exactly the
|
||||
same as above--just nested threshold gates.
|
||||
|
||||
```go
|
||||
r1, _ := msp.StringToRaw("(Alice | Bob) & Carl")
|
||||
r2, _ := msp.StringToRaw("Alice & Bob & Carl")
|
||||
|
||||
fmt.Printf("%v\n", r1.Formatted()) // (2, (1, Alice, Bob), Carl)
|
||||
fmt.Printf("%v\n", r2.Formatted()) // (3, Alice, Bob, Carl)
|
||||
```
|
||||
|
||||
### Splitting & Reconstructing Secrets
|
||||
|
||||
```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) {}
|
||||
```
|
||||
|
||||
To switch from predicate-mode to secret-sharing-mode, just cast your formatted
|
||||
predicate into an MSP something like this:
|
||||
```go
|
||||
predicate := msp.StringToFormatted("(3, Alice, Bob, Carl)")
|
||||
sss := msp.MSP(predicate)
|
||||
```
|
||||
|
||||
Calling `DistributeShares` on it returns a map from a party's name to their set
|
||||
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.
|
||||
@@ -0,0 +1,189 @@
|
||||
package msp
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Formatted struct { // Represents threshold gate (also type of condition)
|
||||
Min int
|
||||
Conds []Condition
|
||||
}
|
||||
|
||||
func StringToFormatted(f string) (out Formatted, err error) {
|
||||
// Automaton. Modification of Dijkstra's Two-Stack Algorithm for parsing
|
||||
// infix notation. Running time linear in the size of the predicate?
|
||||
//
|
||||
// Steps either to the next comma or the next unparenthesis.
|
||||
// ( -> Push new queue onto staging stack
|
||||
// value -> Push onto back of queue at top of staging stack.
|
||||
// ) -> Pop queue off top of staging stack, build threshold gate,
|
||||
// and push gate onto the back of the top queue.
|
||||
//
|
||||
// Staging stack is empty on initialization and should have exactly 1 built
|
||||
// threshold gate at the end of the string.
|
||||
if f[0] != '(' || f[len(f)-1] != ')' {
|
||||
return out, errors.New("Invalid string--wrong format.")
|
||||
}
|
||||
|
||||
getNext := func(f string) (string, string) { // f -> (next, rest)
|
||||
f = strings.TrimSpace(f)
|
||||
|
||||
if f[0] == '(' {
|
||||
return f[0:1], f[1:]
|
||||
}
|
||||
|
||||
nextComma := strings.Index(f, ",")
|
||||
if f[0] == ')' {
|
||||
if nextComma == -1 {
|
||||
return f[0:1], ""
|
||||
}
|
||||
return f[0:1], f[nextComma+1:]
|
||||
} else if nextComma == -1 {
|
||||
return f[0 : len(f)-1], f[len(f)-1:]
|
||||
}
|
||||
|
||||
nextUnParen := strings.Index(f, ")")
|
||||
if nextComma < nextUnParen {
|
||||
return strings.TrimSpace(f[0:nextComma]), f[nextComma+1:]
|
||||
}
|
||||
|
||||
return strings.TrimSpace(f[0:nextUnParen]), f[nextUnParen:]
|
||||
}
|
||||
|
||||
staging := list.New()
|
||||
indices := make(map[string]int, 0)
|
||||
|
||||
var nxt string
|
||||
for len(f) > 0 {
|
||||
nxt, f = getNext(f)
|
||||
|
||||
switch nxt {
|
||||
case "(":
|
||||
staging.PushFront(list.New())
|
||||
case ")":
|
||||
top := staging.Remove(staging.Front()).(*list.List)
|
||||
|
||||
var min int
|
||||
minStr := top.Front()
|
||||
min, err = strconv.Atoi(minStr.Value.(String).string)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
built := Formatted{
|
||||
Min: min,
|
||||
Conds: []Condition{},
|
||||
}
|
||||
|
||||
for cond := minStr.Next(); cond != nil; cond = cond.Next() {
|
||||
built.Conds = append(built.Conds, cond.Value.(Condition))
|
||||
}
|
||||
|
||||
if staging.Len() == 0 {
|
||||
if len(f) == 0 {
|
||||
return built, nil
|
||||
}
|
||||
return built, errors.New("Invalid string--terminated early.")
|
||||
}
|
||||
|
||||
staging.Front().Value.(*list.List).PushBack(built)
|
||||
|
||||
default:
|
||||
if _, there := indices[nxt]; !there {
|
||||
indices[nxt] = 0
|
||||
}
|
||||
|
||||
staging.Front().Value.(*list.List).PushBack(String{nxt, indices[nxt]})
|
||||
indices[nxt]++
|
||||
}
|
||||
}
|
||||
|
||||
return out, errors.New("Invalid string--never terminated.")
|
||||
}
|
||||
|
||||
func (f Formatted) String() string {
|
||||
out := fmt.Sprintf("(%v", f.Min)
|
||||
|
||||
for _, cond := range f.Conds {
|
||||
switch cond.(type) {
|
||||
case String:
|
||||
out += fmt.Sprintf(", %v", cond.(String).string)
|
||||
case Formatted:
|
||||
out += fmt.Sprintf(", %v", (cond.(Formatted)).String())
|
||||
}
|
||||
}
|
||||
|
||||
return out + ")"
|
||||
}
|
||||
|
||||
func (f Formatted) Ok(db *UserDatabase) bool {
|
||||
// Goes through the smallest number of conditions possible to check if the
|
||||
// threshold gate returns true. Sometimes requires recursing down to check
|
||||
// nested threshold gates.
|
||||
rest := f.Min
|
||||
|
||||
for _, cond := range f.Conds {
|
||||
if cond.Ok(db) {
|
||||
rest--
|
||||
}
|
||||
|
||||
if rest == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *Formatted) Compress() {
|
||||
if f.Min == len(f.Conds) {
|
||||
// AND Compression: (n, ..., (m, ...), ...) = (n + m, ...)
|
||||
skip := 0
|
||||
for i, cond := range f.Conds {
|
||||
if skip > 0 {
|
||||
skip--
|
||||
continue
|
||||
}
|
||||
|
||||
switch cond.(type) {
|
||||
case Formatted:
|
||||
cond := cond.(Formatted)
|
||||
cond.Compress()
|
||||
f.Conds[i] = cond
|
||||
|
||||
if cond.Min == len(cond.Conds) {
|
||||
f.Min += cond.Min - 1
|
||||
f.Conds = append(f.Conds[0:i],
|
||||
append(cond.Conds, f.Conds[i+1:]...)...)
|
||||
skip = len(cond.Conds) - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if f.Min == 1 {
|
||||
// OR Compression: (1, ..., (1, ...), ...) = (1, ...)
|
||||
skip := 0
|
||||
for i, cond := range f.Conds {
|
||||
if skip > 0 {
|
||||
skip--
|
||||
continue
|
||||
}
|
||||
|
||||
switch cond.(type) {
|
||||
case Formatted:
|
||||
cond := cond.(Formatted)
|
||||
cond.Compress()
|
||||
f.Conds[i] = cond
|
||||
|
||||
if cond.Min == 1 {
|
||||
f.Conds = append(f.Conds[0:i],
|
||||
append(cond.Conds, f.Conds[i+1:]...)...)
|
||||
skip = len(cond.Conds) - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package msp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFormatted(t *testing.T) {
|
||||
query1 := Formatted{
|
||||
Min: 2,
|
||||
Conds: []Condition{
|
||||
String{"Alice", 0}, String{"Bob", 0}, String{"Carl", 0},
|
||||
},
|
||||
}
|
||||
|
||||
query2 := Formatted{
|
||||
Min: 3,
|
||||
Conds: []Condition{
|
||||
String{"Alice", 0}, String{"Bob", 0}, String{"Carl", 0},
|
||||
},
|
||||
}
|
||||
|
||||
query3 := Formatted{
|
||||
Min: 2,
|
||||
Conds: []Condition{
|
||||
Formatted{
|
||||
Min: 1,
|
||||
Conds: []Condition{
|
||||
String{"Alice", 0}, String{"Bob", 0},
|
||||
},
|
||||
},
|
||||
String{"Carl", 0},
|
||||
},
|
||||
}
|
||||
|
||||
query4 := Formatted{
|
||||
Min: 2,
|
||||
Conds: []Condition{
|
||||
Formatted{
|
||||
Min: 1,
|
||||
Conds: []Condition{
|
||||
String{"Alice", 0}, String{"Carl", 0},
|
||||
},
|
||||
},
|
||||
String{"Bob", 0},
|
||||
},
|
||||
}
|
||||
|
||||
db := UserDatabase(Database(map[string][][]byte{
|
||||
"Alice": [][]byte{[]byte("blah")},
|
||||
"Carl": [][]byte{[]byte("herp")},
|
||||
}))
|
||||
|
||||
if query1.Ok(&db) != true {
|
||||
t.Fatalf("Query #1 was wrong.")
|
||||
}
|
||||
|
||||
if query2.Ok(&db) != false {
|
||||
t.Fatalf("Query #2 was wrong.")
|
||||
}
|
||||
|
||||
if query3.Ok(&db) != true {
|
||||
t.Fatalf("Query #3 was wrong.")
|
||||
}
|
||||
|
||||
if query4.Ok(&db) != false {
|
||||
t.Fatalf("Query #4 was wrong.")
|
||||
}
|
||||
|
||||
query1String := "(2, Alice, Bob, Carl)"
|
||||
query3String := "(2, (1, Alice, Bob), Carl)"
|
||||
|
||||
if query1.String() != query1String {
|
||||
t.Fatalf("Query #1 String was wrong; %v", query1.String())
|
||||
}
|
||||
|
||||
if query3.String() != query3String {
|
||||
t.Fatalf("Query #3 String was wrong; %v", query3.String())
|
||||
}
|
||||
|
||||
decQuery1, err := StringToFormatted(query1String)
|
||||
if err != nil || decQuery1.String() != query1String {
|
||||
t.Fatalf("Query #1 decoded wrong: %v %v", decQuery1.String(), err)
|
||||
}
|
||||
|
||||
decQuery3, err := StringToFormatted(query3String)
|
||||
if err != nil || decQuery3.String() != query3String {
|
||||
t.Fatalf("Query #3 decoded wrong: %v %v", decQuery3.String(), err)
|
||||
}
|
||||
}
|
||||
+369
@@ -0,0 +1,369 @@
|
||||
package msp
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"math/big"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A UserDatabase is an abstraction over the name -> share map returned by the
|
||||
// secret splitter that allows an application to only decrypt or request shares
|
||||
// when needed, rather than re-build a partial map of known data.
|
||||
type UserDatabase interface {
|
||||
ValidUser(name string) bool
|
||||
CanGetShare(string) bool
|
||||
GetShare(string) ([][]byte, error)
|
||||
}
|
||||
|
||||
type Condition interface { // Represents one condition in a predicate
|
||||
Ok(*UserDatabase) bool
|
||||
}
|
||||
|
||||
type String struct { // Type of condition
|
||||
string
|
||||
index int
|
||||
}
|
||||
|
||||
func (s String) Ok(db *UserDatabase) bool {
|
||||
return (*db).CanGetShare(s.string)
|
||||
}
|
||||
|
||||
type TraceElem struct {
|
||||
loc int
|
||||
names []string
|
||||
trace []string
|
||||
}
|
||||
|
||||
type TraceSlice []TraceElem
|
||||
|
||||
func (ts TraceSlice) Len() int { return len(ts) }
|
||||
func (ts TraceSlice) Swap(i, j int) { ts[i], ts[j] = ts[j], ts[i] }
|
||||
|
||||
func (ts TraceSlice) Less(i, j int) bool {
|
||||
return len(ts[i].trace) < len(ts[j].trace)
|
||||
}
|
||||
|
||||
func (ts *TraceSlice) Push(te interface{}) { *ts = append(*ts, te.(TraceElem)) }
|
||||
func (ts *TraceSlice) Pop() interface{} {
|
||||
out := (*ts)[0]
|
||||
*ts = (*ts)[1 : len(*ts)-1]
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (ts TraceSlice) Compact() (index []int, names []string, trace []string) {
|
||||
for _, te := range ts {
|
||||
index = append(index, te.loc)
|
||||
names = append(names, te.names...)
|
||||
trace = append(trace, te.trace...)
|
||||
}
|
||||
|
||||
ptr, cutoff := 0, len(trace)
|
||||
|
||||
TopLoop:
|
||||
for ptr < cutoff {
|
||||
for i := 0; i < ptr; i++ {
|
||||
if trace[i] == trace[ptr] {
|
||||
trace[ptr], trace[cutoff-1] = trace[cutoff-1], trace[ptr]
|
||||
cutoff--
|
||||
|
||||
continue TopLoop
|
||||
}
|
||||
}
|
||||
|
||||
ptr++
|
||||
}
|
||||
trace = trace[0:cutoff]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
default: // Silent fail.
|
||||
modulus = big.NewInt(1) // 2^127 - 1
|
||||
modulus.Lsh(modulus, 127)
|
||||
modulus.Sub(modulus, big.NewInt(1))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func StringToMSP(pred string) (m MSP, err error) {
|
||||
var f Formatted
|
||||
|
||||
if -1 == strings.Index(pred, ",") {
|
||||
var r Raw
|
||||
r, err = StringToRaw(pred)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
f = r.Formatted()
|
||||
} else {
|
||||
f, err = StringToFormatted(pred)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return MSP(f), nil
|
||||
}
|
||||
|
||||
func (m MSP) DerivePath(db *UserDatabase) (ok bool, names []string, locs []int, trace []string) {
|
||||
ts := &TraceSlice{}
|
||||
|
||||
for i, cond := range m.Conds {
|
||||
switch cond.(type) {
|
||||
case String:
|
||||
if (*db).CanGetShare(cond.(String).string) {
|
||||
heap.Push(ts, TraceElem{
|
||||
i,
|
||||
[]string{cond.(String).string},
|
||||
[]string{cond.(String).string},
|
||||
})
|
||||
}
|
||||
|
||||
case Formatted:
|
||||
sok, _, _, strace := MSP(cond.(Formatted)).DerivePath(db)
|
||||
if !sok {
|
||||
continue
|
||||
}
|
||||
|
||||
heap.Push(ts, TraceElem{i, []string{}, strace})
|
||||
}
|
||||
|
||||
if (*ts).Len() > m.Min {
|
||||
*ts = (*ts)[0:m.Min]
|
||||
}
|
||||
}
|
||||
|
||||
ok = (*ts).Len() >= m.Min
|
||||
locs, names, trace = ts.Compact()
|
||||
return
|
||||
}
|
||||
|
||||
func (m MSP) DistributeShares(sec []byte, modulus *big.Int, 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)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
share.Add(share, cell.Mul(cell, junk[j-2])).Mod(share, modulus)
|
||||
}
|
||||
|
||||
switch cond.(type) {
|
||||
case String:
|
||||
name := cond.(String).string
|
||||
if _, ok := out[name]; ok {
|
||||
out[name] = append(out[name], share.Bytes())
|
||||
} else if (*db).ValidUser(name) {
|
||||
out[name] = [][]byte{share.Bytes()}
|
||||
} else {
|
||||
return out, errors.New("Unknown user in predicate.")
|
||||
}
|
||||
|
||||
default:
|
||||
below := MSP(cond.(Formatted))
|
||||
subOut, err := below.DistributeShares(share.Bytes(), modulus, db)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
for name, shares := range subOut {
|
||||
if _, ok := out[name]; ok {
|
||||
out[name] = append(out[name], shares...)
|
||||
} else {
|
||||
out[name] = shares
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (m MSP) RecoverSecret(modulus *big.Int, db *UserDatabase) ([]byte, error) {
|
||||
cache := make(map[string][][]byte, 0) // Caches un-used shares for a user.
|
||||
return m.recoverSecret(modulus, db, cache)
|
||||
}
|
||||
|
||||
func (m MSP) recoverSecret(modulus *big.Int, 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.
|
||||
)
|
||||
|
||||
ok, names, locs, _ := m.DerivePath(db)
|
||||
if !ok {
|
||||
return nil, errors.New("Not enough shares to recover.")
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
if _, cached := cache[name]; !cached {
|
||||
out, err := (*db).GetShare(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cache[name] = out
|
||||
}
|
||||
}
|
||||
|
||||
for _, loc := range locs {
|
||||
gate := m.Conds[loc]
|
||||
index = append(index, loc+1)
|
||||
|
||||
switch gate.(type) {
|
||||
case String:
|
||||
if len(cache[gate.(String).string]) <= gate.(String).index {
|
||||
return nil, errors.New("Predicate / database mismatch!")
|
||||
}
|
||||
|
||||
shares = append(shares, cache[gate.(String).string][gate.(String).index])
|
||||
|
||||
case Formatted:
|
||||
share, err := MSP(gate.(Formatted)).recoverSecret(modulus, db, cache)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
shares = append(shares, 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
|
||||
|
||||
for j := 0; j < m.Min; j++ {
|
||||
matrix[0] = append(matrix[0], [2]int{1, 1})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute dot product of the shares vector and the reconstruction vector to
|
||||
// reconstruct the secret.
|
||||
size := len(modulus.Bytes())
|
||||
out := make([]byte, size)
|
||||
secInt := big.NewInt(0)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
out = append(out, secInt.Bytes()...)
|
||||
return out[len(out)-size:], nil
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package msp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Database map[string][][]byte
|
||||
|
||||
func (d Database) ValidUser(name string) bool {
|
||||
_, ok := d[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (d Database) CanGetShare(name string) bool {
|
||||
_, ok := d[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (d Database) GetShare(name string) ([][]byte, error) {
|
||||
out, ok := d[name]
|
||||
|
||||
if ok {
|
||||
return out, nil
|
||||
} else {
|
||||
return nil, errors.New("Not found!")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMSP(t *testing.T) {
|
||||
db := UserDatabase(Database(map[string][][]byte{
|
||||
"Alice": [][]byte{},
|
||||
"Bob": [][]byte{},
|
||||
"Carl": [][]byte{},
|
||||
}))
|
||||
|
||||
sec := make([]byte, 16)
|
||||
rand.Read(sec)
|
||||
sec[0] &= 63 // Removes first 2 bits of key.
|
||||
|
||||
predicate, _ := StringToMSP("(2, (1, Alice, Bob), Carl)")
|
||||
|
||||
shares1, _ := predicate.DistributeShares(sec, Modulus(127), &db)
|
||||
shares2, _ := predicate.DistributeShares(sec, Modulus(127), &db)
|
||||
|
||||
alice := bytes.Compare(shares1["Alice"][0], shares2["Alice"][0])
|
||||
bob := bytes.Compare(shares1["Bob"][0], shares2["Bob"][0])
|
||||
carl := bytes.Compare(shares1["Carl"][0], shares2["Carl"][0])
|
||||
|
||||
if alice == 0 && bob == 0 && carl == 0 {
|
||||
t.Fatalf("Key splitting isn't random! %v %v", shares1, shares2)
|
||||
}
|
||||
|
||||
db1 := UserDatabase(Database(shares1))
|
||||
db2 := UserDatabase(Database(shares2))
|
||||
|
||||
sec1, err := predicate.RecoverSecret(Modulus(127), &db1)
|
||||
if err != nil {
|
||||
t.Fatalf("#1: %v", err)
|
||||
}
|
||||
|
||||
sec2, err := predicate.RecoverSecret(Modulus(127), &db2)
|
||||
if err != nil {
|
||||
t.Fatalf("#2: %v", err)
|
||||
}
|
||||
|
||||
if !(bytes.Compare(sec, sec1) == 0 && bytes.Compare(sec, sec2) == 0) {
|
||||
t.Fatalf("Secrets derived differed: %v %v %v", sec, sec1, sec2)
|
||||
}
|
||||
}
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
package msp
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type NodeType int // Types of node in the binary expression tree.
|
||||
|
||||
const (
|
||||
NodeAnd NodeType = iota
|
||||
NodeOr
|
||||
)
|
||||
|
||||
func (t NodeType) Type() NodeType {
|
||||
return t
|
||||
}
|
||||
|
||||
type Raw struct { // Represents one node in the tree.
|
||||
NodeType
|
||||
|
||||
Left *Condition
|
||||
Right *Condition
|
||||
}
|
||||
|
||||
func StringToRaw(r string) (out Raw, err error) {
|
||||
// Automaton. Modification of Dijkstra's Two-Stack Algorithm for parsing
|
||||
// infix notation. Reads one long unbroken expression (several operators and
|
||||
// operands with no parentheses) at a time and parses it into a binary
|
||||
// expression tree (giving AND operators precedence). Running time linear in
|
||||
// the size of the predicate?
|
||||
//
|
||||
// Steps to the next (un)parenthesis.
|
||||
// ( -> Push new queue onto staging stack
|
||||
// value -> Push onto back of queue at top of staging stack.
|
||||
// ) -> Pop queue off top of staging stack, build BET, and push tree
|
||||
// onto the back of the top queue.
|
||||
//
|
||||
// To build the binary expression tree, for each type of operation we iterate
|
||||
// through the (Condition, operator) lists compacting where that operation
|
||||
// occurs into tree nodes.
|
||||
//
|
||||
// Staging stack is empty on initialization and should have exactly 1 node
|
||||
// (the root node) at the end of the string.
|
||||
r = "(" + r + ")"
|
||||
|
||||
min := func(a, b, c int) int { // Return smallest non-negative argument.
|
||||
if a > b { // Sort {a, b, c}
|
||||
a, b = b, a
|
||||
}
|
||||
if b > c {
|
||||
b, c = c, b
|
||||
}
|
||||
if a > b {
|
||||
a, b = b, a
|
||||
}
|
||||
|
||||
if a != -1 {
|
||||
return a
|
||||
} else if b != -1 {
|
||||
return b
|
||||
} else {
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
getNext := func(r string) (string, string) { // r -> (next, rest)
|
||||
r = strings.TrimSpace(r)
|
||||
|
||||
if r[0] == '(' || r[0] == ')' || r[0] == '&' || r[0] == '|' {
|
||||
return r[0:1], r[1:]
|
||||
}
|
||||
|
||||
nextOper := min(
|
||||
strings.Index(r, "&"),
|
||||
strings.Index(r, "|"),
|
||||
strings.Index(r, ")"),
|
||||
)
|
||||
|
||||
if nextOper == -1 {
|
||||
return r, ""
|
||||
}
|
||||
return strings.TrimSpace(r[0:nextOper]), r[nextOper:]
|
||||
}
|
||||
|
||||
staging := list.New() // Stack of (Condition list, operator list)
|
||||
indices := make(map[string]int, 0)
|
||||
|
||||
var nxt string
|
||||
for len(r) > 0 {
|
||||
nxt, r = getNext(r)
|
||||
|
||||
switch nxt {
|
||||
case "(":
|
||||
staging.PushFront([2]*list.List{list.New(), list.New()})
|
||||
case ")":
|
||||
top := staging.Remove(staging.Front()).([2]*list.List)
|
||||
if top[0].Len() != (top[1].Len() + 1) {
|
||||
return out, errors.New("Stacks are invalid size.")
|
||||
}
|
||||
|
||||
for typ := NodeAnd; typ <= NodeOr; typ++ {
|
||||
var step *list.Element
|
||||
leftOperand := top[0].Front()
|
||||
|
||||
for oper := top[1].Front(); oper != nil; oper = step {
|
||||
step = oper.Next()
|
||||
|
||||
if oper.Value.(NodeType) == typ {
|
||||
left := leftOperand.Value.(Condition)
|
||||
right := leftOperand.Next().Value.(Condition)
|
||||
|
||||
leftOperand.Next().Value = Raw{
|
||||
NodeType: typ,
|
||||
Left: &left,
|
||||
Right: &right,
|
||||
}
|
||||
|
||||
leftOperand = leftOperand.Next()
|
||||
|
||||
top[0].Remove(leftOperand.Prev())
|
||||
top[1].Remove(oper)
|
||||
} else {
|
||||
leftOperand = leftOperand.Next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if top[0].Len() != 1 || top[1].Len() != 0 {
|
||||
return out, errors.New("Invalid expression--couldn't evaluate.")
|
||||
}
|
||||
|
||||
if staging.Len() == 0 {
|
||||
if len(r) == 0 {
|
||||
return top[0].Front().Value.(Raw), nil
|
||||
}
|
||||
return out, errors.New("Invalid string--terminated early.")
|
||||
}
|
||||
staging.Front().Value.([2]*list.List)[0].PushBack(top[0].Front().Value)
|
||||
|
||||
case "&":
|
||||
staging.Front().Value.([2]*list.List)[1].PushBack(NodeAnd)
|
||||
case "|":
|
||||
staging.Front().Value.([2]*list.List)[1].PushBack(NodeOr)
|
||||
default:
|
||||
if _, there := indices[nxt]; !there {
|
||||
indices[nxt] = 0
|
||||
}
|
||||
|
||||
staging.Front().Value.([2]*list.List)[0].PushBack(String{nxt, indices[nxt]})
|
||||
indices[nxt]++
|
||||
}
|
||||
}
|
||||
|
||||
return out, errors.New("Invalid string--never terminated.")
|
||||
}
|
||||
|
||||
func (r Raw) String() string {
|
||||
out := ""
|
||||
|
||||
switch (*r.Left).(type) {
|
||||
case String:
|
||||
out += (*r.Left).(String).string
|
||||
default:
|
||||
out += "(" + (*r.Left).(Raw).String() + ")"
|
||||
}
|
||||
|
||||
if r.Type() == NodeAnd {
|
||||
out += " & "
|
||||
} else {
|
||||
out += " | "
|
||||
}
|
||||
|
||||
switch (*r.Right).(type) {
|
||||
case String:
|
||||
out += (*r.Right).(String).string
|
||||
default:
|
||||
out += "(" + (*r.Right).(Raw).String() + ")"
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (r Raw) Formatted() (out Formatted) {
|
||||
// Recursively maps a raw predicate to a formatted predicate by mapping AND
|
||||
// gates to (2, A, B) treshold gates and OR gates to (1, A, B) gates.
|
||||
if r.Type() == NodeAnd {
|
||||
out.Min = 2
|
||||
} else {
|
||||
out.Min = 1
|
||||
}
|
||||
|
||||
switch (*r.Left).(type) {
|
||||
case String:
|
||||
out.Conds = []Condition{(*r.Left).(String)}
|
||||
default:
|
||||
out.Conds = []Condition{(*r.Left).(Raw).Formatted()}
|
||||
}
|
||||
|
||||
switch (*r.Right).(type) {
|
||||
case String:
|
||||
out.Conds = append(out.Conds, (*r.Right).(String))
|
||||
default:
|
||||
out.Conds = append(out.Conds, (*r.Right).(Raw).Formatted())
|
||||
}
|
||||
|
||||
out.Compress() // Small amount of predicate compression.
|
||||
return
|
||||
}
|
||||
|
||||
func (r Raw) Ok(db *UserDatabase) bool {
|
||||
if r.Type() == NodeAnd {
|
||||
return (*r.Left).Ok(db) && (*r.Right).Ok(db)
|
||||
} else {
|
||||
return (*r.Left).Ok(db) || (*r.Right).Ok(db)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package msp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRaw(t *testing.T) {
|
||||
alice := Condition(String{"Alice", 0})
|
||||
bob := Condition(String{"Bob", 0})
|
||||
carl := Condition(String{"Carl", 0})
|
||||
|
||||
query1 := Raw{
|
||||
NodeType: NodeAnd,
|
||||
Left: &alice,
|
||||
Right: &bob,
|
||||
}
|
||||
|
||||
aliceOrBob := Condition(Raw{
|
||||
NodeType: NodeOr,
|
||||
Left: &alice,
|
||||
Right: &bob,
|
||||
})
|
||||
|
||||
query2 := Raw{
|
||||
NodeType: NodeAnd,
|
||||
Left: &aliceOrBob,
|
||||
Right: &carl,
|
||||
}
|
||||
|
||||
db := UserDatabase(Database(map[string][][]byte{
|
||||
"Alice": [][]byte{[]byte("blah")},
|
||||
"Carl": [][]byte{[]byte("herp")},
|
||||
}))
|
||||
|
||||
if query1.Ok(&db) != false {
|
||||
t.Fatalf("Query #1 was wrong.")
|
||||
}
|
||||
|
||||
if query2.Ok(&db) != true {
|
||||
t.Fatalf("Query #2 was wrong.")
|
||||
}
|
||||
|
||||
query1String := "Alice & Bob"
|
||||
query2String := "(Alice | Bob) & Carl"
|
||||
|
||||
if query1.String() != query1String {
|
||||
t.Fatalf("Query #1 String was wrong; %v", query1.String())
|
||||
}
|
||||
|
||||
if query2.String() != query2String {
|
||||
t.Fatalf("Query #2 String was wrong; %v", query2.String())
|
||||
}
|
||||
|
||||
decQuery1, err := StringToRaw(query1String)
|
||||
if err != nil || decQuery1.String() != query1String {
|
||||
t.Fatalf("Query #1 decoded wrong: %v %v", decQuery1.String(), err)
|
||||
}
|
||||
|
||||
decQuery2, err := StringToRaw(query2String)
|
||||
if err != nil || decQuery2.String() != query2String {
|
||||
t.Fatalf("Query #2 decoded wrong: %v %v", decQuery2.String(), err)
|
||||
}
|
||||
|
||||
formattedQuery1String := "(2, Alice, Bob)"
|
||||
formattedQuery2String := "(2, (1, Alice, Bob), Carl)"
|
||||
|
||||
if query1.Formatted().String() != formattedQuery1String {
|
||||
t.Fatalf("Query #1 formatted wrong: %v", query1.Formatted().String())
|
||||
}
|
||||
|
||||
if query2.Formatted().String() != formattedQuery2String {
|
||||
t.Fatalf("Query #2 formatted wrong: %v", query2.Formatted().String())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user