Skip to content

Commit 34904b3

Browse files
AkramBitaraaadir
authored andcommitted
Increase test coverage for idemix identity package
Signed-off-by: AkramBitar <akram@il.ibm.com>
1 parent 276d798 commit 34904b3

26 files changed

Lines changed: 4867 additions & 151 deletions

token/core/zkatdlog/nogh/v1/issue/issuer_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,8 @@ func prepareZKIssue(t *testing.T, bits uint64, curveID math.CurveID, numOutputs
231231
// commitments that serve as inputs to the prover/verifier in tests.
232232
func prepareInputsForZKIssue(pp *v1.PublicParams, numOutputs int) ([]*token.Metadata, []*math.G1) {
233233
values := make([]uint64, numOutputs)
234-
for i := range numOutputs {
235-
values[i] = uint64(i*10 + 10)
234+
for i := range values {
235+
values[i] = uint64(i)*10 + 10
236236
}
237237
curve := math.Curves[pp.Curve]
238238
rand, _ := curve.Rand()

token/core/zkatdlog/nogh/v1/transfer_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,10 @@ func newTransferEnv(benchmarkCase *benchmark2.Case, configurations *benchmark.Se
166166
return nil, err
167167
}
168168
outputs := make([]*token.Token, benchmarkCase.NumOutputs)
169-
for i := range benchmarkCase.NumOutputs {
169+
for i := range outputs {
170170
outputs[i] = &token.Token{
171171
Owner: ownerID,
172-
Quantity: token.NewQuantityFromUInt64(uint64(i*10 + 10)).Hex(),
172+
Quantity: token.NewQuantityFromUInt64(uint64(i)*10 + 10).Hex(),
173173
Type: "ABC",
174174
}
175175
}
@@ -178,8 +178,8 @@ func newTransferEnv(benchmarkCase *benchmark2.Case, configurations *benchmark.Se
178178
numInputs := benchmarkCase.NumInputs
179179
ids := make([]*token.ID, numInputs)
180180
values := make([]uint64, numInputs)
181-
for i := range numInputs {
182-
values[i] = uint64(i*10 + 10)
181+
for i := range values {
182+
values[i] = uint64(i)*10 + 10
183183
}
184184
baseTokens, metadata, err := v1token.GetTokensWithWitness(values, "ABC", pp.PedersenGenerators, math.Curves[pp.Curve])
185185
if err != nil {

token/core/zkatdlog/nogh/v1/validator/testutils/env.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,7 @@ func prepareTransfer(
454454
// prepare inputs
455455
inValues := make([]*math.Zr, benchCase.NumInputs)
456456
sumInputs := uint64(0)
457-
for i := range benchCase.NumInputs {
457+
for i := range inValues {
458458
v := uint64(i*10 + 500)
459459
sumInputs += v
460460
inValues[i] = c.NewZrFromUint64(v)

token/services/identity/idemix/cache/cache.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,26 @@ import (
1919

2020
var logger = logging.MustGetLogger()
2121

22+
// IdentityCacheBackendFunc generates an identity descriptor for given audit info.
2223
type IdentityCacheBackendFunc func(ctx context.Context, auditInfo []byte) (*idriver.IdentityDescriptor, error)
2324

25+
// IdentityCache provides a pre-provisioned cache of Idemix identities.
2426
type IdentityCache struct {
25-
once sync.Once
26-
backed IdentityCacheBackendFunc
27+
// Ensures provisioning starts once
28+
once sync.Once
29+
// Backend identity generator
30+
backed IdentityCacheBackendFunc
31+
// Audit info for cache provisioning
2732
auditInfo []byte
28-
29-
cache chan *idriver.IdentityDescriptor
33+
// Buffered channel of identities
34+
cache chan *idriver.IdentityDescriptor
35+
// Max wait time for cached identity
3036
cacheTimeout time.Duration
31-
37+
// Cache performance metrics
3238
metrics *Metrics
3339
}
3440

41+
// NewIdentityCache creates a new identity cache with specified size and backend.
3542
func NewIdentityCache(backed IdentityCacheBackendFunc, size int, auditInfo []byte, metrics *Metrics) *IdentityCache {
3643
logger.Debugf("new identity cache with size [%d]", size)
3744
ci := &IdentityCache{
@@ -45,6 +52,7 @@ func NewIdentityCache(backed IdentityCacheBackendFunc, size int, auditInfo []byt
4552
return ci
4653
}
4754

55+
// Identity retrieves an identity from cache or generates on-demand.
4856
func (c *IdentityCache) Identity(ctx context.Context, auditInfo []byte) (*idriver.IdentityDescriptor, error) {
4957
// Is the auditInfo equal to that used to fill the cache? If yes, use the cache
5058
if !bytes.Equal(auditInfo, c.auditInfo) {
@@ -63,6 +71,7 @@ func (c *IdentityCache) Identity(ctx context.Context, auditInfo []byte) (*idrive
6371
return c.fetchIdentityFromCache(ctx)
6472
}
6573

74+
// fetchIdentityFromCache retrieves identity from cache with timeout.
6675
func (c *IdentityCache) fetchIdentityFromCache(ctx context.Context) (*idriver.IdentityDescriptor, error) {
6776
var identityDescriptor *idriver.IdentityDescriptor
6877

@@ -105,6 +114,7 @@ func (c *IdentityCache) fetchIdentityFromCache(ctx context.Context) (*idriver.Id
105114
return identityDescriptor, nil
106115
}
107116

117+
// fetchIdentityFromBackend generates identity directly from backend, bypassing cache.
108118
func (c *IdentityCache) fetchIdentityFromBackend(ctx context.Context, auditInfo []byte) (*idriver.IdentityDescriptor, error) {
109119
logger.DebugfContext(ctx, "fetching identity from backend")
110120
identityDescriptor, err := c.backed(ctx, auditInfo)
@@ -116,6 +126,7 @@ func (c *IdentityCache) fetchIdentityFromBackend(ctx context.Context, auditInfo
116126
return identityDescriptor, nil
117127
}
118128

129+
// provisionIdentities continuously fills cache with pre-generated identities.
119130
func (c *IdentityCache) provisionIdentities() {
120131
count := 0
121132
ctx := context.Background()

token/services/identity/idemix/cache/cache_test.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ package cache
88

99
import (
1010
"context"
11+
"errors"
1112
"sync"
1213
"testing"
14+
"time"
1315

1416
"github.com/hyperledger-labs/fabric-smart-client/platform/view/services/metrics/disabled"
1517
"github.com/hyperledger-labs/fabric-token-sdk/token/driver"
@@ -18,6 +20,7 @@ import (
1820
"github.com/test-go/testify/require"
1921
)
2022

23+
// TestIdentityCache verifies basic cache functionality and identity retrieval.
2124
func TestIdentityCache(t *testing.T) {
2225
c := NewIdentityCache(func(context.Context, []byte) (*idriver.IdentityDescriptor, error) {
2326
return &idriver.IdentityDescriptor{
@@ -36,6 +39,7 @@ func TestIdentityCache(t *testing.T) {
3639
assert.Equal(t, []byte("audit"), identityDescriptor.AuditInfo)
3740
}
3841

42+
// TestIdentityCacheForRace tests concurrent cache access for thread-safety.
3943
func TestIdentityCacheForRace(t *testing.T) {
4044
c := NewIdentityCache(func(context.Context, []byte) (*idriver.IdentityDescriptor, error) {
4145
return &idriver.IdentityDescriptor{
@@ -61,3 +65,132 @@ func TestIdentityCacheForRace(t *testing.T) {
6165
}
6266
wg.Wait()
6367
}
68+
69+
// TestFetchIdentityFromBackend verifies backend fetch when audit info doesn't match.
70+
func TestFetchIdentityFromBackend(t *testing.T) {
71+
expectedIdentity := &idriver.IdentityDescriptor{
72+
Identity: []byte("backend identity"),
73+
AuditInfo: []byte("backend audit"),
74+
}
75+
76+
c := NewIdentityCache(func(ctx context.Context, auditInfo []byte) (*idriver.IdentityDescriptor, error) {
77+
return expectedIdentity, nil
78+
}, 10, []byte("cache audit"), NewMetrics(&disabled.Provider{}))
79+
80+
// Call with different audit info to trigger backend fetch
81+
identityDescriptor, err := c.Identity(context.Background(), []byte("different audit"))
82+
require.NoError(t, err)
83+
assert.Equal(t, expectedIdentity.Identity, identityDescriptor.Identity)
84+
assert.Equal(t, expectedIdentity.AuditInfo, identityDescriptor.AuditInfo)
85+
}
86+
87+
// TestFetchIdentityFromBackendError verifies error propagation from backend failures.
88+
func TestFetchIdentityFromBackendError(t *testing.T) {
89+
expectedErr := errors.New("backend error")
90+
91+
c := NewIdentityCache(func(ctx context.Context, auditInfo []byte) (*idriver.IdentityDescriptor, error) {
92+
return nil, expectedErr
93+
}, 10, []byte("cache audit"), NewMetrics(&disabled.Provider{}))
94+
95+
// Call with different audit info to trigger backend fetch
96+
_, err := c.Identity(context.Background(), []byte("different audit"))
97+
require.Error(t, err)
98+
assert.Equal(t, expectedErr, err)
99+
}
100+
101+
// TestFetchIdentityFromCacheTimeout verifies on-demand generation after cache timeout.
102+
func TestFetchIdentityFromCacheTimeout(t *testing.T) {
103+
callCount := make(chan struct{}, 10)
104+
c := NewIdentityCache(func(ctx context.Context, auditInfo []byte) (*idriver.IdentityDescriptor, error) {
105+
callCount <- struct{}{}
106+
// Simulate slow backend - not strictly needed for the test
107+
// time.Sleep(10 * time.Millisecond)
108+
return &idriver.IdentityDescriptor{
109+
Identity: []byte("timeout identity"),
110+
AuditInfo: []byte("timeout audit"),
111+
}, nil
112+
}, 0, nil, NewMetrics(&disabled.Provider{})) // cache size 0 to force timeout
113+
114+
// Set short timeout to trigger timeout path
115+
c.cacheTimeout = 1 * time.Millisecond
116+
117+
identityDescriptor, err := c.Identity(context.Background(), nil)
118+
require.NoError(t, err)
119+
assert.Equal(t, driver.Identity([]byte("timeout identity")), identityDescriptor.Identity)
120+
assert.Equal(t, []byte("timeout audit"), identityDescriptor.AuditInfo)
121+
assert.Len(t, callCount, 1)
122+
}
123+
124+
// TestFetchIdentityFromCacheTimeoutError verifies error handling after cache timeout.
125+
func TestFetchIdentityFromCacheTimeoutError(t *testing.T) {
126+
expectedErr := errors.New("timeout backend error")
127+
128+
c := NewIdentityCache(func(ctx context.Context, auditInfo []byte) (*idriver.IdentityDescriptor, error) {
129+
return nil, expectedErr
130+
}, 0, nil, NewMetrics(&disabled.Provider{}))
131+
132+
// Set short timeout to trigger timeout path
133+
c.cacheTimeout = 1 * time.Millisecond
134+
135+
_, err := c.Identity(context.Background(), nil)
136+
require.Error(t, err)
137+
assert.Equal(t, expectedErr, err)
138+
}
139+
140+
// TestProvisionIdentitiesError verifies provisioning retries after errors.
141+
func TestProvisionIdentitiesError(t *testing.T) {
142+
callCount := make(chan struct{}, 100)
143+
maxCalls := 3
144+
145+
c := NewIdentityCache(func(ctx context.Context, auditInfo []byte) (*idriver.IdentityDescriptor, error) {
146+
// Fail 3 times then succeed
147+
callCount <- struct{}{} // send once per call
148+
if len(callCount) <= maxCalls {
149+
return nil, errors.New("provision error")
150+
}
151+
152+
return &idriver.IdentityDescriptor{
153+
Identity: []byte("success identity"),
154+
AuditInfo: []byte("success audit"),
155+
}, nil
156+
}, 10, nil, NewMetrics(&disabled.Provider{}))
157+
158+
// Trigger provisioning
159+
_, err := c.Identity(context.Background(), nil)
160+
require.NoError(t, err)
161+
162+
// Wait a bit for provisioning to attempt multiple times
163+
time.Sleep(50 * time.Millisecond)
164+
165+
// Verify that provisioning continued after errors
166+
assert.Greater(t, len(callCount), maxCalls)
167+
}
168+
169+
// TestFetchIdentityFromCacheNilEntry verifies backend fallback for nil cache entries.
170+
func TestFetchIdentityFromCacheNilEntry(t *testing.T) {
171+
backendCalled := make(chan struct{}, 1)
172+
173+
c := NewIdentityCache(func(ctx context.Context, auditInfo []byte) (*idriver.IdentityDescriptor, error) {
174+
backendCalled <- struct{}{}
175+
176+
return &idriver.IdentityDescriptor{
177+
Identity: []byte("backend fallback"),
178+
AuditInfo: []byte("backend audit"),
179+
}, nil
180+
}, 10, nil, NewMetrics(&disabled.Provider{}))
181+
182+
// Send nil to cache to test nil handling
183+
c.cache <- nil
184+
185+
identityDescriptor, err := c.Identity(context.Background(), nil)
186+
require.NoError(t, err)
187+
assert.Eventually(t, func() bool {
188+
select {
189+
case <-backendCalled:
190+
return true
191+
default:
192+
return false
193+
}
194+
}, time.Second, 10*time.Millisecond)
195+
assert.Equal(t, driver.Identity([]byte("backend fallback")), identityDescriptor.Identity)
196+
}

token/services/identity/idemix/cache/metrics.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,21 @@ import (
1111
)
1212

1313
var (
14+
// LevelOpts defines gauge options for tracking cache level.
1415
LevelOpts = metrics.GaugeOpts{
1516
Name: "cache_level",
1617
Help: "Level of the idemix cache",
1718
LabelNames: []string{"network", "channel", "namespace"},
1819
}
1920
)
2021

21-
// Metrics contains the metrics for this package
22+
// Metrics contains metrics for monitoring identity cache performance.
2223
type Metrics struct {
24+
// Current number of cached identities
2325
CacheLevelGauge metrics.Gauge
2426
}
2527

26-
// NewMetrics instantiate the metrics for this package
28+
// NewMetrics creates a new Metrics instance.
2729
func NewMetrics(p metrics.Provider) *Metrics {
2830
return &Metrics{
2931
CacheLevelGauge: p.NewGauge(LevelOpts),

token/services/identity/idemix/crypto/audit.go

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,47 +13,52 @@ import (
1313
"github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors"
1414
"github.com/hyperledger-labs/fabric-smart-client/pkg/utils/proto"
1515
"github.com/hyperledger-labs/fabric-token-sdk/token/core/common/encoding/json"
16+
"github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/idemix/schema"
1617
)
1718

19+
// Schema represents the version identifier for the credential schema.
1820
type Schema = string
1921

20-
// SchemaManager handles the various credential schemas. A credential schema
21-
// contains information about the number of attributes, which attributes
22-
// must be disclosed when creating proofs, the format of the attributes etc.
23-
type SchemaManager interface {
24-
// EidNymAuditOpts returns the options that `sid` must use to audit an EIDNym
25-
EidNymAuditOpts(schema string, attrs [][]byte) (*csp.EidNymAuditOpts, error)
26-
// RhNymAuditOpts returns the options that `sid` must use to audit an RhNym
27-
RhNymAuditOpts(schema string, attrs [][]byte) (*csp.RhNymAuditOpts, error)
28-
}
29-
22+
// AuditInfo contains cryptographic audit data for an Idemix identity.
3023
type AuditInfo struct {
24+
// Enrollment ID pseudonym audit data
3125
EidNymAuditData *csp.AttrNymAuditData
32-
RhNymAuditData *csp.AttrNymAuditData
33-
Attributes [][]byte
34-
35-
Csp csp.BCCSP `json:"-"`
36-
IssuerPublicKey csp.Key `json:"-"`
37-
SchemaManager SchemaManager `json:"-"`
38-
Schema string
26+
// Revocation handle pseudonym audit data
27+
RhNymAuditData *csp.AttrNymAuditData
28+
// Credential attributes (e.g. EnrollmentID, RevocationHandle strings)
29+
Attributes [][]byte
30+
31+
// Cryptographic service provider
32+
Csp csp.BCCSP `json:"-"`
33+
// Credential issuer's public key
34+
IssuerPublicKey csp.Key `json:"-"`
35+
// Schema-specific operations manager
36+
SchemaManager schema.Manager `json:"-"`
37+
// Credential schema version
38+
Schema string
3939
}
4040

41+
// Bytes serializes the AuditInfo to JSON format.
4142
func (a *AuditInfo) Bytes() ([]byte, error) {
4243
return json.Marshal(a)
4344
}
4445

46+
// FromBytes deserializes the AuditInfo from JSON format.
4547
func (a *AuditInfo) FromBytes(raw []byte) error {
4648
return json.Unmarshal(raw, a)
4749
}
4850

51+
// EnrollmentID returns the enrollment ID from Attributes[2].
4952
func (a *AuditInfo) EnrollmentID() string {
5053
return string(a.Attributes[2])
5154
}
5255

56+
// RevocationHandle returns the revocation handle from Attributes[3].
5357
func (a *AuditInfo) RevocationHandle() string {
5458
return string(a.Attributes[3])
5559
}
5660

61+
// Match verifies the identity matches this audit info by checking EID and RH pseudonyms.
5762
func (a *AuditInfo) Match(_ context.Context, id []byte) error {
5863
serialized := new(SerializedIdemixIdentity)
5964
err := proto.Unmarshal(id, serialized)
@@ -104,6 +109,7 @@ func (a *AuditInfo) Match(_ context.Context, id []byte) error {
104109
return nil
105110
}
106111

112+
// DeserializeAuditInfo deserializes the audit information from JSON.
107113
func DeserializeAuditInfo(raw []byte) (*AuditInfo, error) {
108114
auditInfo := &AuditInfo{}
109115
err := auditInfo.FromBytes(raw)

0 commit comments

Comments
 (0)