Commit 6af7c6ab authored by Sietse Ringers's avatar Sietse Ringers

feat: client reuses revocation updates for all its credential instances of the same type and key

parent bbc1c562
......@@ -10,6 +10,7 @@ import (
"github.com/go-errors/errors"
"github.com/privacybydesign/gabi"
"github.com/privacybydesign/gabi/big"
"github.com/privacybydesign/gabi/revocation"
"github.com/privacybydesign/irmago"
"github.com/privacybydesign/irmago/internal/fs"
"github.com/privacybydesign/keyproof/common"
......@@ -192,7 +193,7 @@ func New(
}
client.jobs = make(chan func(), 100)
client.startRevocation()
client.initRevocation()
client.StartJobs()
return client, schemeMgrErr
......@@ -283,7 +284,7 @@ func (client *Client) addCredential(cred *credential) (err error) {
index := -1
for _, attrlistlist := range client.attributes {
for i, attrs := range attrlistlist {
if attrs.Hash() == cred.AttributeList().Hash() {
if attrs.Hash() == cred.attrs.Hash() {
index = i
break
}
......@@ -307,7 +308,7 @@ func (client *Client) addCredential(cred *credential) (err error) {
}
for i := len(client.attrs(id)) - 1; i >= 0; i-- { // Go backwards through array because remove manipulates it
if client.attrs(id)[i].EqualsExceptMetadata(cred.AttributeList()) {
if client.attrs(id)[i].EqualsExceptMetadata(cred.attrs) {
if err = client.remove(id, i, false); err != nil {
return
}
......@@ -316,7 +317,7 @@ func (client *Client) addCredential(cred *credential) (err error) {
}
// Append the new cred to our attributes and credentials
client.attributes[id] = append(client.attrs(id), cred.AttributeList())
client.attributes[id] = append(client.attrs(id), cred.attrs)
if !id.Empty() {
if _, exists := client.credentialsCache[id]; !exists {
client.credentialsCache[id] = make(map[int]*credential)
......@@ -523,7 +524,7 @@ func (client *Client) credential(id irma.CredentialTypeIdentifier, counter int)
Signature: sig,
NonRevocationWitness: witness,
Pk: pk,
}, client.Configuration)
}, attrs, client.Configuration)
if err != nil {
return nil, err
}
......@@ -772,6 +773,9 @@ func (client *Client) ProofBuilders(choice *irma.DisclosureChoice, request irma.
if err != nil {
return nil, nil, nil, err
}
if cred.attrs.Revoked {
return nil, nil, nil, revocation.ErrorRevoked
}
nonrev := request.Base().RequestsRevocation(cred.CredentialType().Identifier())
builder, err = cred.CreateDisclosureProofBuilder(grp.attrs, nonrev)
if err != nil {
......@@ -895,7 +899,8 @@ func (client *Client) ConstructCredentials(msg []*gabi.IssueSignatureMessage, re
}
for _, gabicred := range gabicreds {
newcred, err := newCredential(gabicred, client.Configuration)
attrs := irma.NewAttributeListFromInts(gabicred.Attributes[1:], client.Configuration)
newcred, err := newCredential(gabicred, attrs, client.Configuration)
if err != nil {
return err
}
......
......@@ -13,7 +13,7 @@ type credential struct {
attrs *irma.AttributeList
}
func newCredential(gabicred *gabi.Credential, conf *irma.Configuration) (*credential, error) {
func newCredential(gabicred *gabi.Credential, attrs *irma.AttributeList, conf *irma.Configuration) (*credential, error) {
meta := irma.MetadataFromInt(gabicred.Attributes[1], conf)
cred := &credential{
Credential: gabicred,
......@@ -30,12 +30,6 @@ func newCredential(gabicred *gabi.Credential, conf *irma.Configuration) (*creden
if err != nil {
return nil, err
}
cred.attrs = attrs
return cred, nil
}
func (cred *credential) AttributeList() *irma.AttributeList {
if cred.attrs == nil {
cred.attrs = irma.NewAttributeListFromInts(cred.Credential.Attributes[1:], cred.MetadataAttribute.Conf)
}
return cred.attrs
}
......@@ -243,7 +243,7 @@ func TestCredentialRemoval(t *testing.T) {
cred, err := client.credential(id, 0)
require.NoError(t, err)
require.NotNil(t, cred)
err = client.RemoveCredentialByHash(cred.AttributeList().Hash())
err = client.RemoveCredentialByHash(cred.attrs.Hash())
require.NoError(t, err)
cred, err = client.credential(id, 0)
require.NoError(t, err)
......
......@@ -7,7 +7,6 @@ import (
"math"
"time"
"github.com/go-errors/errors"
"github.com/privacybydesign/gabi/revocation"
irma "github.com/privacybydesign/irmago"
"github.com/sirupsen/logrus"
......@@ -15,17 +14,17 @@ import (
const nonrevUpdateInterval = 10 // Once every 10 seconds
func (client *Client) startRevocation() {
func (client *Client) initRevocation() {
// For every credential supporting revocation, compute nonrevocation caches in async jobs
for credid, attrsets := range client.attributes {
for typ, attrsets := range client.attributes {
for i, attrs := range attrsets {
if attrs.CredentialType() == nil || !attrs.CredentialType().SupportsRevocation() {
continue
}
credid := credid // make copy of same name to capture the value for closure below
i := i // see https://golang.org/doc/faq#closures_and_goroutines
typ := typ // make copy of same name to capture the value for closure below
i := i // see https://golang.org/doc/faq#closures_and_goroutines
client.jobs <- func() {
if err := client.nonrevCredPrepareCache(credid, i); err != nil {
if err := client.nonrevPrepareCache(typ, i); err != nil {
client.reportError(err)
}
}
......@@ -40,12 +39,12 @@ func (client *Client) startRevocation() {
// We do this by every 10 seconds updating the credential with a low probability, which
// increases over time since the last update.
client.Configuration.Scheduler.Every(nonrevUpdateInterval).Seconds().Do(func() {
for credid, attrsets := range client.attributes {
for typ, attrsets := range client.attributes {
for i, attrs := range attrsets {
if !attrs.CredentialType().SupportsRevocation() {
if attrs.CredentialType() == nil || !attrs.CredentialType().SupportsRevocation() {
continue
}
cred, err := client.credential(credid, i)
cred, err := client.credential(typ, i)
if err != nil {
client.reportError(err)
continue
......@@ -59,19 +58,12 @@ func (client *Client) startRevocation() {
break
}
if r < probability(cred.NonRevocationWitness.Updated) {
irma.Logger.Debugf("scheduling nonrevocation witness remote update for %s-%d", credid, i)
irma.Logger.Debugf("scheduling nonrevocation witness remote update for %s-%d", typ, i)
client.jobs <- func() {
updated, err := cred.NonrevUpdateFromServer(client.Configuration)
if err != nil {
if err = client.nonrevUpdateFromServer(typ); err != nil {
client.reportError(err)
return
}
if updated {
if err = client.nonrevCredPrepareCache(credid, i); err != nil {
client.reportError(err)
return
}
}
}
}
}
......@@ -79,157 +71,161 @@ func (client *Client) startRevocation() {
})
}
// probability returns a float between 0 and asymptote, representing a probability
// that asymptotically increases to the asymptote, reaching
// a reference probability at a reference index.
func probability(lastUpdate time.Time) float64 {
const (
asymptote = 1.0 / 3 // max probability
refindex = 7 * 60 * 60 * 24 // Week
refprobability = 0.75 * asymptote // probability after one week
)
f := math.Tan(math.Pi * refprobability / (2 * asymptote))
i := time.Now().Sub(lastUpdate).Seconds()
return 2 * asymptote / math.Pi * math.Atan(i/refindex*f)
}
// randomfloat between 0 and 1
func randomfloat() (float64, error) {
b := make([]byte, 4)
_, err := rand.Read(b)
if err != nil {
fmt.Println("error:", err)
return 0, err
// nonrevPrepare updates the revocation state for each credential in the request
// requiring a nonrevocation proof, using the updates included in the request, or the remote
// revocation server if those do not suffice.
func (client *Client) nonrevPrepare(request irma.SessionRequest) error {
base := request.Base()
if err := base.RevocationConsistent(); err != nil {
return err
}
c := float64(binary.BigEndian.Uint32(b)) / float64(^uint32(0)) // random int / max int
return c, nil
}
func (client *Client) nonrevCredPrepareCache(credid irma.CredentialTypeIdentifier, index int) error {
irma.Logger.WithFields(logrus.Fields{"credid": credid, "index": index}).Debug("Preparing cache")
cred, err := client.credential(credid, index)
if err != nil {
return err
for typ := range request.Disclosure().Identifiers().CredentialTypes {
credtype := client.Configuration.CredentialTypes[typ]
if !credtype.SupportsRevocation() {
continue
}
if !base.RequestsRevocation(typ) {
continue
}
if err := client.nonrevUpdate(typ, base.RevocationUpdates[typ]); err != nil {
return err
}
}
return cred.NonrevPrepareCache()
return nil
}
// NonrevPrepare updates the revocation state for each credential in the request
// requiring a nonrevocation proof, using the updates included in the request, or the remote
// revocation server if those do not suffice.
func (client *Client) NonrevPreprare(request irma.SessionRequest) error {
var err error
var cred *credential
var updated bool
for id := range request.Disclosure().Identifiers().CredentialTypes {
typ := client.Configuration.CredentialTypes[id]
if !typ.SupportsRevocation() {
// nonrevUpdate updates all contained instances of the specified type, using the specified
// updates if present and if they suffice, and contacting the issuer's server to download updates
// otherwise.
func (client *Client) nonrevUpdate(typ irma.CredentialTypeIdentifier, updates map[uint]*revocation.Update) error {
lowest := map[uint]uint64{}
attrs := client.attrs(typ)
// Per credential and issuer key counter we may posess multiple credential instances.
// Of the nonrevocation witnesses of these, take the lowest index.
for i := 0; i < len(attrs); i++ {
cred, err := client.credential(typ, i)
if err != nil {
return err
}
if cred.NonRevocationWitness == nil {
continue
}
attrs := client.attrs(id)
for i := 0; i < len(attrs); i++ {
if cred, err = client.credential(id, i); err != nil {
return err
}
if updated, err = cred.NonrevPrepare(client.Configuration, request); err != nil {
if err == revocation.ErrorRevoked {
attrs[i].Revoked = true
cred.AttributeList().Revoked = true
if serr := client.storage.StoreAttributes(client.attributes); serr != nil {
client.reportError(serr)
return err
}
client.handler.Revoked(&irma.CredentialIdentifier{
Type: cred.CredentialType().Identifier(),
Hash: cred.AttributeList().Hash(),
})
}
pkid := cred.Pk.Counter
_, present := lowest[pkid]
if !present || cred.NonRevocationWitness.Accumulator.Index < lowest[pkid] {
lowest[pkid] = cred.NonRevocationWitness.Accumulator.Index
}
}
// For each key counter, get an update message starting at the lowest index computed above,
// that can update all of our credential instance of the given type and key counter,
// using the specified update messags if they suffice, or the issuer's server otherwise.
u := map[uint]*revocation.Update{}
for counter, l := range lowest {
update := updates[counter]
if update != nil && len(update.Events) > 0 && update.Events[0].Index <= l {
u[counter] = update
} else {
var err error
u[counter], err = irma.RevocationClient{Conf: client.Configuration}.
FetchUpdateFrom(typ, counter, l+1)
if err != nil {
return err
}
if updated {
if err = client.storage.StoreSignature(cred); err != nil {
return err
}
}
}
}
// Apply the update messages to all instances of the given type and key counter
for counter, update := range u {
if err := client.nonrevApplyUpdates(typ, counter, update); err != nil {
return err
}
}
return nil
}
// nonrevRepopulateCaches repopulates the consumed nonrevocation caches of the credentials involved
// in the request, in background jobs, after the request has finished.
func (client *Client) nonrevRepopulateCaches(request irma.SessionRequest) {
for id := range request.Disclosure().Identifiers().CredentialTypes {
typ := client.Configuration.CredentialTypes[id]
if !typ.SupportsRevocation() {
func (client *Client) nonrevApplyUpdates(typ irma.CredentialTypeIdentifier, counter uint, update *revocation.Update) error {
attrs := client.attrs(typ)
var save bool
for i := 0; i < len(attrs); i++ {
cred, err := client.credential(typ, i)
if err != nil {
return err
}
if cred.NonRevocationWitness == nil || cred.Pk.Counter != counter {
continue
}
for i, attrs := range client.attrs(id) {
if attrs.CredentialType() == nil || !attrs.CredentialType().SupportsRevocation() {
continue
}
id := id
i := i
client.jobs <- func() {
if err := client.nonrevCredPrepareCache(id, i); err != nil {
client.reportError(err)
}
}
updated, err := cred.nonrevApplyUpdates(update, irma.RevocationKeys{Conf: client.Configuration})
if updated {
save = true
}
if err == revocation.ErrorRevoked {
attrs[i].Revoked = true
cred.attrs.Revoked = true
save = true
client.handler.Revoked(&irma.CredentialIdentifier{
Type: cred.CredentialType().Identifier(),
Hash: cred.attrs.Hash(),
})
// Even if this credential is revoked during a session, we may have
// other instances that can satisfy the request. So don't return an
// error which would halt the session.
continue
}
if err != nil {
return err
}
}
}
// NonrevPrepare attempts to update the credential's nonrevocation witness from
// 1) the session request, and then 2) the revocation server if our witness is too far out of date.
// Returns whether or not the credential's nonrevocation state was updated. If so the caller should
// persist the updated credential to storage.
func (cred *credential) NonrevPrepare(conf *irma.Configuration, request irma.SessionRequest) (bool, error) {
credtype := cred.CredentialType().Identifier()
base := request.Base()
if !base.RequestsRevocation(credtype) {
return false, nil
if save {
if err := client.storage.StoreAttributes(client.attributes); err != nil {
client.reportError(err)
return err
}
}
return nil
}
if err := base.RevocationConsistent(); err != nil {
return false, err
func (client *Client) nonrevUpdateFromServer(typ irma.CredentialTypeIdentifier) error {
if err := client.nonrevUpdate(typ, map[uint]*revocation.Update{}); err != nil {
return err
}
return nil
}
// first try to update witness by applying the revocation update messages attached to the session request
var (
revupdates = base.RevocationUpdates[credtype][cred.Pk.Counter]
updated bool
err error
)
if revupdates == nil {
return false, errors.Errorf("revocation updates for key %d not found in session request", cred.Pk.Counter)
}
updated, err = cred.NonrevApplyUpdates(revupdates, irma.RevocationKeys{Conf: conf})
func (client *Client) nonrevPrepareCache(typ irma.CredentialTypeIdentifier, index int) error {
irma.Logger.WithFields(logrus.Fields{"credtype": typ, "index": index}).Debug("Preparing cache")
cred, err := client.credential(typ, index)
if err != nil {
return updated, err
}
count := len(revupdates.Events)
if cred.NonRevocationWitness.Accumulator.Index >= revupdates.Events[count-1].Index {
return updated, nil
return err
}
// nonrevocation witness is still out of date after applying the updates from the request:
// we were too far behind. Update from revocation server.
return cred.NonrevUpdateFromServer(conf)
return cred.NonrevPrepareCache()
}
func (cred *credential) NonrevUpdateFromServer(conf *irma.Configuration) (bool, error) {
credtype := cred.CredentialType().Identifier()
revupdates, err := irma.RevocationClient{Conf: conf}.
FetchUpdateFrom(credtype, cred.Pk.Counter, cred.NonRevocationWitness.Accumulator.Index+1)
if err != nil {
return false, err
// nonrevRepopulateCaches repopulates the consumed nonrevocation caches of the credentials involved
// in the request, in background jobs, after the request has finished.
func (client *Client) nonrevRepopulateCaches(request irma.SessionRequest) {
for typ := range request.Disclosure().Identifiers().CredentialTypes {
credtype := client.Configuration.CredentialTypes[typ]
if !credtype.SupportsRevocation() {
continue
}
for i := range client.attrs(typ) {
typ := typ
i := i
client.jobs <- func() {
if err := client.nonrevPrepareCache(typ, i); err != nil {
client.reportError(err)
}
}
}
}
return cred.NonrevApplyUpdates(revupdates, irma.RevocationKeys{Conf: conf})
}
// NonrevApplyUpdates updates the credential's nonrevocation witness using the specified messages,
// nonrevApplyUpdates updates the credential's nonrevocation witness using the specified messages,
// if they all verify and if their indices are ahead and adjacent to that of our witness.
func (cred *credential) NonrevApplyUpdates(update *revocation.Update, keys irma.RevocationKeys) (bool, error) {
func (cred *credential) nonrevApplyUpdates(update *revocation.Update, keys irma.RevocationKeys) (bool, error) {
oldindex := cred.NonRevocationWitness.Accumulator.Index
pk, err := keys.PublicKey(cred.CredentialType().IssuerIdentifier(), update.SignedAccumulator.PKCounter)
......@@ -242,3 +238,29 @@ func (cred *credential) NonrevApplyUpdates(update *revocation.Update, keys irma.
return cred.NonRevocationWitness.Accumulator.Index != oldindex, nil
}
// probability returns a float between 0 and asymptote, representing a probability
// that asymptotically increases to the asymptote, reaching
// a reference probability at a reference index.
func probability(lastUpdate time.Time) float64 {
const (
asymptote = 1.0 / 3 // max probability
refindex = 7 * 60 * 60 * 24 // Week
refprobability = 0.75 * asymptote // probability after one week
)
f := math.Tan(math.Pi * refprobability / (2 * asymptote))
i := time.Now().Sub(lastUpdate).Seconds()
return 2 * asymptote / math.Pi * math.Atan(i/refindex*f)
}
// randomfloat between 0 and 1
func randomfloat() (float64, error) {
b := make([]byte, 4)
_, err := rand.Read(b)
if err != nil {
fmt.Println("error:", err)
return 0, err
}
c := float64(binary.BigEndian.Uint32(b)) / float64(^uint32(0)) // random int / max int
return c, nil
}
......@@ -313,7 +313,7 @@ func (session *session) processSessionInfo() {
// Prepare and update all revocation state asynchroniously while the user makes her choices
go func() {
session.prepRevocation <- session.client.NonrevPreprare(session.request)
session.prepRevocation <- session.client.nonrevPrepare(session.request)
}()
// Ask for permission to execute the session
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment