Skip to content

Commit ba130ea

Browse files
adecarosid200727
authored andcommitted
split token driver in two hyperledger-labs#864 (hyperledger-labs#1399)
Signed-off-by: Angelo De Caro <adc@zurich.ibm.com> Signed-off-by: Siddhi Khandelwal <siddhi.200727@gmail.com>
1 parent e711ae5 commit ba130ea

32 files changed

Lines changed: 800 additions & 530 deletions

File tree

cmd/tokengen/cobra/certfier/keypairgen_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func (m *mockPPMFactory) NewPublicParametersManager(pp driver.PublicParameters)
3333
return m.publicParamsManager, m.publicParamsManagerErr
3434
}
3535

36-
func (m *mockPPMFactory) DefaultValidator(pp driver.PublicParameters) (driver.Validator, error) {
36+
func (m *mockPPMFactory) NewValidator(pp driver.PublicParameters) (driver.Validator, error) {
3737
return nil, nil
3838
}
3939

docs/driverapi.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
The **Driver API** serves as the interface bridging the generic Token API with specific token implementations. It defines the protocols for token creation, transfer, and management within a given system.
44

5-
Each driver must implement the `driver.Driver` interface, fulfilling three primary objectives:
6-
1. **Public Parameters Discovery**: Decodes raw bytes into driver-specific public parameters.
7-
2. **TMS Instantiation**: Provides a mechanism to instantiate a new `TokenManagementService` (TMS) tailored to the driver and its public parameters.
8-
3. **Default Validation**: Returns a default `Validator` instance configured with the driver's public parameters.
5+
Each driver must implement the `driver.Driver` and `driver.ValidatorDriver` interfaces, fulfilling three primary objectives:
6+
1. **Public Parameters**: Through `driver.PPReader` (embedded in both), facilitates the retrieval of driver-specific public parameters from bytes.
7+
2. **Token Management Service (TMS)**: Through `driver.Driver`, provides a mechanism to instantiate a new TMS tailored to the driver.
8+
3. **Validation**: Through `driver.ValidatorDriver`, provides a mechanism to instantiate a new validator from public parameters.
99

1010
## Core Architecture
1111

@@ -220,6 +220,7 @@ The Token SDK comes equipped with two reference drivers:
220220

221221
- [**FabToken**](./drivers/fabtoken.md): A straightforward implementation prioritizing simplicity. It stores token transaction details (type, value, owner) in cleartext on the ledger, using X.509 certificates for identities.
222222
- [**DLOG w/o Graph Hiding (NOGH)**](./drivers/dlogwogh.md): A privacy-preserving driver using Zero-Knowledge Proofs (ZKP) to hide token types and values via Pedersen commitments. It leverages Idemix for owner anonymity while revealing the spending graph.
223+
- [**Extending a Validator Driver&&](./drivers/extending_validator.md)
223224

224225
## Observability
225226

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Extending a Validator Driver
2+
3+
This guide explains how to extend an existing token validator driver with custom validation functions.
4+
This is useful when you need to enforce additional business rules or compliance checks beyond the default logic provided by the token drivers (e.g., `FabToken` or `ZKAT-DLog`).
5+
6+
## Overview
7+
8+
The Token SDK uses a `ValidatorDriverService` to manage factories for creating `driver.Validator` instances.
9+
Each driver version is identified by a unique string (e.g., `zkatdlognogh.v1`).
10+
11+
To extend a validator, you typically:
12+
1. Implement a custom `driver.ValidatorDriver` that wraps an existing one.
13+
2. Override the `NewValidator` method to inject additional validation logic.
14+
3. Register your custom driver factory in the SDK's dependency injection container.
15+
16+
## Architecture
17+
18+
The `ValidatorDriverService` (found in `token/core/service.go`) maintains a map of driver identifiers to `driver.ValidatorDriver` implementations.
19+
20+
```go
21+
type ValidatorDriverService struct {
22+
*factoryDirectory[driver.ValidatorDriver]
23+
}
24+
25+
func (s *ValidatorDriverService) NewValidator(pp driver.PublicParameters) (driver.Validator, error) {
26+
if driver, ok := s.factories[DriverIdentifierFromPP(pp)]; ok {
27+
return driver.NewValidator(pp)
28+
}
29+
return nil, errors.Errorf("no validator found for token driver [%s]", DriverIdentifierFromPP(pp))
30+
}
31+
```
32+
33+
By providing a custom factory with the same identifier as an existing driver, you can effectively "hijack" the validator creation process.
34+
35+
## Example: Extending the ZKAT-DLog Validator
36+
37+
Suppose you want to add a custom check to all transfer operations in a `ZKAT-DLog` system.
38+
39+
### 1. Define your custom validation function
40+
41+
First, define a function that matches the signature expected by the validator. For `ZKAT-DLog` (NOGH v1), this is `ValidateTransferFunc`.
42+
43+
```go
44+
package myextension
45+
46+
import (
47+
v1 "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/nogh/v1/setup"
48+
"github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/nogh/v1/token"
49+
"github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/nogh/v1/transfer"
50+
"github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/nogh/v1/validator"
51+
"github.com/hyperledger-labs/fabric-token-sdk/token/driver"
52+
)
53+
54+
func MyCustomTransferValidation(ctx validator.Context, tr *transfer.Action) error {
55+
// Perform your custom validation logic here.
56+
// For example, check if the transfer metadata contains a specific attribute.
57+
if len(tr.Metadata) == 0 {
58+
return errors.New("transfer metadata is missing")
59+
}
60+
return nil
61+
}
62+
```
63+
64+
### 2. Create a custom Validator Driver
65+
66+
Implement the `driver.ValidatorDriver` interface by wrapping the standard one.
67+
68+
```go
69+
type MyValidatorDriver struct {
70+
driver.ValidatorDriver // Wrap the existing driver
71+
}
72+
73+
func (d *MyValidatorDriver) NewValidator(pp driver.PublicParameters) (driver.Validator, error) {
74+
// We can't easily use the wrapped driver's NewValidator if we want to
75+
// inject functions into its internal pipeline, so we replicate its logic.
76+
77+
ppp, ok := pp.(*v1.PublicParams)
78+
if !ok {
79+
return nil, errors.Errorf("invalid public parameters type [%T]", pp)
80+
}
81+
82+
deserializer, err := driver.NewDeserializer(ppp) // Assume driver is the zkatdlog driver package
83+
if err != nil {
84+
return nil, err
85+
}
86+
87+
logger := logging.DriverLoggerFromPP("token-sdk.driver.myextension", string(pp.TokenDriverName()))
88+
89+
// Instantiate the validator with your custom function
90+
return validator.New(
91+
logger,
92+
ppp,
93+
deserializer,
94+
[]validator.ValidateTransferFunc{MyCustomTransferValidation}, // Extra transfer validators
95+
nil, // Extra issuer validators
96+
nil, // Extra auditor validators
97+
), nil
98+
}
99+
```
100+
101+
### 3. Register the extension
102+
103+
Register your custom factory using the SDK's registration mechanism. If you are using the `dig` container (standard in FSC-based applications), you can provide it to the `token-validator-drivers` group.
104+
105+
```go
106+
func NewMyValidatorDriver() core.NamedFactory[driver.ValidatorDriver] {
107+
return core.NamedFactory[driver.ValidatorDriver]{
108+
Name: core.DriverIdentifier(v1.DLogNoGHDriverName, v1.ProtocolV1),
109+
Driver: &MyValidatorDriver{
110+
// You might need to initialize the wrapped driver here
111+
},
112+
}
113+
}
114+
```
115+
116+
By using the same `Name` as the original driver, the `ValidatorDriverService` will use your factory instead of the default one.
117+
118+
## Alternative: Generic Validator Wrapping
119+
120+
If you want to add validation that is independent of the driver's internal implementation, you can wrap the `driver.Validator` interface directly.
121+
122+
```go
123+
type WrappedValidator struct {
124+
driver.Validator
125+
}
126+
127+
func (v *WrappedValidator) VerifyTokenRequestFromRaw(ctx context.Context, getState driver.GetStateFnc, anchor driver.TokenRequestAnchor, raw []byte) ([]interface{}, driver.ValidationAttributes, error) {
128+
// Call the original validator first
129+
actions, attrs, err := v.Validator.VerifyTokenRequestFromRaw(ctx, getState, anchor, raw)
130+
if err != nil {
131+
return nil, nil, err
132+
}
133+
134+
// Perform post-validation
135+
for _, action := range actions {
136+
if err := myGlobalCheck(action); err != nil {
137+
return nil, nil, err
138+
}
139+
}
140+
141+
return actions, attrs, nil
142+
}
143+
```
144+
145+
This approach is highly portable and works across all token drivers.

integration/token/common/sdk/fall/sdk.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ func (p *SDK) Install() error {
3535
err := errors.Join(
3636
sdk.RegisterTokenDriverDependencies(p.Container()),
3737
p.Container().Provide(fabric.NewGenericDriver, dig.Group("network-drivers")),
38-
p.Container().Provide(fabtoken.NewDriver, dig.Group("token-drivers")),
39-
p.Container().Provide(dlog.NewDriver, dig.Group("token-drivers")),
38+
p.Container().Provide(fabtoken.NewTokenDriver, dig.Group("token-drivers")),
39+
p.Container().Provide(fabtoken.NewValidatorDriver, dig.Group("validator-drivers")),
40+
p.Container().Provide(dlog.NewTokenDriver, dig.Group("token-drivers")),
41+
p.Container().Provide(dlog.NewValidatorDriver, dig.Group("validator-drivers")),
4042
)
4143
if err != nil {
4244
return err

integration/token/common/sdk/fdlog/sdk.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ func (p *SDK) Install() error {
3434
err := errors.Join(
3535
sdk.RegisterTokenDriverDependencies(p.Container()),
3636
p.Container().Provide(fabric.NewGenericDriver, dig.Group("network-drivers")),
37-
p.Container().Provide(dlog.NewDriver, dig.Group("token-drivers")),
37+
p.Container().Provide(dlog.NewTokenDriver, dig.Group("token-drivers")),
38+
p.Container().Provide(dlog.NewValidatorDriver, dig.Group("validator-drivers")),
3839
)
3940
if err != nil {
4041
return err

integration/token/common/sdk/ffabtoken/sdk.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ func (p *SDK) Install() error {
3434
err := errors.Join(
3535
sdk.RegisterTokenDriverDependencies(p.Container()),
3636
p.Container().Provide(fabric.NewGenericDriver, dig.Group("network-drivers")),
37-
p.Container().Provide(fabtoken.NewDriver, dig.Group("token-drivers")),
37+
p.Container().Provide(fabtoken.NewTokenDriver, dig.Group("token-drivers")),
38+
p.Container().Provide(fabtoken.NewValidatorDriver, dig.Group("validator-drivers")),
3839
)
3940
if err != nil {
4041
return err

integration/token/common/sdk/fxdlog/sdk.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ func (p *SDK) Install() error {
4747
err := errors.Join(
4848
// token driver
4949
sdk.RegisterTokenDriverDependencies(p.Container()),
50-
p.Container().Provide(dlog.NewDriver, dig.Group("token-drivers")),
50+
p.Container().Provide(dlog.NewTokenDriver, dig.Group("token-drivers")),
51+
p.Container().Provide(dlog.NewValidatorDriver, dig.Group("validator-drivers")),
5152

5253
// fabricx
5354
p.Container().Provide(fabricx.NewDriver, dig.Group("network-drivers")),

token/core/fabtoken/v1/driver/deserializer.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ SPDX-License-Identifier: Apache-2.0
77
package driver
88

99
import (
10+
"github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors"
1011
"github.com/hyperledger-labs/fabric-token-sdk/token/core/common"
1112
v1 "github.com/hyperledger-labs/fabric-token-sdk/token/core/fabtoken/v1/setup"
1213
"github.com/hyperledger-labs/fabric-token-sdk/token/driver"
@@ -55,3 +56,16 @@ func NewEIDRHDeserializer() *EIDRHDeserializer {
5556

5657
return d
5758
}
59+
60+
// PublicParametersDeserializer contains the logic to deserialize public parameters
61+
type PublicParametersDeserializer struct{}
62+
63+
// PublicParametersFromBytes unmarshals the passed bytes into fabtoken public parameters.
64+
func (d PublicParametersDeserializer) PublicParametersFromBytes(params []byte) (driver.PublicParameters, error) {
65+
pp, err := v1.NewPublicParamsFromBytes(params, v1.FabTokenDriverName, v1.ProtocolV1)
66+
if err != nil {
67+
return nil, errors.Wrap(err, "failed to unmarshal public parameters")
68+
}
69+
70+
return pp, nil
71+
}

token/core/fabtoken/v1/driver/driver.go

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424

2525
// Driver contains the non-static logic of the fabtoken driver (including services).
2626
type Driver struct {
27-
*base
27+
BaseWalletServiceFactory
2828
metricsProvider cdriver.MetricsProvider
2929
tracerProvider cdriver.TracerProvider
3030
configService cdriver.ConfigService
@@ -35,8 +35,8 @@ type Driver struct {
3535
vaultProvider cdriver.VaultProvider
3636
}
3737

38-
// NewDriver returns a new factory for the fabtoken driver.
39-
func NewDriver(
38+
// NewTokenDriver returns a new factory for the fabtoken driver.
39+
func NewTokenDriver(
4040
metricsProvider cdriver.MetricsProvider,
4141
tracerProvider cdriver.TracerProvider,
4242
configService cdriver.ConfigService,
@@ -48,17 +48,38 @@ func NewDriver(
4848
) core.NamedFactory[driver.Driver] {
4949
return core.NamedFactory[driver.Driver]{
5050
Name: core.DriverIdentifier(v1setup.FabTokenDriverName, 1),
51-
Driver: &Driver{
52-
base: &base{},
53-
metricsProvider: metricsProvider,
54-
tracerProvider: tracerProvider,
55-
configService: configService,
56-
storageProvider: storageProvider,
57-
identityProvider: identityProvider,
58-
endpointService: endpointService,
59-
networkProvider: networkProvider,
60-
vaultProvider: vaultProvider,
61-
},
51+
Driver: newTokenDriver(
52+
metricsProvider,
53+
tracerProvider,
54+
configService,
55+
storageProvider,
56+
identityProvider,
57+
endpointService,
58+
networkProvider,
59+
vaultProvider,
60+
),
61+
}
62+
}
63+
64+
func newTokenDriver(
65+
metricsProvider cdriver.MetricsProvider,
66+
tracerProvider cdriver.TracerProvider,
67+
configService cdriver.ConfigService,
68+
storageProvider cdriver.StorageProvider,
69+
identityProvider cdriver.IdentityProvider,
70+
endpointService cdriver.NetworkBinderService,
71+
networkProvider cdriver.NetworkProvider,
72+
vaultProvider cdriver.VaultProvider,
73+
) *Driver {
74+
return &Driver{
75+
metricsProvider: metricsProvider,
76+
tracerProvider: tracerProvider,
77+
configService: configService,
78+
storageProvider: storageProvider,
79+
identityProvider: identityProvider,
80+
endpointService: endpointService,
81+
networkProvider: networkProvider,
82+
vaultProvider: vaultProvider,
6283
}
6384
}
6485

@@ -160,13 +181,3 @@ func (d *Driver) NewTokenService(tmsID driver.TMSID, publicParams []byte) (drive
160181

161182
return service, nil
162183
}
163-
164-
// NewDefaultValidator returns a new fabtoken validator for the passed public parameters.
165-
func (d *Driver) NewDefaultValidator(params driver.PublicParameters) (driver.Validator, error) {
166-
pp, ok := params.(*v1setup.PublicParams)
167-
if !ok {
168-
return nil, errors.Errorf("invalid public parameters type [%T]", params)
169-
}
170-
171-
return d.DefaultValidator(pp)
172-
}

token/core/fabtoken/v1/driver/driver_test.go

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func TestNewDriver(t *testing.T) {
3535
networkProvider := &mock2.NetworkProvider{}
3636
vaultProvider := &mock2.VaultProvider{}
3737

38-
factory := driver.NewDriver(
38+
factory := driver.NewTokenDriver(
3939
metricsProvider,
4040
nil,
4141
configService,
@@ -60,7 +60,7 @@ func TestNewTokenService(t *testing.T) {
6060
networkProvider := &mock2.NetworkProvider{}
6161
vaultProvider := &mock2.VaultProvider{}
6262

63-
d := driver.NewDriver(
63+
d := driver.NewTokenDriver(
6464
metricsProvider,
6565
nil,
6666
configService,
@@ -156,34 +156,17 @@ func TestNewTokenService(t *testing.T) {
156156

157157
// TestNewDefaultValidator tests the creation of a default fabtoken validator.
158158
func TestNewDefaultValidator(t *testing.T) {
159-
metricsProvider := &mock2.MetricsProvider{}
160-
configService := &mock2.ConfigService{}
161-
storageProvider := &imock.StorageProvider{}
162-
identityProvider := &mock2.IdentityProvider{}
163-
endpointService := &idmock.NetworkBinderService{}
164-
networkProvider := &mock2.NetworkProvider{}
165-
vaultProvider := &mock2.VaultProvider{}
166-
167-
d := driver.NewDriver(
168-
metricsProvider,
169-
nil,
170-
configService,
171-
storageProvider,
172-
identityProvider,
173-
endpointService,
174-
networkProvider,
175-
vaultProvider,
176-
).Driver.(*driver.Driver)
159+
d := driver.NewValidatorDriver().Driver
177160

178161
pp, _ := setup.NewWith(setup.FabTokenDriverName, setup.ProtocolV1, 64)
179162

180163
// Case 1: Valid public parameters
181-
v, err := d.NewDefaultValidator(pp)
164+
v, err := d.NewValidator(pp)
182165
require.NoError(t, err)
183166
assert.NotNil(t, v)
184167

185168
// Case 2: Invalid public parameters type
186-
v, err = d.NewDefaultValidator(&dmock.PublicParameters{})
169+
v, err = d.NewValidator(&dmock.PublicParameters{})
187170
require.Error(t, err)
188171
assert.Nil(t, v)
189172
assert.Contains(t, err.Error(), "invalid public parameters type")

0 commit comments

Comments
 (0)