Commit 8e573885 authored by Sietse Ringers's avatar Sietse Ringers
Browse files

feat: support issuer key rollover by each key having its own revocation update update

parent 23ecc5de
...@@ -140,18 +140,7 @@ func (s *Server) CancelSession(token string) error { ...@@ -140,18 +140,7 @@ func (s *Server) CancelSession(token string) error {
} }
func (s *Server) Revoke(credid irma.CredentialTypeIdentifier, key string) error { func (s *Server) Revoke(credid irma.CredentialTypeIdentifier, key string) error {
sk, err := s.conf.IrmaConfiguration.PrivateKey(credid.IssuerIdentifier()) return s.conf.IrmaConfiguration.Revocation.Revoke(credid, key)
if err != nil {
return err
}
if sk == nil {
return errors.Errorf("cannot revoke: private key of %s not found", credid.IssuerIdentifier())
}
rsk, err := sk.RevocationKey()
if err != nil {
return err
}
return s.conf.IrmaConfiguration.Revocation.Revoke(credid, key, rsk)
} }
func ParsePath(path string) (token, noun string, arg []string, err error) { func ParsePath(path string) (token, noun string, arg []string, err error) {
...@@ -382,20 +371,31 @@ func (s *Server) handleClientMessage( ...@@ -382,20 +371,31 @@ func (s *Server) handleClientMessage(
func (s *Server) handleRevocationMessage( func (s *Server) handleRevocationMessage(
noun, method string, args []string, headers map[string][]string, message []byte, noun, method string, args []string, headers map[string][]string, message []byte,
) (int, []byte) { ) (int, []byte) {
if (noun == "updatefrom" || noun == "updatelatest") && method == http.MethodGet { if (noun == "updatefrom") && method == http.MethodGet {
if len(args) != 2 { if len(args) != 2 {
return server.BinaryResponse(nil, server.RemoteError(server.ErrorInvalidRequest, "GET "+noun+" expects 2 url arguments")) return server.BinaryResponse(nil, server.RemoteError(server.ErrorInvalidRequest, "GET updatefrom expects 3 url arguments"))
} }
i, err := strconv.ParseUint(args[1], 10, 64) i, err := strconv.ParseUint(args[1], 10, 64)
if err != nil { if err != nil {
return server.BinaryResponse(nil, server.RemoteError(server.ErrorMalformedInput, err.Error())) return server.BinaryResponse(nil, server.RemoteError(server.ErrorMalformedInput, err.Error()))
} }
pkindex, err := strconv.ParseUint(args[2], 10, 32)
if err != nil {
return server.BinaryResponse(nil, server.RemoteError(server.ErrorMalformedInput, err.Error()))
}
cred := irma.NewCredentialTypeIdentifier(args[0]) cred := irma.NewCredentialTypeIdentifier(args[0])
if noun == "updatefrom" { return server.BinaryResponse(s.handleGetUpdateFrom(cred, uint(pkindex), i))
return server.BinaryResponse(s.handleGetUpdateFrom(cred, i)) }
} else { if noun == "updatelatest" && method == http.MethodGet {
return server.BinaryResponse(s.handleGetUpdateLatest(cred, i)) if len(args) != 2 {
return server.BinaryResponse(nil, server.RemoteError(server.ErrorInvalidRequest, "GET updatelatest expects 2 url arguments"))
}
i, err := strconv.ParseUint(args[1], 10, 64)
if err != nil {
return server.BinaryResponse(nil, server.RemoteError(server.ErrorMalformedInput, err.Error()))
} }
cred := irma.NewCredentialTypeIdentifier(args[0])
return server.BinaryResponse(s.handleGetUpdateLatest(cred, i))
} }
if noun == "update" && method == http.MethodPost { if noun == "update" && method == http.MethodPost {
if len(args) != 1 { if len(args) != 1 {
......
...@@ -189,7 +189,7 @@ func (session *session) handlePostCommitments(commitments *irma.IssueCommitmentM ...@@ -189,7 +189,7 @@ func (session *session) handlePostCommitments(commitments *irma.IssueCommitmentM
for i, cred := range request.Credentials { for i, cred := range request.Credentials {
id := cred.CredentialTypeID.IssuerIdentifier() id := cred.CredentialTypeID.IssuerIdentifier()
pk, _ := session.conf.IrmaConfiguration.PublicKey(id, cred.KeyCounter) pk, _ := session.conf.IrmaConfiguration.PublicKey(id, cred.KeyCounter)
sk, _ := session.conf.IrmaConfiguration.PrivateKey(id) sk, _ := session.conf.IrmaConfiguration.PrivateKeyLatest(id)
issuer := gabi.NewIssuer(sk, pk, one) issuer := gabi.NewIssuer(sk, pk, one)
proof, ok := commitments.Proofs[i+discloseCount].(*gabi.ProofU) proof, ok := commitments.Proofs[i+discloseCount].(*gabi.ProofU)
if !ok { if !ok {
...@@ -223,15 +223,15 @@ func (s *Server) handlePostUpdate(typ irma.CredentialTypeIdentifier, update *rev ...@@ -223,15 +223,15 @@ func (s *Server) handlePostUpdate(typ irma.CredentialTypeIdentifier, update *rev
return nil, nil return nil, nil
} }
// GET revocation/updatefrom/{credtype}/{index} // GET revocation/updatefrom/{credtype}/{pkindex}/{index}
func (s *Server) handleGetUpdateFrom( func (s *Server) handleGetUpdateFrom(
cred irma.CredentialTypeIdentifier, index uint64, cred irma.CredentialTypeIdentifier, pkindex uint, index uint64,
) (*revocation.Update, *irma.RemoteError) { ) (*revocation.Update, *irma.RemoteError) {
if settings := s.conf.RevocationSettings[cred]; settings == nil || if settings := s.conf.RevocationSettings[cred]; settings == nil ||
!(settings.Mode == irma.RevocationModeProxy || settings.Mode == irma.RevocationModeServer) { !(settings.Mode == irma.RevocationModeProxy || settings.Mode == irma.RevocationModeServer) {
return nil, server.RemoteError(server.ErrorInvalidRequest, "not supported by this server") return nil, server.RemoteError(server.ErrorInvalidRequest, "not supported by this server")
} }
update, err := s.conf.IrmaConfiguration.Revocation.UpdateFrom(cred, index) update, err := s.conf.IrmaConfiguration.Revocation.UpdateFrom(cred, pkindex, index)
if err != nil { if err != nil {
return nil, server.RemoteError(server.ErrorUnknown, err.Error()) // TODO error type return nil, server.RemoteError(server.ErrorUnknown, err.Error()) // TODO error type
} }
...@@ -241,7 +241,7 @@ func (s *Server) handleGetUpdateFrom( ...@@ -241,7 +241,7 @@ func (s *Server) handleGetUpdateFrom(
// GET revocation/updatelatest/{credtype}/{count} // GET revocation/updatelatest/{credtype}/{count}
func (s *Server) handleGetUpdateLatest( func (s *Server) handleGetUpdateLatest(
cred irma.CredentialTypeIdentifier, count uint64, cred irma.CredentialTypeIdentifier, count uint64,
) (*revocation.Update, *irma.RemoteError) { ) (map[uint]*revocation.Update, *irma.RemoteError) {
if settings := s.conf.RevocationSettings[cred]; settings == nil || if settings := s.conf.RevocationSettings[cred]; settings == nil ||
!(settings.Mode == irma.RevocationModeProxy || settings.Mode == irma.RevocationModeServer) { !(settings.Mode == irma.RevocationModeProxy || settings.Mode == irma.RevocationModeServer) {
return nil, server.RemoteError(server.ErrorInvalidRequest, "not supported by this server") return nil, server.RemoteError(server.ErrorInvalidRequest, "not supported by this server")
......
...@@ -95,10 +95,14 @@ func (session *session) issuanceHandleRevocation( ...@@ -95,10 +95,14 @@ func (session *session) issuanceHandleRevocation(
// Fetch latest revocation record, and then extract the current value of the accumulator // Fetch latest revocation record, and then extract the current value of the accumulator
// from it to generate the witness from // from it to generate the witness from
u, err := rs.UpdateLatest(id, 0) updates, err := rs.UpdateLatest(id, 0)
if err != nil { if err != nil {
return return
} }
u := updates[uint(cred.KeyCounter)]
if u == nil {
return nil, nil, errors.Errorf("no revocation updates found for key %d", cred.KeyCounter)
}
sig := u.SignedAccumulator sig := u.SignedAccumulator
pk, err := rs.Keys.PublicKey(id.IssuerIdentifier(), sig.PKIndex) pk, err := rs.Keys.PublicKey(id.IssuerIdentifier(), sig.PKIndex)
if err != nil { if err != nil {
...@@ -117,6 +121,7 @@ func (session *session) issuanceHandleRevocation( ...@@ -117,6 +121,7 @@ func (session *session) issuanceHandleRevocation(
nonrevAttr = witness.E nonrevAttr = witness.E
issrecord := &irma.IssuanceRecord{ issrecord := &irma.IssuanceRecord{
CredType: id, CredType: id,
PKIndex: sk.Counter,
Key: cred.RevocationKey, Key: cred.RevocationKey,
Attr: (*irma.RevocationAttribute)(nonrevAttr), Attr: (*irma.RevocationAttribute)(nonrevAttr),
Issued: time.Now().UnixNano(), // or (floored) cred issuance time? Issued: time.Now().UnixNano(), // or (floored) cred issuance time?
...@@ -130,7 +135,7 @@ func (s *Server) validateIssuanceRequest(request *irma.IssuanceRequest) error { ...@@ -130,7 +135,7 @@ func (s *Server) validateIssuanceRequest(request *irma.IssuanceRequest) error {
for _, cred := range request.Credentials { for _, cred := range request.Credentials {
// Check that we have the appropriate private key // Check that we have the appropriate private key
iss := cred.CredentialTypeID.IssuerIdentifier() iss := cred.CredentialTypeID.IssuerIdentifier()
privatekey, err := s.conf.IrmaConfiguration.PrivateKey(iss) privatekey, err := s.conf.IrmaConfiguration.PrivateKeyLatest(iss)
if err != nil { if err != nil {
return err return err
} }
......
...@@ -59,10 +59,9 @@ func StartRevocationServer(t *testing.T) { ...@@ -59,10 +59,9 @@ func StartRevocationServer(t *testing.T) {
var err error var err error
irma.Logger = logger irma.Logger = logger
dbstr := "host=127.0.0.1 port=5432 user=testuser dbname=test password='testpassword' sslmode=disable"
dbtype := "postgres" //dbtype, dbstr := "postgres", "host=127.0.0.1 port=5432 user=testuser dbname=test password='testpassword' sslmode=disable"
//dbstr := "testuser:testpassword@tcp(127.0.0.1)/test" dbtype, dbstr := "mysql", "testuser:testpassword@tcp(127.0.0.1)/test"
//dbtype := "mysql"
cred := irma.NewCredentialTypeIdentifier("irma-demo.MijnOverheid.root") cred := irma.NewCredentialTypeIdentifier("irma-demo.MijnOverheid.root")
settings := map[irma.CredentialTypeIdentifier]*irma.RevocationSetting{ settings := map[irma.CredentialTypeIdentifier]*irma.RevocationSetting{
cred: {Mode: irma.RevocationModeServer}, cred: {Mode: irma.RevocationModeServer},
...@@ -97,7 +96,7 @@ func StartRevocationServer(t *testing.T) { ...@@ -97,7 +96,7 @@ func StartRevocationServer(t *testing.T) {
require.NoError(t, g.Close()) require.NoError(t, g.Close())
// Enable revocation for our credential type // Enable revocation for our credential type
sk, err := irmaconf.Revocation.Keys.PrivateKey(cred.IssuerIdentifier()) sk, err := irmaconf.Revocation.Keys.PrivateKeyLatest(cred.IssuerIdentifier())
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, irmaconf.Revocation.EnableRevocation(cred, sk)) require.NoError(t, irmaconf.Revocation.EnableRevocation(cred, sk))
......
package irmaclient package irmaclient
import ( import (
"github.com/go-errors/errors"
"github.com/privacybydesign/gabi" "github.com/privacybydesign/gabi"
"github.com/privacybydesign/gabi/revocation" "github.com/privacybydesign/gabi/revocation"
"github.com/privacybydesign/irmago" "github.com/privacybydesign/irmago"
...@@ -57,20 +58,28 @@ func (cred *credential) NonrevPrepare(conf *irma.Configuration, request irma.Ses ...@@ -57,20 +58,28 @@ func (cred *credential) NonrevPrepare(conf *irma.Configuration, request irma.Ses
} }
// first try to update witness by applying the revocation update messages attached to the session request // first try to update witness by applying the revocation update messages attached to the session request
keys := irma.RevocationKeys{Conf: conf} var (
revupdates := base.RevocationUpdates[credtype] keys = irma.RevocationKeys{Conf: conf}
count := len(revupdates.Events) revupdates = base.RevocationUpdates[credtype][cred.Pk.Counter]
updated, err := cred.NonrevApplyUpdates(revupdates, keys) 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, keys)
if err != nil { if err != nil {
return updated, err return updated, err
} }
count := len(revupdates.Events)
if cred.NonRevocationWitness.Accumulator.Index >= revupdates.Events[count-1].Index { if cred.NonRevocationWitness.Accumulator.Index >= revupdates.Events[count-1].Index {
return updated, nil return updated, nil
} }
// nonrevocation witness is still out of date after applying the updates from the request: // nonrevocation witness is still out of date after applying the updates from the request:
// we were too far behind. Update from revocation server. // we were too far behind. Update from revocation server.
revupdates, err = irma.RevocationClient{Conf: conf}.FetchUpdateFrom(credtype, cred.NonRevocationWitness.Accumulator.Index+1) revupdates, err = irma.RevocationClient{Conf: conf}.
FetchUpdateFrom(credtype, cred.Pk.Counter, cred.NonRevocationWitness.Accumulator.Index+1)
if err != nil { if err != nil {
return updated, err return updated, err
} }
......
...@@ -48,7 +48,7 @@ type Configuration struct { ...@@ -48,7 +48,7 @@ type Configuration struct {
// Issuer private keys. If set (after calling ParseFolder()), will use these keys // Issuer private keys. If set (after calling ParseFolder()), will use these keys
// instead of keys in irma_configuration/$issuer/PrivateKeys. // instead of keys in irma_configuration/$issuer/PrivateKeys.
PrivateKeys map[IssuerIdentifier]*gabi.PrivateKey PrivateKeys map[IssuerIdentifier]map[int]*gabi.PrivateKey
Revocation *RevocationStorage Revocation *RevocationStorage
...@@ -166,7 +166,7 @@ func (conf *Configuration) clear() { ...@@ -166,7 +166,7 @@ func (conf *Configuration) clear() {
conf.DisabledSchemeManagers = make(map[SchemeManagerIdentifier]*SchemeManagerError) conf.DisabledSchemeManagers = make(map[SchemeManagerIdentifier]*SchemeManagerError)
conf.kssPublicKeys = make(map[SchemeManagerIdentifier]map[int]*rsa.PublicKey) conf.kssPublicKeys = make(map[SchemeManagerIdentifier]map[int]*rsa.PublicKey)
conf.publicKeys = make(map[IssuerIdentifier]map[int]*gabi.PublicKey) conf.publicKeys = make(map[IssuerIdentifier]map[int]*gabi.PublicKey)
conf.PrivateKeys = make(map[IssuerIdentifier]*gabi.PrivateKey) conf.PrivateKeys = make(map[IssuerIdentifier]map[int]*gabi.PrivateKey)
conf.reverseHashes = make(map[string]CredentialTypeIdentifier) conf.reverseHashes = make(map[string]CredentialTypeIdentifier)
} }
...@@ -320,47 +320,42 @@ func (conf *Configuration) ParseSchemeManagerFolder(dir string, manager *SchemeM ...@@ -320,47 +320,42 @@ func (conf *Configuration) ParseSchemeManagerFolder(dir string, manager *SchemeM
return return
} }
// PrivateKey returns the latest private key of the specified issuer, or nil if not present in the Configuration. // PrivateKey returns the specified private key of the specified issuer if present; an error otherwise.
func (conf *Configuration) PrivateKey(id IssuerIdentifier) (*gabi.PrivateKey, error) { func (conf *Configuration) PrivateKey(id IssuerIdentifier, counter int) (*gabi.PrivateKey, error) {
if sk := conf.PrivateKeys[id]; sk != nil { if _, haveIssuer := conf.PrivateKeys[id]; haveIssuer {
return sk, nil if sk := conf.PrivateKeys[id][counter]; sk != nil {
return sk, nil
}
} }
path := fmt.Sprintf(privkeyPattern, conf.Path, id.SchemeManagerIdentifier().Name(), id.Name()) path := fmt.Sprintf(privkeyPattern, conf.Path, id.SchemeManagerIdentifier().Name(), id.Name())
files, err := filepath.Glob(path) file := strings.Replace(path, "*", strconv.Itoa(counter), 1)
sk, err := gabi.NewPrivateKeyFromFile(file)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(files) == 0 { if int(sk.Counter) != counter {
return nil, nil return nil, errors.Errorf("Private key %s of issuer %s has wrong <Counter>", file, id.String())
} }
// List private keys and get highest counter if conf.PrivateKeys[id] == nil {
counters := make([]int, 0, len(files)) conf.PrivateKeys[id] = make(map[int]*gabi.PrivateKey)
for _, file := range files {
filename := filepath.Base(file)
count := filename[:len(filename)-4]
i, err := strconv.Atoi(count)
if err != nil {
return nil, err
}
counters = append(counters, i)
} }
sort.Ints(counters) conf.PrivateKeys[id][counter] = sk
counter := counters[len(counters)-1]
// Read private key return sk, nil
file := strings.Replace(path, "*", strconv.Itoa(counter), 1) }
sk, err := gabi.NewPrivateKeyFromFile(file)
// PrivateKeyLatest returns the latest private key of the specified issuer, or nil if not present in the Configuration.
func (conf *Configuration) PrivateKeyLatest(id IssuerIdentifier) (*gabi.PrivateKey, error) {
indices, err := conf.PrivateKeyIndices(id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if int(sk.Counter) != counter { if len(indices) == 0 {
return nil, errors.Errorf("Private key %s of issuer %s has wrong <Counter>", file, id.String()) return nil, errors.New("no private keys found")
} }
conf.PrivateKeys[id] = sk return conf.PrivateKey(id, indices[len(indices)-1])
return sk, nil
} }
// PublicKey returns the specified public key, or nil if not present in the Configuration. // PublicKey returns the specified public key, or nil if not present in the Configuration.
...@@ -535,6 +530,10 @@ func (conf *Configuration) parseKeysFolder(issuerid IssuerIdentifier) error { ...@@ -535,6 +530,10 @@ func (conf *Configuration) parseKeysFolder(issuerid IssuerIdentifier) error {
return nil return nil
} }
func (conf *Configuration) PrivateKeyIndices(issuerid IssuerIdentifier) (i []int, err error) {
return conf.matchKeyPattern(issuerid, privkeyPattern)
}
func (conf *Configuration) PublicKeyIndices(issuerid IssuerIdentifier) (i []int, err error) { func (conf *Configuration) PublicKeyIndices(issuerid IssuerIdentifier) (i []int, err error) {
return conf.matchKeyPattern(issuerid, pubkeyPattern) return conf.matchKeyPattern(issuerid, pubkeyPattern)
} }
......
...@@ -39,7 +39,7 @@ type BaseRequest struct { ...@@ -39,7 +39,7 @@ type BaseRequest struct {
Revocation []CredentialTypeIdentifier `json:"revocation,omitempty"` Revocation []CredentialTypeIdentifier `json:"revocation,omitempty"`
// RevocationUpdates contains revocation update messages for the client to update its // RevocationUpdates contains revocation update messages for the client to update its
// revocation state. // revocation state.
RevocationUpdates map[CredentialTypeIdentifier]*revocation.Update `json:"revocationUpdates,omitempty"` RevocationUpdates map[CredentialTypeIdentifier]map[uint]*revocation.Update `json:"revocationUpdates,omitempty"`
ids *IrmaIdentifierSet // cache for Identifiers() method ids *IrmaIdentifierSet // cache for Identifiers() method
...@@ -237,7 +237,7 @@ func (b *BaseRequest) GetNonce(*atum.Timestamp) *big.Int { ...@@ -237,7 +237,7 @@ func (b *BaseRequest) GetNonce(*atum.Timestamp) *big.Int {
// RequestsRevocation indicates whether or not the requestor requires a nonrevocation proof for // RequestsRevocation indicates whether or not the requestor requires a nonrevocation proof for
// the given credential type; that is, whether or not it included revocation update messages. // the given credential type; that is, whether or not it included revocation update messages.
func (b *BaseRequest) RequestsRevocation(id CredentialTypeIdentifier) bool { func (b *BaseRequest) RequestsRevocation(id CredentialTypeIdentifier) bool {
return len(b.RevocationUpdates) > 0 && len(b.RevocationUpdates[id].Events) > 0 return len(b.RevocationUpdates) > 0 && len(b.RevocationUpdates[id]) > 0
} }
func (b *BaseRequest) RevocationConsistent() error { func (b *BaseRequest) RevocationConsistent() error {
......
...@@ -74,12 +74,13 @@ type ( ...@@ -74,12 +74,13 @@ type (
AccumulatorRecord struct { AccumulatorRecord struct {
CredType CredentialTypeIdentifier `gorm:"primary_key"` CredType CredentialTypeIdentifier `gorm:"primary_key"`
Data signedMessage Data signedMessage
PKIndex uint PKIndex uint `gorm:"primary_key;auto_increment:false"`
} }
EventRecord struct { EventRecord struct {
Index uint64 `gorm:"primary_key;column:eventindex"` Index uint64 `gorm:"primary_key;column:eventindex"`
CredType CredentialTypeIdentifier `gorm:"primary_key"` CredType CredentialTypeIdentifier `gorm:"primary_key"`
PKIndex uint `gorm:"primary_key;auto_increment:false"`
E *RevocationAttribute E *RevocationAttribute
ParentHash eventHash ParentHash eventHash
} }
...@@ -88,6 +89,7 @@ type ( ...@@ -88,6 +89,7 @@ type (
IssuanceRecord struct { IssuanceRecord struct {
Key string `gorm:"primary_key;column:revocationkey"` Key string `gorm:"primary_key;column:revocationkey"`
CredType CredentialTypeIdentifier `gorm:"primary_key"` CredType CredentialTypeIdentifier `gorm:"primary_key"`
PKIndex uint
Attr *RevocationAttribute Attr *RevocationAttribute
Issued int64 Issued int64
ValidUntil int64 ValidUntil int64
...@@ -136,12 +138,12 @@ const ( ...@@ -136,12 +138,12 @@ const (
// only way to create such an initial accumulator and it must be called before anyone can use // only way to create such an initial accumulator and it must be called before anyone can use
// revocation for this credential type. Requires the issuer private key. // revocation for this credential type. Requires the issuer private key.
func (rs *RevocationStorage) EnableRevocation(typ CredentialTypeIdentifier, sk *revocation.PrivateKey) error { func (rs *RevocationStorage) EnableRevocation(typ CredentialTypeIdentifier, sk *revocation.PrivateKey) error {
hasRecords, err := rs.db.HasRecords(typ, (*EventRecord)(nil)) enabled, err := rs.RevocationEnabled(typ)
if err != nil { if err != nil {
return err return err
} }
if hasRecords { if enabled {
return errors.New("revocation event record table not empty") return errors.New("revocation already enabled")
} }
update, err := revocation.NewAccumulator(sk) update, err := revocation.NewAccumulator(sk)
...@@ -159,7 +161,7 @@ func (rs *RevocationStorage) EnableRevocation(typ CredentialTypeIdentifier, sk * ...@@ -159,7 +161,7 @@ func (rs *RevocationStorage) EnableRevocation(typ CredentialTypeIdentifier, sk *
// by checking if any revocation record exists in the database. // by checking if any revocation record exists in the database.
func (rs *RevocationStorage) RevocationEnabled(typ CredentialTypeIdentifier) (bool, error) { func (rs *RevocationStorage) RevocationEnabled(typ CredentialTypeIdentifier) (bool, error) {
if rs.sqlMode { if rs.sqlMode {
return rs.db.HasRecords(typ, (*EventRecord)(nil)) return rs.db.Exists((*EventRecord)(nil), nil)
} else { } else {
return rs.memdb.HasRecords(typ), nil return rs.memdb.HasRecords(typ), nil
} }
...@@ -170,16 +172,16 @@ func (rs *RevocationStorage) RevocationEnabled(typ CredentialTypeIdentifier) (bo ...@@ -170,16 +172,16 @@ func (rs *RevocationStorage) RevocationEnabled(typ CredentialTypeIdentifier) (bo
// UpdateFrom returns all records that a client requires to update its revocation state if it is currently // UpdateFrom returns all records that a client requires to update its revocation state if it is currently
// at the specified index, that is, all records whose end index is greater than or equal to // at the specified index, that is, all records whose end index is greater than or equal to
// the specified index. // the specified index.
func (rs *RevocationStorage) UpdateFrom(typ CredentialTypeIdentifier, index uint64) (*revocation.Update, error) { func (rs *RevocationStorage) UpdateFrom(typ CredentialTypeIdentifier, pkindex uint, index uint64) (*revocation.Update, error) {
// Only requires SQL implementation // Only requires SQL implementation
var update *revocation.Update var update *revocation.Update
if err := rs.db.Transaction(func(tx revStorage) error { if err := rs.db.Transaction(func(tx revStorage) error {
acc, _, err := rs.currentAccumulator(tx, typ) acc, _, err := rs.accumulator(tx, typ, pkindex)
if err != nil { if err != nil {
return err return err
} }
var events []*EventRecord var events []*EventRecord
if err := tx.From(typ, "index", index, &events); err != nil { if err := tx.From(&events, "cred_type = ? and pk_index = ? and index >= ?", typ, pkindex, index); err != nil {
return err return err
} }
update = rs.newUpdate(acc, events) update = rs.newUpdate(acc, events)
...@@ -190,20 +192,22 @@ func (rs *RevocationStorage) UpdateFrom(typ CredentialTypeIdentifier, index uint ...@@ -190,20 +192,22 @@ func (rs *RevocationStorage) UpdateFrom(typ CredentialTypeIdentifier, index uint
return update, nil return update, nil
} }
func (rs *RevocationStorage) UpdateLatest(typ CredentialTypeIdentifier, count uint64) (*revocation.Update, error) { func (rs *RevocationStorage) UpdateLatest(typ CredentialTypeIdentifier, count uint64) (map[uint]*revocation.Update, error) {
// TODO what should this function and UpdateFrom return when no records are found? // TODO what should this function and UpdateFrom return when no records are found?
if rs.sqlMode { if rs.sqlMode {
var update *revocation.Update var update map[uint]*revocation.Update
if err := rs.db.Transaction(func(tx revStorage) error { if err := rs.db.Transaction(func(tx revStorage) error {
acc, _, err := rs.currentAccumulator(tx, typ) var (
if err != nil { records []*AccumulatorRecord
events []*EventRecord
)
if err := tx.Last(&records, map[string]interface{}{"cred_type": typ}); err != nil {
return err return err
} }
var events []*EventRecord if err := tx.Latest(&events, count, map[string]interface{}{"cred_type": typ}); err != nil {
if err := tx.Latest(typ, "eventindex", count, &events); err != nil {
return err return err
} }
update = rs.newUpdate(acc, events) update = rs.newUpdates(records, events)
return nil return nil
}); err != nil { }); err != nil {
return nil, err return nil, err
...@@ -214,7 +218,28 @@ func (rs *RevocationStorage) UpdateLatest(typ CredentialTypeIdentifier, count ui ...@@ -214,7 +218,28 @@ func (rs *RevocationStorage) UpdateLatest(typ CredentialTypeIdentifier, count ui
} }
} }
func (*RevocationStorage) newUpdate(acc *revocation.SignedAccumulator, events []*EventRecord) *revocation.Update { func (*RevocationStorage) newUpdates(records []*AccumulatorRecord, events []*EventRecord) map[uint]*revocation.Update {
accs := map[uint]*revocation.SignedAccumulator{}
for _, r := range records {
accs[r.PKIndex] = r.SignedAccumulator()
}
updates := make(map[uint]*revocation.Update, len(accs))
for _, e := range events {
i := e.PKIndex
if accs[i] == nil {
continue
}
update, present := updates[i]
if !present {
update = &revocation.Update{SignedAccumulator: accs[i]}
updates[i] = update
}
update.Events = append(update.Events, e.Event())
}
return updates
}
func (rs *RevocationStorage) newUpdate(acc *revocation.SignedAccumulator, events []*EventRecord) *revocation.Update {
updates := make([]*revocation.Event, len(events)) updates := make([]*revocation.Event, len(events))
for i := range events { for i := range events {
updates[i] = events[i].Event()