Skip to content

Commit 95da747

Browse files
committed
perf: cache verifiers to eliminate duplicate Idemix proofs
Signed-off-by: AkramBitar <akram@il.ibm.com>
1 parent 57b15b2 commit 95da747

2 files changed

Lines changed: 90 additions & 5 deletions

File tree

token/core/zkatdlog/nogh/v1/validator/validator_extra_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,3 +444,74 @@ func TestDeserializeActionsErrors(t *testing.T) {
444444
_, _, err = ad.DeserializeActions(tr)
445445
require.Error(t, err)
446446
}
447+
448+
// TestTransferVerifierCache tests that when multiple inputs have the same owner,
449+
// the verifier is only deserialized once (cache hit)
450+
func TestTransferVerifierCache(t *testing.T) {
451+
ctx := context.Background()
452+
453+
// Create a transfer action with 3 inputs, where 2 have the same owner
454+
sameOwner := []byte("same-owner-identity")
455+
differentOwner := []byte("different-owner-identity")
456+
457+
transferAction := &transfer.Action{
458+
Inputs: []*transfer.ActionInput{
459+
{Token: &token.Token{Owner: sameOwner}}, // Input 0: same owner
460+
{Token: &token.Token{Owner: sameOwner}}, // Input 1: same owner (should hit cache)
461+
{Token: &token.Token{Owner: differentOwner}}, // Input 2: different owner
462+
},
463+
Outputs: []*token.Token{
464+
{Owner: []byte("output-owner")},
465+
},
466+
}
467+
468+
// Track deserializer calls
469+
deserializerCallCount := 0
470+
ownerCallsByIdentity := make(map[string]int)
471+
472+
mockDes := &mock3.Deserializer{}
473+
mockDes.GetOwnerVerifierCalls(func(ctx context.Context, id driver.Identity) (driver.Verifier, error) {
474+
deserializerCallCount++
475+
key := string(id)
476+
ownerCallsByIdentity[key]++
477+
return &mock3.Verifier{}, nil
478+
})
479+
480+
mockSigProv := &mockSignatureProvider{
481+
HasBeenSignedByFunc: func(ctx context.Context, id driver.Identity, verifier driver.Verifier) ([]byte, error) {
482+
return []byte("signature"), nil
483+
},
484+
}
485+
486+
validatorCtx := &validator.Context{
487+
Logger: &logging.MockLogger{},
488+
TransferAction: transferAction,
489+
Deserializer: mockDes,
490+
SignatureProvider: mockSigProv,
491+
PP: &v1.PublicParams{},
492+
}
493+
494+
// Execute validation
495+
err := validator.TransferSignatureValidate(ctx, validatorCtx)
496+
require.NoError(t, err)
497+
498+
// Verify results - WITH cache implementation
499+
t.Logf("Total GetOwnerVerifier calls: %d", deserializerCallCount)
500+
t.Logf("Calls for same owner: %d", ownerCallsByIdentity[string(sameOwner)])
501+
t.Logf("Calls for different owner: %d", ownerCallsByIdentity[string(differentOwner)])
502+
503+
// With verifier cache: deserializer is called only once per unique owner
504+
// Expected: 2 calls total (once for sameOwner, once for differentOwner)
505+
require.Equal(t, 2, deserializerCallCount, "With cache: should call deserializer only for unique owners")
506+
require.Equal(t, 1, ownerCallsByIdentity[string(sameOwner)], "With cache: same owner called only once")
507+
require.Equal(t, 1, ownerCallsByIdentity[string(differentOwner)], "Different owner called once")
508+
509+
// Calculate cache effectiveness
510+
uniqueOwners := 2
511+
totalInputs := 3
512+
cacheHits := totalInputs - uniqueOwners
513+
cacheHitRate := float64(cacheHits) / float64(totalInputs) * 100
514+
515+
t.Logf("Cache hit rate: %.1f%% (%d cache hits out of %d inputs)",
516+
cacheHitRate, cacheHits, totalInputs)
517+
}

token/core/zkatdlog/nogh/v1/validator/validator_transfer.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ func TransferSignatureValidate(c context.Context, ctx *Context) error {
3636
return ErrInvalidInputs
3737
}
3838

39+
// Cache verifiers within this transaction to avoid redundant verifyProof() calls
40+
// for inputs with the same owner identity
41+
verifierCache := make(map[string]driver.Verifier)
42+
3943
var isRedeem bool
4044
var inputToken []*token.Token
4145
for i, in := range ctx.TransferAction.Inputs {
@@ -44,10 +48,19 @@ func TransferSignatureValidate(c context.Context, ctx *Context) error {
4448

4549
// check sender signature
4650
ctx.Logger.Debugf("check sender [%d][%s]", i, driver.Identity(tok.Owner).UniqueID())
47-
verifier, err := ctx.Deserializer.GetOwnerVerifier(c, tok.Owner)
48-
if err != nil {
49-
return errors.Wrapf(err, "failed deserializing owner [%d][%v][%s]", i, in, driver.Identity(tok.Owner))
51+
52+
// Check cache first to avoid redundant expensive verifyProof() calls
53+
ownerKey := driver.Identity(tok.Owner).UniqueID()
54+
verifier, cached := verifierCache[ownerKey]
55+
if !cached {
56+
var err error
57+
verifier, err = ctx.Deserializer.GetOwnerVerifier(c, tok.Owner)
58+
if err != nil {
59+
return errors.Wrapf(err, "failed deserializing owner [%d][%v][%s]", i, in, driver.Identity(tok.Owner))
60+
}
61+
verifierCache[ownerKey] = verifier
5062
}
63+
5164
ctx.Logger.Debugf("signature verification [%d][%v][%s]", i, in, driver.Identity(tok.Owner).UniqueID())
5265
sigma, err := ctx.SignatureProvider.HasBeenSignedBy(c, tok.Owner, verifier)
5366
if err != nil {
@@ -132,10 +145,11 @@ func TransferZKProofValidate(c context.Context, ctx *Context) error {
132145
in[i] = tok.Data
133146
}
134147

135-
if err := transfer.NewVerifier(
148+
err := transfer.NewVerifier(
136149
in,
137150
ctx.TransferAction.GetOutputCommitments(),
138-
ctx.PP).Verify(ctx.TransferAction.GetProof()); err != nil {
151+
ctx.PP).Verify(ctx.TransferAction.GetProof())
152+
if err != nil {
139153
return errors.Join(err, ErrInvalidZKP)
140154
}
141155

0 commit comments

Comments
 (0)