From 26133bdaf59484aa7861360ba1ea1bb13d95d9b2 Mon Sep 17 00:00:00 2001 From: Alexandros Filios Date: Thu, 29 Feb 2024 13:47:07 +0100 Subject: [PATCH] Transferred MSP from FSC to TokenSDK Signed-off-by: Alexandros Filios --- integration/nwo/token/common/ppmgen.go | 5 +- .../zkatdlog/crypto/audit/auditor_test.go | 2 +- .../crypto/validator/validator_test.go | 2 +- .../core/zkatdlog/nogh/driver/deserializer.go | 2 +- token/services/identity/msp/common/common.go | 2 +- token/services/identity/msp/idemix/bccsp.go | 2 +- token/services/identity/msp/idemix/cache.go | 2 +- .../identity/msp/idemix/deserializer.go | 2 +- token/services/identity/msp/idemix/lm.go | 2 +- .../services/identity/msp/msp/idemix/audit.go | 101 ++++ .../services/identity/msp/msp/idemix/cache.go | 132 +++++ .../identity/msp/msp/idemix/cache_test.go | 33 ++ .../identity/msp/msp/idemix/config.go | 158 ++++++ .../identity/msp/msp/idemix/crypto.go | 76 +++ .../identity/msp/msp/idemix/deserializer.go | 125 +++++ token/services/identity/msp/msp/idemix/id.go | 210 ++++++++ .../identity/msp/msp/idemix/idemix.go | 148 ++++++ .../identity/msp/msp/idemix/loader.go | 71 +++ .../identity/msp/msp/idemix/provider.go | 481 ++++++++++++++++++ .../identity/msp/msp/idemix/provider_test.go | 414 +++++++++++++++ .../services/identity/msp/msp/idemix/roles.go | 72 +++ .../testdata/charlie.ExtraId2/IssuerPublicKey | Bin 0 -> 843 bytes .../IssuerRevocationPublicKey | 5 + ...estchannel-token-chaincode-example-com.pem | 14 + .../charlie.ExtraId2/user/SignerConfig | 1 + .../testdata/idemix/msp/IssuerPublicKey | Bin 0 -> 843 bytes .../testdata/idemix/msp/RevocationPublicKey | 5 + .../idemix/testdata/idemix/user/SignerConfig | Bin 0 -> 665 bytes .../testdata/idemix2/msp/IssuerPublicKey | Bin 0 -> 843 bytes .../testdata/idemix2/msp/RevocationPublicKey | 5 + .../idemix/testdata/idemix2/user/SignerConfig | Bin 0 -> 663 bytes .../sameissuer/idemix/msp/IssuerPublicKey | Bin 0 -> 843 bytes .../sameissuer/idemix/msp/RevocationPublicKey | 5 + .../sameissuer/idemix/user/SignerConfig | Bin 0 -> 664 bytes .../sameissuer/idemix2/msp/IssuerPublicKey | Bin 0 -> 843 bytes .../idemix2/msp/RevocationPublicKey | 5 + .../sameissuer/idemix2/user/SignerConfig | Bin 0 -> 660 bytes token/services/identity/msp/msp/x509/audit.go | 22 + token/services/identity/msp/msp/x509/cert.go | 32 ++ .../identity/msp/msp/x509/deserializer.go | 59 +++ token/services/identity/msp/msp/x509/ecdsa.go | 230 +++++++++ .../identity/msp/msp/x509/identity.go | 111 ++++ .../services/identity/msp/msp/x509/loader.go | 116 +++++ .../identity/msp/msp/x509/provider.go | 151 ++++++ token/services/identity/msp/msp/x509/setup.go | 267 ++++++++++ .../Admin@org1.example.com-cert.pem | 14 + .../msp/cacerts/ca.org1.example.com-cert.pem | 15 + .../msp/x509/testdata/msp/keystore/priv_sk | 5 + .../auditor.org1.example.com-cert.pem | 14 + .../tlsca.org1.example.com-cert.pem | 15 + .../identity/msp/msp/x509/x509_test.go | 32 ++ token/services/identity/msp/x509/lm.go | 2 +- token/services/identity/msp/x509/mspx509.go | 2 +- 53 files changed, 3156 insertions(+), 13 deletions(-) create mode 100644 token/services/identity/msp/msp/idemix/audit.go create mode 100644 token/services/identity/msp/msp/idemix/cache.go create mode 100644 token/services/identity/msp/msp/idemix/cache_test.go create mode 100644 token/services/identity/msp/msp/idemix/config.go create mode 100644 token/services/identity/msp/msp/idemix/crypto.go create mode 100644 token/services/identity/msp/msp/idemix/deserializer.go create mode 100644 token/services/identity/msp/msp/idemix/id.go create mode 100644 token/services/identity/msp/msp/idemix/idemix.go create mode 100644 token/services/identity/msp/msp/idemix/loader.go create mode 100644 token/services/identity/msp/msp/idemix/provider.go create mode 100644 token/services/identity/msp/msp/idemix/provider_test.go create mode 100644 token/services/identity/msp/msp/idemix/roles.go create mode 100644 token/services/identity/msp/msp/idemix/testdata/charlie.ExtraId2/IssuerPublicKey create mode 100644 token/services/identity/msp/msp/idemix/testdata/charlie.ExtraId2/IssuerRevocationPublicKey create mode 100644 token/services/identity/msp/msp/idemix/testdata/charlie.ExtraId2/cacerts/localhost-7054-default-testchannel-token-chaincode-example-com.pem create mode 100644 token/services/identity/msp/msp/idemix/testdata/charlie.ExtraId2/user/SignerConfig create mode 100644 token/services/identity/msp/msp/idemix/testdata/idemix/msp/IssuerPublicKey create mode 100644 token/services/identity/msp/msp/idemix/testdata/idemix/msp/RevocationPublicKey create mode 100644 token/services/identity/msp/msp/idemix/testdata/idemix/user/SignerConfig create mode 100644 token/services/identity/msp/msp/idemix/testdata/idemix2/msp/IssuerPublicKey create mode 100644 token/services/identity/msp/msp/idemix/testdata/idemix2/msp/RevocationPublicKey create mode 100644 token/services/identity/msp/msp/idemix/testdata/idemix2/user/SignerConfig create mode 100644 token/services/identity/msp/msp/idemix/testdata/sameissuer/idemix/msp/IssuerPublicKey create mode 100644 token/services/identity/msp/msp/idemix/testdata/sameissuer/idemix/msp/RevocationPublicKey create mode 100644 token/services/identity/msp/msp/idemix/testdata/sameissuer/idemix/user/SignerConfig create mode 100644 token/services/identity/msp/msp/idemix/testdata/sameissuer/idemix2/msp/IssuerPublicKey create mode 100644 token/services/identity/msp/msp/idemix/testdata/sameissuer/idemix2/msp/RevocationPublicKey create mode 100644 token/services/identity/msp/msp/idemix/testdata/sameissuer/idemix2/user/SignerConfig create mode 100644 token/services/identity/msp/msp/x509/audit.go create mode 100644 token/services/identity/msp/msp/x509/cert.go create mode 100644 token/services/identity/msp/msp/x509/deserializer.go create mode 100644 token/services/identity/msp/msp/x509/ecdsa.go create mode 100644 token/services/identity/msp/msp/x509/identity.go create mode 100644 token/services/identity/msp/msp/x509/loader.go create mode 100644 token/services/identity/msp/msp/x509/provider.go create mode 100644 token/services/identity/msp/msp/x509/setup.go create mode 100644 token/services/identity/msp/msp/x509/testdata/msp/admincerts/Admin@org1.example.com-cert.pem create mode 100644 token/services/identity/msp/msp/x509/testdata/msp/cacerts/ca.org1.example.com-cert.pem create mode 100644 token/services/identity/msp/msp/x509/testdata/msp/keystore/priv_sk create mode 100644 token/services/identity/msp/msp/x509/testdata/msp/signcerts/auditor.org1.example.com-cert.pem create mode 100644 token/services/identity/msp/msp/x509/testdata/msp/tlscacerts/tlsca.org1.example.com-cert.pem create mode 100644 token/services/identity/msp/msp/x509/x509_test.go diff --git a/integration/nwo/token/common/ppmgen.go b/integration/nwo/token/common/ppmgen.go index b206d1db5..c38aa937a 100644 --- a/integration/nwo/token/common/ppmgen.go +++ b/integration/nwo/token/common/ppmgen.go @@ -11,16 +11,15 @@ import ( "path/filepath" "strconv" - msp2 "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp" - msp "github.com/IBM/idemix" math3 "github.com/IBM/mathlib" - "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/core/generic/msp/x509" "github.com/hyperledger-labs/fabric-token-sdk/integration/nwo/token/generators" "github.com/hyperledger-labs/fabric-token-sdk/integration/nwo/token/generators/dlog" "github.com/hyperledger-labs/fabric-token-sdk/integration/nwo/token/topology" "github.com/hyperledger-labs/fabric-token-sdk/token/core/fabtoken" cryptodlog "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto" + msp2 "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp/msp/x509" "github.com/pkg/errors" ) diff --git a/token/core/zkatdlog/crypto/audit/auditor_test.go b/token/core/zkatdlog/crypto/audit/auditor_test.go index dc455b815..2a359495d 100644 --- a/token/core/zkatdlog/crypto/audit/auditor_test.go +++ b/token/core/zkatdlog/crypto/audit/auditor_test.go @@ -12,7 +12,6 @@ import ( "time" math "github.com/IBM/mathlib" - idemix2 "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/core/generic/msp/idemix" sig2 "github.com/hyperledger-labs/fabric-smart-client/platform/view/core/sig" _ "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/db/driver/memory" "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/kvs" @@ -27,6 +26,7 @@ import ( "github.com/hyperledger-labs/fabric-token-sdk/token/driver" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp/idemix" + idemix2 "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp/msp/idemix" msp2 "github.com/hyperledger/fabric/msp" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" diff --git a/token/core/zkatdlog/crypto/validator/validator_test.go b/token/core/zkatdlog/crypto/validator/validator_test.go index cf8a63421..35f24fbf9 100644 --- a/token/core/zkatdlog/crypto/validator/validator_test.go +++ b/token/core/zkatdlog/crypto/validator/validator_test.go @@ -15,7 +15,6 @@ import ( zkatdlog "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/nogh/driver" math "github.com/IBM/mathlib" - idemix2 "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/core/generic/msp/idemix" "github.com/hyperledger-labs/fabric-smart-client/platform/view/core/sig" _ "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/db/driver/memory" "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/kvs" @@ -33,6 +32,7 @@ import ( "github.com/hyperledger-labs/fabric-token-sdk/token/driver" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp/idemix" + idemix2 "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp/msp/idemix" msp2 "github.com/hyperledger/fabric/msp" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" diff --git a/token/core/zkatdlog/nogh/driver/deserializer.go b/token/core/zkatdlog/nogh/driver/deserializer.go index 652b89fff..ceb792a45 100644 --- a/token/core/zkatdlog/nogh/driver/deserializer.go +++ b/token/core/zkatdlog/nogh/driver/deserializer.go @@ -13,13 +13,13 @@ import ( "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/hash" - idemix2 "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/core/generic/msp/idemix" "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto" "github.com/hyperledger-labs/fabric-token-sdk/token/driver" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/interop/htlc" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp/idemix" + idemix2 "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp/msp/idemix" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp/x509" "github.com/pkg/errors" ) diff --git a/token/services/identity/msp/common/common.go b/token/services/identity/msp/common/common.go index e49d70d5d..eff52fb4e 100644 --- a/token/services/identity/msp/common/common.go +++ b/token/services/identity/msp/common/common.go @@ -9,11 +9,11 @@ package common import ( "reflect" - "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/core/generic/msp/idemix" "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/driver" view2 "github.com/hyperledger-labs/fabric-smart-client/platform/view" "github.com/hyperledger-labs/fabric-smart-client/platform/view/core/sig" "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp/msp/idemix" "github.com/pkg/errors" ) diff --git a/token/services/identity/msp/idemix/bccsp.go b/token/services/identity/msp/idemix/bccsp.go index 3956b2500..fcd2ef46a 100644 --- a/token/services/identity/msp/idemix/bccsp.go +++ b/token/services/identity/msp/idemix/bccsp.go @@ -11,7 +11,7 @@ import ( "github.com/IBM/idemix/bccsp/keystore" bccsp "github.com/IBM/idemix/bccsp/types" math "github.com/IBM/mathlib" - "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/core/generic/msp/idemix" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp/msp/idemix" "github.com/pkg/errors" ) diff --git a/token/services/identity/msp/idemix/cache.go b/token/services/identity/msp/idemix/cache.go index 1d7688a4c..6ecc8b225 100644 --- a/token/services/identity/msp/idemix/cache.go +++ b/token/services/identity/msp/idemix/cache.go @@ -9,9 +9,9 @@ package idemix import ( "time" - "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/core/generic/msp/idemix" driver2 "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/driver" "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp/msp/idemix" "go.uber.org/zap/zapcore" ) diff --git a/token/services/identity/msp/idemix/deserializer.go b/token/services/identity/msp/idemix/deserializer.go index c8488c925..86fb0195e 100644 --- a/token/services/identity/msp/idemix/deserializer.go +++ b/token/services/identity/msp/idemix/deserializer.go @@ -9,9 +9,9 @@ package idemix import ( bccsp "github.com/IBM/idemix/bccsp/types" math "github.com/IBM/mathlib" - "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/core/generic/msp/idemix" "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" "github.com/hyperledger-labs/fabric-token-sdk/token/driver" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp/msp/idemix" "github.com/pkg/errors" ) diff --git a/token/services/identity/msp/idemix/lm.go b/token/services/identity/msp/idemix/lm.go index e8bb584f6..e2b46c375 100644 --- a/token/services/identity/msp/idemix/lm.go +++ b/token/services/identity/msp/idemix/lm.go @@ -17,7 +17,6 @@ import ( "github.com/IBM/idemix/idemixmsp" math3 "github.com/IBM/mathlib" "github.com/hyperledger-labs/fabric-smart-client/pkg/utils/proto" - idemix2 "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/core/generic/msp/idemix" driver2 "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/driver" "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/flogging" "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/hash" @@ -27,6 +26,7 @@ import ( driver3 "github.com/hyperledger-labs/fabric-token-sdk/token/services/db/driver" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp/common" config2 "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp/config" + idemix2 "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp/msp/idemix" "github.com/hyperledger/fabric-protos-go/msp" "github.com/pkg/errors" "go.uber.org/zap/zapcore" diff --git a/token/services/identity/msp/msp/idemix/audit.go b/token/services/identity/msp/msp/idemix/audit.go new file mode 100644 index 000000000..ae6bd31f4 --- /dev/null +++ b/token/services/identity/msp/msp/idemix/audit.go @@ -0,0 +1,101 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "encoding/json" + + csp "github.com/IBM/idemix/bccsp/types" + "github.com/hyperledger-labs/fabric-smart-client/pkg/utils/proto" + m "github.com/hyperledger/fabric-protos-go/msp" + "github.com/pkg/errors" +) + +type AuditInfo struct { + EidNymAuditData *csp.AttrNymAuditData + RhNymAuditData *csp.AttrNymAuditData + Attributes [][]byte + Csp csp.BCCSP `json:"-"` + IssuerPublicKey csp.Key `json:"-"` +} + +func (a *AuditInfo) Bytes() ([]byte, error) { + return json.Marshal(a) +} + +func (a *AuditInfo) FromBytes(raw []byte) error { + return json.Unmarshal(raw, a) +} + +func (a *AuditInfo) EnrollmentID() string { + return string(a.Attributes[2]) +} + +func (a *AuditInfo) RevocationHandle() string { + return string(a.Attributes[3]) +} + +func (a *AuditInfo) Match(id []byte) error { + si := &m.SerializedIdentity{} + err := proto.Unmarshal(id, si) + if err != nil { + return errors.Wrap(err, "failed to unmarshal to msp.SerializedIdentity{}") + } + + serialized := new(m.SerializedIdemixIdentity) + err = proto.Unmarshal(si.IdBytes, serialized) + if err != nil { + return errors.Wrap(err, "could not deserialize a SerializedIdemixIdentity") + } + + // Audit EID + valid, err := a.Csp.Verify( + a.IssuerPublicKey, + serialized.Proof, + nil, + &csp.EidNymAuditOpts{ + EidIndex: EIDIndex, + EnrollmentID: string(a.Attributes[EIDIndex]), + RNymEid: a.EidNymAuditData.Rand, + }, + ) + if err != nil { + return errors.Wrap(err, "error while verifying the nym eid") + } + if !valid { + return errors.New("invalid nym rh") + } + + // Audit RH + valid, err = a.Csp.Verify( + a.IssuerPublicKey, + serialized.Proof, + nil, + &csp.RhNymAuditOpts{ + RhIndex: RHIndex, + RevocationHandle: string(a.Attributes[RHIndex]), + RNymRh: a.RhNymAuditData.Rand, + }, + ) + if err != nil { + return errors.Wrap(err, "error while verifying the nym rh") + } + if !valid { + return errors.New("invalid nym eid") + } + + return nil +} + +func DeserializeAuditInfo(raw []byte) (*AuditInfo, error) { + auditInfo := &AuditInfo{} + err := auditInfo.FromBytes(raw) + if err != nil { + return nil, err + } + return auditInfo, nil +} diff --git a/token/services/identity/msp/msp/idemix/cache.go b/token/services/identity/msp/msp/idemix/cache.go new file mode 100644 index 000000000..4466edb56 --- /dev/null +++ b/token/services/identity/msp/msp/idemix/cache.go @@ -0,0 +1,132 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "runtime" + "sync" + "time" + + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/driver" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "go.uber.org/zap/zapcore" +) + +type IdentityCacheBackendFunc func(opts *driver.IdentityOptions) (view.Identity, []byte, error) + +type identityCacheEntry struct { + Identity view.Identity + Audit []byte +} + +type IdentityCache struct { + once sync.Once + backed IdentityCacheBackendFunc + cache chan identityCacheEntry + opts *driver.IdentityOptions +} + +func NewIdentityCache(backed IdentityCacheBackendFunc, size int, opts *driver.IdentityOptions) *IdentityCache { + ci := &IdentityCache{ + backed: backed, + cache: make(chan identityCacheEntry, size), + opts: opts, + } + + return ci +} + +func (c *IdentityCache) Identity(opts *driver.IdentityOptions) (view.Identity, []byte, error) { + if opts != nil { + return c.fetchIdentityFromBackend(opts) + } + + c.once.Do(func() { + if cap(c.cache) > 0 { + // Spin up as many background goroutines as we need to prepare identities in the background. + for i := 0; i < runtime.NumCPU(); i++ { + go c.provisionIdentities() + } + } + }) + + if logger.IsEnabledFor(zapcore.DebugLevel) { + logger.Debugf("fetching identity from cache...") + } + + return c.fetchIdentityFromCache(opts) + +} + +func (c *IdentityCache) fetchIdentityFromCache(opts *driver.IdentityOptions) (view.Identity, []byte, error) { + var identity view.Identity + var audit []byte + + var start time.Time + + if logger.IsEnabledFor(zapcore.DebugLevel) { + start = time.Now() + } + + timeout := time.NewTimer(time.Second) + defer timeout.Stop() + + select { + + case entry := <-c.cache: + identity = entry.Identity + audit = entry.Audit + + if logger.IsEnabledFor(zapcore.DebugLevel) { + logger.Debugf("fetching identity from cache [%s][%d] took %v", identity, len(audit), time.Since(start)) + } + + case <-timeout.C: + id, a, err := c.backed(opts) + if err != nil { + return nil, nil, err + } + identity = id + audit = a + + if logger.IsEnabledFor(zapcore.DebugLevel) { + logger.Debugf("fetching identity from backend after a timeout [%s][%d] took %v", identity, len(audit), time.Since(start)) + } + } + + return identity, audit, nil +} + +func (c *IdentityCache) fetchIdentityFromBackend(opts *driver.IdentityOptions) (view.Identity, []byte, error) { + if logger.IsEnabledFor(zapcore.DebugLevel) { + logger.Debugf("fetching identity from backend") + } + id, audit, err := c.backed(opts) + if err != nil { + return nil, nil, err + } + if logger.IsEnabledFor(zapcore.DebugLevel) { + logger.Debugf("fetch identity from backend done [%s][%d]", id, len(audit)) + } + + return id, audit, nil +} + +func (c *IdentityCache) provisionIdentities() { + count := 0 + for { + id, audit, err := c.backed(c.opts) + if err != nil { + logger.Errorf("failed to provision identity [%s]", err) + continue + } + if logger.IsEnabledFor(zapcore.DebugLevel) { + logger.Debugf("generated new idemix identity [%d]", count) + } + c.cache <- identityCacheEntry{Identity: id, Audit: audit} + } +} diff --git a/token/services/identity/msp/msp/idemix/cache_test.go b/token/services/identity/msp/msp/idemix/cache_test.go new file mode 100644 index 000000000..b04f785c5 --- /dev/null +++ b/token/services/identity/msp/msp/idemix/cache_test.go @@ -0,0 +1,33 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "testing" + + api2 "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/driver" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/stretchr/testify/assert" +) + +func TestIdentityCache(t *testing.T) { + c := NewIdentityCache(func(opts *api2.IdentityOptions) (view.Identity, []byte, error) { + return []byte("hello world"), []byte("audit"), nil + }, 100, nil) + id, audit, err := c.Identity(&api2.IdentityOptions{ + EIDExtension: true, + AuditInfo: nil, + }) + assert.NoError(t, err) + assert.Equal(t, view.Identity([]byte("hello world")), id) + assert.Equal(t, []byte("audit"), audit) + + id, audit, err = c.Identity(nil) + assert.NoError(t, err) + assert.Equal(t, view.Identity([]byte("hello world")), id) + assert.Equal(t, []byte("audit"), audit) +} diff --git a/token/services/identity/msp/msp/idemix/config.go b/token/services/identity/msp/msp/idemix/config.go new file mode 100644 index 000000000..614acc8f5 --- /dev/null +++ b/token/services/identity/msp/msp/idemix/config.go @@ -0,0 +1,158 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "encoding/json" + "os" + "path/filepath" + + "github.com/IBM/idemix" + im "github.com/IBM/idemix/idemixmsp" + "github.com/hyperledger-labs/fabric-smart-client/pkg/utils/proto" + m "github.com/hyperledger/fabric-protos-go/msp" + "github.com/hyperledger/fabric/msp" + "github.com/pkg/errors" +) + +// SignerConfig contains the crypto material to set up an idemix signing identity +type SignerConfig struct { + // Cred represents the serialized idemix credential of the default signer + Cred []byte `protobuf:"bytes,1,opt,name=Cred,proto3" json:"Cred,omitempty"` + // Sk is the secret key of the default signer, corresponding to credential Cred + Sk []byte `protobuf:"bytes,2,opt,name=Sk,proto3" json:"Sk,omitempty"` + // OrganizationalUnitIdentifier defines the organizational unit the default signer is in + OrganizationalUnitIdentifier string `protobuf:"bytes,3,opt,name=organizational_unit_identifier,json=organizationalUnitIdentifier" json:"organizational_unit_identifier,omitempty"` + // Role defines whether the default signer is admin, member, peer, or client + Role int `protobuf:"varint,4,opt,name=role,json=role" json:"role,omitempty"` + // EnrollmentID contains the enrollment id of this signer + EnrollmentID string `protobuf:"bytes,5,opt,name=enrollment_id,json=enrollmentId" json:"enrollment_id,omitempty"` + // CRI contains a serialized Credential Revocation Information + CredentialRevocationInformation []byte `protobuf:"bytes,6,opt,name=credential_revocation_information,json=credentialRevocationInformation,proto3" json:"credential_revocation_information,omitempty"` + // RevocationHandle is the handle used to single out this credential and determine its revocation status + RevocationHandle string `protobuf:"bytes,7,opt,name=revocation_handle,json=revocationHandle,proto3" json:"revocation_handle,omitempty"` + // CurveID specifies the name of the Idemix curve to use, defaults to 'amcl.Fp256bn' + CurveID string `protobuf:"bytes,8,opt,name=curve_id,json=curveID" json:"curveID,omitempty"` +} + +const ( + ConfigDirUser = "user" + ConfigFileIssuerPublicKey = "IssuerPublicKey" + IdemixConfigFileRevocationPublicKey = "IssuerRevocationPublicKey" + ConfigFileSigner = "SignerConfig" +) + +func ReadFile(file string) ([]byte, error) { + fileCont, err := os.ReadFile(file) + if err != nil { + return nil, errors.Wrapf(err, "could not read file %s", file) + } + + return fileCont, nil +} + +func GetLocalMspConfigWithType(dir string, id string) (*m.MSPConfig, error) { + mspConfig, err := GetIdemixMspConfigWithType(dir, id) + if err != nil { + // load it using the fabric-ca format + mspConfig2, err2 := GetFabricCAIdemixMspConfig(dir, id) + if err2 != nil { + return nil, errors.Wrapf(err2, "cannot get idemix msp config from [%s]: [%s]", dir, err) + } + mspConfig = mspConfig2 + } + return mspConfig, nil +} + +// GetIdemixMspConfigWithType returns the configuration for the Idemix MSP of the specified type +func GetIdemixMspConfigWithType(dir string, ID string) (*m.MSPConfig, error) { + ipkBytes, err := ReadFile(filepath.Join(dir, idemix.IdemixConfigDirMsp, idemix.IdemixConfigFileIssuerPublicKey)) + if err != nil { + return nil, errors.Wrapf(err, "failed to read issuer public key file") + } + + revocationPkBytes, err := ReadFile(filepath.Join(dir, idemix.IdemixConfigDirMsp, idemix.IdemixConfigFileRevocationPublicKey)) + if err != nil { + return nil, errors.Wrapf(err, "failed to read revocation public key file") + } + + idemixConfig := &im.IdemixMSPConfig{ + Name: ID, + Ipk: ipkBytes, + RevocationPk: revocationPkBytes, + } + + signerBytes, err := ReadFile(filepath.Join(dir, idemix.IdemixConfigDirUser, idemix.IdemixConfigFileSigner)) + if err == nil { + signerConfig := &im.IdemixMSPSignerConfig{} + err = proto.Unmarshal(signerBytes, signerConfig) + if err != nil { + return nil, err + } + idemixConfig.Signer = signerConfig + } + + confBytes, err := proto.Marshal(idemixConfig) + if err != nil { + return nil, err + } + + return &m.MSPConfig{Config: confBytes, Type: int32(idemix.IDEMIX)}, nil +} + +// GetFabricCAIdemixMspConfig returns the configuration for the Idemix MSP generated by Fabric-CA +func GetFabricCAIdemixMspConfig(dir string, ID string) (*m.MSPConfig, error) { + path := filepath.Join(dir, ConfigFileIssuerPublicKey) + ipkBytes, err := ReadFile(path) + if err != nil { + return nil, errors.Wrapf(err, "failed to read issuer public key file at [%s]", path) + } + + path = filepath.Join(dir, IdemixConfigFileRevocationPublicKey) + revocationPkBytes, err := ReadFile(path) + if err != nil { + return nil, errors.Wrapf(err, "failed to read revocation public key file at [%s]", path) + } + + idemixConfig := &im.IdemixMSPConfig{ + Name: ID, + Ipk: ipkBytes, + RevocationPk: revocationPkBytes, + } + + path = filepath.Join(dir, ConfigDirUser, ConfigFileSigner) + signerBytes, err := ReadFile(path) + if err == nil { + // signerBytes is a json structure, convert it to protobuf + si := &SignerConfig{} + if err := json.Unmarshal(signerBytes, si); err != nil { + return nil, errors.Wrapf(err, "failed to json unmarshal signer config read at [%s]", path) + } + + signerConfig := &im.IdemixMSPSignerConfig{ + Cred: si.Cred, + Sk: si.Sk, + OrganizationalUnitIdentifier: si.OrganizationalUnitIdentifier, + Role: int32(si.Role), + EnrollmentId: si.EnrollmentID, + CredentialRevocationInformation: si.CredentialRevocationInformation, + RevocationHandle: si.RevocationHandle, + } + idemixConfig.Signer = signerConfig + } else { + if !os.IsNotExist(errors.Cause(err)) { + return nil, errors.Wrapf(err, "failed to read the content of signer config at [%s]", path) + } + } + + confBytes, err := proto.Marshal(idemixConfig) + if err != nil { + return nil, err + } + + return &m.MSPConfig{Config: confBytes, Type: int32(msp.IDEMIX)}, nil +} diff --git a/token/services/identity/msp/msp/idemix/crypto.go b/token/services/identity/msp/msp/idemix/crypto.go new file mode 100644 index 000000000..31ccf727d --- /dev/null +++ b/token/services/identity/msp/msp/idemix/crypto.go @@ -0,0 +1,76 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + idemix "github.com/IBM/idemix/bccsp" + "github.com/IBM/idemix/bccsp/keystore" + idemix2 "github.com/IBM/idemix/bccsp/schemes/dlog/crypto" + "github.com/IBM/idemix/bccsp/schemes/dlog/crypto/translator/amcl" + bccsp "github.com/IBM/idemix/bccsp/types" + math "github.com/IBM/mathlib" + "github.com/pkg/errors" +) + +// NewBCCSP returns an instance of the idemix BCCSP for the given curve +func NewBCCSP(curveID math.CurveID) (bccsp.BCCSP, error) { + curve, tr, err := GetCurveAndTranslator(curveID) + if err != nil { + return nil, err + } + cryptoProvider, err := idemix.New(&keystore.Dummy{}, curve, tr, true) + if err != nil { + return nil, errors.Wrap(err, "failed getting crypto provider") + } + return cryptoProvider, nil +} + +// NewKSVBCCSP returns an instance of the idemix BCCSP for the given curve and kvsStore +func NewKSVBCCSP(kvsStore keystore.KVS, curveID math.CurveID, aries bool) (bccsp.BCCSP, error) { + curve, tr, err := GetCurveAndTranslator(curveID) + if err != nil { + return nil, err + } + + keyStore := &keystore.KVSStore{ + KVS: kvsStore, + Curve: curve, + Translator: tr, + } + + var cryptoProvider bccsp.BCCSP + if aries { + cryptoProvider, err = idemix.NewAries(keyStore, curve, tr, true) + } else { + cryptoProvider, err = idemix.New(keyStore, curve, tr, true) + } + if err != nil { + return nil, errors.Wrap(err, "failed getting crypto provider") + } + + return cryptoProvider, nil +} + +func GetCurveAndTranslator(curveID math.CurveID) (*math.Curve, idemix2.Translator, error) { + curve := math.Curves[curveID] + var tr idemix2.Translator + switch curveID { + case math.BN254: + tr = &amcl.Gurvy{C: curve} + case math.BLS12_377_GURVY: + tr = &amcl.Gurvy{C: curve} + case math.FP256BN_AMCL: + tr = &amcl.Fp256bn{C: curve} + case math.FP256BN_AMCL_MIRACL: + tr = &amcl.Fp256bnMiracl{C: curve} + case math.BLS12_381_BBS: + tr = &amcl.Gurvy{C: curve} + default: + return nil, nil, errors.Errorf("unsupported curve ID: %d", curveID) + } + return curve, tr, nil +} diff --git a/token/services/identity/msp/msp/idemix/deserializer.go b/token/services/identity/msp/msp/idemix/deserializer.go new file mode 100644 index 000000000..01a261d00 --- /dev/null +++ b/token/services/identity/msp/msp/idemix/deserializer.go @@ -0,0 +1,125 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "fmt" + + msp "github.com/IBM/idemix" + csp "github.com/IBM/idemix/bccsp/types" + math "github.com/IBM/mathlib" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/driver" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/hash" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/pkg/errors" +) + +type Deserializer struct { + *Idemix +} + +// NewDeserializer returns a new deserializer for the best effort strategy +func NewDeserializer(ipk []byte) (*Deserializer, error) { + bccsp, err := NewBCCSP(math.FP256BN_AMCL) + if err != nil { + return nil, errors.WithMessagef(err, "failed to instantiate bccsp") + } + return NewDeserializerWithBCCSP(ipk, csp.BestEffort, nil, bccsp) +} + +func NewDeserializerWithBCCSP(ipk []byte, verType csp.VerificationType, nymEID []byte, cryptoProvider csp.BCCSP) (*Deserializer, error) { + logger.Debugf("Setting up Idemix-based MSP instance") + + // Import Issuer Public Key + var issuerPublicKey csp.Key + var err error + if len(ipk) != 0 { + issuerPublicKey, err = cryptoProvider.KeyImport( + ipk, + &csp.IdemixIssuerPublicKeyImportOpts{ + Temporary: true, + AttributeNames: []string{ + msp.AttributeNameOU, + msp.AttributeNameRole, + msp.AttributeNameEnrollmentId, + msp.AttributeNameRevocationHandle, + }, + }) + if err != nil { + return nil, err + } + } + + return &Deserializer{ + Idemix: &Idemix{ + Ipk: ipk, + Csp: cryptoProvider, + IssuerPublicKey: issuerPublicKey, + VerType: verType, + NymEID: nymEID, + }, + }, nil +} + +func (i *Deserializer) DeserializeVerifier(raw []byte) (driver.Verifier, error) { + identity, err := i.Deserialize(raw, true) + if err != nil { + return nil, err + } + + return &NymSignatureVerifier{ + CSP: i.Idemix.Csp, + IPK: i.Idemix.IssuerPublicKey, + NymPK: identity.NymPublicKey, + }, nil +} + +func (i *Deserializer) DeserializeVerifierAgainstNymEID(raw []byte, nymEID []byte) (driver.Verifier, error) { + identity, err := i.Idemix.DeserializeAgainstNymEID(raw, true, nymEID) + if err != nil { + return nil, err + } + + return &NymSignatureVerifier{ + CSP: i.Idemix.Csp, + IPK: i.Idemix.IssuerPublicKey, + NymPK: identity.NymPublicKey, + }, nil +} + +func (i *Deserializer) DeserializeSigner(raw []byte) (driver.Signer, error) { + return nil, errors.New("not supported") +} + +func (i *Deserializer) DeserializeAuditInfo(raw []byte) (*AuditInfo, error) { + return i.Idemix.DeserializeAuditInfo(raw) +} + +func (i *Deserializer) Info(raw []byte, auditInfo []byte) (string, error) { + r, err := i.Deserialize(raw, false) + if err != nil { + return "", err + } + + eid := "" + if len(auditInfo) != 0 { + ai, err := DeserializeAuditInfo(auditInfo) + if err != nil { + return "", err + } + if err := ai.Match(raw); err != nil { + return "", err + } + eid = ai.EnrollmentID() + } + + return fmt.Sprintf("MSP.Idemix: [%s][%s][%s][%s][%s]", eid, view.Identity(raw).UniqueID(), r.SerializedIdentity.Mspid, r.OU.OrganizationalUnitIdentifier, r.Role.Role.String()), nil +} + +func (i *Deserializer) String() string { + return fmt.Sprintf("Idemix with IPK [%s]", hash.Hashable(i.Ipk).String()) +} diff --git a/token/services/identity/msp/msp/idemix/id.go b/token/services/identity/msp/msp/idemix/id.go new file mode 100644 index 000000000..a6e1bcd58 --- /dev/null +++ b/token/services/identity/msp/msp/idemix/id.go @@ -0,0 +1,210 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "bytes" + "time" + + bccsp "github.com/IBM/idemix/bccsp/types" + "github.com/hyperledger-labs/fabric-smart-client/pkg/utils/proto" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/driver" + m "github.com/hyperledger/fabric-protos-go/msp" + "github.com/hyperledger/fabric/msp" + "github.com/pkg/errors" +) + +type MSPIdentity struct { + NymPublicKey bccsp.Key + Idemix *Idemix + ID *msp.IdentityIdentifier + Role *m.MSPRole + OU *m.OrganizationUnit + // AssociationProof contains cryptographic proof that this identity + // belongs to the MSP id.provider, i.e., it proves that the pseudonym + // is constructed from a secret key on which the CA issued a credential. + AssociationProof []byte + VerificationType bccsp.VerificationType +} + +func NewMSPIdentityWithVerType(idemix *Idemix, NymPublicKey bccsp.Key, role *m.MSPRole, ou *m.OrganizationUnit, proof []byte, verificationType bccsp.VerificationType) (*MSPIdentity, error) { + id := &MSPIdentity{} + id.Idemix = idemix + id.NymPublicKey = NymPublicKey + id.Role = role + id.OU = ou + id.AssociationProof = proof + id.VerificationType = verificationType + + raw, err := NymPublicKey.Bytes() + if err != nil { + return nil, errors.Wrapf(err, "failed to marshal nym public key") + } + id.ID = &msp.IdentityIdentifier{ + Mspid: idemix.Name, + Id: bytes.NewBuffer(raw).String(), + } + + return id, nil +} + +func (id *MSPIdentity) Anonymous() bool { + return true +} + +func (id *MSPIdentity) ExpiresAt() time.Time { + // Idemix MSP currently does not use expiration dates or revocation, + // so we return the zero time to indicate this. + return time.Time{} +} + +func (id *MSPIdentity) GetIdentifier() *msp.IdentityIdentifier { + return id.ID +} + +func (id *MSPIdentity) GetMSPIdentifier() string { + return id.Idemix.Name +} + +func (id *MSPIdentity) GetOrganizationalUnits() []*msp.OUIdentifier { + // we use the (serialized) public key of this MSP as the CertifiersIdentifier + certifiersIdentifier, err := id.Idemix.IssuerPublicKey.Bytes() + if err != nil { + logger.Errorf("Failed to marshal ipk in GetOrganizationalUnits: %s", err) + return nil + } + + return []*msp.OUIdentifier{{CertifiersIdentifier: certifiersIdentifier, OrganizationalUnitIdentifier: id.OU.OrganizationalUnitIdentifier}} +} + +func (id *MSPIdentity) Validate() error { + // logger.Debugf("Validating identity %+v", id) + if id.GetMSPIdentifier() != id.Idemix.Name { + return errors.Errorf("the supplied identity does not belong to this msp") + } + return id.verifyProof() +} + +func (id *MSPIdentity) Verify(msg []byte, sig []byte) error { + _, err := id.Idemix.Csp.Verify( + id.NymPublicKey, + sig, + msg, + &bccsp.IdemixNymSignerOpts{ + IssuerPK: id.Idemix.IssuerPublicKey, + }, + ) + return err +} + +func (id *MSPIdentity) SatisfiesPrincipal(principal *m.MSPPrincipal) error { + return errors.Errorf("not supported") +} + +func (id *MSPIdentity) Serialize() ([]byte, error) { + serialized := &m.SerializedIdemixIdentity{} + + raw, err := id.NymPublicKey.Bytes() + if err != nil { + return nil, errors.Wrapf(err, "could not serialize nym of identity %s", id.ID) + } + // This is an assumption on how the underlying idemix implementation work. + // TODO: change this in future version + serialized.NymX = raw[:len(raw)/2] + serialized.NymY = raw[len(raw)/2:] + ouBytes, err := proto.Marshal(id.OU) + if err != nil { + return nil, errors.Wrapf(err, "could not marshal OU of identity %s", id.ID) + } + + roleBytes, err := proto.Marshal(id.Role) + if err != nil { + return nil, errors.Wrapf(err, "could not marshal role of identity %s", id.ID) + } + + serialized.Ou = ouBytes + serialized.Role = roleBytes + serialized.Proof = id.AssociationProof + + idemixIDBytes, err := proto.Marshal(serialized) + if err != nil { + return nil, err + } + + sID := &m.SerializedIdentity{Mspid: id.GetMSPIdentifier(), IdBytes: idemixIDBytes} + idBytes, err := proto.Marshal(sID) + if err != nil { + return nil, errors.Wrapf(err, "could not marshal a SerializedIdentity structure for identity %s", id.ID) + } + + return idBytes, nil +} + +func (id *MSPIdentity) verifyProof() error { + // Verify signature + var metadata *bccsp.IdemixSignerMetadata + if len(id.Idemix.NymEID) != 0 { + metadata = &bccsp.IdemixSignerMetadata{ + EidNym: id.Idemix.NymEID, + RhNym: id.Idemix.RhNym, + } + } + + valid, err := id.Idemix.Csp.Verify( + id.Idemix.IssuerPublicKey, + id.AssociationProof, + nil, + &bccsp.IdemixSignerOpts{ + RevocationPublicKey: id.Idemix.RevocationPK, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute, Value: []byte(id.OU.OrganizationalUnitIdentifier)}, + {Type: bccsp.IdemixIntAttribute, Value: GetIdemixRoleFromMSPRole(id.Role)}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: RHIndex, + EidIndex: EIDIndex, + Epoch: id.Idemix.Epoch, + VerificationType: id.VerificationType, + Metadata: metadata, + }, + ) + if err == nil && !valid { + return errors.Errorf("unexpected condition, an error should be returned for an invalid signature") + } + + return err +} + +type MSPSigningIdentity struct { + *MSPIdentity `json:"-"` + Cred []byte + UserKey bccsp.Key `json:"-"` + NymKey bccsp.Key `json:"-"` + EnrollmentId string +} + +func (id *MSPSigningIdentity) Sign(msg []byte) ([]byte, error) { + // logger.Debugf("Idemix identity %s is signing", id.GetIdentifier()) + + sig, err := id.Idemix.Csp.Sign( + id.UserKey, + msg, + &bccsp.IdemixNymSignerOpts{ + Nym: id.NymKey, + IssuerPK: id.Idemix.IssuerPublicKey, + }, + ) + if err != nil { + return nil, err + } + return sig, nil +} + +func (id *MSPSigningIdentity) GetPublicVersion() driver.Identity { + return id.MSPIdentity +} diff --git a/token/services/identity/msp/msp/idemix/idemix.go b/token/services/identity/msp/msp/idemix/idemix.go new file mode 100644 index 000000000..c8ffb2850 --- /dev/null +++ b/token/services/identity/msp/msp/idemix/idemix.go @@ -0,0 +1,148 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + bccsp "github.com/IBM/idemix/bccsp/types" + "github.com/hyperledger-labs/fabric-smart-client/pkg/utils/proto" + m "github.com/hyperledger/fabric-protos-go/msp" + "github.com/pkg/errors" +) + +type Identity struct { + Identity *MSPIdentity + NymPublicKey bccsp.Key + SerializedIdentity *m.SerializedIdentity + OU *m.OrganizationUnit + Role *m.MSPRole +} + +type Idemix struct { + Name string + Ipk []byte + Csp bccsp.BCCSP + IssuerPublicKey bccsp.Key + RevocationPK bccsp.Key + Epoch int + VerType bccsp.VerificationType + NymEID []byte + RhNym []byte +} + +func (c *Idemix) Deserialize(raw []byte, checkValidity bool) (*Identity, error) { + return c.DeserializeAgainstNymEID(raw, checkValidity, nil) +} + +func (c *Idemix) DeserializeAgainstNymEID(raw []byte, checkValidity bool, nymEID []byte) (*Identity, error) { + si := &m.SerializedIdentity{} + err := proto.Unmarshal(raw, si) + if err != nil { + return nil, errors.Wrap(err, "failed to unmarshal to msp.SerializedIdentity{}") + } + + serialized := new(m.SerializedIdemixIdentity) + err = proto.Unmarshal(si.IdBytes, serialized) + if err != nil { + return nil, errors.Wrap(err, "could not deserialize a SerializedIdemixIdentity") + } + if serialized.NymX == nil || serialized.NymY == nil { + return nil, errors.Errorf("unable to deserialize idemix identity: pseudonym is invalid") + } + + // Import NymPublicKey + var rawNymPublicKey []byte + rawNymPublicKey = append(rawNymPublicKey, serialized.NymX...) + rawNymPublicKey = append(rawNymPublicKey, serialized.NymY...) + NymPublicKey, err := c.Csp.KeyImport( + rawNymPublicKey, + &bccsp.IdemixNymPublicKeyImportOpts{Temporary: true}, + ) + if err != nil { + return nil, errors.WithMessage(err, "failed to import nym public key") + } + + // OU + ou := &m.OrganizationUnit{} + err = proto.Unmarshal(serialized.Ou, ou) + if err != nil { + return nil, errors.Wrap(err, "cannot deserialize the OU of the identity") + } + + // Role + role := &m.MSPRole{} + err = proto.Unmarshal(serialized.Role, role) + if err != nil { + return nil, errors.Wrap(err, "cannot deserialize the role of the identity") + } + + idemix := c + if len(nymEID) != 0 { + idemix = &Idemix{ + Name: c.Name, + Ipk: c.Ipk, + Csp: c.Csp, + IssuerPublicKey: c.IssuerPublicKey, + RevocationPK: c.RevocationPK, + Epoch: c.Epoch, + VerType: c.VerType, + NymEID: nymEID, + } + } + + id, err := NewMSPIdentityWithVerType( + idemix, + NymPublicKey, + role, + ou, + serialized.Proof, + c.VerType, + ) + if err != nil { + return nil, errors.Wrap(err, "cannot deserialize") + } + if checkValidity { + if err := id.Validate(); err != nil { + return nil, errors.Wrap(err, "cannot deserialize, invalid identity") + } + } + + return &Identity{ + Identity: id, + NymPublicKey: NymPublicKey, + SerializedIdentity: si, + OU: ou, + Role: role, + }, nil +} + +func (c *Idemix) DeserializeAuditInfo(raw []byte) (*AuditInfo, error) { + ai, err := DeserializeAuditInfo(raw) + if err != nil { + return nil, errors.Wrapf(err, "failed deserializing audit info [%s]", string(raw)) + } + ai.Csp = c.Csp + ai.IssuerPublicKey = c.IssuerPublicKey + return ai, nil +} + +type NymSignatureVerifier struct { + CSP bccsp.BCCSP + IPK bccsp.Key + NymPK bccsp.Key +} + +func (v *NymSignatureVerifier) Verify(message, sigma []byte) error { + _, err := v.CSP.Verify( + v.NymPK, + sigma, + message, + &bccsp.IdemixNymSignerOpts{ + IssuerPK: v.IPK, + }, + ) + return err +} diff --git a/token/services/identity/msp/msp/idemix/loader.go b/token/services/identity/msp/msp/idemix/loader.go new file mode 100644 index 000000000..2100b3a0b --- /dev/null +++ b/token/services/identity/msp/msp/idemix/loader.go @@ -0,0 +1,71 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "os" + "path/filepath" + + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/core/generic/config" + "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/core/generic/msp/driver" + msp2 "github.com/hyperledger/fabric/msp" + "github.com/pkg/errors" +) + +const ( + MSPType = "idemix" +) + +type IdentityLoader struct{} + +func (i *IdentityLoader) Load(manager driver.Manager, c config.MSP) error { + conf, err := msp2.GetLocalMspConfigWithType(manager.Config().TranslatePath(c.Path), nil, c.MSPID, c.MSPType) + if err != nil { + return errors.Wrapf(err, "failed reading idemix msp configuration from [%s]", manager.Config().TranslatePath(c.Path)) + } + provider, err := NewProviderWithAnyPolicy(conf, manager.ServiceProvider()) + if err != nil { + return errors.Wrapf(err, "failed instantiating idemix msp provider from [%s]", manager.Config().TranslatePath(c.Path)) + } + manager.AddDeserializer(provider) + cacheSize := manager.CacheSize() + if c.CacheSize > 0 { + cacheSize = c.CacheSize + } + manager.AddMSP(c.ID, c.MSPType, provider.EnrollmentID(), NewIdentityCache(provider.Identity, cacheSize, nil).Identity) + logger.Debugf("added %s msp for id %s with cache of size %d", c.MSPType, c.ID+"@"+provider.EnrollmentID(), cacheSize) + + return nil +} + +type FolderIdentityLoader struct { + *IdentityLoader +} + +func (f *FolderIdentityLoader) Load(manager driver.Manager, c config.MSP) error { + entries, err := os.ReadDir(manager.Config().TranslatePath(c.Path)) + if err != nil { + logger.Warnf("failed reading from [%s]: [%s]", manager.Config().TranslatePath(c.Path), err) + return errors.Wrapf(err, "failed reading from [%s]", manager.Config().TranslatePath(c.Path)) + } + for _, entry := range entries { + if !entry.IsDir() { + continue + } + id := entry.Name() + + if err := f.IdentityLoader.Load(manager, config.MSP{ + ID: id, + MSPType: MSPType, + MSPID: id, + Path: filepath.Join(manager.Config().TranslatePath(c.Path), id), + }); err != nil { + return errors.WithMessagef(err, "failed to load Idemix MSP configuration [%s]", id) + } + } + return nil +} diff --git a/token/services/identity/msp/msp/idemix/provider.go b/token/services/identity/msp/msp/idemix/provider.go new file mode 100644 index 000000000..f1897503f --- /dev/null +++ b/token/services/identity/msp/msp/idemix/provider.go @@ -0,0 +1,481 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + "fmt" + "reflect" + "strconv" + + "go.uber.org/zap/zapcore" + + "github.com/IBM/idemix" + bccsp "github.com/IBM/idemix/bccsp/types" + "github.com/IBM/idemix/idemixmsp" + math "github.com/IBM/mathlib" + "github.com/hyperledger-labs/fabric-smart-client/pkg/utils/proto" + driver2 "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/driver" + view2 "github.com/hyperledger-labs/fabric-smart-client/platform/view" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/driver" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/flogging" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/hash" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/kvs" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + m "github.com/hyperledger/fabric-protos-go/msp" + "github.com/pkg/errors" +) + +var logger = flogging.MustGetLogger("fabric-sdk.msp.idemix") + +const ( + EIDIndex = 2 + RHIndex = 3 +) + +const ( + Any bccsp.SignatureType = 100 +) + +type SignerService interface { + RegisterSigner(identity view.Identity, signer driver.Signer, verifier driver.Verifier) error +} + +func GetSignerService(ctx view2.ServiceProvider) SignerService { + s, err := ctx.GetService(reflect.TypeOf((*SignerService)(nil))) + if err != nil { + panic(err) + } + return s.(SignerService) +} + +type Provider struct { + *Idemix + userKey bccsp.Key + conf idemixmsp.IdemixMSPConfig + SignerService SignerService + + sigType bccsp.SignatureType + verType bccsp.VerificationType +} + +func NewProviderWithEidRhNymPolicy(conf1 *m.MSPConfig, sp view2.ServiceProvider) (*Provider, error) { + return NewProviderWithSigType(conf1, sp, bccsp.EidNymRhNym) +} + +func NewProviderWithStandardPolicy(conf1 *m.MSPConfig, sp view2.ServiceProvider) (*Provider, error) { + return NewProviderWithSigType(conf1, sp, bccsp.Standard) +} + +func NewProviderWithAnyPolicy(conf1 *m.MSPConfig, sp view2.ServiceProvider) (*Provider, error) { + return NewProviderWithSigType(conf1, sp, Any) +} + +func NewProviderWithAnyPolicyAndCurve(conf1 *m.MSPConfig, sp view2.ServiceProvider, curveID math.CurveID) (*Provider, error) { + cryptoProvider, err := NewKSVBCCSP(kvs.GetService(sp), curveID, false) + if err != nil { + return nil, err + } + return NewProvider(conf1, GetSignerService(sp), Any, cryptoProvider) +} + +func NewProviderWithSigType(conf1 *m.MSPConfig, sp view2.ServiceProvider, sigType bccsp.SignatureType) (*Provider, error) { + cryptoProvider, err := NewKSVBCCSP(kvs.GetService(sp), math.FP256BN_AMCL, false) + if err != nil { + return nil, err + } + return NewProvider(conf1, GetSignerService(sp), sigType, cryptoProvider) +} + +func NewProviderWithSigTypeAncCurve(conf1 *m.MSPConfig, sp view2.ServiceProvider, sigType bccsp.SignatureType, curveID math.CurveID) (*Provider, error) { + cryptoProvider, err := NewKSVBCCSP(kvs.GetService(sp), curveID, false) + if err != nil { + return nil, err + } + return NewProvider(conf1, GetSignerService(sp), sigType, cryptoProvider) +} + +func NewProvider(conf1 *m.MSPConfig, signerService SignerService, sigType bccsp.SignatureType, cryptoProvider bccsp.BCCSP) (*Provider, error) { + logger.Debugf("Setting up Idemix-based MSP instance") + + if conf1 == nil { + return nil, errors.Errorf("setup error: nil conf reference") + } + + var conf idemixmsp.IdemixMSPConfig + err := proto.Unmarshal(conf1.Config, &conf) + if err != nil { + return nil, errors.Wrap(err, "failed unmarshalling idemix provider config") + } + + logger.Debugf("Setting up Idemix MSP instance %s", conf.Name) + + // Import Issuer Public Key + issuerPublicKey, err := cryptoProvider.KeyImport( + conf.Ipk, + &bccsp.IdemixIssuerPublicKeyImportOpts{ + Temporary: true, + AttributeNames: []string{ + idemix.AttributeNameOU, + idemix.AttributeNameRole, + idemix.AttributeNameEnrollmentId, + idemix.AttributeNameRevocationHandle, + }, + }) + if err != nil { + return nil, err + } + + // Import revocation public key + RevocationPublicKey, err := cryptoProvider.KeyImport( + conf.RevocationPk, + &bccsp.IdemixRevocationPublicKeyImportOpts{Temporary: true}, + ) + if err != nil { + return nil, errors.WithMessage(err, "failed to import revocation public key") + } + + if conf.Signer == nil { + // No credential in config, so we don't setup a default signer + return nil, errors.Errorf("no signer information found") + } + + var userKey bccsp.Key + if len(conf.Signer.Sk) != 0 && len(conf.Signer.Cred) != 0 { + // A credential is present in the config, so we set up a default signer + logger.Debugf("the signer contains key material, load it") + + // Import User secret key + userKey, err = cryptoProvider.KeyImport(conf.Signer.Sk, &bccsp.IdemixUserSecretKeyImportOpts{Temporary: true}) + if err != nil { + return nil, errors.WithMessage(err, "failed importing signer secret key") + } + + // Verify credential + role := &m.MSPRole{ + MspIdentifier: conf.Name, + Role: m.MSPRole_MEMBER, + } + if CheckRole(int(conf.Signer.Role), ADMIN) { + role.Role = m.MSPRole_ADMIN + } + valid, err := cryptoProvider.Verify( + userKey, + conf.Signer.Cred, + nil, + &bccsp.IdemixCredentialSignerOpts{ + IssuerPK: issuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute, Value: []byte(conf.Signer.OrganizationalUnitIdentifier)}, + {Type: bccsp.IdemixIntAttribute, Value: GetIdemixRoleFromMSPRole(role)}, + {Type: bccsp.IdemixBytesAttribute, Value: []byte(conf.Signer.EnrollmentId)}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + }, + ) + if err != nil || !valid { + return nil, errors.WithMessage(err, "credential is not cryptographically valid") + } + } else { + logger.Debugf("the signer does not contain full key material [cred=%d,sk=%d]", len(conf.Signer.Cred), len(conf.Signer.Sk)) + } + + var verType bccsp.VerificationType + switch sigType { + case bccsp.Standard: + verType = bccsp.ExpectStandard + case bccsp.EidNymRhNym: + verType = bccsp.ExpectEidNymRhNym + case Any: + verType = bccsp.BestEffort + default: + panic("invalid sig type") + } + if verType == bccsp.BestEffort { + sigType = bccsp.Standard + } + + return &Provider{ + Idemix: &Idemix{ + Name: conf.Name, + Csp: cryptoProvider, + IssuerPublicKey: issuerPublicKey, + RevocationPK: RevocationPublicKey, + Epoch: 0, + VerType: verType, + }, + userKey: userKey, + conf: conf, + SignerService: signerService, + sigType: sigType, + verType: verType, + }, nil +} + +func (p *Provider) Identity(opts *driver2.IdentityOptions) (view.Identity, []byte, error) { + // Derive NymPublicKey + nymKey, err := p.Csp.KeyDeriv( + p.userKey, + &bccsp.IdemixNymKeyDerivationOpts{ + Temporary: false, + IssuerPK: p.IssuerPublicKey, + }, + ) + if err != nil { + return nil, nil, errors.WithMessage(err, "failed deriving nym") + } + NymPublicKey, err := nymKey.PublicKey() + if err != nil { + return nil, nil, errors.Wrapf(err, "failed getting public nym key") + } + + role := &m.MSPRole{ + MspIdentifier: p.Name, + Role: m.MSPRole_MEMBER, + } + if CheckRole(int(p.conf.Signer.Role), ADMIN) { + role.Role = m.MSPRole_ADMIN + } + + ou := &m.OrganizationUnit{ + MspIdentifier: p.Name, + OrganizationalUnitIdentifier: p.conf.Signer.OrganizationalUnitIdentifier, + CertifiersIdentifier: p.IssuerPublicKey.SKI(), + } + + enrollmentID := p.conf.Signer.EnrollmentId + rh := p.conf.Signer.RevocationHandle + sigType := p.sigType + var signerMetadata *bccsp.IdemixSignerMetadata + if opts != nil { + if opts.EIDExtension { + sigType = bccsp.EidNymRhNym + } + if len(opts.AuditInfo) != 0 { + ai, err := p.DeserializeAuditInfo(opts.AuditInfo) + if err != nil { + return nil, nil, err + } + + signerMetadata = &bccsp.IdemixSignerMetadata{ + EidNymAuditData: ai.EidNymAuditData, + RhNymAuditData: ai.RhNymAuditData, + } + } + } + + // Create the cryptographic evidence that this identity is valid + sigOpts := &bccsp.IdemixSignerOpts{ + Credential: p.conf.Signer.Cred, + Nym: nymKey, + IssuerPK: p.IssuerPublicKey, + Attributes: []bccsp.IdemixAttribute{ + {Type: bccsp.IdemixBytesAttribute}, + {Type: bccsp.IdemixIntAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + {Type: bccsp.IdemixHiddenAttribute}, + }, + RhIndex: RHIndex, + EidIndex: EIDIndex, + CRI: p.conf.Signer.CredentialRevocationInformation, + SigType: sigType, + Metadata: signerMetadata, + } + proof, err := p.Csp.Sign( + p.userKey, + nil, + sigOpts, + ) + if err != nil { + return nil, nil, errors.WithMessage(err, "Failed to setup cryptographic proof of identity") + } + + // Set up default signer + id, err := NewMSPIdentityWithVerType(p.Idemix, NymPublicKey, role, ou, proof, p.verType) + if err != nil { + return nil, nil, err + } + sID := &MSPSigningIdentity{ + MSPIdentity: id, + Cred: p.conf.Signer.Cred, + UserKey: p.userKey, + NymKey: nymKey, + EnrollmentId: enrollmentID, + } + raw, err := sID.Serialize() + if err != nil { + return nil, nil, err + } + + if p.SignerService != nil { + if err := p.SignerService.RegisterSigner(raw, sID, sID); err != nil { + return nil, nil, err + } + } + + var infoRaw []byte + switch sigType { + case bccsp.Standard: + infoRaw = nil + case bccsp.EidNymRhNym: + auditInfo := &AuditInfo{ + Csp: p.Csp, + IssuerPublicKey: p.IssuerPublicKey, + EidNymAuditData: sigOpts.Metadata.EidNymAuditData, + RhNymAuditData: sigOpts.Metadata.RhNymAuditData, + Attributes: [][]byte{ + []byte(p.conf.Signer.OrganizationalUnitIdentifier), + []byte(strconv.Itoa(GetIdemixRoleFromMSPRole(role))), + []byte(enrollmentID), + []byte(rh), + }, + } + if logger.IsEnabledFor(zapcore.DebugLevel) { + logger.Debugf("new idemix identity generated with [%s:%s]", enrollmentID, hash.Hashable(auditInfo.Attributes[3]).String()) + } + infoRaw, err = auditInfo.Bytes() + if err != nil { + return nil, nil, err + } + default: + panic("invalid sig type") + } + return raw, infoRaw, nil +} + +func (p *Provider) IsRemote() bool { + return p.userKey == nil +} + +func (p *Provider) DeserializeVerifier(raw []byte) (driver.Verifier, error) { + r, err := p.Deserialize(raw, true) + if err != nil { + return nil, err + } + + return r.Identity, nil +} + +func (p *Provider) DeserializeSigner(raw []byte) (driver.Signer, error) { + r, err := p.Deserialize(raw, true) + if err != nil { + return nil, err + } + + nymKey, err := p.Csp.GetKey(r.NymPublicKey.SKI()) + if err != nil { + return nil, errors.Wrap(err, "cannot find nym secret key") + } + + si := &MSPSigningIdentity{ + MSPIdentity: r.Identity, + Cred: p.conf.Signer.Cred, + UserKey: p.userKey, + NymKey: nymKey, + EnrollmentId: p.conf.Signer.EnrollmentId, + } + msg := []byte("hello world!!!") + sigma, err := si.Sign(msg) + if err != nil { + return nil, errors.Wrap(err, "failed generating verification signature") + } + if err := si.Verify(msg, sigma); err != nil { + return nil, errors.Wrap(err, "failed verifying verification signature") + } + return si, nil +} + +func (p *Provider) Info(raw []byte, auditInfo []byte) (string, error) { + r, err := p.Deserialize(raw, true) + if err != nil { + return "", err + } + + eid := "" + if len(auditInfo) != 0 { + ai := &AuditInfo{ + Csp: p.Csp, + IssuerPublicKey: p.IssuerPublicKey, + } + if err := ai.FromBytes(auditInfo); err != nil { + return "", err + } + if err := ai.Match(raw); err != nil { + return "", err + } + eid = ai.EnrollmentID() + } + + return fmt.Sprintf("MSP.Idemix: [%s][%s][%s][%s][%s]", eid, view.Identity(raw).UniqueID(), r.SerializedIdentity.Mspid, r.OU.OrganizationalUnitIdentifier, r.Role.Role.String()), nil +} + +func (p *Provider) String() string { + return fmt.Sprintf("Idemix Provider [%s]", hash.Hashable(p.Ipk).String()) +} + +func (p *Provider) EnrollmentID() string { + return p.conf.Signer.EnrollmentId +} + +func (p *Provider) DeserializeSigningIdentity(raw []byte) (driver.SigningIdentity, error) { + si := &m.SerializedIdentity{} + err := proto.Unmarshal(raw, si) + if err != nil { + return nil, errors.Wrap(err, "failed to unmarshal to msp.SerializedIdentity{}") + } + + serialized := new(m.SerializedIdemixIdentity) + err = proto.Unmarshal(si.IdBytes, serialized) + if err != nil { + return nil, errors.Wrap(err, "could not deserialize a SerializedIdemixIdentity") + } + if serialized.NymX == nil || serialized.NymY == nil { + return nil, errors.Errorf("unable to deserialize idemix identity: pseudonym is invalid") + } + + // Import NymPublicKey + var rawNymPublicKey []byte + rawNymPublicKey = append(rawNymPublicKey, serialized.NymX...) + rawNymPublicKey = append(rawNymPublicKey, serialized.NymY...) + NymPublicKey, err := p.Csp.KeyImport( + rawNymPublicKey, + &bccsp.IdemixNymPublicKeyImportOpts{Temporary: true}, + ) + if err != nil { + return nil, errors.WithMessage(err, "failed to import nym public key") + } + + // OU + ou := &m.OrganizationUnit{} + err = proto.Unmarshal(serialized.Ou, ou) + if err != nil { + return nil, errors.Wrap(err, "cannot deserialize the OU of the identity") + } + + // Role + role := &m.MSPRole{} + err = proto.Unmarshal(serialized.Role, role) + if err != nil { + return nil, errors.Wrap(err, "cannot deserialize the role of the identity") + } + + id, _ := NewMSPIdentityWithVerType(p.Idemix, NymPublicKey, role, ou, serialized.Proof, p.verType) + if err := id.Validate(); err != nil { + return nil, errors.Wrap(err, "cannot deserialize, invalid identity") + } + nymKey, err := p.Csp.GetKey(NymPublicKey.SKI()) + if err != nil { + return nil, errors.Wrap(err, "cannot find nym secret key") + } + + return &MSPSigningIdentity{ + MSPIdentity: id, + Cred: p.conf.Signer.Cred, + UserKey: p.userKey, + NymKey: nymKey, + EnrollmentId: p.conf.Signer.EnrollmentId, + }, nil +} diff --git a/token/services/identity/msp/msp/idemix/provider_test.go b/token/services/identity/msp/msp/idemix/provider_test.go new file mode 100644 index 000000000..42574f4a4 --- /dev/null +++ b/token/services/identity/msp/msp/idemix/provider_test.go @@ -0,0 +1,414 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix_test + +import ( + "strings" + "testing" + + bccsp "github.com/IBM/idemix/bccsp/types" + math "github.com/IBM/mathlib" + driver2 "github.com/hyperledger-labs/fabric-smart-client/platform/fabric/driver" + sig2 "github.com/hyperledger-labs/fabric-smart-client/platform/view/core/sig" + _ "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/db/driver/memory" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/kvs" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/kvs/mock" + registry2 "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/registry" + idemix2 "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/msp/msp/idemix" + msp2 "github.com/hyperledger/fabric/msp" + "github.com/stretchr/testify/assert" +) + +func TestProvider(t *testing.T) { + registry := registry2.New() + + kvss, err := kvs.NewWithConfig(registry, "memory", "", &mock.ConfigProvider{}) + assert.NoError(t, err) + assert.NoError(t, registry.RegisterService(kvss)) + sigService := sig2.NewSignService(registry, nil, kvss) + assert.NoError(t, registry.RegisterService(sigService)) + + config, err := msp2.GetLocalMspConfigWithType("./testdata/idemix", nil, "idemix", "idemix") + assert.NoError(t, err) + + p, err := idemix2.NewProviderWithEidRhNymPolicy(config, registry) + assert.NoError(t, err) + assert.NotNil(t, p) + + p, err = idemix2.NewProviderWithSigType(config, registry, bccsp.Standard) + assert.NoError(t, err) + assert.NotNil(t, p) + + p, err = idemix2.NewProviderWithSigType(config, registry, bccsp.EidNymRhNym) + assert.NoError(t, err) + assert.NotNil(t, p) +} + +func TestIdentityWithEidRhNymPolicy(t *testing.T) { + registry := registry2.New() + + kvss, err := kvs.NewWithConfig(registry, "memory", "", &mock.ConfigProvider{}) + assert.NoError(t, err) + assert.NoError(t, registry.RegisterService(kvss)) + sigService := sig2.NewSignService(registry, nil, kvss) + assert.NoError(t, registry.RegisterService(sigService)) + + config, err := msp2.GetLocalMspConfigWithType("./testdata/idemix", nil, "idemix", "idemix") + assert.NoError(t, err) + + p, err := idemix2.NewProviderWithEidRhNymPolicy(config, registry) + assert.NoError(t, err) + assert.NotNil(t, p) + + id, audit, err := p.Identity(nil) + assert.NoError(t, err) + assert.NotNil(t, id) + assert.NotNil(t, audit) + info, err := p.Info(id, audit) + assert.NoError(t, err) + assert.True(t, strings.HasPrefix(info, "MSP.Idemix: [alice]")) + assert.True(t, strings.HasSuffix(info, "[idemix][idemixorg.example.com][ADMIN]")) + + auditInfo, err := p.DeserializeAuditInfo(audit) + assert.NoError(t, err) + assert.NoError(t, auditInfo.Match(id)) + + signer, err := p.DeserializeSigner(id) + assert.NoError(t, err) + verifier, err := p.DeserializeVerifier(id) + assert.NoError(t, err) + + sigma, err := signer.Sign([]byte("hello world!!!")) + assert.NoError(t, err) + assert.NoError(t, verifier.Verify([]byte("hello world!!!"), sigma)) + + p, err = idemix2.NewProviderWithAnyPolicy(config, registry) + assert.NoError(t, err) + assert.NotNil(t, p) + + id, audit, err = p.Identity(&driver2.IdentityOptions{EIDExtension: true}) + assert.NoError(t, err) + assert.NotNil(t, id) + assert.NotNil(t, audit) + info, err = p.Info(id, audit) + assert.NoError(t, err) + assert.True(t, strings.HasPrefix(info, "MSP.Idemix: [alice]")) + assert.True(t, strings.HasSuffix(info, "[idemix][idemixorg.example.com][ADMIN]")) + + auditInfo, err = p.DeserializeAuditInfo(audit) + assert.NoError(t, err) + assert.NoError(t, auditInfo.Match(id)) + + signer, err = p.DeserializeSigner(id) + assert.NoError(t, err) + verifier, err = p.DeserializeVerifier(id) + assert.NoError(t, err) + + sigma, err = signer.Sign([]byte("hello world!!!")) + assert.NoError(t, err) + assert.NoError(t, verifier.Verify([]byte("hello world!!!"), sigma)) +} + +func TestIdentityStandard(t *testing.T) { + registry := registry2.New() + + kvss, err := kvs.NewWithConfig(registry, "memory", "", &mock.ConfigProvider{}) + assert.NoError(t, err) + assert.NoError(t, registry.RegisterService(kvss)) + sigService := sig2.NewSignService(registry, nil, kvss) + assert.NoError(t, registry.RegisterService(sigService)) + + config, err := msp2.GetLocalMspConfigWithType("./testdata/idemix", nil, "idemix", "idemix") + assert.NoError(t, err) + + p, err := idemix2.NewProviderWithSigType(config, registry, bccsp.Standard) + assert.NoError(t, err) + assert.NotNil(t, p) + + id, audit, err := p.Identity(nil) + assert.NoError(t, err) + assert.NotNil(t, id) + assert.Nil(t, audit) + + signer, err := p.DeserializeSigner(id) + assert.NoError(t, err) + verifier, err := p.DeserializeVerifier(id) + assert.NoError(t, err) + + sigma, err := signer.Sign([]byte("hello world!!!")) + assert.NoError(t, err) + assert.NoError(t, verifier.Verify([]byte("hello world!!!"), sigma)) + + p, err = idemix2.NewProviderWithStandardPolicy(config, registry) + assert.NoError(t, err) + assert.NotNil(t, p) + + id, audit, err = p.Identity(nil) + assert.NoError(t, err) + assert.NotNil(t, id) + assert.Nil(t, audit) + + signer, err = p.DeserializeSigner(id) + assert.NoError(t, err) + verifier, err = p.DeserializeVerifier(id) + assert.NoError(t, err) + + sigma, err = signer.Sign([]byte("hello world!!!")) + assert.NoError(t, err) + assert.NoError(t, verifier.Verify([]byte("hello world!!!"), sigma)) + + p, err = idemix2.NewProviderWithAnyPolicy(config, registry) + assert.NoError(t, err) + assert.NotNil(t, p) + + id, audit, err = p.Identity(nil) + assert.NoError(t, err) + assert.NotNil(t, id) + assert.Nil(t, audit) + + signer, err = p.DeserializeSigner(id) + assert.NoError(t, err) + verifier, err = p.DeserializeVerifier(id) + assert.NoError(t, err) + + sigma, err = signer.Sign([]byte("hello world!!!")) + assert.NoError(t, err) + assert.NoError(t, verifier.Verify([]byte("hello world!!!"), sigma)) +} + +func TestAuditWithEidRhNymPolicy(t *testing.T) { + registry := registry2.New() + + kvss, err := kvs.NewWithConfig(registry, "memory", "", &mock.ConfigProvider{}) + assert.NoError(t, err) + assert.NoError(t, registry.RegisterService(kvss)) + sigService := sig2.NewSignService(registry, nil, kvss) + assert.NoError(t, registry.RegisterService(sigService)) + + config, err := msp2.GetLocalMspConfigWithType("./testdata/idemix", nil, "idemix", "idemix") + assert.NoError(t, err) + p, err := idemix2.NewProviderWithEidRhNymPolicy(config, registry) + assert.NoError(t, err) + assert.NotNil(t, p) + + config, err = msp2.GetLocalMspConfigWithType("./testdata/idemix2", nil, "idemix", "idemix") + assert.NoError(t, err) + p2, err := idemix2.NewProviderWithEidRhNymPolicy(config, registry) + assert.NoError(t, err) + assert.NotNil(t, p2) + + id, audit, err := p.Identity(nil) + assert.NoError(t, err) + assert.NotNil(t, id) + assert.NotNil(t, audit) + id2, audit2, err := p2.Identity(nil) + assert.NoError(t, err) + assert.NotNil(t, id2) + assert.NotNil(t, audit2) + + auditInfo, err := p.DeserializeAuditInfo(audit) + assert.NoError(t, err) + assert.NoError(t, auditInfo.Match(id)) + assert.Error(t, auditInfo.Match(id2)) + + auditInfo, err = p2.DeserializeAuditInfo(audit) + assert.NoError(t, err) + assert.NoError(t, auditInfo.FromBytes(audit2)) + assert.NoError(t, auditInfo.Match(id2)) + assert.Error(t, auditInfo.Match(id)) +} + +func TestProvider_DeserializeSigner(t *testing.T) { + registry := registry2.New() + + kvss, err := kvs.NewWithConfig(registry, "memory", "", &mock.ConfigProvider{}) + assert.NoError(t, err) + assert.NoError(t, registry.RegisterService(kvss)) + sigService := sig2.NewSignService(registry, nil, kvss) + assert.NoError(t, registry.RegisterService(sigService)) + + config, err := msp2.GetLocalMspConfigWithType("./testdata/sameissuer/idemix", nil, "idemix", "idemix") + assert.NoError(t, err) + p, err := idemix2.NewProviderWithEidRhNymPolicy(config, registry) + assert.NoError(t, err) + assert.NotNil(t, p) + + config, err = msp2.GetLocalMspConfigWithType("./testdata/sameissuer/idemix2", nil, "idemix", "idemix") + assert.NoError(t, err) + p2, err := idemix2.NewProviderWithEidRhNymPolicy(config, registry) + assert.NoError(t, err) + assert.NotNil(t, p2) + + id, _, err := p.Identity(nil) + assert.NoError(t, err) + + id2, _, err := p2.Identity(nil) + assert.NoError(t, err) + + // This must work + signer, err := p.DeserializeSigner(id) + assert.NoError(t, err) + verifier, err := p.DeserializeVerifier(id) + assert.NoError(t, err) + msg := []byte("Hello World!!!") + sigma, err := signer.Sign(msg) + assert.NoError(t, err) + assert.NoError(t, verifier.Verify(msg, sigma)) + + // Try to deserialize id2 with provider for id, must fail + signer, err = p.DeserializeSigner(id2) + assert.Error(t, err) + verifier, err = p.DeserializeVerifier(id2) + assert.NoError(t, err) + + // this must work + des, err := sig2.NewMultiplexDeserializer(registry) + assert.NoError(t, err) + des.AddDeserializer(p) + des.AddDeserializer(p2) + signer, err = des.DeserializeSigner(id) + assert.NoError(t, err) + verifier, err = des.DeserializeVerifier(id) + assert.NoError(t, err) + sigma, err = signer.Sign(msg) + assert.NoError(t, err) + assert.NoError(t, verifier.Verify(msg, sigma)) +} + +func TestIdentityFromFabricCA(t *testing.T) { + registry := registry2.New() + + kvss, err := kvs.NewWithConfig(registry, "memory", "", &mock.ConfigProvider{}) + assert.NoError(t, err) + assert.NoError(t, registry.RegisterService(kvss)) + sigService := sig2.NewSignService(registry, nil, kvss) + assert.NoError(t, registry.RegisterService(sigService)) + + config, err := idemix2.GetLocalMspConfigWithType("./testdata/charlie.ExtraId2", "charlie.ExtraId2") + assert.NoError(t, err) + + p, err := idemix2.NewProviderWithSigTypeAncCurve(config, registry, bccsp.Standard, math.BN254) + assert.NoError(t, err) + assert.NotNil(t, p) + + id, audit, err := p.Identity(nil) + assert.NoError(t, err) + assert.NotNil(t, id) + assert.Nil(t, audit) + + signer, err := p.DeserializeSigner(id) + assert.NoError(t, err) + verifier, err := p.DeserializeVerifier(id) + assert.NoError(t, err) + + sigma, err := signer.Sign([]byte("hello world!!!")) + assert.NoError(t, err) + assert.NoError(t, verifier.Verify([]byte("hello world!!!"), sigma)) + + p, err = idemix2.NewProviderWithSigTypeAncCurve(config, registry, bccsp.Standard, math.BN254) + assert.NoError(t, err) + assert.NotNil(t, p) + + id, audit, err = p.Identity(nil) + assert.NoError(t, err) + assert.NotNil(t, id) + assert.Nil(t, audit) + + signer, err = p.DeserializeSigner(id) + assert.NoError(t, err) + verifier, err = p.DeserializeVerifier(id) + assert.NoError(t, err) + + sigma, err = signer.Sign([]byte("hello world!!!")) + assert.NoError(t, err) + assert.NoError(t, verifier.Verify([]byte("hello world!!!"), sigma)) + + p, err = idemix2.NewProviderWithSigTypeAncCurve(config, registry, idemix2.Any, math.BN254) + assert.NoError(t, err) + assert.NotNil(t, p) + + id, audit, err = p.Identity(nil) + assert.NoError(t, err) + assert.NotNil(t, id) + assert.Nil(t, audit) + + signer, err = p.DeserializeSigner(id) + assert.NoError(t, err) + verifier, err = p.DeserializeVerifier(id) + assert.NoError(t, err) + + sigma, err = signer.Sign([]byte("hello world!!!")) + assert.NoError(t, err) + assert.NoError(t, verifier.Verify([]byte("hello world!!!"), sigma)) +} + +func TestIdentityFromFabricCAWithEidRhNymPolicy(t *testing.T) { + registry := registry2.New() + + kvss, err := kvs.NewWithConfig(registry, "memory", "", &mock.ConfigProvider{}) + assert.NoError(t, err) + assert.NoError(t, registry.RegisterService(kvss)) + sigService := sig2.NewSignService(registry, nil, kvss) + assert.NoError(t, registry.RegisterService(sigService)) + + config, err := idemix2.GetLocalMspConfigWithType("./testdata/charlie.ExtraId2", "charlie.ExtraId2") + assert.NoError(t, err) + + p, err := idemix2.NewProviderWithSigTypeAncCurve(config, registry, bccsp.EidNymRhNym, math.BN254) + assert.NoError(t, err) + assert.NotNil(t, p) + + // get an identity with its own audit info from the provider + // id is in its serialized form + id, audit, err := p.Identity(nil) + assert.NoError(t, err) + assert.NotNil(t, id) + assert.NotNil(t, audit) + info, err := p.Info(id, audit) + assert.NoError(t, err) + assert.True(t, strings.HasPrefix(info, "MSP.Idemix: [charlie.ExtraId2]")) + assert.True(t, strings.HasSuffix(info, "[charlie.ExtraId2][][MEMBER]")) + + auditInfo, err := p.DeserializeAuditInfo(audit) + assert.NoError(t, err) + assert.NoError(t, auditInfo.Match(id)) + + signer, err := p.DeserializeSigner(id) + assert.NoError(t, err) + verifier, err := p.DeserializeVerifier(id) + assert.NoError(t, err) + + sigma, err := signer.Sign([]byte("hello world!!!")) + assert.NoError(t, err) + assert.NoError(t, verifier.Verify([]byte("hello world!!!"), sigma)) + + p, err = idemix2.NewProviderWithAnyPolicyAndCurve(config, registry, math.BN254) + assert.NoError(t, err) + assert.NotNil(t, p) + + id, audit, err = p.Identity(&driver2.IdentityOptions{EIDExtension: true}) + assert.NoError(t, err) + assert.NotNil(t, id) + assert.NotNil(t, audit) + info, err = p.Info(id, audit) + assert.NoError(t, err) + assert.True(t, strings.HasPrefix(info, "MSP.Idemix: [charlie.ExtraId2]")) + assert.True(t, strings.HasSuffix(info, "[charlie.ExtraId2][][MEMBER]")) + + auditInfo, err = p.DeserializeAuditInfo(audit) + assert.NoError(t, err) + assert.NoError(t, auditInfo.Match(id)) + + signer, err = p.DeserializeSigner(id) + assert.NoError(t, err) + verifier, err = p.DeserializeVerifier(id) + assert.NoError(t, err) + + sigma, err = signer.Sign([]byte("hello world!!!")) + assert.NoError(t, err) + assert.NoError(t, verifier.Verify([]byte("hello world!!!"), sigma)) +} diff --git a/token/services/identity/msp/msp/idemix/roles.go b/token/services/identity/msp/msp/idemix/roles.go new file mode 100644 index 000000000..e6459a6ef --- /dev/null +++ b/token/services/identity/msp/msp/idemix/roles.go @@ -0,0 +1,72 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package idemix + +import ( + m "github.com/hyperledger/fabric-protos-go/msp" +) + +// Role : Represents a IdemixRole +type Role int32 + +// The expected roles are 4; We can combine them using a bitmask +const ( + MEMBER Role = 1 + ADMIN Role = 2 + CLIENT Role = 4 + PEER Role = 8 + // Next role values: 16, 32, 64 ... +) + +func (role Role) getValue() int { + return int(role) +} + +// CheckRole Prove that the desired role is contained or not in the bitmask +func CheckRole(bitmask int, role Role) bool { + return (bitmask & role.getValue()) == role.getValue() +} + +// GetRoleMaskFromIdemixRoles Receive a list of roles to combine in a single bitmask +func GetRoleMaskFromIdemixRoles(roles []Role) int { + mask := 0 + for _, role := range roles { + mask = mask | role.getValue() + } + return mask +} + +// GetRoleMaskFromIdemixRole return a bitmask for one role +func GetRoleMaskFromIdemixRole(role Role) int { + return GetRoleMaskFromIdemixRoles([]Role{role}) +} + +// GetIdemixRoleFromMSPRole gets a MSP Role type and returns the integer value +func GetIdemixRoleFromMSPRole(role *m.MSPRole) int { + return GetIdemixRoleFromMSPRoleType(role.GetRole()) +} + +// GetIdemixRoleFromMSPRoleType gets a MSP role type and returns the integer value +func GetIdemixRoleFromMSPRoleType(rtype m.MSPRole_MSPRoleType) int { + return GetIdemixRoleFromMSPRoleValue(int(rtype)) +} + +// GetIdemixRoleFromMSPRoleValue Receives a MSP role value and returns the idemix equivalent +func GetIdemixRoleFromMSPRoleValue(role int) int { + switch role { + case int(m.MSPRole_ADMIN): + return ADMIN.getValue() + case int(m.MSPRole_CLIENT): + return CLIENT.getValue() + case int(m.MSPRole_MEMBER): + return MEMBER.getValue() + case int(m.MSPRole_PEER): + return PEER.getValue() + default: + return MEMBER.getValue() + } +} diff --git a/token/services/identity/msp/msp/idemix/testdata/charlie.ExtraId2/IssuerPublicKey b/token/services/identity/msp/msp/idemix/testdata/charlie.ExtraId2/IssuerPublicKey new file mode 100644 index 0000000000000000000000000000000000000000..12682ebdbab2af9d49f49e316f6799fe19251eec GIT binary patch literal 843 zcmV-R1GM}K0#8*61X6EoWeN;MZgOvIY;9$3bV)=C5K?7!Z)0I}X>V>wVQyq>WfDXR zAOmd_7Y-4Hk;+VsoCZhDBvyH58Ky_c6jG>o?CFmrXA&R^_W`R~hPo~I(GlwTv2^A* z0ZVLsZ|gX!b#et&y~0HrL<%4P{7RE2iLCALGAutWPq;g!EMPo##_X_@x@5J?^#hO+ zAQRWvlC|X;)WvIuvz+3i6MUO=#G z7*R?8sMqx!n#X{OYUB{Jyb>TU>FN4$SV2=NaOU0vX@5NB3IJ6i6z5~7l=dqD1(qiw zL<%4P!5^s^FHs%Lvrs#1k(}z^%MsW|tPK}wdX~1zBw<1IeASbbl%MiBB%Zo`x?jx(LHPQB}zgrqujy?H_kxAF+ zZ4w|S_Mh%tks?8)wZM8_fLTcI-6b8Xzd$gT2jzlf zLxE$o5oEuDl5{9^Mfo~A@eBGAAR}Is9kPF48A?;zA{kdQ_!JyJ!yU-Ax8uvU8%BAK zAS#Ff3Lq0)tJ#&}2Oc<7K6``#{Otf*`8lEowlp|}KxOAMU+xkhAUdHlRO{6TNC>U) zta3XQj(^2GW^2NG^A#VwgNq3v8XzibpJTzDE`A1~+K40?k0`g2O?yfJ{}=b!EjbB5 zLMb93A$9~&CcKT9!_7C%&Mo}OHE?W*zndb%zi}+HXY|QpGDHd>8simvFoaxb$JWca zKX|JjX&8;peWbe)k>PdfB5$M>5+D_}I$+)3bxspr9)ZiBqsmGh&e-BmXLc0P6Mxs( zthhQv3Lq{i8-azbufonaNN-X42|sP0Z`u#E13qI4eE~vsudWgx4z(Y)0^x?tl#R-t z^p^KO+Qu@vGPM)Pcv~8GqZF7nLLeUuo;dvRhB_irR=@T5rl}6tAKAB-Yz9hMfrtTF z7T`)CC#{lV>wVQyq>WfDXR zAZDRF!xtw1PidCd`K<$bO8UjEWwO+mFkn&%@dGJd?-C$ zhz)$tGALZS@+z*F!=;vL{}LeYaZEu&m*WBDM=*U@L^`eq@djSu?LAk>X^Mtlh>XP| zL<%5##$+Bmb49~=wQQEaq3)2EX}}y#k}|1A0hwTJD=?K3Af_{<=nmJY*S=saA({Bt_$w)_A$rFF%V(N$)7L%ttkI$B;BLJ)+ zGsGewzjDz3KsPF#P|838Sw<0iPtbqmR*ZdQR`?!KQfoMLGDHd>9IVg%cQqmr1o-h< zZnOySx(}F(oB7AZr|Fh|Pv~ib5+F3@lFmPuo=XK0O-hezju9VRfvgb>iA%IdSs^<}2zL zDVR^%`=<+^4%OdBG_W^@i`XIVLLh-aD^Qo2wTh{>tm<|Y>&3`gDes`L2*(%`y}ulT z1`A3cryeO!KC?FF8Tw|RtX~U>J61Z=M!h`r>DozWL>nwMQXm-`U*L232uV8;KbZP@ V9MrbXulEPt!==odU9hKgFN?iYKkkV-b zn<*bY>Sx$3WysV&J7;!~mcqp|degV=Dz*FA`>w3Ad2b~%JKLSN!Pd8hO0P}(U*7Rf zOMwADVA4|1zQJ~~o{wR@l>A|%{^iLl)ut<-xZS_TfAadm+Y-5X6SNd&J=%MH-`hja z(~fOVZ4KrVn-nNoP;8{ZZTWl_=iEZmVFz>gJg5R=)@ZzomA-AI*9y7loyi?_F z#iRBMrk$5E-Fe$;94=CX3$QXxOP!1t$BN_!I{JYI5cY20^5{lAg| zYslk0mpcwkaC*1QQ?qW<{BECp4NX!rHa`$*6!colGFvL$AeG4=rQl`M&RZW?d2OGV z^;;}WUYqmhe%5OKZ(D4qUl*vBZanAW(ImnC?Ua+_lD|RXUY$&a4Bb0Q<~3aYxBa7o xj*UZ+=$lrlS$-1)%GLx_PhI=@q_gdgUry2?#T~CqP1?4xC2pBy#cXJ5008JCBC7xZ literal 0 HcmV?d00001 diff --git a/token/services/identity/msp/msp/idemix/testdata/idemix2/msp/IssuerPublicKey b/token/services/identity/msp/msp/idemix/testdata/idemix2/msp/IssuerPublicKey new file mode 100644 index 0000000000000000000000000000000000000000..adcd955cad7c82e0869dbc76715118724efecf60 GIT binary patch literal 843 zcmV-R1GM}K0#8*61X6EoWeN;MZgOvIY;9$3bV)=C5K?7!Z)0I}X>V>wVQyq>WfDXR zAZDRF!xtw1PidCd`K<$bO8UjEWwO+mFkn&%@dGJd?-C$ zhz)$tGALZS@+z*F!=;vL{}LeYaZEu&m*WBDM=*U@L^`eq@djSu?LAk>X^Mtlh>XP| zL<%5##$+Bmb49~=wQQEaq3)2EX}}y#k}|1A0hwTJD=?K3Af_{<=nmJY*S=saA({Bt_$w)_A$rFF%V(N$)7L%ttkI$B;BLJ)+ zGsGewzjDz3KsPF#P|838Sw<0iPtbqmR*ZdQR`?!KQfoMLGDHd>9IVg%cQqmr1o-h< zZnOySx(}F(oB7AZr|Fh|Pv~ib5+F3@lFmPuo=XK0O-hezju9VRfvgb>iA%IdSs^<}2zL zDVR^%`=<+^4%OdBG_W^@i`XIVLLh-aD^Qo2wTh{>tm<|Y>&3`gDes`L2*(%`y}ulT z1`A3cryeO!KC?FF8Tw|RtX~U>J61Z=M!h`r>DozWL>nwMQXm-`U*L232uV8;KbZP@ V9MrbfQ-vAyNV1 z6OK48Dtc9v8y{0Gq;UVtg{FS*WkNqei7!o@Rs)3@#_wfornuB@_oZzVH3+nu+;*0+U9uTA@3-tkUL zfdM~Y(o$&LW`5((rDL~}BcDw(6kaIK`+Vw??W#L`<+y$R&O6%kQcGc}&~0_cvkIAZ z-eMnTOnk0A|D(>U>*|Sr;wD{M_1WFwppe1`m47E6uzhg2yU<^_?a*t@;%!%yoeHp;lusE5bQZwT*Hxm)q5 z{eo%drOdY`i|ewqr4$Mj+@2qGTD-ZeoVQfS&o1!&X_eC6#0Za@6iuTQ?&0lST~p7pS5n;ry0>4y{+!E*D@Ity4o-85<8&(<@4-= vX46e?*t=ACJYZ@zzqiNb|KF9-E3S40aN2p)^G{FwGOaN%^sW`Np_u^y|4Aou literal 0 HcmV?d00001 diff --git a/token/services/identity/msp/msp/idemix/testdata/sameissuer/idemix/msp/IssuerPublicKey b/token/services/identity/msp/msp/idemix/testdata/sameissuer/idemix/msp/IssuerPublicKey new file mode 100644 index 0000000000000000000000000000000000000000..c0fe761fb2ce71596d3f9359c4cf3ec93dec50ea GIT binary patch literal 843 zcmV-R1GM}K0#8*61X6EoWeN;MZgOvIY;9$3bV)=C5K?7!Z)0I}X>V>wVQyq>WfDXR zAUZ``zZQ0H0gt_1)aRA!Bleeais2^XI~r0z5)lz;WD+2>ocu4g{DCp1NnaNgTfy`= zyheD#xu>hPsZ|2o!K(opL<%4XUT~SH+Gy&tF}O~+(dTc|N2?Qs>@Fy_Xiz#u7m!mD zAj**qYCbsagJQ^o&$&0a>HgI}0ASP~!7Gd19%-SUficVkSP~remmYR;2f;of zL<%6sdPF`xsrZOBtw`5tc^0yM)}+ zo8e68nM(2;XiCaU_fM8LEh0n;Alsr@*YENpyg2HI*Wi1D^``T$v_G0)( z_9}=03Lw9`o2^@x<6qQ_K>E2|!~SG6*z$l%@1-AnbP(o zl@bY}JQKkbHpvUde`eA{{rep^5+JN>gm{qaFu@E1dTlIKw4_gc)4)4D7eFvTzb0wsoBbmx6*#^AyXb z=vL|v884prl!fwY+k%CN9Gh)*LLk9Pyypp-wJ0T)uLqUi7q6}w@#3u1v!^_ZhDlz) z?1M@mvDJWeHVb%+6z>K%)wB_CfWw2IFd|b;+s6G#(ihSAQXp>{Rq>^%vax>kB;LlS VEtj=sLmKtg!i|!!LB`vCOLn@q0(qp#V?On@~4?3-#Y?>1)rJyeD z#3H16H*)bg9qiTsrmRnx!^M3Kn02Kkke8f3ocGX|)frMxX8S zHV8^wC||Uz>QJ&n?6gl>3K!4lP2ak!)b3;NyRypWy_L-DYKp8zFARvf}j~aLJDVkcl?$8 zr90EqW>K{E3g+1sm)2;Sr0p<&asTHG)~;C=QlgnDskxaI`96+Z4ZvD%=bKkpMWbM~1Y zrkVN?zQQUjKrO2u2VUFAtW|J)Y5Um~Cmh>Ty(!k+b?@I2>_7s41xdv literal 0 HcmV?d00001 diff --git a/token/services/identity/msp/msp/idemix/testdata/sameissuer/idemix2/msp/IssuerPublicKey b/token/services/identity/msp/msp/idemix/testdata/sameissuer/idemix2/msp/IssuerPublicKey new file mode 100644 index 0000000000000000000000000000000000000000..c0fe761fb2ce71596d3f9359c4cf3ec93dec50ea GIT binary patch literal 843 zcmV-R1GM}K0#8*61X6EoWeN;MZgOvIY;9$3bV)=C5K?7!Z)0I}X>V>wVQyq>WfDXR zAUZ``zZQ0H0gt_1)aRA!Bleeais2^XI~r0z5)lz;WD+2>ocu4g{DCp1NnaNgTfy`= zyheD#xu>hPsZ|2o!K(opL<%4XUT~SH+Gy&tF}O~+(dTc|N2?Qs>@Fy_Xiz#u7m!mD zAj**qYCbsagJQ^o&$&0a>HgI}0ASP~!7Gd19%-SUficVkSP~remmYR;2f;of zL<%6sdPF`xsrZOBtw`5tc^0yM)}+ zo8e68nM(2;XiCaU_fM8LEh0n;Alsr@*YENpyg2HI*Wi1D^``T$v_G0)( z_9}=03Lw9`o2^@x<6qQ_K>E2|!~SG6*z$l%@1-AnbP(o zl@bY}JQKkbHpvUde`eA{{rep^5+JN>gm{qaFu@E1dTlIKw4_gc)4)4D7eFvTzb0wsoBbmx6*#^AyXb z=vL|v884prl!fwY+k%CN9Gh)*LLk9Pyypp-wJ0T)uLqUi7q6}w@#3u1v!^_ZhDlz) z?1M@mvDJWeHVb%+6z>K%)wB_CfWw2IFd|b;+s6G#(ihSAQXp>{Rq>^%vax>kB;LlS VEtj=s(^G8RSM9j{uIOdB5YUuLxea0GTez57|3&wotnSwBTN!+> zIfkRh++Yna>&&&^gcNrEjSRW4!FP{QpzCF~C(E3+qa%(psaP*+>siUrKHfabb8d0r7T-Y-tAt-wRn$p zU6N7BtrL=s4`$rh7`4s(mX^ZBGkVjv?kctW*!!-mvUzVMGdtUzx53u8g-WkY`(NJi zPD_CSKVZ^Q=yRG^YUq38Osx8goBvnqdJ1Wls=hz_Yx05%?r+Q;8ebALW15h{LS}Z` zwJF=~d<}UixUw@QxvPpf?8h!H5A7#Q-`Mvk=SqoYrljU(R^%6@>!nsC<`(3n>Lusr zDlloW<)r4Nq!t-{W)$jR3E{R;>~5{yrn{Z zc7g9ttCaR8MtHpB+|s!3j{1Kk1=f(qdoFhzn&9+qnWtvmrup4I`x=_0W^8^S)F|k+ zmSwh7nn4Paf%5b01#&7?xsTl^EAL84(>u^(_omobWZ&<~-M_?2U409VXl0(3V{fkU z`ciVpZryq&1HaW7dUEN@_E{8#zSzGdT(RfC#xe&dhq)Zv3a