Merge pull request #57 from Bren2010/master

Support for Arbitrarily Complex Predicates
This commit is contained in:
Nick Sullivan
2015-11-24 14:39:30 -08:00
17 changed files with 1747 additions and 95 deletions

View File

@@ -110,7 +110,7 @@ Example query:
$ curl --cacert cert/server.crt https://localhost:8080/delegate \
-d '{"Name":"Dodo","Password":"Dodgson","Time":"2h34m","Uses":3}'
{"Status":"ok"}
### Create User
Create Users creates a new user account. Allows an optional "UserType"
@@ -174,6 +174,13 @@ Example query:
-d '{"Name":"Alice","Password":"Lewis","Minimum":2, "Owners":["Alice","Bill","Cat","Dodo"],"Data":"V2h5IGlzIGEgcmF2ZW4gbGlrZSBhIHdyaXRpbmcgZGVzaz8K"}'
{"Status":"ok","Response":"eyJWZXJzaW9uIj...NSSllzPSJ9"}
Example query with a predicate:
$ curl --cacert cert/server.crt https://localhost:8080/encrypt \
-d '{"Name":"Alice","Password":"Lewis","Predicate":"Alice & (Bob | Carl)",
Data":"V2h5IGlzIGEgcmF2ZW4gbGlrZSBhIHdyaXRpbmcgZGVzaz8K"}'
{"Status":"ok","Response":"eyJWZXJzaW9uIj...NSSllzPSJ9"}
The data expansion is not tied to the size of the input.
### Decrypt

View File

@@ -72,6 +72,7 @@ type EncryptRequest struct {
Owners []string
LeftOwners []string
RightOwners []string
Predicate string
Data []byte
@@ -124,8 +125,9 @@ type DecryptWithDelegates struct {
}
type OwnersData struct {
Status string
Owners []string
Status string
Owners []string
Predicate string
}
// Helper functions that create JSON responses sent by core
@@ -454,6 +456,7 @@ func Encrypt(jsonIn []byte) ([]byte, error) {
Names: s.Owners,
LeftNames: s.LeftOwners,
RightNames: s.RightOwners,
Predicate: s.Predicate,
}
resp, err := crypt.Encrypt(s.Data, s.Labels, access)
@@ -617,12 +620,12 @@ func Owners(jsonIn []byte) ([]byte, error) {
return jsonStatusError(err)
}
names, err := crypt.GetOwners(s.Data)
names, predicate, err := crypt.GetOwners(s.Data)
if err != nil {
return jsonStatusError(err)
}
return json.Marshal(OwnersData{Status: "ok", Owners: names})
return json.Marshal(OwnersData{Status: "ok", Owners: names, Predicate: predicate})
}
// Export returns a backed up vault.

View File

