mirror of
https://github.com/cloudflare/redoctober.git
synced 2026-04-23 17:50:51 +00:00
Merge pull request #57 from Bren2010/master
Support for Arbitrarily Complex Predicates
This commit is contained in:
@@ -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
|
||||
|
||||
11
core/core.go
11
core/core.go
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
117
index.html
117
index.html
@@ -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>
|
||||
|
||||
@@ -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
134
msp/README.md
Normal 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
189
msp/formatted.go
Normal 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
89
msp/formatted_test.go
Normal 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
149
msp/matrix.go
Normal 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
44
msp/matrix_test.go
Normal 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
295
msp/msp.go
Normal 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
72
msp/msp_test.go
Normal 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
116
msp/number.go
Normal 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
50
msp/number_test.go
Normal 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
225
msp/raw.go
Normal 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
82
msp/raw_test.go
Normal 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!")
|
||||
}
|
||||
}
|
||||
@@ -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>' }) );
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user