Commit ea64823a authored by Sietse Ringers's avatar Sietse Ringers
Browse files

Allow scheme manager to remotely add new attributes to credential types

Attributes may now also remotely be made optional.
parent 0231a02f
......@@ -62,6 +62,22 @@ type CredentialType struct {
Valid bool `xml:"-"`
}
// AttributeDescription is a description of an attribute within a credential type.
type AttributeDescription struct {
ID string `xml:"id,attr"`
Optional string `xml:"optional,attr"`
Name TranslatedString
Description TranslatedString
}
func (ad AttributeDescription) GetAttributeTypeIdentifier(cred CredentialTypeIdentifier) AttributeTypeIdentifier {
return NewAttributeTypeIdentifier(cred.String() + "." + ad.ID)
}
func (ad AttributeDescription) IsOptional() bool {
return ad.Optional == "true"
}
// ContainsAttribute tests whether the specified attribute is contained in this
// credentialtype.
func (ct *CredentialType) ContainsAttribute(ai AttributeTypeIdentifier) bool {
......@@ -76,18 +92,6 @@ func (ct *CredentialType) ContainsAttribute(ai AttributeTypeIdentifier) bool {
return false
}
// AttributeDescription is a description of an attribute within a credential type.
type AttributeDescription struct {
ID string `xml:"id,attr"`
Optional string `xml:"optional,attr"`
Name TranslatedString
Description TranslatedString
}
func (ad AttributeDescription) GetAttributeTypeIdentifier(cred CredentialTypeIdentifier) AttributeTypeIdentifier {
return NewAttributeTypeIdentifier(cred.String() + "." + ad.ID)
}
// IndexOf returns the index of the specified attribute if present,
// or an error (and -1) if not present.
func (ct CredentialType) IndexOf(ai AttributeTypeIdentifier) (int, error) {
......@@ -102,6 +106,14 @@ func (ct CredentialType) IndexOf(ai AttributeTypeIdentifier) (int, error) {
return -1, errors.New("Attribute identifier not found")
}
func (ct CredentialType) AttributeDescription(ai AttributeTypeIdentifier) *AttributeDescription {
i, _ := ct.IndexOf(ai)
if i == -1 {
return nil
}
return &ct.Attributes[i]
}
// TranslatedString is a map of translated strings.
type TranslatedString map[string]string
......
......@@ -4,8 +4,10 @@ import (
"encoding/json"
"errors"
"math/big"
"net/http"
"os"
"testing"
"time"
"github.com/mhe/gabi"
"github.com/privacybydesign/irmago"
......@@ -15,10 +17,19 @@ import (
)
func TestMain(m *testing.M) {
// Create HTTP server for scheme managers
server := &http.Server{Addr: ":48681", Handler: http.FileServer(http.Dir("../testdata"))}
go func() {
server.ListenAndServe()
}()
time.Sleep(100 * time.Millisecond) // Give server time to start
test.ClearTestStorage(nil)
test.CreateTestStorage(nil)
retCode := m.Run()
test.ClearTestStorage(nil)
server.Close()
os.Exit(retCode)
}
......@@ -330,6 +341,58 @@ func TestWrongSchemeManager(t *testing.T) {
test.ClearTestStorage(t)
}
func TestDisclosureNewAttributeUpdateSchemeManager(t *testing.T) {
client := parseStorage(t)
schemeid := irma.NewSchemeManagerIdentifier("irma-demo")
credid := irma.NewCredentialTypeIdentifier("irma-demo.RU.studentCard")
attrid := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.newAttribute")
require.False(t, client.Configuration.CredentialTypes[credid].ContainsAttribute(attrid))
client.Configuration.SchemeManagers[schemeid].URL = "http://localhost:48681/irma_configuration_updated/irma-demo"
disclosureRequest := irma.DisclosureRequest{
Content: irma.AttributeDisjunctionList{
&irma.AttributeDisjunction{
Label: "foo",
Attributes: []irma.AttributeTypeIdentifier{
attrid,
},
},
},
}
client.Configuration.Download(&disclosureRequest)
require.True(t, client.Configuration.CredentialTypes[credid].ContainsAttribute(attrid))
}
func TestIssueNewAttributeUpdateSchemeManager(t *testing.T) {
client := parseStorage(t)
schemeid := irma.NewSchemeManagerIdentifier("irma-demo")
credid := irma.NewCredentialTypeIdentifier("irma-demo.RU.studentCard")
attrid := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.newAttribute")
require.False(t, client.Configuration.CredentialTypes[credid].ContainsAttribute(attrid))
client.Configuration.SchemeManagers[schemeid].URL = "http://localhost:48681/irma_configuration_updated/irma-demo"
issuanceRequest := getIssuanceRequest(true)
issuanceRequest.Credentials[0].Attributes["newAttribute"] = "foobar"
client.Configuration.Download(issuanceRequest)
require.True(t, client.Configuration.CredentialTypes[credid].ContainsAttribute(attrid))
}
func TestIssueOptionalAttributeUpdateSchemeManager(t *testing.T) {
client := parseStorage(t)
schemeid := irma.NewSchemeManagerIdentifier("irma-demo")
credid := irma.NewCredentialTypeIdentifier("irma-demo.RU.studentCard")
attrid := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.level")
require.False(t, client.Configuration.CredentialTypes[credid].AttributeDescription(attrid).IsOptional())
client.Configuration.SchemeManagers[schemeid].URL = "http://localhost:48681/irma_configuration_updated/irma-demo"
issuanceRequest := getIssuanceRequest(true)
delete(issuanceRequest.Credentials[0].Attributes, "level")
client.Configuration.Download(issuanceRequest)
require.True(t, client.Configuration.CredentialTypes[credid].AttributeDescription(attrid).IsOptional())
}
// Test installing a new scheme manager from a qr, and do a(n issuance) session
// within this manager to test the autmatic downloading of credential definitions,
// issuers, and public keys.
......@@ -345,7 +408,7 @@ func TestDownloadSchemeManager(t *testing.T) {
// Do an add-scheme-manager-session
qr := &irma.Qr{
Type: irma.ActionSchemeManager,
URL: "https://raw.githubusercontent.com/credentials/irma-demo-schememanager/master",
URL: "http://localhost:48681/irma_configuration/irma-demo",
}
c := make(chan *irma.SessionError)
client.NewSession(qr, TestHandler{t, c, client})
......
......@@ -208,7 +208,7 @@ func (session *session) checkAndUpateConfiguration() bool {
}
// Download missing credential types/issuers/public keys from the scheme manager
downloaded, err := session.client.Configuration.Download(session.irmaSession.Identifiers())
downloaded, err := session.client.Configuration.Download(session.irmaSession)
if err != nil {
session.fail(&irma.SessionError{ErrorType: irma.ErrorConfigurationDownload, Err: err})
return false
......
......@@ -553,17 +553,6 @@ func (conf *Configuration) DownloadSchemeManagerSignature(manager *SchemeManager
index := filepath.Join(path, "index")
sig := filepath.Join(path, "index.sig")
// Backup so we can restore last valid signature if the new signature is invalid
if err := conf.backupManagerSignature(index, sig); err != nil {
return err
}
defer func() {
if err != nil {
_ = conf.restoreManagerSignature(index, sig)
}
}()
if err = t.GetFile("index", index); err != nil {
return
}
......@@ -580,69 +569,110 @@ func (conf *Configuration) DownloadSchemeManagerSignature(manager *SchemeManager
return
}
func (conf *Configuration) backupManagerSignature(index, sig string) error {
if err := fs.Copy(index, index+".backup"); err != nil {
return err
}
if err := fs.Copy(sig, sig+".backup"); err != nil {
return err
}
return nil
}
func (conf *Configuration) restoreManagerSignature(index, sig string) error {
if err := fs.Copy(index+".backup", index); err != nil {
return err
}
if err := fs.Copy(sig+".backup", sig); err != nil {
return err
}
return nil
}
// Download downloads the issuers, credential types and public keys specified in set
// if the current Configuration does not already have them, and checks their authenticity
// using the scheme manager index.
func (conf *Configuration) Download(set *IrmaIdentifierSet) (downloaded *IrmaIdentifierSet, err error) {
func (conf *Configuration) Download(session IrmaSession) (downloaded *IrmaIdentifierSet, err error) {
managers := make(map[string]struct{}) // Managers that we must update
downloaded = &IrmaIdentifierSet{
SchemeManagers: map[SchemeManagerIdentifier]struct{}{},
Issuers: map[IssuerIdentifier]struct{}{},
CredentialTypes: map[CredentialTypeIdentifier]struct{}{},
}
managers := make(map[SchemeManagerIdentifier]struct{})
// Calculate which scheme managers must be updated
if err = conf.checkIssuers(session.Identifiers(), managers); err != nil {
return
}
if err = conf.checkCredentialTypes(session, managers); err != nil {
return
}
// Update the scheme managers found above and parse them, if necessary
for id := range managers {
if err = conf.UpdateSchemeManager(NewSchemeManagerIdentifier(id), downloaded); err != nil {
return
}
}
if !downloaded.Empty() {
return downloaded, conf.ParseFolder()
}
return
}
func (conf *Configuration) checkCredentialTypes(session IrmaSession, managers map[string]struct{}) error {
var disjunctions AttributeDisjunctionList
var typ *CredentialType
var contains bool
switch s := session.(type) {
case *IssuanceRequest:
for _, credreq := range s.Credentials {
// First check if we have this credential type
typ, contains = conf.CredentialTypes[*credreq.CredentialTypeID]
if !contains {
managers[credreq.CredentialTypeID.Root()] = struct{}{}
continue
}
newAttrs := make(map[string]string)
for k, v := range credreq.Attributes {
newAttrs[k] = v
}
// For each of the attributes in the credentialtype, see if it is present; if so remove it from newAttrs
// If not, check that it is optional; if not the credentialtype must be updated
for _, attrtyp := range typ.Attributes {
_, contains = newAttrs[attrtyp.ID]
if !contains && !attrtyp.IsOptional() {
managers[credreq.CredentialTypeID.Root()] = struct{}{}
break
}
delete(newAttrs, attrtyp.ID)
}
// If there is anything left in newAttrs, then these are attributes that are not in the credentialtype
if len(newAttrs) > 0 {
managers[credreq.CredentialTypeID.Root()] = struct{}{}
}
}
disjunctions = s.Disclose
case *DisclosureRequest:
disjunctions = s.Content
case *SignatureRequest:
disjunctions = s.Content
}
for _, disjunction := range disjunctions {
for _, attrid := range disjunction.Attributes {
credid := attrid.CredentialTypeIdentifier()
if typ, contains = conf.CredentialTypes[credid]; !contains {
managers[credid.Root()] = struct{}{}
}
if !typ.ContainsAttribute(attrid) {
managers[credid.Root()] = struct{}{}
}
}
}
return nil
}
func (conf *Configuration) checkIssuers(set *IrmaIdentifierSet, managers map[string]struct{}) error {
for issid := range set.Issuers {
if _, contains := conf.Issuers[issid]; !contains {
managers[issid.SchemeManagerIdentifier()] = struct{}{}
managers[issid.Root()] = struct{}{}
}
}
for issid, keyids := range set.PublicKeys {
for _, keyid := range keyids {
pk, err := conf.PublicKey(issid, keyid)
if err != nil {
return nil, err
return err
}
if pk == nil {
managers[issid.SchemeManagerIdentifier()] = struct{}{}
managers[issid.Root()] = struct{}{}
}
}
}
for credid := range set.CredentialTypes {
if _, contains := conf.CredentialTypes[credid]; !contains {
managers[credid.IssuerIdentifier().SchemeManagerIdentifier()] = struct{}{}
}
}
for id := range managers {
if err = conf.UpdateSchemeManager(id, downloaded); err != nil {
return
}
}
if !downloaded.Empty() {
return downloaded, conf.ParseFolder()
}
return
return nil
}
func (i SchemeManagerIndex) String() string {
......@@ -818,6 +848,20 @@ func (conf *Configuration) UpdateSchemeManager(id SchemeManagerIdentifier, downl
return errors.Errorf("Cannot update unknown scheme manager %s", id)
}
// Check remote timestamp and see if we have to do anything
transport := NewHTTPTransport(manager.URL + "/")
timestampBts, err := transport.GetBytes("timestamp")
if err != nil {
return err
}
timestamp, err := parseTimestamp(timestampBts)
if err != nil {
return err
}
if !manager.Timestamp.Before(timestamp) {
return nil
}
// Download the new index and its signature, and check that the new index
// is validly signed by the new signature
// By aborting immediately in case of error, and restoring backup versions
......@@ -833,7 +877,6 @@ func (conf *Configuration) UpdateSchemeManager(id SchemeManagerIdentifier, downl
issPattern := regexp.MustCompile("(.+)/(.+)/description\\.xml")
credPattern := regexp.MustCompile("(.+)/(.+)/Issues/(.+)/description\\.xml")
transport := NewHTTPTransport("")
// TODO: how to recover/fix local copy if err != nil below?
for filename, newHash := range newIndex {
......@@ -853,7 +896,7 @@ func (conf *Configuration) UpdateSchemeManager(id SchemeManagerIdentifier, downl
}
stripped := filename[len(manager.ID)+1:] // Scheme manager URL already ends with its name
// Download the new file, store it in our own irma_configuration folder
if err = transport.GetFile(manager.URL+"/"+stripped, path); err != nil {
if err = transport.GetSignedFile(stripped, path, newHash); err != nil {
return
}
// See if the file is a credential type or issuer, and add it to the downloaded set if so
......
......@@ -385,8 +385,15 @@ func readTimestamp(path string) (Timestamp, error) {
return Timestamp(time.Unix(0, 0)), errors.New("Could not read scheme manager timestamp")
}
return parseTimestamp(bts)
}
func parseTimestamp(bts []byte) (Timestamp, error) {
// Remove final character which is \n; convert from byte slice to string; parse as int
str, err := strconv.ParseInt(string(bts[:len(bts)-1]), 10, 64)
if err != nil {
return Timestamp(time.Unix(0, 0)), err
}
return Timestamp(time.Unix(str, 0)), nil
}
......
......@@ -9,7 +9,6 @@ import (
"github.com/spf13/cobra"
)
// TODO: add flag to update timestamp of irma_configuration folder
var updateCmd = &cobra.Command{
Use: "update path...",
Short: "[Experimental] Update a scheme manager",
......
<SchemeManager version="7">
<Id>irma-demo</Id>
<Url>https://privacybydesign.foundation/schememanager/irma-demo</Url>
<Url>http://localhost:48681/irma_configuration/irma-demo</Url>
<Name>
<en>Irma Demo</en>
<nl>Irma Demo</nl>
......
......@@ -10,5 +10,5 @@ ffa2349b0a638132837c767df12c7b15cbac85f648c614a0811b3edb751d0a6d irma-demo/RU/Pu
e298a2e6dca3bdb923d22734dc4f76ba7b48c5364eb8d7b60e6ec4e940921f89 irma-demo/RU/PublicKeys/2.xml
a4f6cc35cace3e9dc9388b29a8756ea83e5884f799d75cadd4efa60e1a12d855 irma-demo/RU/description.xml
35697bb7ffb19518a0ac6739ac3eef6b0272cd322c4619b075328b88c06ac43d irma-demo/RU/logo.png
8dc2ac6f2b697599d788e6580afbeacb1f3d090ba7ba2188c58f165e0da6c776 irma-demo/description.xml
499936dc1a45c3dbe10934be949cd2db180159d3cd72ed916c5145aeec8c7341 irma-demo/timestamp
fb1a7b3ef863e87487ee21541d586e2ff4458afa0b87c81bdd9d97c2cb576d71 irma-demo/description.xml
0af68532917c987aa1aebfa01b191e6c31e7fb769b9563a53400c89624b387ce irma-demo/timestamp
<SchemeManager version="7">
<Id>test</Id>
<Url>https://privacybydesign.foundation/</Url>
<Url>http://localhost:48681/irma_configuration/test</Url>
<Name>
<en>Test Scheme Manager</en>
<nl>Test scheme manager</nl>
......
c01fb73d8b9f3ae1e530f6335bbb1857d67d5022c5c25e5188f0dd9e0f688707 test/description.xml
684bf07929aa5227c65318ee3573e175c56ffe6c8dba817fadcbf3517952493a test/description.xml
61aa81ab57c7e4812955d77e00aeb0afefa4c88a90bee5cd75fe9aaea9d4effb test/test/Issues/email/description.xml
61a1fc7f161e43f8fc5b0c6ac2997cfe6bc0da7d27009b9914a04dca79ec6718 test/test/Issues/email/logo.png
32de560adea94257989b4d8fd688304919d365b13b5f4ad83114d21f6f728ddc test/test/Issues/mijnirma/description.xml
......@@ -9,4 +9,4 @@ c01fb73d8b9f3ae1e530f6335bbb1857d67d5022c5c25e5188f0dd9e0f688707 test/descriptio
a7f792bd702d6d97fd34d4104ddcf56bb3a0995e77fb36784099d4bd05c2df27 test/test/PublicKeys/3.xml
adc18a59954caeb907b999ab09b710c652665a0b150e5e4a7aff55199a4908dd test/test/description.xml
48f04181af6874a2f63f97d0a1a79b95f274da3e4d0efd9e5936b0ec0858b1cc test/test/logo.png
2337d5b392bd77582daf165994df54b95567d0357c859579c534b87b7c5aaf1c test/timestamp
cb69bfa41d3cc84331d83e1a9e6738b924176bc9f026174c34fb34685af72a65 test/timestamp
<IssueSpecification version="4">
<Name>
<en>Name</en>
<nl>Naam</nl>
</Name>
<ShortName>
<en>Name</en>
<nl>Naam</nl>
</ShortName>
<SchemeManager>irma-demo</SchemeManager>
<IssuerID>MijnOverheid</IssuerID>
<CredentialID>fullName</CredentialID>
<Description>
<en>Your full name, as it is known to the government</en>
<nl>Uw volledige naam, zoals bekend bij de overheid</nl>
</Description>
<ShouldBeSingleton>true</ShouldBeSingleton>
<Attributes>
<Attribute id="firstnames">
<Name>
<en>First names</en>
<nl>Voornamen</nl>
</Name>
<Description>
<en>All of your first names</en>
<nl>Al uw voornamen</nl>
</Description>
</Attribute>
<Attribute id="firstname">
<Name>
<en>First name</en>
<nl>Voornaam</nl>
</Name>
<Description>
<en>Your first name</en>
<nl>Uw voornaam</nl>
</Description>
</Attribute>
<Attribute id="familyname">
<Name>
<en>Family name</en>
<nl>Achternaam</nl>
</Name>
<Description>
<en>Your family name</en>
<nl>Uw achternaam</nl>
</Description>
</Attribute>
<Attribute id="prefix" optional="true">
<Name>
<en>Prefix</en>
<nl>Tussenvoegsel</nl>
</Name>
<Description>
<en>Family name prefix</en>
<nl>Tussenvoegsel van uw achternaam</nl>
</Description>
</Attribute>
</Attributes>
</IssueSpecification>
<IssueSpecification version="4">
<Name>
<en>Root</en>
<nl>Root</nl>
</Name>
<ShortName>
<en>Root</en>
<nl>Root</nl>
</ShortName>
<SchemeManager>irma-demo</SchemeManager>
<IssuerID>MijnOverheid</IssuerID>
<CredentialID>root</CredentialID>
<Description>
<en>Root credential issued by MijnOverheid.nl</en>
<nl>Root-credential uitgegeven door MijnOverheid.nl</nl>
</Description>
<ShouldBeSingleton>true</ShouldBeSingleton>
<Attributes>
<Attribute id="BSN">
<Name>
<en>BSN</en>
<nl>BSN</nl>
</Name>
<Description>
<en>Your BSN-number</en>
<nl>Uw BSN-nummer</nl>
</Description>
</Attribute>
</Attributes>
</IssueSpecification>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<IssuerPrivateKey xmlns="http://www.zurich.ibm.com/security/idemix" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.zurich.ibm.com/security/idemix IssuerPrivateKey.xsd">
<Counter>0</Counter>
<ExpiryDate>1491436800</ExpiryDate>
<References>
<IssuerPublicKey>http://www.irmacard.org/credentials/phase1/MijnOverheid/ipk.xml</IssuerPublicKey>
</References>
<Elements>
<n>96063359353814070257464989369098573470645843347358957127875426328487326540633303185702306359400766259130239226832166456957259123554826741975265634464478609571816663003684533868318795865194004795637221226902067194633407757767792795252414073029114153019362701793292862118990912516058858923030408920700061749321</n>
<p>10436034022637868273483137633548989700482895839559909621411910579140541345632481969613724849214412062500244238926015929148144084368427474551770487566048119</p>
<pPrime>5218017011318934136741568816774494850241447919779954810705955289570270672816240984806862424607206031250122119463007964574072042184213737275885243783024059</pPrime>
<q>9204968012315139729618449685392284928468933831570080795536662422367142181432679739143882888540883909887054345986640656981843559062844656131133512640733759</q>
<qPrime>4602484006157569864809224842696142464234466915785040397768331211183571090716339869571941444270441954943527172993320328490921779531422328065566756320366879</qPrime>
</Elements>
</IssuerPrivateKey>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<IssuerPrivateKey xmlns="http://www.zurich.ibm.com/security/idemix" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.zurich.ibm.com/security/idemix IssuerPrivateKey.xsd">
<Counter>0</Counter>
<ExpiryDate>1893456000</ExpiryDate>
<References>
<IssuerPublicKey>http://www.irmacard.org/credentials/phase1/MijnOverheid/ipk.xml</IssuerPublicKey>
</References>
<Elements>
<n>96063359353814070257464989369098573470645843347358957127875426328487326540633303185702306359400766259130239226832166456957259123554826741975265634464478609571816663003684533868318795865194004795637221226902067194633407757767792795252414073029114153019362701793292862118990912516058858923030408920700061749321</n>
<p>10436034022637868273483137633548989700482895839559909621411910579140541345632481969613724849214412062500244238926015929148144084368427474551770487566048119</p>
<pPrime>5218017011318934136741568816774494850241447919779954810705955289570270672816240984806862424607206031250122119463007964574072042184213737275885243783024059</pPrime>
<q>9204968012315139729618449685392284928468933831570080795536662422367142181432679739143882888540883909887054345986640656981843559062844656131133512640733759</q>
<qPrime>4602484006157569864809224842696142464234466915785040397768331211183571090716339869571941444270441954943527172993320328490921779531422328065566756320366879</qPrime>
</Elements>
</IssuerPrivateKey>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<IssuerPublicKey xmlns="http://www.zurich.ibm.com/security/idemix" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.zurich.ibm.com/security/idemix IssuerPublicKey.xsd">
<Counter>0</Counter>
<ExpiryDate>1491436800</ExpiryDate>
<References>
<GroupParameters>http://www.irmacard.org/credentials/phase1/MijnOverheid/gp.xml</GroupParameters>
</References>
<Elements>
<S>68460510129747727135744503403370273952956360997532594630007762045745171031173231339034881007977792852962667675924510408558639859602742661846943843432940752427075903037429735029814040501385798095836297700111333573975220392538916785564158079116348699773855815825029476864341585033111676283214405517983188761136</S>
<Z>44579327840225837958738167571392618381868336415293109834301264408385784355849790902532728798897199236650711385876328647206143271336410651651791998475869027595051047904885044274040212624547595999947339956165755500019260290516022753290814461070607850420459840370288988976468437318992206695361417725670417150636</Z>
<n>96063359353814070257464989369098573470645843347358957127875426328487326540633303185702306359400766259130239226832166456957259123554826741975265634464478609571816663003684533868318795865194004795637221226902067194633407757767792795252414073029114153019362701793292862118990912516058858923030408920700061749321</n>
<Bases num="6">
<Base_0>75350858539899247205099195870657569095662997908054835686827949842616918065279527697469302927032348256512990413925385972530386004430200361722733856287145745926519366823425418198189091190950415327471076288381822950611094023093577973125683837586451857056904547886289627214081538422503416179373023552964235386251</Base_0>
<Base_1>16493273636283143082718769278943934592373185321248797185217530224336539646051357956879850630049668377952487166494198481474513387080523771033539152347804895674103957881435528189990601782516572803731501616717599698546778915053348741763191226960285553875185038507959763576845070849066881303186850782357485430766</Base_1>
<Base_2>13291821743359694134120958420057403279203178581231329375341327975072292378295782785938004910295078955941500173834360776477803543971319031484244018438746973179992753654070994560440903251579649890648424366061116003693414594252721504213975050604848134539324290387019471337306533127861703270017452296444985692840</Base_2>
<Base_3>86332479314886130384736453625287798589955409703988059270766965934046079318379171635950761546707334446554224830120982622431968575935564538920183267389540869023066259053290969633312602549379541830869908306681500988364676409365226731817777230916908909465129739617379202974851959354453994729819170838277127986187</Base_3>
<Base_4>68324072803453545276056785581824677993048307928855083683600441649711633245772441948750253858697288489650767258385115035336890900077233825843691912005645623751469455288422721175655533702255940160761555155932357171848703103682096382578327888079229101354304202688749783292577993444026613580092677609916964914513</Base_4>
<Base_5>65082646756773276491139955747051924146096222587013375084161255582716233287172212541454173762000144048198663356249316446342046266181487801411025319914616581971563024493732489885161913779988624732795125008562587549337253757085766106881836850538709151996387829026336509064994632876911986826959512297657067426387</Base_5>
</Bases>
</Elements>
<Features>
<Epoch length="432000"/>
</Features>
</IssuerPublicKey>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<IssuerPublicKey xmlns="http://www.zurich.ibm.com/security/idemix" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.zurich.ibm.com/security/idemix IssuerPublicKey.xsd">
<Counter>