@@ -16,6 +16,7 @@ import (
"strconv"
"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"
@@ -38,12 +39,53 @@ func New(records *passvault.Records, cache *keycache.Cache) Cryptor {
// encrypted data. If len(Names) > 0, then at least 2 of the users in the list
// must be delegated to decrypt. If len(LeftNames) > 0 & len(RightNames) > 0,
// then at least one from each list must be delegated (if the same user is in
// both, then he can decrypt it alone).
// both, then he can decrypt it alone). If a predicate is present, it must be
// satisfied to decrypt.
type AccessStructure struct {
Names []string
LeftNames []string
RightNames []string
Predicate string
}
// Implements msp.UserDatabase
type UserDatabase struct {
names *[]string
records *passvault.Records
cache *keycache.Cache
user string
labels []string
keySet map[string]SingleWrappedKey
shareSet map[string][][]byte
}
func (u UserDatabase) ValidUser(name string) bool {
_, ok := u.records.GetRecord(name)
return ok
}
func (u UserDatabase) CanGetShare(name string) bool {
_, _, ok1 := u.cache.MatchUser(name, u.user, u.labels)
_, ok2 := u.shareSet[name]
_, ok3 := u.keySet[name]
return ok1 && ok2 && ok3
}
func (u UserDatabase) GetShare(name string) ([][]byte, error) {
*u.names = append(*u.names, name)
return u.cache.DecryptShares(
u.shareSet[name],
name,
u.user,
u.labels,
u.keySet[name].Key,
)
}
// MultiWrappedKey is a structure containing a 16-byte key encrypted
@@ -67,8 +109,10 @@ type EncryptedData struct {
Version int
VaultId int `json:",omitempty"`
Labels []string `json:",omitempty"`
Predicate string `json:",omitempty"`
KeySet []MultiWrappedKey `json:",omitempty"`
KeySetRSA map[string]SingleWrappedKey `json:",omitempty"`
ShareSet map[string][][]byte `json:",omitempty"`
IV []byte `json:",omitempty"`
Data []byte
Signature []byte
@@ -189,8 +233,7 @@ func (encrypted *EncryptedData) unlock(key []byte) (err error) {
return json.Unmarshal(encrypted.Data, encrypted)
}
// wrapKey encrypts the clear key such that a minimum number of delegated keys
// are required to decrypt. NOTE: Currently the max value for min is 2.
// wrapKey encrypts the clear key according to an access structure.
func (encrypted *EncryptedData) wrapKey(records *passvault.Records, clearKey []byte, access AccessStructure) (err error) {
generateRandomKey := func(name string) (singleWrappedKey SingleWrappedKey, err error) {
rec, ok := records.GetRecord(name)
@@ -259,8 +302,6 @@ func (encrypted *EncryptedData) wrapKey(records *passvault.Records, clearKey []b
encrypted.KeySet = append(encrypted.KeySet, out)
}
}
return nil
} else if len(access.LeftNames) > 0 && len(access.RightNames) > 0 {
// Generate a random AES key for each user and RSA/ECIES encrypt it
encrypted.KeySetRSA = make(map[string]SingleWrappedKey)
@@ -301,11 +342,44 @@ func (encrypted *EncryptedData) wrapKey(records *passvault.Records, clearKey []b
encrypted.KeySet = append(encrypted.KeySet, out)
}
}
} else if len(access.Predicate) > 0 {
encrypted.KeySetRSA = make(map[string]SingleWrappedKey)
return nil
sss, err := msp.StringToMSP(access.Predicate)
if err != nil {
return err
}
db := msp.UserDatabase(UserDatabase{records: records})
shareSet, err := sss.DistributeShares(clearKey, &db)
if err != nil {
return err
}
for name, _ := range shareSet {
encrypted.KeySetRSA[name], err = generateRandomKey(name)
if err != nil {
return err
}
crypt, err := aes.NewCipher(encrypted.KeySetRSA[name].aesKey)
if err != nil {
return err
}
for i, _ := range shareSet[name] {
tmp := make([]byte, 16)
crypt.Encrypt(tmp, shareSet[name][i])
shareSet[name][i] = tmp
}
}
encrypted.ShareSet = shareSet
encrypted.Predicate = access.Predicate
} else {
return errors.New("Invalid access structure.")
}
return nil
}
// unwrapKey decrypts first key in keys whose encryption keys are in keycache
@@ -316,50 +390,70 @@ func (encrypted *EncryptedData) unwrapKey(cache *keycache.Cache, user string) (u
nameSet = map[string]bool{}
)
for _, mwKey := range encrypted.KeySet {
// validate the size of the keys
if len(mwKey.Key) != 16 {
err = errors.New("Invalid Input")
if len(encrypted.Predicate) == 0 {
for _, mwKey := range encrypted.KeySet {
// validate the size of the keys
if len(mwKey.Key) != 16 {
err = errors.New("Invalid Input")
}
if err != nil {
return nil, nil, err
}
// loop through users to see if they are all delegated
fullMatch = true
for _, mwName := range mwKey.Name {
if valid := cache.Valid(mwName, user, encrypted.Labels); !valid {
fullMatch = false
break
}
nameSet[mwName] = true
}
// if the keys are delegated, decrypt the mwKey with them
if fullMatch == true {
tmpKeyValue := mwKey.Key
for _, mwName := range mwKey.Name {
pubEncrypted := encrypted.KeySetRSA[mwName]
if tmpKeyValue, keyFound = cache.DecryptKey(tmpKeyValue, mwName, user, encrypted.Labels, pubEncrypted.Key); keyFound != nil {
break
}
}
unwrappedKey = tmpKeyValue
break
}
}
if !fullMatch {
err = errors.New("Need more delegated keys")
names = nil
}
names = make([]string, 0, len(nameSet))
for name := range nameSet {
names = append(names, name)
}
return
} else {
var sss msp.MSP
sss, err = msp.StringToMSP(encrypted.Predicate)
if err != nil {
return nil, nil, err
}
// loop through users to see if they are all delegated
fullMatch = true
for _, mwName := range mwKey.Name {
if valid := cache.Valid(mwName, user, encrypted.Labels); !valid {
fullMatch = false
break
}
nameSet[mwName] = true
}
db := msp.UserDatabase(UserDatabase{
names: &names,
cache: cache,
user: user,
labels: encrypted.Labels,
keySet: encrypted.KeySetRSA,
shareSet: encrypted.ShareSet,
})
unwrappedKey, err = sss.RecoverSecret(&db)
// if the keys are delegated, decrypt the mwKey with them
if fullMatch == true {
tmpKeyValue := mwKey.Key
for _, mwName := range mwKey.Name {
pubEncrypted := encrypted.KeySetRSA[mwName]
if tmpKeyValue, keyFound = cache.DecryptKey(tmpKeyValue, mwName, user, encrypted.Labels, pubEncrypted.Key); keyFound != nil {
break
}
}
unwrappedKey = tmpKeyValue
break
}
return
}
if !fullMatch {
err = errors.New("Need more delegated keys")
names = nil
}
names = make([]string, 0, len(nameSet))
for name := range nameSet {
names = append(names, name)
}
return
}
// Encrypt encrypts data with the keys associated with names. This
@@ -474,7 +568,7 @@ func (c *Cryptor) Decrypt(in []byte, user string) (resp []byte, names []string,
// GetOwners returns the list of users that can delegate their passwords
// to decrypt the given encrypted secret.
func (c *Cryptor) GetOwners(in []byte) (names []string, err error) {
func (c *Cryptor) GetOwners(in []byte) (names []string, predicate string, err error) {
// unwrap encrypted file
var encrypted EncryptedData
if err = json.Unmarshal(in, &encrypted); err != nil {
@@ -512,7 +606,7 @@ func (c *Cryptor) GetOwners(in []byte) (names []string, err error) {
}
addedNames := make(map[string]bool)
for _, mwKey := range encrypted.KeySet {
for _, mwKey := range encrypted.KeySet { // names from the combinatorial method
for _, mwName := range mwKey.Name {
if !addedNames[mwName] {
names = append(names, mwName)
@@ -521,5 +615,14 @@ func (c *Cryptor) GetOwners(in []byte) (names []string, err error) {
}
}
for name, _ := range encrypted.ShareSet { // names from the secret splitting method
if !addedNames[name] {
names = append(names, name)
addedNames[name] = true
}
}
predicate = encrypted.Predicate
return
}

View File

@@ -22,13 +22,11 @@
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li><a href="#delegate">Delegate</a></li>
<li><a href="#admin">Admin</a></li>
<li><a href="#create">Create</a></li>
<li><a href="#summary">Summary</a></li>
<li><a href="#change-password">Change Password</a></li>
<li><a href="#modify-user">Modify User</a></li>
<li><a href="#admin">Admin</a></li>
<li><a href="#encrypt">Encrypt</a></li>
<li><a href="#decrypt">Decrypt</a></li>
<li><a href="#owners">Owners</a></li>
</ul>
</div>
</div>
@@ -80,31 +78,13 @@
<button type="submit" class="btn btn-primary">Delegate</button>
</form>
</div>
</section>
<hr />
<section class="row">
<div class="col-md-6">
<h3 id="admin">Create vault/admin</h3>
<form id="vault-create" class="form-inline ro-admin-create" role="form" action="/create" method="post">
<div class="feedback admin-feedback"></div>
<div class="form-group">
<label class="sr-only" for="admin-create-user">User name</label>
<input type="text" name="Name" class="form-control" id="admin-create-user" placeholder="User name" required />
</div>
<div class="form-group">
<label class="sr-only" for="admin-create-pass">Password</label>
<input type="password" name="Password" class="form-control" id="admin-create-pass" placeholder="Password" required />
</div>
<button type="submit" class="btn btn-primary">Create Admin</button>
</form>
<hr />
<h3 id="summary">User summary / delegation list</h3>
<div id="summary" class="col-md-6">
<h3>User summary / delegation list</h3>
<form id="vault-summary" class="form-inline ro-summary" role="form" action="/summary" method="post">
<div class="feedback summary-feedback"></div>
@@ -128,11 +108,32 @@
<ul class="list-group summary-all-users"></ul>
</div>
</div>
</section>
<div class="col-md-6">
<h3 id="create">Create</h3>
<hr />
<form id="user-create" class="ro-user-create" role="form" action="/delegate" method="post">
<section class="row">
<div class="col-md-6" id="admin">
<h3>Create vault</h3>
<form id="vault-create" class="form-inline ro-admin-create" role="form" action="/create" method="post">
<div class="feedback admin-feedback"></div>
<div class="form-group">
<label class="sr-only" for="admin-create-user">User name</label>
<input type="text" name="Name" class="form-control" id="admin-create-user" placeholder="User name" required />
</div>
<div class="form-group">
<label class="sr-only" for="admin-create-pass">Password</label>
<input type="password" name="Password" class="form-control" id="admin-create-pass" placeholder="Password" required />
</div>
<button type="submit" class="btn btn-primary">Create Admin</button>
</form>
<hr />
<h3>Create User</h3>
<form id="user-create" class="ro-user-create" role="form" action="/create-user" method="post">
<div class="feedback create-feedback"></div>
<div class="form-group row">
@@ -146,11 +147,12 @@
</div>
</div>
<div class="form-group row">
<div class="col-md-6">
<input type="hidden" name="Time" class="form-control" id="create-user-time" value="0h" required />
</div>
<div class="col-md-6">
<input type="hidden" name="Uses" class="form-control" id="create-uses" value="0" required />
<div class="col-md-12">
<label for="create-user-type">User Type</label>
<select name="UserType" class="form-control" id="create-user-type">
<option value="RSA">RSA</option>
<option value="ECC">ECC</option>
</select>
</div>
</div>
<button type="submit" class="btn btn-primary">Create</button>
@@ -183,9 +185,7 @@
</div>
<button type="submit" class="btn btn-primary">Change password</button>
</form>
</div>
<div id="modify" class="col-md-6">
<h3>Modify user</h3>
<form id="user-modify" class="ro-user-modify" role="form" action="/modify" method="post">
@@ -221,7 +221,7 @@
</section>
<hr />
<section class="row">
<div id="encrypt-data" class="col-md-6">
<div id="encrypt" class="col-md-6">
<h3>Encrypt data</h3>
<form id="encrypt" class="ro-user-encrypt" role="form" action="/encrypt" method="post">
@@ -240,11 +240,17 @@
<div class="form-group row">
<div class="col-md-6">
<label for="encrypt-minimum">Minimum number of users for access</label>
<input type="number" name="Minimum" class="form-control" id="encrypt-minimum" placeholder="2" required />
<input type="number" name="Minimum" class="form-control" id="encrypt-minimum" placeholder="2" />
</div>
<div class="col-md-6">
<label for="encrypt-owners">Owners <small>(comma separated users)</small></label>
<input type="text" name="Owners" class="form-control" id="encrypt-owners" placeholder="e.g., Carol, Bob" required />
<input type="text" name="Owners" class="form-control" id="encrypt-owners" placeholder="e.g., Carol, Bob" />
</div>
</div>
<div class="form-group row">
<div class="col-md-12">
<label for="encrypt-predicate">(OR) Predicate for decryption</label>
<input type="text" name="Predicate" class="form-control" id="encrypt-predicate" placeholder="(Alice | Bob) & Carol" />
</div>
</div>
<div class="form-group row">
@@ -260,7 +266,10 @@
<button type="submit" class="btn btn-primary">Encrypt!</button>
</form>
</div>
<div id="decrypt-data" class="col-md-6">
</section>
<hr />
<section class="row">
<div id="decrypt" class="col-md-6">
<h3>Decrypt data</h3>
<form id="decrypt" class="ro-user-decrypt" role="form" action="/decrypt" method="post">
@@ -284,6 +293,22 @@
</form>
</div>
</section>
<hr />
<section class="row">
<div id="owners" class="col-md-6">
<h3>Get owners</h3>
<form id="owners" class="ro-user-owners" role="form" action="/owners" method="post">
<div class="feedback owners-feedback"></div>
<div class="form-group">
<label for="owners-data">Data</label>
<textarea name="Data" class="form-control" id="owners-data" rows="5" required></textarea>
</div>
<button type="submit" class="btn btn-primary">Get Owners</button>
</form>
</div>
</section>
</div>
<footer id="footer" class="footer">
@@ -480,7 +505,7 @@
});
// Decrypt data
$('body').on('submit', '#decrypt', function(evt){
$('body').on('submit', 'form#decrypt', function(evt){
evt.preventDefault();
var $form = $(evt.currentTarget),
data = serialize($form);
@@ -493,6 +518,20 @@
}
});
});
// Get owners
$('body').on('submit', 'form#owners', function(evt){
evt.preventDefault();
var $form = $(evt.currentTarget),
data = serialize($form);
submit( $form, {
data : data,
success : function(d){
$form.find('.feedback').empty().append( makeAlert({ type: 'success', message: '<p>Owners: '+d.Owners.sort().join(', ')+(d.Predicate == '' ? '' : '<br />Predicate: '+d.Predicate)+'</p>' }) );
}
});
});
});
</script>
</body>

View File

@@ -112,9 +112,9 @@ func (cache *Cache) Valid(name, user string, labels []string) (present bool) {
return false
}
// matchUser returns the matching active user if present
// MatchUser returns the matching active user if present
// and a boolean to indicate its presence.
func (cache *Cache) matchUser(name, user string, labels []string) (ActiveUser, string, bool) {
func (cache *Cache) MatchUser(name, user string, labels []string) (ActiveUser, string, bool) {
var key ActiveUser
for d, key := range cache.UserKeys {
if d.Name != name {
@@ -131,7 +131,7 @@ func (cache *Cache) matchUser(name, user string, labels []string) (ActiveUser, s
// useKey decrements the counter on an active key
// for decryption or symmetric encryption
func (cache *Cache) useKey(name, user, slot string, labels []string) {
if val, slot, present := cache.matchUser(name, user, labels); present {
if val, slot, present := cache.MatchUser(name, user, labels); present {
val.Usage.Uses -= 1
cache.setUser(val, name, slot)
}
@@ -214,7 +214,7 @@ func (cache *Cache) AddKeyFromRecord(record passvault.PasswordRecord, name, pass
func (cache *Cache) DecryptKey(in []byte, name, user string, labels []string, pubEncryptedKey []byte) (out []byte, err error) {
cache.Refresh()
decryptKey, slot, ok := cache.matchUser(name, user, labels)
decryptKey, slot, ok := cache.MatchUser(name, user, labels)
if !ok {
return nil, errors.New("Key not delegated")
}
@@ -252,3 +252,52 @@ func (cache *Cache) DecryptKey(in []byte, name, user string, labels []string, pu
return
}
// DecryptShares decrypts an array of 16 byte shares using the key corresponding
// to the name parameter.
func (cache *Cache) DecryptShares(in [][]byte, name, user string, labels []string, pubEncryptedKey []byte) (out [][]byte, err error) {
cache.Refresh()
decryptKey, slot, ok := cache.MatchUser(name, user, labels)
if !ok {
return nil, errors.New("Key not delegated")
}
var aesKey []byte
// pick the aesKey to use for decryption
switch decryptKey.Type {
case passvault.RSARecord:
// extract the aes key from the pubEncryptedKey
aesKey, err = rsa.DecryptOAEP(sha1.New(), rand.Reader, &decryptKey.rsaKey, pubEncryptedKey, nil)
if err != nil {
return
}
case passvault.ECCRecord:
// extract the aes key from the pubEncryptedKey
aesKey, err = ecdh.Decrypt(decryptKey.eccKey, pubEncryptedKey)
if err != nil {
return
}
default:
return nil, errors.New("unknown type")
}
// decrypt
aesSession, err := aes.NewCipher(aesKey)
if err != nil {
return
}
for _, encShare := range in {
tmp := make([]byte, 16)
aesSession.Decrypt(tmp, encShare)
out = append(out, tmp)
}
cache.useKey(name, user, slot, labels)
return
}

134
msp/README.md Normal file
View File

@@ -0,0 +1,134 @@
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 (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
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.

189
msp/formatted.go Normal file
View File

@@ -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: Needs to begin and end with parentheses.")
}
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.(Name).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: Can't parse anymore, but there's still data. Too many closing parentheses or too few opening parentheses?")
}
staging.Front().Value.(*list.List).PushBack(built)
default:
if _, there := indices[nxt]; !there {
indices[nxt] = 0
}
staging.Front().Value.(*list.List).PushBack(Name{nxt, indices[nxt]})
indices[nxt]++
}
}
return out, errors.New("Invalid string: Not finished parsing, but out of data. Too many opening parentheses or too few closing parentheses?")
}
func (f Formatted) String() string {
out := fmt.Sprintf("(%v", f.Min)
for _, cond := range f.Conds {
switch cond.(type) {
case Name:
out += fmt.Sprintf(", %v", cond.(Name).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
}
}
}
}
}

89
msp/formatted_test.go Normal file
View File

@@ -0,0 +1,89 @@
package msp
import (
"testing"
)
func TestFormatted(t *testing.T) {
query1 := Formatted{
Min: 2,
Conds: []Condition{
Name{"Alice", 0}, Name{"Bob", 0}, Name{"Carl", 0},
},
}
query2 := Formatted{
Min: 3,
Conds: []Condition{
Name{"Alice", 0}, Name{"Bob", 0}, Name{"Carl", 0},
},
}
query3 := Formatted{
Min: 2,
Conds: []Condition{
Formatted{
Min: 1,
Conds: []Condition{
Name{"Alice", 0}, Name{"Bob", 0},
},
},
Name{"Carl", 0},
},
}
query4 := Formatted{
Min: 2,
Conds: []Condition{
Formatted{
Min: 1,
Conds: []Condition{
Name{"Alice", 0}, Name{"Carl", 0},
},
},
Name{"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)
}
}

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!")
}
}
}

295
msp/msp.go Normal file
View File

@@ -0,0 +1,295 @@
package msp
import (
"container/heap"
"crypto/rand"
"errors"
"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(name string) bool
GetShare(name string) ([][]byte, error)
}
type Condition interface { // Represents one condition in a predicate
Ok(*UserDatabase) bool
}
type Name struct { // Type of condition
string
index int
}
func (n Name) Ok(db *UserDatabase) bool {
return (*db).CanGetShare(n.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{} {
old := *ts
n := len(old)
*ts = old[0 : n-1]
out := old[n-1]
return out
}
// Compact takes a trace slice and merges all of its fields.
//
// index: Union of all locations in the slice.
// names: Union of all names in the slice.
// trace: Union of all the traces in the slice.
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...)
}
// This is a QuickSort related algorithm. It makes all the names in the trace unique so we don't double-count people.
//
// Invariant: There are no duplicates in trace[0:ptr]
// Algorithm: Advance ptr by 1 and enforce the invariant.
ptr, cutoff := 0, len(trace)
TopLoop:
for ptr < cutoff { // Choose the next un-checked element of the slice.
for i := 0; i < ptr; i++ { // Compare it to all elements before it.
if trace[i] == trace[ptr] { // If we find a duplicate...
trace[ptr], trace[cutoff-1] = trace[cutoff-1], trace[ptr] // Push the dup to the end of the surviving slice.
cutoff-- // Mark it for removal.
continue TopLoop // Because trace[ptr] has been mutated, try to verify the invariant again w/o advancing ptr.
}
}
ptr++ // There are no duplicates; move the ptr forward and start again.
}
trace = trace[0:cutoff]
return
}
type MSP Formatted
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
}
// DerivePath returns the cheapest way to satisfy the MSP (the one with the minimal number of delegations).
//
// ok: True if the MSP can be satisfied with current delegations; false if not.
// names: The names in the top-level threshold gate that need to be delegated.
// locs: The index in the treshold gate for each name.
// trace: All names that must be delegated for for this gate to be satisfied.
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 Name:
if (*db).CanGetShare(cond.(Name).string) {
heap.Push(ts, TraceElem{
i,
[]string{cond.(Name).string},
[]string{cond.(Name).string},
})
}
case Formatted:
sok, _, _, strace := MSP(cond.(Formatted)).DerivePath(db)
if sok {
heap.Push(ts, TraceElem{i, []string{}, strace})
}
}
if (*ts).Len() > m.Min { // If we can otherwise satisfy the threshold gate
heap.Pop(ts) // Drop the TraceElem with the heaviest trace (the one that requires the most delegations).
}
}
ok = (*ts).Len() >= m.Min
locs, names, trace = ts.Compact()
return
}
// 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)
// Generate a Vandermonde matrix.
height, width := len(m.Conds), m.Min
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)
}
}
// Convert secret vector.
s := NewRow(width)
s[0] = FieldElem(sec)
for i := 1; i < width; i++ {
r := NewFieldElem()
rand.Read(r)
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], share)
} else if (*db).ValidUser(name) {
out[name] = [][]byte{share}
} else {
return out, errors.New("Unknown user in predicate.")
}
default:
below := MSP(cond.(Formatted))
subOut, err := below.DistributeShares(share, 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
}
// 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(db, cache)
}
func (m MSP) recoverSecret(db *UserDatabase, cache map[string][][]byte) ([]byte, error) {
var (
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)
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 Name:
if len(cache[gate.(Name).string]) <= gate.(Name).index {
return nil, errors.New("Predicate / database mismatch!")
}
shares = append(shares, FieldElem(cache[gate.(Name).string][gate.(Name).index]))
case Formatted:
share, err := MSP(gate.(Formatted)).recoverSecret(db, cache)
if err != nil {
return nil, err
}
shares = append(shares, FieldElem(share))
}
}
// Generate the Vandermonde matrix specific to whichever users' shares we're using.
MSub := Matrix(make([]Row, m.Min))
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)
}
}
// 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
// recover the secret.
s := Row(shares).DotProduct(r)
return []byte(s), nil
}

