Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/tokengen/cobra/certfier/keypairgen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func (m *mockPPMFactory) NewPublicParametersManager(pp driver.PublicParameters)
return m.publicParamsManager, m.publicParamsManagerErr
}

func (m *mockPPMFactory) DefaultValidator(pp driver.PublicParameters) (driver.Validator, error) {
func (m *mockPPMFactory) NewValidator(pp driver.PublicParameters) (driver.Validator, error) {
return nil, nil
}

Expand Down
9 changes: 5 additions & 4 deletions docs/driverapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

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.

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

## Core Architecture

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

- [**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.
- [**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.
- [**Extending a Validator Driver&&](./drivers/extending_validator.md)

## Observability

Expand Down
145 changes: 145 additions & 0 deletions docs/drivers/extending_validator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# Extending a Validator Driver

This guide explains how to extend an existing token validator driver with custom validation functions.
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`).

## Overview

The Token SDK uses a `ValidatorDriverService` to manage factories for creating `driver.Validator` instances.
Each driver version is identified by a unique string (e.g., `zkatdlognogh.v1`).

To extend a validator, you typically:
1. Implement a custom `driver.ValidatorDriver` that wraps an existing one.
2. Override the `NewValidator` method to inject additional validation logic.
3. Register your custom driver factory in the SDK's dependency injection container.

## Architecture

The `ValidatorDriverService` (found in `token/core/service.go`) maintains a map of driver identifiers to `driver.ValidatorDriver` implementations.

```go
type ValidatorDriverService struct {
*factoryDirectory[driver.ValidatorDriver]
}

func (s *ValidatorDriverService) NewValidator(pp driver.PublicParameters) (driver.Validator, error) {
if driver, ok := s.factories[DriverIdentifierFromPP(pp)]; ok {
return driver.NewValidator(pp)
}
return nil, errors.Errorf("no validator found for token driver [%s]", DriverIdentifierFromPP(pp))
}
```

By providing a custom factory with the same identifier as an existing driver, you can effectively "hijack" the validator creation process.

## Example: Extending the ZKAT-DLog Validator

Suppose you want to add a custom check to all transfer operations in a `ZKAT-DLog` system.

### 1. Define your custom validation function

First, define a function that matches the signature expected by the validator. For `ZKAT-DLog` (NOGH v1), this is `ValidateTransferFunc`.

```go
package myextension

import (
v1 "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/nogh/v1/setup"
"github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/nogh/v1/token"
"github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/nogh/v1/transfer"
"github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/nogh/v1/validator"
"github.com/hyperledger-labs/fabric-token-sdk/token/driver"
)

func MyCustomTransferValidation(ctx validator.Context, tr *transfer.Action) error {
// Perform your custom validation logic here.
// For example, check if the transfer metadata contains a specific attribute.
if len(tr.Metadata) == 0 {
return errors.New("transfer metadata is missing")
}
return nil
}
```

### 2. Create a custom Validator Driver

Implement the `driver.ValidatorDriver` interface by wrapping the standard one.

```go
type MyValidatorDriver struct {
driver.ValidatorDriver // Wrap the existing driver
}

func (d *MyValidatorDriver) NewValidator(pp driver.PublicParameters) (driver.Validator, error) {
// We can't easily use the wrapped driver's NewValidator if we want to
// inject functions into its internal pipeline, so we replicate its logic.

ppp, ok := pp.(*v1.PublicParams)
if !ok {
return nil, errors.Errorf("invalid public parameters type [%T]", pp)
}

deserializer, err := driver.NewDeserializer(ppp) // Assume driver is the zkatdlog driver package
if err != nil {
return nil, err
}

logger := logging.DriverLoggerFromPP("token-sdk.driver.myextension", string(pp.TokenDriverName()))

// Instantiate the validator with your custom function
return validator.New(
logger,
ppp,
deserializer,
[]validator.ValidateTransferFunc{MyCustomTransferValidation}, // Extra transfer validators
nil, // Extra issuer validators
nil, // Extra auditor validators
), nil
}
```

### 3. Register the extension

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.

```go
func NewMyValidatorDriver() core.NamedFactory[driver.ValidatorDriver] {
return core.NamedFactory[driver.ValidatorDriver]{
Name: core.DriverIdentifier(v1.DLogNoGHDriverName, v1.ProtocolV1),
Driver: &MyValidatorDriver{
// You might need to initialize the wrapped driver here
},
}
}
```

By using the same `Name` as the original driver, the `ValidatorDriverService` will use your factory instead of the default one.

## Alternative: Generic Validator Wrapping

If you want to add validation that is independent of the driver's internal implementation, you can wrap the `driver.Validator` interface directly.

```go
type WrappedValidator struct {
driver.Validator
}

func (v *WrappedValidator) VerifyTokenRequestFromRaw(ctx context.Context, getState driver.GetStateFnc, anchor driver.TokenRequestAnchor, raw []byte) ([]interface{}, driver.ValidationAttributes, error) {
// Call the original validator first
actions, attrs, err := v.Validator.VerifyTokenRequestFromRaw(ctx, getState, anchor, raw)
if err != nil {
return nil, nil, err
}

// Perform post-validation
for _, action := range actions {
if err := myGlobalCheck(action); err != nil {
return nil, nil, err
}
}

return actions, attrs, nil
}
```

This approach is highly portable and works across all token drivers.
6 changes: 4 additions & 2 deletions integration/token/common/sdk/fall/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ func (p *SDK) Install() error {
err := errors.Join(
sdk.RegisterTokenDriverDependencies(p.Container()),
p.Container().Provide(fabric.NewGenericDriver, dig.Group("network-drivers")),
p.Container().Provide(fabtoken.NewDriver, dig.Group("token-drivers")),
p.Container().Provide(dlog.NewDriver, dig.Group("token-drivers")),
p.Container().Provide(fabtoken.NewTokenDriver, dig.Group("token-drivers")),
p.Container().Provide(fabtoken.NewValidatorDriver, dig.Group("validator-drivers")),
p.Container().Provide(dlog.NewTokenDriver, dig.Group("token-drivers")),
p.Container().Provide(dlog.NewValidatorDriver, dig.Group("validator-drivers")),
)
if err != nil {
return err
Expand Down
3 changes: 2 additions & 1 deletion integration/token/common/sdk/fdlog/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ func (p *SDK) Install() error {
err := errors.Join(
sdk.RegisterTokenDriverDependencies(p.Container()),
p.Container().Provide(fabric.NewGenericDriver, dig.Group("network-drivers")),
p.Container().Provide(dlog.NewDriver, dig.Group("token-drivers")),
p.Container().Provide(dlog.NewTokenDriver, dig.Group("token-drivers")),
p.Container().Provide(dlog.NewValidatorDriver, dig.Group("validator-drivers")),
)
if err != nil {
return err
Expand Down
3 changes: 2 additions & 1 deletion integration/token/common/sdk/ffabtoken/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ func (p *SDK) Install() error {
err := errors.Join(
sdk.RegisterTokenDriverDependencies(p.Container()),
p.Container().Provide(fabric.NewGenericDriver, dig.Group("network-drivers")),
p.Container().Provide(fabtoken.NewDriver, dig.Group("token-drivers")),
p.Container().Provide(fabtoken.NewTokenDriver, dig.Group("token-drivers")),
p.Container().Provide(fabtoken.NewValidatorDriver, dig.Group("validator-drivers")),
)
if err != nil {
return err
Expand Down
3 changes: 2 additions & 1 deletion integration/token/common/sdk/fxdlog/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ func (p *SDK) Install() error {
err := errors.Join(
// token driver
sdk.RegisterTokenDriverDependencies(p.Container()),
p.Container().Provide(dlog.NewDriver, dig.Group("token-drivers")),
p.Container().Provide(dlog.NewTokenDriver, dig.Group("token-drivers")),
p.Container().Provide(dlog.NewValidatorDriver, dig.Group("validator-drivers")),

// fabricx
p.Container().Provide(fabricx.NewDriver, dig.Group("network-drivers")),
Expand Down
14 changes: 14 additions & 0 deletions token/core/fabtoken/v1/driver/deserializer.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ SPDX-License-Identifier: Apache-2.0
package driver

import (
"github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors"
"github.com/hyperledger-labs/fabric-token-sdk/token/core/common"
v1 "github.com/hyperledger-labs/fabric-token-sdk/token/core/fabtoken/v1/setup"
"github.com/hyperledger-labs/fabric-token-sdk/token/driver"
Expand Down Expand Up @@ -52,3 +53,16 @@ func NewEIDRHDeserializer() *EIDRHDeserializer {

return d
}

// PublicParametersDeserializer contains the logic to deserialize public parameters
type PublicParametersDeserializer struct{}

// PublicParametersFromBytes unmarshals the passed bytes into fabtoken public parameters.
func (d PublicParametersDeserializer) PublicParametersFromBytes(params []byte) (driver.PublicParameters, error) {
pp, err := v1.NewPublicParamsFromBytes(params, v1.FabTokenDriverName, v1.ProtocolV1)
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal public parameters")
}

return pp, nil
}
59 changes: 35 additions & 24 deletions token/core/fabtoken/v1/driver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (

// Driver contains the non-static logic of the fabtoken driver (including services).
type Driver struct {
*base
BaseWalletServiceFactory
metricsProvider cdriver.MetricsProvider
tracerProvider cdriver.TracerProvider
configService cdriver.ConfigService
Expand All @@ -35,8 +35,8 @@ type Driver struct {
vaultProvider cdriver.VaultProvider
}

// NewDriver returns a new factory for the fabtoken driver.
func NewDriver(
// NewTokenDriver returns a new factory for the fabtoken driver.
func NewTokenDriver(
metricsProvider cdriver.MetricsProvider,
tracerProvider cdriver.TracerProvider,
configService cdriver.ConfigService,
Expand All @@ -48,17 +48,38 @@ func NewDriver(
) core.NamedFactory[driver.Driver] {
return core.NamedFactory[driver.Driver]{
Name: core.DriverIdentifier(v1setup.FabTokenDriverName, 1),
Driver: &Driver{
base: &base{},
metricsProvider: metricsProvider,
tracerProvider: tracerProvider,
configService: configService,
storageProvider: storageProvider,
identityProvider: identityProvider,
endpointService: endpointService,
networkProvider: networkProvider,
vaultProvider: vaultProvider,
},
Driver: newTokenDriver(
metricsProvider,
tracerProvider,
configService,
storageProvider,
identityProvider,
endpointService,
networkProvider,
vaultProvider,
),
}
}

func newTokenDriver(
metricsProvider cdriver.MetricsProvider,
tracerProvider cdriver.TracerProvider,
configService cdriver.ConfigService,
storageProvider cdriver.StorageProvider,
identityProvider cdriver.IdentityProvider,
endpointService cdriver.NetworkBinderService,
networkProvider cdriver.NetworkProvider,
vaultProvider cdriver.VaultProvider,
) *Driver {
return &Driver{
metricsProvider: metricsProvider,
tracerProvider: tracerProvider,
configService: configService,
storageProvider: storageProvider,
identityProvider: identityProvider,
endpointService: endpointService,
networkProvider: networkProvider,
vaultProvider: vaultProvider,
}
}

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

return service, nil
}

// NewDefaultValidator returns a new fabtoken validator for the passed public parameters.
func (d *Driver) NewDefaultValidator(params driver.PublicParameters) (driver.Validator, error) {
pp, ok := params.(*v1setup.PublicParams)
if !ok {
return nil, errors.Errorf("invalid public parameters type [%T]", params)
}

return d.DefaultValidator(pp)
}
27 changes: 5 additions & 22 deletions token/core/fabtoken/v1/driver/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestNewDriver(t *testing.T) {
networkProvider := &mock2.NetworkProvider{}
vaultProvider := &mock2.VaultProvider{}

factory := driver.NewDriver(
factory := driver.NewTokenDriver(
metricsProvider,
nil,
configService,
Expand All @@ -60,7 +60,7 @@ func TestNewTokenService(t *testing.T) {
networkProvider := &mock2.NetworkProvider{}
vaultProvider := &mock2.VaultProvider{}

d := driver.NewDriver(
d := driver.NewTokenDriver(
metricsProvider,
nil,
configService,
Expand Down Expand Up @@ -156,34 +156,17 @@ func TestNewTokenService(t *testing.T) {

// TestNewDefaultValidator tests the creation of a default fabtoken validator.
func TestNewDefaultValidator(t *testing.T) {
metricsProvider := &mock2.MetricsProvider{}
configService := &mock2.ConfigService{}
storageProvider := &imock.StorageProvider{}
identityProvider := &mock2.IdentityProvider{}
endpointService := &idmock.NetworkBinderService{}
networkProvider := &mock2.NetworkProvider{}
vaultProvider := &mock2.VaultProvider{}

d := driver.NewDriver(
metricsProvider,
nil,
configService,
storageProvider,
identityProvider,
endpointService,
networkProvider,
vaultProvider,
).Driver.(*driver.Driver)
d := driver.NewValidatorDriver().Driver

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

// Case 1: Valid public parameters
v, err := d.NewDefaultValidator(pp)
v, err := d.NewValidator(pp)
require.NoError(t, err)
assert.NotNil(t, v)

// Case 2: Invalid public parameters type
v, err = d.NewDefaultValidator(&dmock.PublicParameters{})
v, err = d.NewValidator(&dmock.PublicParameters{})
require.Error(t, err)
assert.Nil(t, v)
assert.Contains(t, err.Error(), "invalid public parameters type")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
)

// PPMFactory contains the static logic of the fabtoken driver.
type PPMFactory struct{ *base }
type PPMFactory struct{ ValidatorDriver }

// NewPPMFactory returns a new factory for the fabtoken public parameters manager.
func NewPPMFactory() core.NamedFactory[driver.PPMFactory] {
Expand Down
Loading
Loading