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
4 changes: 2 additions & 2 deletions token/core/zkatdlog/nogh/v1/crypto/upgrade/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,11 +187,11 @@ func (s *Service) ProcessTokens(ledgerTokens []token.LedgerToken) ([]token.Token
// for each token, extract type and value
tokens := make([]token.Token, len(ledgerTokens))
for i, tok := range ledgerTokens {
precision, ok := token2.Precisions[tok.Format]
_, ok := token2.Precisions[tok.Format]
if !ok {
return nil, errors.Errorf("unsupported token format [%s]", tok.Format)
}
fabToken, _, err := token2.ParseFabtokenToken(tok.Token, precision, s.MaxPrecision)
fabToken, _, err := token2.ParseFabtokenToken(tok.Token, s.MaxPrecision)
if err != nil {
return nil, errors.Wrap(err, "failed to check unspent tokens")
}
Expand Down
28 changes: 14 additions & 14 deletions token/core/zkatdlog/nogh/v1/token/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,32 @@ package token
import "github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors"

var (
// ErrEmptyType is returned when a token type is empty
// ErrEmptyType is returned when a token type is empty.
ErrEmptyType = errors.New("missing Type")
// ErrEmptyValue is returned when a token value is nil
// ErrEmptyValue is returned when a token value is nil.
ErrEmptyValue = errors.New("missing Value")
// ErrEmptyBlindingFactor is returned when a token blinding factor is nil
// ErrEmptyBlindingFactor is returned when a token blinding factor is nil.
ErrEmptyBlindingFactor = errors.New("missing BlindingFactor")
// ErrMissingIssuer is returned when an issuer is required but missing
// ErrMissingIssuer is returned when an issuer is required but missing.
ErrMissingIssuer = errors.New("missing Issuer")
// ErrUnexpectedIssuer is returned when an issuer is present but should not be
// ErrUnexpectedIssuer is returned when an issuer is present but should not be.
ErrUnexpectedIssuer = errors.New("issuer should not be there")
// ErrEmptyOwner is returned when a token owner is empty
// ErrEmptyOwner is returned when a token owner is empty.
ErrEmptyOwner = errors.New("token owner cannot be empty")
// ErrEmptyTokenData is returned when token data is nil
// ErrEmptyTokenData is returned when token data is nil.
ErrEmptyTokenData = errors.New("token data cannot be empty")
// ErrNilCommitElement is returned when trying to commit a nil element
// ErrNilCommitElement is returned when trying to commit a nil element.
ErrNilCommitElement = errors.New("cannot commit a nil element")
// ErrTokenMismatch is returned when a token does not match its metadata
// ErrTokenMismatch is returned when a token commitment does not match its metadata.
ErrTokenMismatch = errors.New("cannot retrieve token in the clear: output does not match provided opening")
// ErrMissingFabToken is returned when FabToken is nil in UpgradeWitness
// ErrMissingFabToken is returned when the Fabtoken output is missing in an upgrade witness.
ErrMissingFabToken = errors.New("missing FabToken")
// ErrMissingFabTokenOwner is returned when FabToken owner is empty
// ErrMissingFabTokenOwner is returned when the Fabtoken owner is empty.
ErrMissingFabTokenOwner = errors.New("missing FabToken.Owner")
// ErrMissingFabTokenType is returned when FabToken type is empty
// ErrMissingFabTokenType is returned when the Fabtoken type is empty.
ErrMissingFabTokenType = errors.New("missing FabToken.Type")
// ErrMissingFabTokenQuantity is returned when FabToken quantity is empty
// ErrMissingFabTokenQuantity is returned when the Fabtoken quantity is empty.
ErrMissingFabTokenQuantity = errors.New("missing FabToken.Quantity")
// ErrMissingUpgradeBlindingFactor is returned when upgrade blinding factor is nil
// ErrMissingUpgradeBlindingFactor is returned when the blinding factor is missing in an upgrade witness.
ErrMissingUpgradeBlindingFactor = errors.New("missing BlindingFactor")
)
10 changes: 4 additions & 6 deletions token/core/zkatdlog/nogh/v1/token/fabtoken.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,15 @@ import (
"github.com/hyperledger-labs/fabric-token-sdk/token/token"
)

func ParseFabtokenToken(tok []byte, precision uint64, maxPrecision uint64) (*actions.Output, uint64, error) {
if precision < maxPrecision {
return nil, 0, errors.Errorf("unsupported precision [%d], max [%d]", precision, maxPrecision)
}

// ParseFabtokenToken unmarshals a raw fabtoken output and converts its quantity to a uint64 value
// using maxPrecision.
func ParseFabtokenToken(tok []byte, maxPrecision uint64) (*actions.Output, uint64, error) {
output := &actions.Output{}
err := output.Deserialize(tok)
if err != nil {
return nil, 0, errors.Wrap(err, "failed to unmarshal fabtoken")
}
q, err := token.NewUBigQuantity(output.Quantity, precision)
q, err := token.NewUBigQuantity(output.Quantity, maxPrecision)
if err != nil {
return nil, 0, errors.Wrap(err, "failed to create quantity")
}
Expand Down
41 changes: 30 additions & 11 deletions token/core/zkatdlog/nogh/v1/token/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,27 @@ import (
"github.com/hyperledger-labs/fabric-token-sdk/token/token"
)

// Precisions maps token formats to their corresponding bit-lengths.
var Precisions = map[token.Format]uint64{
utils.MustGet(v1.SupportedTokenFormat(16)): 16,
utils.MustGet(v1.SupportedTokenFormat(32)): 32,
utils.MustGet(v1.SupportedTokenFormat(64)): 64,
}

// TokensService provides functions for managing ZKAT-DLOG tokens,
// including deobfuscation, serialization, and upgrading from Fabtoken.
type TokensService struct {
Logger logging.Logger
PublicParametersManager common.PublicParametersManager[*setup.PublicParams]
IdentityDeserializer driver.Deserializer

OutputTokenFormat token.Format
// OutputTokenFormat is the default format used for output tokens.
OutputTokenFormat token.Format
// SupportedTokenFormatList lists all token formats this service can handle.
SupportedTokenFormatList []token.Format
}

// NewTokensService creates a new TokensService and initializes its supported token formats.
func NewTokensService(logger logging.Logger, publicParametersManager common.PublicParametersManager[*setup.PublicParams], identityDeserializer driver.Deserializer) (*TokensService, error) {
// compute supported tokens
pp := publicParametersManager.PublicParams()
Expand Down Expand Up @@ -84,6 +90,7 @@ func NewTokensService(logger logging.Logger, publicParametersManager common.Publ
}, nil
}

// Recipients returns the identities of the token recipients.
func (s *TokensService) Recipients(output driver.TokenOutput) ([]driver.Identity, error) {
tok := &Token{}
if err := tok.Deserialize(output); err != nil {
Expand All @@ -97,10 +104,8 @@ func (s *TokensService) Recipients(output driver.TokenOutput) ([]driver.Identity
return recipients, nil
}

// Deobfuscate unmarshals a token and token metadata from raw bytes.
// We assume here that the format of the output is the default output format supported
// It checks if the un-marshalled token matches the token info. If not, it returns
// an error. Else it returns the token in cleartext and the identity of its issuer
// Deobfuscate reveals the cleartext token and its issuer from an obfuscated output and its metadata.
// It first attempts to deobfuscate as a ZKAT-DLOG (commitment) token, falling back to Fabtoken if that fails.
func (s *TokensService) Deobfuscate(ctx context.Context, output driver.TokenOutput, outputMetadata driver.TokenOutputMetadata) (*token.Token, driver.Identity, []driver.Identity, token.Format, error) {
// we support fabtoken.Type and comm.Type

Expand All @@ -110,9 +115,15 @@ func (s *TokensService) Deobfuscate(ctx context.Context, output driver.TokenOutp
return tok, issuer, recipients, format, nil
}
// try fabtoken type
return s.deobfuscateAsFabtokenType(output, outputMetadata)
tok, issuer, recipients, format, err = s.deobfuscateAsFabtokenType(output, outputMetadata)
if err != nil {
return nil, nil, nil, "", errors.Wrapf(err, "failed to deobfuscate token")
}

return tok, issuer, recipients, format, nil
}

// deobfuscateAsCommType attempts to deobfuscate the token assuming it uses Pedersen commitments.
func (s *TokensService) deobfuscateAsCommType(ctx context.Context, output driver.TokenOutput, outputMetadata driver.TokenOutputMetadata) (*token.Token, driver.Identity, []driver.Identity, token.Format, error) {
_, metadata, tok, err := s.deserializeCommToken(ctx, output, outputMetadata, false)
if err != nil {
Expand All @@ -126,6 +137,7 @@ func (s *TokensService) deobfuscateAsCommType(ctx context.Context, output driver
return tok, metadata.Issuer, recipients, s.OutputTokenFormat, nil
}

// deobfuscateAsFabtokenType attempts to deobfuscate the token assuming it is a plain Fabtoken.
func (s *TokensService) deobfuscateAsFabtokenType(output driver.TokenOutput, outputMetadata driver.TokenOutputMetadata) (*token.Token, driver.Identity, []driver.Identity, token.Format, error) {
// TODO: refer only to the protos
tok := &actions.Output{}
Expand All @@ -150,16 +162,19 @@ func (s *TokensService) deobfuscateAsFabtokenType(output driver.TokenOutput, out
}, metadata.Issuer, recipients, s.OutputTokenFormat, nil
}

// SupportedTokenFormats returns the list of all token formats supported by this service.
func (s *TokensService) SupportedTokenFormats() []token.Format {
return s.SupportedTokenFormatList
}

// DeserializeToken unmarshals raw token data and metadata into their respective structures.
// It handles both ZKAT-DLOG tokens and automatic upgrades from Fabtoken to ZKAT-DLOG.
func (s *TokensService) DeserializeToken(ctx context.Context, outputFormat token.Format, outputRaw []byte, metadataRaw []byte) (*Token, *Metadata, *UpgradeWitness, error) {
// Here we have to check if what we get in input is already as expected.
// If not, we need to check if a token upgrade is possible.
// If not, a failure is to be returned
if !slices.Contains(s.SupportedTokenFormatList, outputFormat) {
return nil, nil, nil, errors.Errorf("invalid token type [%s], expected [%s]", outputFormat, s.OutputTokenFormat)
return nil, nil, nil, errors.Errorf("invalid token format [%s], expected one of [%v]", outputFormat, s.SupportedTokenFormatList)
}

if outputFormat == s.OutputTokenFormat {
Expand All @@ -173,11 +188,11 @@ func (s *TokensService) DeserializeToken(ctx context.Context, outputFormat token
}

// if we reach this point, we need to upgrade the token locally
precision, ok := Precisions[outputFormat]
_, ok := Precisions[outputFormat]
if !ok {
return nil, nil, nil, errors.Errorf("unsupported token format [%s]", outputFormat)
}
fabToken, value, err := ParseFabtokenToken(outputRaw, precision, s.PublicParametersManager.PublicParams().RangeProofParams.BitLength)
fabToken, value, err := ParseFabtokenToken(outputRaw, s.PublicParametersManager.PublicParams().QuantityPrecision)
if err != nil {
return nil, nil, nil, errors.Wrapf(err, "failed to unmarshal fabtoken token")
}
Expand All @@ -201,6 +216,7 @@ func (s *TokensService) DeserializeToken(ctx context.Context, outputFormat token
}, nil
}

// deserializeTokenWithOutputTokenFormat deserializes the token using the default ZKAT-DLOG format.
func (s *TokensService) deserializeTokenWithOutputTokenFormat(ctx context.Context, outputRaw []byte, metadataRaw []byte) (*Token, *Metadata, error) {
// get zkatdlog token
output, err := s.getOutput(ctx, outputRaw, false)
Expand All @@ -218,6 +234,7 @@ func (s *TokensService) deserializeTokenWithOutputTokenFormat(ctx context.Contex
return output, metadata, nil
}

// deserializeCommToken deserializes and verifies a commitment-based token.
func (s *TokensService) deserializeCommToken(ctx context.Context, outputRaw []byte, metadataRaw []byte, checkOwner bool) (*Token, *Metadata, *token.Token, error) {
// get zkatdlog token
output, err := s.getOutput(ctx, outputRaw, checkOwner)
Expand All @@ -241,6 +258,7 @@ func (s *TokensService) deserializeCommToken(ctx context.Context, outputRaw []by
return output, metadata, tok, nil
}

// getOutput unmarshals and validates the token output from raw bytes.
func (s *TokensService) getOutput(ctx context.Context, outputRaw []byte, checkOwner bool) (*Token, error) {
output := &Token{}
if err := output.Deserialize(outputRaw); err != nil {
Expand All @@ -250,12 +268,13 @@ func (s *TokensService) getOutput(ctx context.Context, outputRaw []byte, checkOw
return nil, errors.Errorf("token owner not found in output")
}
if err := math.CheckElement(output.Data, s.PublicParametersManager.PublicParams().Curve); err != nil {
return nil, errors.Wrap(err, "data in invalid in output")
return nil, errors.Wrap(err, "data is invalid in output")
}

return output, nil
}

// SupportedTokenFormat computes a unique token format identifier based on public parameters and precision.
func SupportedTokenFormat(pp *setup.PublicParams, precision uint64) (token.Format, error) {
hasher := utils2.NewSHA256Hasher()
if err := errors2.Join(
Expand All @@ -264,7 +283,7 @@ func SupportedTokenFormat(pp *setup.PublicParams, precision uint64) (token.Forma
hasher.AddUInt64(precision),
hasher.AddG1s(pp.PedersenGenerators),
); err != nil {
return "", errors.Wrapf(err, "failed to generator token type")
return "", errors.Wrapf(err, "failed to generate token format")
}

return token.Format(hasher.HexDigest()), nil
Expand Down
38 changes: 24 additions & 14 deletions token/core/zkatdlog/nogh/v1/token/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,21 @@ import (
"github.com/hyperledger-labs/fabric-token-sdk/token/token"
)

// Token encodes Type, Value, Owner
// Token represents a ZKAT-DLOG token without graph hiding.
// It encodes the token owner and the Pedersen commitment to its type, value, and blinding factor.
type Token comm.Token

// GetOwner returns the owner of the token.
func (t *Token) GetOwner() []byte {
return t.Owner
}

// IsRedeem returns true if the token has an empty owner field
// IsRedeem returns true if the token is a redemption (i.e., has no owner).
func (t *Token) IsRedeem() bool {
return len(t.Owner) == 0
}

// Serialize marshals Token
// Serialize marshals the Token into bytes, including its type information for proper unwrapping.
func (t *Token) Serialize() ([]byte, error) {
data, err := utils.ToProtoG1(t.Data)
if err != nil {
Expand All @@ -48,7 +50,7 @@ func (t *Token) Serialize() ([]byte, error) {
return comm.WrapTokenWithType(raw)
}

// Deserialize unmarshals Token
// Deserialize unmarshals the Token from bytes and validates its type.
func (t *Token) Deserialize(bytes []byte) error {
typed, err := comm.UnmarshalTypedToken(bytes)
if err != nil {
Expand All @@ -67,7 +69,8 @@ func (t *Token) Deserialize(bytes []byte) error {
return err
}

// ToClear returns Token in the clear
// ToClear verifies the token commitment against the provided metadata and public parameters.
// If valid, it returns the token in cleartext (type, quantity, and owner).
func (t *Token) ToClear(meta *Metadata, pp *noghv1.PublicParams) (*token.Token, error) {
com, err := commit([]*math.Zr{
math.Curves[pp.Curve].HashToZr([]byte(meta.Type)),
Expand All @@ -89,6 +92,7 @@ func (t *Token) ToClear(meta *Metadata, pp *noghv1.PublicParams) (*token.Token,
}, nil
}

// Validate checks if the token structure is well-formed.
func (t *Token) Validate(checkOwner bool) error {
if checkOwner && len(t.Owner) == 0 {
return ErrEmptyOwner
Expand All @@ -100,6 +104,7 @@ func (t *Token) Validate(checkOwner bool) error {
return nil
}

// computeTokens generates Pedersen commitments for a list of token metadata.
func computeTokens(tw []*Metadata, pp []*math.G1, c *math.Curve) ([]*math.G1, error) {
tokens := make([]*math.G1, len(tw))
var err error
Expand All @@ -114,6 +119,8 @@ func computeTokens(tw []*Metadata, pp []*math.G1, c *math.Curve) ([]*math.G1, er
return tokens, nil
}

// GetTokensWithWitness generates commitments and metadata for a given set of values and token type.
// It uses a cryptographically secure random number generator for blinding factors.
func GetTokensWithWitness(values []uint64, tokenType token.Type, pp []*math.G1, c *math.Curve) ([]*math.G1, []*Metadata, error) {
if c == nil {
return nil, nil, errors.New("cannot get tokens with witness: please initialize curve")
Expand All @@ -138,10 +145,10 @@ func GetTokensWithWitness(values []uint64, tokenType token.Type, pp []*math.G1,
return tokens, tw, nil
}

// Metadata contains the metadata of a token
// Metadata contains the opening information (type, value, blinding factor) for a token commitment.
type Metadata comm.Metadata

// NewMetadata returns an array of Metadata that corresponds to the passed arguments
// NewMetadata creates a slice of Metadata objects from the provided values and blinding factors.
func NewMetadata(curve math.CurveID, tokenType token.Type, values []uint64, bfs []*math.Zr) []*Metadata {
witness := make([]*Metadata, len(values))
for i, v := range values {
Expand All @@ -152,7 +159,7 @@ func NewMetadata(curve math.CurveID, tokenType token.Type, values []uint64, bfs
return witness
}

// Deserialize un-marshals Metadata
// Deserialize unmarshals Metadata from bytes and validates its structure.
func (m *Metadata) Deserialize(b []byte) error {
typed, err := comm.UnmarshalTypedToken(b)
if err != nil {
Expand All @@ -178,15 +185,15 @@ func (m *Metadata) Deserialize(b []byte) error {
return nil
}

// Serialize un-marshals Metadata
// Serialize marshals Metadata into bytes, including its type information for proper unwrapping.
func (m *Metadata) Serialize() ([]byte, error) {
value, err := utils.ToProtoZr(m.Value)
if err != nil {
return nil, errors.Wrapf(err, "failed to deserialize metadata")
return nil, errors.Wrapf(err, "failed to serialize metadata")
}
blindingFactor, err := utils.ToProtoZr(m.BlindingFactor)
if err != nil {
return nil, errors.Wrapf(err, "failed to deserialize metadata")
return nil, errors.Wrapf(err, "failed to serialize metadata")
}
raw, err := proto.Marshal(&actions.TokenMetadata{
Type: string(m.Type),
Expand All @@ -201,6 +208,7 @@ func (m *Metadata) Serialize() ([]byte, error) {
return comm.WrapMetadataWithType(raw)
}

// Clone creates a deep copy of the Metadata.
func (m *Metadata) Clone() *Metadata {
return &Metadata{
Type: m.Type,
Expand All @@ -210,9 +218,7 @@ func (m *Metadata) Clone() *Metadata {
}
}

// Validate checks that Metadata is well-formed.
// If checkIssuer is true, it checks that the Issuer field is set.
// If checkIssuer is false, it checks that the Issuer field is not set.
// Validate ensures the Metadata is well-formed and checks the presence of the issuer if required.
func (m *Metadata) Validate(checkIssuer bool) error {
if len(m.Type) == 0 {
return ErrEmptyType
Expand All @@ -233,6 +239,7 @@ func (m *Metadata) Validate(checkIssuer bool) error {
return nil
}

// commit computes a Pedersen commitment to a vector of field elements using the provided generators.
func commit(vector []*math.Zr, generators []*math.G1, c *math.Curve) (*math.G1, error) {
com := c.NewG1()
for i := range vector {
Expand All @@ -245,12 +252,15 @@ func commit(vector []*math.Zr, generators []*math.G1, c *math.Curve) (*math.G1,
return com, nil
}

// UpgradeWitness contains the original Fabtoken output and the blinding factor
// used to create the upgraded ZKAT-DLOG commitment.
type UpgradeWitness struct {
FabToken *fabtokenv1.Output
// BlindingFactor is the blinding factor used to commit type and value
BlindingFactor *math.Zr
}

// Validate ensures the UpgradeWitness is well-formed.
func (u *UpgradeWitness) Validate() error {
if u.FabToken == nil {
return ErrMissingFabToken
Expand Down
Loading
Loading