72
msp/msp_test.go Normal file
View File

@@ -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, &db)
shares2, _ := predicate.DistributeShares(sec, &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(&db1)
if err != nil {
t.Fatalf("#1: %v", err)
}
sec2, err := predicate.RecoverSecret(&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)
}
}

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, xInv, yx, xy)
}
}

225
msp/raw.go Normal file
View File

@@ -0,0 +1,225 @@
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("Invalid string: There needs to be an operator (& or |) for every pair of operands.")
}
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 string: Couldn't evaluate all of the operators.")
}
if staging.Len() == 0 {
if len(r) == 0 {
res := top[0].Front().Value
switch res.(type) {
case Raw:
return res.(Raw), nil
default:
return out, errors.New("Invalid string: Only one condition was found.")
}
}
return out, errors.New("Invalid string: Can't parse anymore, but there's still data. Too many closing parentheses or too few opening parentheses?")
}
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(Name{nxt, indices[nxt]})
indices[nxt]++
}
}
return out, errors.New("Invalid string: Not finished parsing, but out of data. Too many opening parentheses or too few closing parentheses?")
}
func (r Raw) String() string {
out := ""
switch (*r.Left).(type) {
case Name:
out += (*r.Left).(Name).string
default:
out += "(" + (*r.Left).(Raw).String() + ")"
}
if r.Type() == NodeAnd {
out += " & "
} else {
out += " | "
}
switch (*r.Right).(type) {
case Name:
out += (*r.Right).(Name).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 Name:
out.Conds = []Condition{(*r.Left).(Name)}
default:
out.Conds = []Condition{(*r.Left).(Raw).Formatted()}
}
switch (*r.Right).(type) {
case Name:
out.Conds = append(out.Conds, (*r.Right).(Name))
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)
}
}

82
msp/raw_test.go Normal file
View File

@@ -0,0 +1,82 @@
package msp
import (
"testing"
)
func TestRaw(t *testing.T) {
alice := Condition(Name{"Alice", 0})
bob := Condition(Name{"Bob", 0})
carl := Condition(Name{"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())
}
}
func TestOneCondition(t *testing.T) {
_, err := StringToRaw("(Alice or Bob)")
if err == nil {
t.Fatalf("A predicate with only one condition should fail to parse!")
}
}

View File

@@ -513,11 +513,17 @@ var indexHtml = []byte(`<!DOCTYPE html>
<div class="form-group row">
<div class="col-md-6">
<label for="encrypt-minimum">Minimum number of users for access</label>
<input type="number" name="Minimum" class="form-control" id="encrypt-minimum" placeholder="2" required />
<input type="number" name="Minimum" class="form-control" id="encrypt-minimum" placeholder="2" />
</div>
<div class="col-md-6">
<label for="encrypt-owners">Owners <small>(comma separated users)</small></label>
<input type="text" name="Owners" class="form-control" id="encrypt-owners" placeholder="e.g., Carol, Bob" required />
<input type="text" name="Owners" class="form-control" id="encrypt-owners" placeholder="e.g., Carol, Bob" />
</div>
</div>
<div class="form-group row">
<div class="col-md-12">
<label for="encrypt-predicate">(OR) Predicate for decryption</label>
<input type="text" name="Predicate" class="form-control" id="encrypt-predicate" placeholder="(Alice | Bob) & Carol" />
</div>
</div>
<div class="form-group row">
@@ -795,7 +801,7 @@ var indexHtml = []byte(`<!DOCTYPE html>
submit( $form, {
data : data,
success : function(d){
$form.find('.feedback').empty().append( makeAlert({ type: 'success', message: '<p>Owners: '+d.Owners.sort().join(', ')+'</p>' }) );
$form.find('.feedback').empty().append( makeAlert({ type: 'success', message: '<p>Owners: '+d.Owners.sort().join(', ')+(d.Predicate == '' ? '' : '<br />Predicate: '+d.Predicate)+'</p>' }) );
}
});
});