diff --git a/token/core/common/backend.go b/token/core/common/backend.go new file mode 100644 index 000000000..36de2cd1d --- /dev/null +++ b/token/core/common/backend.go @@ -0,0 +1,48 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package common + +import ( + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token/driver" + "github.com/pkg/errors" +) + +type Backend struct { + // Ledger to access the ledger state + Ledger driver.GetStateFnc + // signed Message + Message []byte + // Cursor is used to iterate over the signatures + Cursor int + // signatures on Message + Sigs [][]byte +} + +func NewBackend(ledger driver.GetStateFnc, message []byte, sigs [][]byte) *Backend { + return &Backend{Ledger: ledger, Message: message, Sigs: sigs} +} + +// HasBeenSignedBy checks if a given Message has been signed by the signing identity matching +// the passed verifier +func (b *Backend) HasBeenSignedBy(id view.Identity, verifier driver.Verifier) ([]byte, error) { + if b.Cursor >= len(b.Sigs) { + return nil, errors.New("invalid state, insufficient number of signatures") + } + sigma := b.Sigs[b.Cursor] + b.Cursor++ + + return sigma, verifier.Verify(b.Message, sigma) +} + +func (b *Backend) GetState(key string) ([]byte, error) { + return b.Ledger(key) +} + +func (b *Backend) Signatures() [][]byte { + return b.Sigs +} diff --git a/token/core/common/metadata.go b/token/core/common/metadata.go new file mode 100644 index 000000000..1f449ca09 --- /dev/null +++ b/token/core/common/metadata.go @@ -0,0 +1,28 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package common + +import ( + "strings" + + "github.com/hyperledger-labs/fabric-token-sdk/token" +) + +// SetTransferActionMetadata extracts the transfer metadata from the passed attributes and +// sets them to the passed metadata +func SetTransferActionMetadata(attrs map[interface{}]interface{}, metadata map[string][]byte) { + for key, value := range attrs { + k, ok1 := key.(string) + v, ok2 := value.([]byte) + if ok1 && ok2 { + if strings.HasPrefix(k, token.TransferMetadataPrefix) { + mKey := strings.TrimPrefix(k, token.TransferMetadataPrefix) + metadata[mKey] = v + } + } + } +} diff --git a/token/core/fabtoken/actions.go b/token/core/fabtoken/actions.go index 573509646..5313a8f31 100644 --- a/token/core/fabtoken/actions.go +++ b/token/core/fabtoken/actions.go @@ -15,16 +15,6 @@ import ( "github.com/pkg/errors" ) -// Signature contains metadata -type Signature struct { - metadata map[string][]byte // metadata may include for example the preimage of an htlc script -} - -// Metadata returns the contained metadata -func (s *Signature) Metadata() map[string][]byte { - return s.metadata -} - // OutputMetadata contains a serialization of the issuer of the token. // type, value and owner of token can be derived from the token itself. type OutputMetadata struct { @@ -127,6 +117,8 @@ type TransferAction struct { Inputs []string // outputs to be created as a result of the transfer Outputs []*Output + // Metadata contains the transfer action's metadata + Metadata map[string][]byte } // Serialize marshals TransferAction @@ -196,7 +188,46 @@ func (t *TransferAction) Deserialize(raw []byte) error { return json.Unmarshal(raw, t) } -// GetMetadata returns nil, indicating that fabtoken TransferAction carries no metadata -func (t *TransferAction) GetMetadata() []byte { - return nil +// GetMetadata returns the transfer action's metadata +func (t *TransferAction) GetMetadata() map[string][]byte { + return t.Metadata +} + +// UnmarshalIssueTransferActions returns the deserialized issue and transfer actions contained in the passed TokenRequest +func UnmarshalIssueTransferActions(tr *driver.TokenRequest, binding string) ([]*IssueAction, []*TransferAction, error) { + ia, err := UnmarshalIssueActions(tr.Issues) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to retrieve issue actions [%s]", binding) + } + ta, err := UnmarshalTransferActions(tr.Transfers) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to retrieve transfer actions [%s]", binding) + } + return ia, ta, nil +} + +// UnmarshalTransferActions returns an array of deserialized TransferAction from raw bytes +func UnmarshalTransferActions(raw [][]byte) ([]*TransferAction, error) { + res := make([]*TransferAction, len(raw)) + for i := 0; i < len(raw); i++ { + ta := &TransferAction{} + if err := ta.Deserialize(raw[i]); err != nil { + return nil, err + } + res[i] = ta + } + return res, nil +} + +// UnmarshalIssueActions returns an array of deserialized IssueAction from raw bytes +func UnmarshalIssueActions(raw [][]byte) ([]*IssueAction, error) { + res := make([]*IssueAction, len(raw)) + for i := 0; i < len(raw); i++ { + ia := &IssueAction{} + if err := ia.Deserialize(raw[i]); err != nil { + return nil, err + } + res[i] = ia + } + return res, nil } diff --git a/token/core/fabtoken/deserializer.go b/token/core/fabtoken/deserializer.go index fb3c9a862..34a3ee4f2 100644 --- a/token/core/fabtoken/deserializer.go +++ b/token/core/fabtoken/deserializer.go @@ -12,7 +12,7 @@ import ( "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" "github.com/hyperledger-labs/fabric-token-sdk/token/core/identity" "github.com/hyperledger-labs/fabric-token-sdk/token/core/identity/msp/x509" - "github.com/hyperledger-labs/fabric-token-sdk/token/core/interop" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/interop/htlc" "github.com/hyperledger-labs/fabric-token-sdk/token/driver" ) @@ -35,13 +35,13 @@ func NewDeserializer() *deserializer { return &deserializer{ auditorDeserializer: &x509.MSPIdentityDeserializer{}, issuerDeserializer: &x509.MSPIdentityDeserializer{}, - ownerDeserializer: identity.NewRawOwnerIdentityDeserializer(&x509.MSPIdentityDeserializer{}), + ownerDeserializer: htlc.NewDeserializer(identity.NewRawOwnerIdentityDeserializer(&x509.MSPIdentityDeserializer{})), } } // GetOwnerVerifier deserializes the verifier for the passed owner identity func (d *deserializer) GetOwnerVerifier(id view.Identity) (driver.Verifier, error) { - return interop.NewDeserializer(d.ownerDeserializer).GetOwnerVerifier(id) + return d.ownerDeserializer.DeserializeVerifier(id) } // GetIssuerVerifier deserializes the verifier for the passed issuer identity @@ -74,7 +74,7 @@ func (e *enrollmentService) GetEnrollmentID(auditInfo []byte) (string, error) { } // Try to unmarshal it as ScriptInfo - si := &interop.ScriptInfo{} + si := &htlc.ScriptInfo{} err := json.Unmarshal(auditInfo, si) if err == nil && (len(si.Sender) != 0 || len(si.Recipient) != 0) { if len(si.Recipient) != 0 { diff --git a/token/core/fabtoken/sender.go b/token/core/fabtoken/sender.go index 97e3d8360..4b0ef9c62 100644 --- a/token/core/fabtoken/sender.go +++ b/token/core/fabtoken/sender.go @@ -8,8 +8,9 @@ package fabtoken import ( "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/common" "github.com/hyperledger-labs/fabric-token-sdk/token/core/identity" - "github.com/hyperledger-labs/fabric-token-sdk/token/core/interop" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/interop/htlc" "github.com/hyperledger-labs/fabric-token-sdk/token/driver" token2 "github.com/hyperledger-labs/fabric-token-sdk/token/token" "github.com/pkg/errors" @@ -46,10 +47,14 @@ func (s *Service) Transfer(txID string, wallet driver.OwnerWallet, ids []*token2 // assemble transfer action transfer := &TransferAction{ - Inputs: inputIDs, - Outputs: outs, + Inputs: inputIDs, + Outputs: outs, + Metadata: map[string][]byte{}, } + // add transfer action's metadata + common.SetTransferActionMetadata(opts.Attributes, transfer.Metadata) + // assemble transfer metadata var receivers []view.Identity for i, output := range outs { @@ -68,7 +73,7 @@ func (s *Service) Transfer(txID string, wallet driver.OwnerWallet, ids []*token2 receivers = append(receivers, output.Output.Owner.Raw) continue } - _, recipient, err := interop.GetScriptSenderAndRecipient(owner) + _, recipient, err := htlc.GetScriptSenderAndRecipient(owner) if err != nil { return nil, nil, errors.Wrap(err, "failed getting script sender and recipient") } @@ -77,7 +82,7 @@ func (s *Service) Transfer(txID string, wallet driver.OwnerWallet, ids []*token2 var senderAuditInfos [][]byte for _, t := range inputTokens { - auditInfo, err := interop.GetOwnerAuditInfo(t.Owner.Raw, s.SP) + auditInfo, err := htlc.GetOwnerAuditInfo(t.Owner.Raw, s.SP) if err != nil { return nil, nil, errors.Wrapf(err, "failed getting audit info for sender identity [%s]", view.Identity(t.Owner.Raw).String()) } @@ -86,7 +91,7 @@ func (s *Service) Transfer(txID string, wallet driver.OwnerWallet, ids []*token2 var receiverAuditInfos [][]byte for _, output := range outs { - auditInfo, err := interop.GetOwnerAuditInfo(output.Output.Owner.Raw, s.SP) + auditInfo, err := htlc.GetOwnerAuditInfo(output.Output.Owner.Raw, s.SP) if err != nil { return nil, nil, errors.Wrapf(err, "failed getting audit info for recipient identity [%s]", view.Identity(output.Output.Owner.Raw).String()) } diff --git a/token/core/fabtoken/validator.go b/token/core/fabtoken/validator.go index 34440ff39..cd7776650 100644 --- a/token/core/fabtoken/validator.go +++ b/token/core/fabtoken/validator.go @@ -9,109 +9,50 @@ package fabtoken import ( "bytes" "encoding/json" - "time" "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/hash" - "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" - "github.com/hyperledger-labs/fabric-token-sdk/token/core/identity" - "github.com/hyperledger-labs/fabric-token-sdk/token/core/interop" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/common" "github.com/hyperledger-labs/fabric-token-sdk/token/driver" - "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/htlc" token2 "github.com/hyperledger-labs/fabric-token-sdk/token/token" "github.com/pkg/errors" "go.uber.org/zap/zapcore" ) -var defaultValidators = []ValidateTransfer{SerializedIdentityTypeExtraValidator, ScriptTypeHTLCExtraValidator} - -type ValidateTransfer func(inputTokens []*token2.Token, tr driver.TransferAction) error - -func SerializedIdentityTypeExtraValidator(inputTokens []*token2.Token, tr driver.TransferAction) error { - // noting else to validate - return nil -} - -func ScriptTypeHTLCExtraValidator(inputTokens []*token2.Token, tr driver.TransferAction) error { - for _, in := range inputTokens { - owner, err := identity.UnmarshallRawOwner(in.Owner.Raw) - if err != nil { - return errors.Wrap(err, "failed to unmarshal owner of input token") - } - if owner.Type == htlc.ScriptType { - if len(inputTokens) != 1 || len(tr.GetOutputs()) != 1 { - return errors.Errorf("invalid transfer action: an htlc script only transfers the ownership of a token") - } - - out := tr.GetOutputs()[0].(*Output).Output - if inputTokens[0].Type != out.Type { - return errors.Errorf("invalid transfer action: type of input does not match type of output") - } - if inputTokens[0].Quantity != out.Quantity { - return errors.Errorf("invalid transfer action: quantity of input does not match quantity of output") - } - - // check that owner field in output is correct - if err := interop.VerifyTransferFromHTLCScript(inputTokens[0].Owner.Raw, out.Owner.Raw); err != nil { - return errors.Wrap(err, "failed to verify transfer from htlc script") - } - } - } - - for _, o := range tr.GetOutputs() { - out, ok := o.(*Output) - if !ok { - return errors.Errorf("invalid output") - } - if out.IsRedeem() { - continue - } - owner, err := identity.UnmarshallRawOwner(out.Output.Owner.Raw) - if err != nil { - return err - } - if owner.Type == htlc.ScriptType { - script := &htlc.Script{} - err = json.Unmarshal(owner.Identity, script) - if err != nil { - return err - } - if script.Deadline.Before(time.Now()) { - return errors.Errorf("htlc script invalid: expiration date has already passed") - } - continue - } - } - return nil -} - // Validator checks the validity of fabtoken TokenRequest type Validator struct { // fabtoken public parameters pp *PublicParams - // deserializer for identities used in fabtoken + // deserializer for identities deserializer driver.Deserializer - // extraValidators for performing additional validation - extraValidators []ValidateTransfer + // transferValidators for performing transfer action validation + transferValidators []ValidateTransferFunc } // NewValidator initializes a Validator with the passed parameters -func NewValidator(pp *PublicParams, deserializer driver.Deserializer, extraValidators ...ValidateTransfer) (*Validator, error) { +func NewValidator(pp *PublicParams, deserializer driver.Deserializer, extraValidators ...ValidateTransferFunc) (*Validator, error) { if pp == nil { return nil, errors.New("please provide a non-nil public parameters") } if deserializer == nil { return nil, errors.New("please provide a non-nil deserializer") } - defaultValidators = append(defaultValidators, extraValidators...) - return &Validator{ - pp: pp, - deserializer: deserializer, - extraValidators: defaultValidators, - }, nil + validators := []ValidateTransferFunc{ + TransferSignatureValidate, + TransferBalanceValidate, + TransferHTLCValidate, + } + validators = append(validators, extraValidators...) + v := &Validator{ + pp: pp, + deserializer: deserializer, + transferValidators: validators, + } + return v, nil } // VerifyTokenRequest validates the passed token request against data in the ledger, the signature provided and the binding func (v *Validator) VerifyTokenRequest(ledger driver.Ledger, signatureProvider driver.SignatureProvider, binding string, tr *driver.TokenRequest) ([]interface{}, error) { + // validate arguments if ledger == nil { return nil, errors.New("please provide a non-nil ledger") } @@ -152,20 +93,7 @@ func (v *Validator) VerifyTokenRequest(ledger driver.Ledger, signatureProvider d for _, action := range ta { actions = append(actions, action) } - for _, sig := range signatureProvider.Signatures() { - claim := &htlc.ClaimSignature{} - if err = json.Unmarshal(sig, claim); err != nil { - continue - } - if len(claim.Preimage) == 0 || len(claim.RecipientSignature) == 0 { - return nil, errors.New("expected a valid claim preImage and recipient signature") - } - actions = append(actions, &Signature{ - metadata: map[string][]byte{ - "claimPreimage": claim.Preimage, - }, - }) - } + // actions are returned and will be used later to update the ledger return actions, nil } @@ -188,7 +116,7 @@ func (v *Validator) VerifyTokenRequestFromRaw(getState driver.GetStateFnc, bindi return nil, errors.Wrap(err, "failed to unmarshal token request") } - // Prepare message expected to be signed + // Prepare Message expected to be signed // TODO: encapsulate this somewhere req := &driver.TokenRequest{} req.Transfers = tr.Transfers @@ -211,11 +139,7 @@ func (v *Validator) VerifyTokenRequestFromRaw(getState driver.GetStateFnc, bindi signatures = tr.Signatures } - backend := &backend{ - getState: getState, - message: signed, - signatures: signatures, - } + backend := common.NewBackend(getState, signed, signatures) return v.VerifyTokenRequest(backend, backend, binding, tr) } @@ -228,7 +152,8 @@ func (v *Validator) VerifyAuditorSignature(signatureProvider driver.SignaturePro return errors.New("failed to deserialize auditor's public key") } - return signatureProvider.HasBeenSignedBy(v.pp.AuditorIdentity(), verifier) + _, err = signatureProvider.HasBeenSignedBy(v.pp.AuditorIdentity(), verifier) + return err } return nil } @@ -238,7 +163,7 @@ func (v *Validator) VerifyAuditorSignature(signatureProvider driver.SignaturePro func (v *Validator) VerifyIssues(issues []*IssueAction, signatureProvider driver.SignatureProvider) error { for _, issue := range issues { // verify that issue is valid - if err := v.verifyIssue(issue); err != nil { + if err := v.VerifyIssue(issue); err != nil { return errors.Wrap(err, "failed to verify issue action") } @@ -263,42 +188,15 @@ func (v *Validator) VerifyIssues(issues []*IssueAction, signatureProvider driver return errors.Wrapf(err, "failed getting verifier for [%s]", issue.Issuer.String()) } // verify if the token request concatenated with the binding was signed by the issuer - if err := signatureProvider.HasBeenSignedBy(issue.Issuer, verifier); err != nil { + if _, err := signatureProvider.HasBeenSignedBy(issue.Issuer, verifier); err != nil { return errors.Wrapf(err, "failed verifying signature") } } return nil } -// VerifyTransfers checks if the created output tokens are valid and if the content of the token request concatenated -// with the binding was signed by the owners of the input tokens -func (v *Validator) VerifyTransfers(ledger driver.Ledger, transferActions []*TransferAction, signatureProvider driver.SignatureProvider) error { - logger.Debugf("check sender start...") - defer logger.Debugf("check sender finished.") - for i, t := range transferActions { - // get inputs used in the current transfer action - inputTokens, err := RetrieveInputsFromTransferAction(t, ledger) - if err != nil { - return errors.Wrapf(err, "failed to retrieve input from transfer action at index %d", i) - } - // verify if the token request concatenated with the binding was signed by the owners of the inputs - // in the current transfer action - err = v.CheckSendersSignatures(inputTokens, i, signatureProvider) - if err != nil { - return err - } - // verify if input tokens and output tokens in the current transfer action have the same type - // verify if sum of input tokens in the current transfer action equals the sum of output tokens - // in the current transfer action - if err := v.VerifyTransfer(inputTokens, t); err != nil { - return errors.Wrapf(err, "failed to verify transfer action at index %d", i) - } - } - return nil -} - -// verifyIssue checks if all outputs in IssueAction are valid (no zero-value outputs) -func (v *Validator) verifyIssue(issue driver.IssueAction) error { +// VerifyIssue checks if all outputs in IssueAction are valid (no zero-value outputs) +func (v *Validator) VerifyIssue(issue driver.IssueAction) error { if issue.NumOutputs() == 0 { return errors.Errorf("there is no output") } @@ -316,139 +214,41 @@ func (v *Validator) verifyIssue(issue driver.IssueAction) error { return nil } -// VerifyTransfer checks that sum of inputTokens in TransferAction equals sum of outputs in TransferAction -// It also checks that all outputs and inputs have the same type -func (v *Validator) VerifyTransfer(inputTokens []*token2.Token, tr driver.TransferAction) error { - if tr.NumOutputs() == 0 { - return errors.Errorf("there is no output") - } - if len(inputTokens) == 0 { - return errors.Errorf("there is no input") - } - if inputTokens[0] == nil { - return errors.Errorf("first input is nil") - } - typ := inputTokens[0].Type - inputSum := token2.NewZeroQuantity(v.pp.QuantityPrecision) - outputSum := token2.NewZeroQuantity(v.pp.QuantityPrecision) - for i, input := range inputTokens { - if input == nil { - return errors.Errorf("input %d is nil", i) - } - q, err := token2.ToQuantity(input.Quantity, v.pp.QuantityPrecision) - if err != nil { - return errors.Wrapf(err, "failed parsing quantity [%s]", input.Quantity) - } - inputSum.Add(q) - // check that all inputs have the same type - if input.Type != typ { - return errors.Errorf("input type %s does not match type %s", input.Type, typ) - } - } - for _, output := range tr.GetOutputs() { - out := output.(*Output).Output - q, err := token2.ToQuantity(out.Quantity, v.pp.QuantityPrecision) +// VerifyTransfers checks if the created output tokens are valid and if the content of the token request concatenated +// with the binding was signed by the owners of the input tokens +func (v *Validator) VerifyTransfers(ledger driver.Ledger, transferActions []*TransferAction, signatureProvider driver.SignatureProvider) error { + logger.Debugf("check sender start...") + defer logger.Debugf("check sender finished.") + for i, t := range transferActions { + // get inputs used in the current transfer action + inputTokens, err := RetrieveInputsFromTransferAction(t, ledger) if err != nil { - return errors.Wrapf(err, "failed parsing quantity [%s]", out.Quantity) - } - outputSum.Add(q) - // check that all outputs have the same type and it is the same type as inputs - if out.Type != typ { - return errors.Errorf("output type %s does not match type %s", out.Type, typ) + return errors.Wrapf(err, "failed to retrieve input from transfer action at index %d", i) } - } - // check equality of sum of inputs and outputs - if inputSum.Cmp(outputSum) != 0 { - return errors.Errorf("input sum %v does not match output sum %v", inputSum, outputSum) - } - for _, v := range v.extraValidators { - if err := v(inputTokens, tr); err != nil { - return err + // verify if input tokens and output tokens in the current transfer action have the same type + // verify if sum of input tokens in the current transfer action equals the sum of output tokens + // in the current transfer action + if err := v.VerifyTransfer(ledger, inputTokens, t, signatureProvider); err != nil { + return errors.Wrapf(err, "failed to verify transfer action at index %d", i) } } return nil } -type backend struct { - getState driver.GetStateFnc - // signed message - message []byte - index int - // signatures on message - signatures [][]byte -} - -// HasBeenSignedBy checks if a given message has been signed by the signing identity matching -// the passed verifier -// todo shall we remove id from the parameters -func (b *backend) HasBeenSignedBy(id view.Identity, verifier driver.Verifier) error { - if b.index >= len(b.signatures) { - return errors.Errorf("invalid state, insufficient number of signatures") - } - sigma := b.signatures[b.index] - b.index++ - - return verifier.Verify(b.message, sigma) -} - -func (b *backend) GetState(key string) ([]byte, error) { - return b.getState(key) -} - -func (b *backend) Signatures() [][]byte { - return b.signatures -} - -// UnmarshalIssueTransferActions returns the deserialized issue and transfer actions contained in the passed TokenRequest -func UnmarshalIssueTransferActions(tr *driver.TokenRequest, binding string) ([]*IssueAction, []*TransferAction, error) { - ia, err := unmarshalIssueActions(tr.Issues) - if err != nil { - return nil, nil, errors.Wrapf(err, "failed to retrieve issue actions [%s]", binding) - } - ta, err := unmarshalTransferActions(tr.Transfers) - if err != nil { - return nil, nil, errors.Wrapf(err, "failed to retrieve transfer actions [%s]", binding) - } - return ia, ta, nil -} - -// unmarshalTransferActions returns an array of deserialized TransferAction from raw bytes -func unmarshalTransferActions(raw [][]byte) ([]*TransferAction, error) { - res := make([]*TransferAction, len(raw)) - for i := 0; i < len(raw); i++ { - ta := &TransferAction{} - if err := ta.Deserialize(raw[i]); err != nil { - return nil, err - } - res[i] = ta - } - return res, nil -} - -// unmarshalIssueActions returns an array of deserialized IssueAction from raw bytes -func unmarshalIssueActions(raw [][]byte) ([]*IssueAction, error) { - res := make([]*IssueAction, len(raw)) - for i := 0; i < len(raw); i++ { - ia := &IssueAction{} - if err := ia.Deserialize(raw[i]); err != nil { - return nil, err - } - res[i] = ia - } - return res, nil -} - -// CheckSendersSignatures verifies if a TokenRequest was signed by the owners of the inputs in the TokenRequest -func (v *Validator) CheckSendersSignatures(inputTokens []*token2.Token, actionIndex int, signatureProvider driver.SignatureProvider) error { - for _, tok := range inputTokens { - logger.Debugf("check sender [%d][%s]", actionIndex, view.Identity(tok.Owner.Raw).UniqueID()) - verifier, err := v.deserializer.GetOwnerVerifier(tok.Owner.Raw) - if err != nil { - return errors.Wrapf(err, "failed deserializing owner [%d][%v][%s]", actionIndex, tok, view.Identity(tok.Owner.Raw).UniqueID()) - } - logger.Debugf("signature verification [%d][%v][%s]", actionIndex, tok, view.Identity(tok.Owner.Raw).UniqueID()) - if err := signatureProvider.HasBeenSignedBy(tok.Owner.Raw, verifier); err != nil { - return errors.Wrapf(err, "failed signature verification [%d][%v][%s]", actionIndex, tok, view.Identity(tok.Owner.Raw).UniqueID()) +// VerifyTransfer checks that sum of inputTokens in TransferAction equals sum of outputs in TransferAction +// It also checks that all outputs and inputs have the same type +func (v *Validator) VerifyTransfer(ledger driver.Ledger, inputTokens []*token2.Token, tr driver.TransferAction, signatureProvider driver.SignatureProvider) error { + ctx := &Context{ + PP: v.pp, + Deserializer: v.deserializer, + SignatureProvider: signatureProvider, + InputTokens: inputTokens, + Action: tr.(*TransferAction), + Ledger: ledger, + } + for _, validator := range v.transferValidators { + if err := validator(ctx); err != nil { + return err } } return nil diff --git a/token/core/fabtoken/validator_transfer.go b/token/core/fabtoken/validator_transfer.go new file mode 100644 index 000000000..1ae524e78 --- /dev/null +++ b/token/core/fabtoken/validator_transfer.go @@ -0,0 +1,204 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package fabtoken + +import ( + "bytes" + "encoding/json" + "time" + + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/identity" + htlc2 "github.com/hyperledger-labs/fabric-token-sdk/token/core/interop/htlc" + "github.com/hyperledger-labs/fabric-token-sdk/token/driver" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/htlc" + "github.com/hyperledger-labs/fabric-token-sdk/token/token" + "github.com/pkg/errors" +) + +type Context struct { + PP *PublicParams + Deserializer driver.Deserializer + SignatureProvider driver.SignatureProvider + Signatures [][]byte + InputTokens []*token.Token + Action *TransferAction + Ledger driver.Ledger +} + +// ValidateTransferFunc is the prototype of a validation function for a transfer action +type ValidateTransferFunc func(ctx *Context) error + +// TransferSignatureValidate validates the signatures for the inputs spent by an action +func TransferSignatureValidate(ctx *Context) error { + for _, tok := range ctx.InputTokens { + logger.Debugf("check sender [%s]", view.Identity(tok.Owner.Raw).UniqueID()) + verifier, err := ctx.Deserializer.GetOwnerVerifier(tok.Owner.Raw) + if err != nil { + return errors.Wrapf(err, "failed deserializing owner [%v][%s]", tok, view.Identity(tok.Owner.Raw).UniqueID()) + } + logger.Debugf("signature verification [%v][%s]", tok, view.Identity(tok.Owner.Raw).UniqueID()) + sigma, err := ctx.SignatureProvider.HasBeenSignedBy(tok.Owner.Raw, verifier) + if err != nil { + return errors.Wrapf(err, "failed signature verification [%v][%s]", tok, view.Identity(tok.Owner.Raw).UniqueID()) + } + ctx.Signatures = append(ctx.Signatures, sigma) + } + return nil +} + +// TransferBalanceValidate checks that the sum of the inputs is equal to the sum of the outputs +func TransferBalanceValidate(ctx *Context) error { + if ctx.Action.NumOutputs() == 0 { + return errors.New("there is no output") + } + if len(ctx.InputTokens) == 0 { + return errors.New("there is no input") + } + if ctx.InputTokens[0] == nil { + return errors.New("first input is nil") + } + typ := ctx.InputTokens[0].Type + inputSum := token.NewZeroQuantity(ctx.PP.QuantityPrecision) + outputSum := token.NewZeroQuantity(ctx.PP.QuantityPrecision) + for i, input := range ctx.InputTokens { + if input == nil { + return errors.Errorf("input %d is nil", i) + } + q, err := token.ToQuantity(input.Quantity, ctx.PP.QuantityPrecision) + if err != nil { + return errors.Wrapf(err, "failed parsing quantity [%s]", input.Quantity) + } + inputSum.Add(q) + // check that all inputs have the same type + if input.Type != typ { + return errors.Errorf("input type %s does not match type %s", input.Type, typ) + } + } + for _, output := range ctx.Action.GetOutputs() { + out := output.(*Output).Output + q, err := token.ToQuantity(out.Quantity, ctx.PP.QuantityPrecision) + if err != nil { + return errors.Wrapf(err, "failed parsing quantity [%s]", out.Quantity) + } + outputSum.Add(q) + // check that all outputs have the same type, and it is the same type as inputs + if out.Type != typ { + return errors.Errorf("output type %s does not match type %s", out.Type, typ) + } + } + // check equality of sum of inputs and outputs + if inputSum.Cmp(outputSum) != 0 { + return errors.Errorf("input sum %v does not match output sum %v", inputSum, outputSum) + } + + return nil +} + +// TransferHTLCValidate checks the validity of the HTLC scripts, if any +func TransferHTLCValidate(ctx *Context) error { + now := time.Now() + + for i, in := range ctx.InputTokens { + owner, err := identity.UnmarshallRawOwner(in.Owner.Raw) + if err != nil { + return errors.Wrap(err, "failed to unmarshal owner of input token") + } + // is it owned by an htlc script? + if owner.Type == htlc.ScriptType { + // Then, the first output must be compatible with this input. + if len(ctx.Action.GetOutputs()) != 1 { + return errors.New("invalid transfer action: an htlc script only transfers the ownership of a token") + } + + // check type and quantity + output := ctx.Action.GetOutputs()[0].(*Output) + tok := output.Output + if ctx.InputTokens[0].Type != tok.Type { + return errors.New("invalid transfer action: type of input does not match type of output") + } + if ctx.InputTokens[0].Quantity != tok.Quantity { + return errors.New("invalid transfer action: quantity of input does not match quantity of output") + } + if output.IsRedeem() { + return errors.New("invalid transfer action: the output corresponding to an htlc spending should not be a redeem") + } + + // check owner field + _, op, err := htlc2.VerifyOwner(ctx.InputTokens[0].Owner.Raw, tok.Owner.Raw, now) + if err != nil { + return errors.Wrap(err, "failed to verify transfer from htlc script") + } + + // check metadata + sigma := ctx.Signatures[i] + if err := HTLCMetadataCheck(ctx, op, sigma); err != nil { + return errors.Wrapf(err, "failed to check htlc metadata") + } + } + } + + for _, o := range ctx.Action.GetOutputs() { + out, ok := o.(*Output) + if !ok { + return errors.New("invalid output") + } + if out.IsRedeem() { + continue + } + + // if it is an htlc script then the deadline must still be valid + owner, err := identity.UnmarshallRawOwner(out.Output.Owner.Raw) + if err != nil { + return err + } + if owner.Type == htlc.ScriptType { + script := &htlc.Script{} + err = json.Unmarshal(owner.Identity, script) + if err != nil { + return err + } + if script.Deadline.Before(now) { + return errors.New("htlc script invalid: expiration date has already passed") + } + continue + } + } + return nil +} + +// HTLCMetadataCheck checks that the HTLC metadata is in place +func HTLCMetadataCheck(ctx *Context, op htlc2.OperationType, sig []byte) error { + if op == htlc2.Reclaim { + // No metadata in this case + return nil + } + + // Unmarshal signature to ClaimSignature + claim := &htlc.ClaimSignature{} + if err := json.Unmarshal(sig, claim); err != nil { + return errors.Wrapf(err, "failed unmarshalling cliam signature [%s]", string(sig)) + } + // Check that it is well-formed + if len(claim.Preimage) == 0 || len(claim.RecipientSignature) == 0 { + return errors.New("expected a valid claim preImage and recipient signature") + } + + // Check that the pre-image is in the action's metadata + if len(ctx.Action.Metadata) == 0 { + return errors.New("cannot find htlc pre-image, no metadata") + } + value, ok := ctx.Action.Metadata[htlc.ClaimPreImage] + if !ok { + return errors.New("cannot find htlc pre-image, missing metadata entry") + } + if !bytes.Equal(value, claim.Preimage) { + return errors.Errorf("invalid action, cannot match htlc pre-image with metadata [%x]!=[%x]", value, claim.Preimage) + } + + return nil +} diff --git a/token/core/interop/deserializer.go b/token/core/interop/htlc/deserializer.go similarity index 95% rename from token/core/interop/deserializer.go rename to token/core/interop/htlc/deserializer.go index a3c6df5c0..51c6a24d1 100644 --- a/token/core/interop/deserializer.go +++ b/token/core/interop/htlc/deserializer.go @@ -4,7 +4,7 @@ Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -package interop +package htlc import ( "encoding/json" @@ -28,7 +28,7 @@ func NewDeserializer(ownerDeserializer VerifierDES) *Deserializer { return &Deserializer{OwnerDeserializer: ownerDeserializer} } -func (d *Deserializer) GetOwnerVerifier(id view.Identity) (driver.Verifier, error) { +func (d *Deserializer) DeserializeVerifier(id view.Identity) (driver.Verifier, error) { si, err := identity.UnmarshallRawOwner(id) if err != nil { return nil, errors.Wrap(err, "failed to unmarshal RawOwner") diff --git a/token/core/interop/info.go b/token/core/interop/htlc/info.go similarity index 99% rename from token/core/interop/info.go rename to token/core/interop/htlc/info.go index 7307bba1e..535453630 100644 --- a/token/core/interop/info.go +++ b/token/core/interop/htlc/info.go @@ -4,7 +4,7 @@ Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -package interop +package htlc import ( "encoding/json" diff --git a/token/core/interop/validator.go b/token/core/interop/htlc/validator.go similarity index 50% rename from token/core/interop/validator.go rename to token/core/interop/htlc/validator.go index fdcc605f1..bcbd34e12 100644 --- a/token/core/interop/validator.go +++ b/token/core/interop/htlc/validator.go @@ -4,7 +4,7 @@ Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -package interop +package htlc import ( "encoding/json" @@ -15,29 +15,37 @@ import ( "github.com/pkg/errors" ) -// VerifyTransferFromHTLCScript validates the owners of the transfer in the htlc script -func VerifyTransferFromHTLCScript(senderRawOwner []byte, outRawOwner []byte) error { +type OperationType int + +const ( + None OperationType = iota + Claim + Reclaim +) + +// VerifyOwner validates the owners of the transfer in the htlc script +func VerifyOwner(senderRawOwner []byte, outRawOwner []byte, now time.Time) (*htlc.Script, OperationType, error) { sender, err := identity.UnmarshallRawOwner(senderRawOwner) if err != nil { - return err + return nil, None, err } script := &htlc.Script{} err = json.Unmarshal(sender.Identity, script) if err != nil { - return err + return nil, None, err } - if time.Now().Before(script.Deadline) { + if now.Before(script.Deadline) { // this should be a claim if !script.Recipient.Equal(outRawOwner) { - return errors.Errorf("owner of output token does not correspond to recipient in htlc request") + return nil, None, errors.New("owner of output token does not correspond to recipient in htlc request") } + return script, Claim, nil } else { // this should be a reclaim if !script.Sender.Equal(outRawOwner) { - return errors.Errorf("owner of output token does not correspond to sender in htlc request") + return nil, None, errors.New("owner of output token does not correspond to sender in htlc request") } + return script, Reclaim, nil } - - return nil } diff --git a/token/core/zkatdlog/crypto/audit/auditor.go b/token/core/zkatdlog/crypto/audit/auditor.go index 98d876ed1..d3faa2de8 100644 --- a/token/core/zkatdlog/crypto/audit/auditor.go +++ b/token/core/zkatdlog/crypto/audit/auditor.go @@ -14,7 +14,7 @@ import ( "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/flogging" "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/hash" "github.com/hyperledger-labs/fabric-token-sdk/token/core/identity" - "github.com/hyperledger-labs/fabric-token-sdk/token/core/interop" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/interop/htlc" "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto/common" issue2 "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto/issue" "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto/token" @@ -278,11 +278,11 @@ func inspectTokenOwnerOfScript(des Deserializer, token *AuditableToken, index in if err != nil { return errors.Errorf("input owner at index [%d] cannot be unmarshalled", index) } - scriptInf := &interop.ScriptInfo{} + scriptInf := &htlc.ScriptInfo{} if err := json.Unmarshal(token.Owner.OwnerInfo, scriptInf); err != nil { return errors.Wrapf(err, "failed to unmarshal script info") } - scriptSender, scriptRecipient, err := interop.GetScriptSenderAndRecipient(owner) + scriptSender, scriptRecipient, err := htlc.GetScriptSenderAndRecipient(owner) if err != nil { return errors.Wrap(err, "failed getting script sender and recipient") } diff --git a/token/core/zkatdlog/crypto/transfer/sender.go b/token/core/zkatdlog/crypto/transfer/sender.go index 65b34dacf..b02e9b11f 100644 --- a/token/core/zkatdlog/crypto/transfer/sender.go +++ b/token/core/zkatdlog/crypto/transfer/sender.go @@ -3,6 +3,7 @@ Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ + package transfer import ( @@ -110,6 +111,8 @@ type TransferAction struct { OutputTokens []*token.Token // ZK Proof that shows that the transfer is correct Proof []byte + // Metadata contains the transfer action's metadata + Metadata map[string][]byte } // NewTransfer returns the TransferAction that matches the passed arguments @@ -128,7 +131,9 @@ func NewTransfer(inputs []string, inputCommitments []*math.G1, outputs []*math.G Inputs: inputs, InputCommitments: inputCommitments, OutputTokens: tokens, - Proof: proof}, nil + Proof: proof, + Metadata: map[string][]byte{}, + }, nil } // GetInputs returns the inputs in the TransferAction @@ -204,9 +209,8 @@ func (t *TransferAction) IsGraphHiding() bool { } // GetMetadata returns metadata of the TransferAction -// zkatdlog TransferAction does not carry any metadata -func (t *TransferAction) GetMetadata() []byte { - return nil +func (t *TransferAction) GetMetadata() map[string][]byte { + return t.Metadata } func getTokenData(tokens []*token.Token) []*math.G1 { diff --git a/token/core/zkatdlog/crypto/validator/validator.go b/token/core/zkatdlog/crypto/validator/validator.go index f79dd0162..f6f25e383 100644 --- a/token/core/zkatdlog/crypto/validator/validator.go +++ b/token/core/zkatdlog/crypto/validator/validator.go @@ -8,97 +8,37 @@ package validator import ( "bytes" - "encoding/json" - "time" - math "github.com/IBM/mathlib" "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/flogging" "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/hash" "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" - "github.com/hyperledger-labs/fabric-token-sdk/token/core/identity" - "github.com/hyperledger-labs/fabric-token-sdk/token/core/interop" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/common" "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto" issue2 "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto/issue" - "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto/token" "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto/transfer" "github.com/hyperledger-labs/fabric-token-sdk/token/driver" - "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/htlc" "github.com/pkg/errors" ) var logger = flogging.MustGetLogger("token-sdk.zkatdlog") -var defaultValidators = []ValidateTransfer{SerializedIdentityTypeExtraValidator, ScriptTypeHTLCExtraValidator} - -type ValidateTransfer func(tokens []*token.Token, tr *transfer.TransferAction) error - -func SerializedIdentityTypeExtraValidator(tokens []*token.Token, tr *transfer.TransferAction) error { - // noting else to validate - return nil -} - -func ScriptTypeHTLCExtraValidator(tokens []*token.Token, tr *transfer.TransferAction) error { - for _, in := range tokens { - owner, err := identity.UnmarshallRawOwner(in.Owner) - if err != nil { - return errors.Wrap(err, "failed to unmarshal owner of input token") - } - if owner.Type == htlc.ScriptType { - if len(tokens) != 1 || len(tr.GetOutputs()) != 1 { - return errors.Errorf("invalid transfer action: an htlc script only transfers the ownership of a token") - } - - out := tr.GetOutputs()[0].(*token.Token) - if tokens[0].Data.Equals(out.Data) { - return errors.Errorf("invalid transfer action: content of input does not match content of output") - } - - // check that owner field in output is correct - if err := interop.VerifyTransferFromHTLCScript(tokens[0].Owner, out.Owner); err != nil { - return errors.Wrap(err, "failed to verify transfer from htlc script") - } - } - } - - for _, o := range tr.GetOutputs() { - out, ok := o.(*token.Token) - if !ok { - return errors.Errorf("invalid output") - } - if out.IsRedeem() { - continue - } - owner, err := identity.UnmarshallRawOwner(out.Owner) - if err != nil { - return err - } - if owner.Type == htlc.ScriptType { - script := &htlc.Script{} - err = json.Unmarshal(owner.Identity, script) - if err != nil { - return err - } - if script.Deadline.Before(time.Now()) { - return errors.Errorf("htlc script invalid: expiration date has already passed") - } - continue - } - } - return nil -} - type Validator struct { - pp *crypto.PublicParams - deserializer driver.Deserializer - extraValidators []ValidateTransfer + pp *crypto.PublicParams + deserializer driver.Deserializer + validators []ValidateTransferFunc } -func New(pp *crypto.PublicParams, deserializer driver.Deserializer, extraValidators ...ValidateTransfer) *Validator { - defaultValidators = append(defaultValidators, extraValidators...) +func New(pp *crypto.PublicParams, deserializer driver.Deserializer, extraValidators ...ValidateTransferFunc) *Validator { + validators := []ValidateTransferFunc{ + TransferSignatureValidate, + TransferZKProofValidate, + TransferHTLCValidate, + } + validators = append(validators, extraValidators...) return &Validator{ - pp: pp, - deserializer: deserializer, - extraValidators: defaultValidators, + pp: pp, + deserializer: deserializer, + validators: validators, } } @@ -132,22 +72,10 @@ func (v *Validator) VerifyTokenRequestFromRaw(getState driver.GetStateFnc, bindi signatures = tr.Signatures } - backend := &backend{ - getState: getState, - message: signed, - signatures: signatures, - } + backend := common.NewBackend(getState, signed, signatures) return v.VerifyTokenRequest(backend, backend, binding, tr) } -type Signature struct { - metadata map[string][]byte // metadata may include for example the preimage of an htlc script -} - -func (s *Signature) Metadata() map[string][]byte { - return s.metadata -} - func (v *Validator) VerifyTokenRequest(ledger driver.Ledger, signatureProvider driver.SignatureProvider, binding string, tr *driver.TokenRequest) ([]interface{}, error) { if err := v.verifyAuditorSignature(signatureProvider); err != nil { return nil, errors.Wrapf(err, "failed to verifier auditor's signature [%s]", binding) @@ -176,22 +104,6 @@ func (v *Validator) VerifyTokenRequest(ledger driver.Ledger, signatureProvider d for _, action := range ta { actions = append(actions, action) } - - for _, sig := range signatureProvider.Signatures() { - claim := &htlc.ClaimSignature{} - if err = json.Unmarshal(sig, claim); err != nil { - continue - } - if len(claim.Preimage) == 0 || len(claim.RecipientSignature) == 0 { - return nil, errors.New("expected a valid claim preImage and recipient signature") - } - actions = append(actions, &Signature{ - metadata: map[string][]byte{ - "claimPreimage": claim.Preimage, - }, - }) - } - return actions, nil } @@ -226,7 +138,8 @@ func (v *Validator) verifyAuditorSignature(signatureProvider driver.SignaturePro return errors.Errorf("failed to deserialize auditor's public key") } - return signatureProvider.HasBeenSignedBy(v.pp.Auditor, verifier) + _, err = signatureProvider.HasBeenSignedBy(v.pp.Auditor, verifier) + return err } return nil } @@ -258,54 +171,13 @@ func (v *Validator) verifyIssues(issues []driver.IssueAction, signatureProvider if err != nil { return errors.Wrapf(err, "failed getting verifier for [%s]", view.Identity(a.Issuer).String()) } - if err := signatureProvider.HasBeenSignedBy(a.Issuer, verifier); err != nil { + if _, err := signatureProvider.HasBeenSignedBy(a.Issuer, verifier); err != nil { return errors.Wrapf(err, "failed verifying signature") } } return nil } -func (v *Validator) verifyTransfers(ledger driver.Ledger, transferActions []driver.TransferAction, signatureProvider driver.SignatureProvider) error { - logger.Debugf("check sender start...") - defer logger.Debugf("check sender finished.") - for i, t := range transferActions { - var inputTokens [][]byte - inputs, err := t.GetInputs() - if err != nil { - return errors.Wrapf(err, "failed to retrieve inputs to spend") - } - for _, in := range inputs { - logger.Debugf("load token [%d][%s]", i, in) - bytes, err := ledger.GetState(in) - if err != nil { - return errors.Wrapf(err, "failed to retrieve input to spend [%s]", in) - } - if len(bytes) == 0 { - return errors.Errorf("input to spend [%s] does not exists", in) - } - inputTokens = append(inputTokens, bytes) - tok := &token.Token{} - err = tok.Deserialize(bytes) - if err != nil { - return errors.Wrapf(err, "failed to deserialize input to spend [%s]", in) - } - logger.Debugf("check sender [%d][%s]", i, view.Identity(tok.Owner).UniqueID()) - verifier, err := v.deserializer.GetOwnerVerifier(tok.Owner) - if err != nil { - return errors.Wrapf(err, "failed deserializing owner [%d][%s][%s]", i, in, view.Identity(tok.Owner).UniqueID()) - } - logger.Debugf("signature verification [%d][%s][%s]", i, in, view.Identity(tok.Owner).UniqueID()) - if err := signatureProvider.HasBeenSignedBy(tok.Owner, verifier); err != nil { - return errors.Wrapf(err, "failed signature verification [%d][%s][%s]", i, in, view.Identity(tok.Owner).UniqueID()) - } - } - if err := v.verifyTransfer(inputTokens, t); err != nil { - return errors.Wrapf(err, "failed to verify transfer action") - } - } - return nil -} - func (v *Validator) verifyIssue(issue driver.IssueAction) error { action := issue.(*issue2.IssueAction) coms, err := action.GetCommitments() @@ -318,55 +190,30 @@ func (v *Validator) verifyIssue(issue driver.IssueAction) error { v.pp).Verify(action.GetProof()) } -func (v *Validator) verifyTransfer(inputTokens [][]byte, tr driver.TransferAction) error { - action := tr.(*transfer.TransferAction) - tokens := make([]*token.Token, len(inputTokens)) - in := make([]*math.G1, len(inputTokens)) - for i, raw := range inputTokens { - tokens[i] = &token.Token{} - if err := tokens[i].Deserialize(raw); err != nil { - return errors.Wrapf(err, "invalid transfer: failed to deserialize input [%d]", i) +func (v *Validator) verifyTransfers(ledger driver.Ledger, transferActions []driver.TransferAction, signatureProvider driver.SignatureProvider) error { + logger.Debugf("check sender start...") + defer logger.Debugf("check sender finished.") + for _, t := range transferActions { + if err := v.verifyTransfer(t, ledger, signatureProvider); err != nil { + return errors.Wrapf(err, "failed to verify transfer action") } - in[i] = tokens[i].GetCommitment() - } - - if err := transfer.NewVerifier( - in, - action.GetOutputCommitments(), - v.pp).Verify(action.GetProof()); err != nil { - return err } + return nil +} - for _, v := range v.extraValidators { - if err := v(tokens, action); err != nil { +func (v *Validator) verifyTransfer(tr driver.TransferAction, ledger driver.Ledger, signatureProvider driver.SignatureProvider) error { + action := tr.(*transfer.TransferAction) + context := &Context{ + PP: v.pp, + Deserializer: v.deserializer, + Action: action, + Ledger: ledger, + SignatureProvider: signatureProvider, + } + for _, v := range v.validators { + if err := v(context); err != nil { return err } } - return nil } - -type backend struct { - getState driver.GetStateFnc - message []byte - index int - signatures [][]byte -} - -func (b *backend) HasBeenSignedBy(id view.Identity, verifier driver.Verifier) error { - if b.index >= len(b.signatures) { - return errors.Errorf("invalid state, insufficient number of signatures") - } - sigma := b.signatures[b.index] - b.index++ - - return verifier.Verify(b.message, sigma) -} - -func (b *backend) GetState(key string) ([]byte, error) { - return b.getState(key) -} - -func (b *backend) Signatures() [][]byte { - return b.signatures -} diff --git a/token/core/zkatdlog/crypto/validator/validator_transfer.go b/token/core/zkatdlog/crypto/validator/validator_transfer.go new file mode 100644 index 000000000..4d01ebf57 --- /dev/null +++ b/token/core/zkatdlog/crypto/validator/validator_transfer.go @@ -0,0 +1,182 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package validator + +import ( + "bytes" + "encoding/json" + "time" + + math "github.com/IBM/mathlib" + "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/identity" + htlc2 "github.com/hyperledger-labs/fabric-token-sdk/token/core/interop/htlc" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto/transfer" + "github.com/hyperledger-labs/fabric-token-sdk/token/driver" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/interop/htlc" + "github.com/pkg/errors" +) + +type Context struct { + PP *crypto.PublicParams + Deserializer driver.Deserializer + SignatureProvider driver.SignatureProvider + Signatures [][]byte + InputTokens []*token.Token + Action *transfer.TransferAction + Ledger driver.Ledger +} + +type ValidateTransferFunc func(ctx *Context) error + +func TransferSignatureValidate(ctx *Context) error { + var tokens []*token.Token + var signatures [][]byte + + inputs, err := ctx.Action.GetInputs() + if err != nil { + return errors.Wrapf(err, "failed to retrieve inputs to spend") + } + for i, in := range inputs { + logger.Debugf("load token [%d][%s]", i, in) + bytes, err := ctx.Ledger.GetState(in) + if err != nil { + return errors.Wrapf(err, "failed to retrieve input to spend [%s]", in) + } + if len(bytes) == 0 { + return errors.Errorf("input to spend [%s] does not exists", in) + } + + tok := &token.Token{} + if err := tok.Deserialize(bytes); err != nil { + return errors.Wrapf(err, "failed to deserialize input to spend [%s]", in) + } + tokens = append(tokens, tok) + logger.Debugf("check sender [%d][%s]", i, view.Identity(tok.Owner).UniqueID()) + verifier, err := ctx.Deserializer.GetOwnerVerifier(tok.Owner) + if err != nil { + return errors.Wrapf(err, "failed deserializing owner [%d][%s][%s]", i, in, view.Identity(tok.Owner).UniqueID()) + } + logger.Debugf("signature verification [%d][%s][%s]", i, in, view.Identity(tok.Owner).UniqueID()) + sigma, err := ctx.SignatureProvider.HasBeenSignedBy(tok.Owner, verifier) + if err != nil { + return errors.Wrapf(err, "failed signature verification [%d][%s][%s]", i, in, view.Identity(tok.Owner).UniqueID()) + } + signatures = append(signatures, sigma) + } + + ctx.InputTokens = tokens + ctx.Signatures = signatures + + return nil +} + +func TransferZKProofValidate(ctx *Context) error { + in := make([]*math.G1, len(ctx.InputTokens)) + for i, tok := range ctx.InputTokens { + in[i] = tok.GetCommitment() + } + + if err := transfer.NewVerifier( + in, + ctx.Action.GetOutputCommitments(), + ctx.PP).Verify(ctx.Action.GetProof()); err != nil { + return err + } + + return nil +} + +func TransferHTLCValidate(ctx *Context) error { + now := time.Now() + + for i, in := range ctx.InputTokens { + owner, err := identity.UnmarshallRawOwner(in.Owner) + if err != nil { + return errors.Wrap(err, "failed to unmarshal owner of input token") + } + if owner.Type == htlc.ScriptType { + if len(ctx.InputTokens) != 1 || len(ctx.Action.GetOutputs()) != 1 { + return errors.Errorf("invalid transfer action: an htlc script only transfers the ownership of a token") + } + + out := ctx.Action.GetOutputs()[0].(*token.Token) + + // check that owner field in output is correct + _, op, err := htlc2.VerifyOwner(ctx.InputTokens[0].Owner, out.Owner, now) + if err != nil { + return errors.Wrap(err, "failed to verify transfer from htlc script") + } + + // check metadata + sigma := ctx.Signatures[i] + if err := HTLCMetadataCheck(ctx, op, sigma); err != nil { + return errors.WithMessagef(err, "failed to check htlc metadata") + } + } + } + + for _, o := range ctx.Action.GetOutputs() { + out, ok := o.(*token.Token) + if !ok { + return errors.Errorf("invalid output") + } + if out.IsRedeem() { + continue + } + owner, err := identity.UnmarshallRawOwner(out.Owner) + if err != nil { + return err + } + if owner.Type == htlc.ScriptType { + script := &htlc.Script{} + err = json.Unmarshal(owner.Identity, script) + if err != nil { + return err + } + if script.Deadline.Before(now) { + return errors.Errorf("htlc script invalid: expiration date has already passed") + } + continue + } + } + return nil +} + +// HTLCMetadataCheck checks that the HTLC metadata is in place +func HTLCMetadataCheck(ctx *Context, op htlc2.OperationType, sig []byte) error { + if op == htlc2.Reclaim { + // No metadata in this case + return nil + } + + // Unmarshal signature to ClaimSignature + claim := &htlc.ClaimSignature{} + if err := json.Unmarshal(sig, claim); err != nil { + return errors.Wrapf(err, "failed unmarshalling cliam signature [%s]", string(sig)) + } + // Check that it is well-formed + if len(claim.Preimage) == 0 || len(claim.RecipientSignature) == 0 { + return errors.New("expected a valid claim preImage and recipient signature") + } + + // Check the pre-image is in the action's metadata + if len(ctx.Action.Metadata) == 0 { + return errors.New("cannot find htlc pre-image, no metadata") + } + value, ok := ctx.Action.Metadata[htlc.ClaimPreImage] + if !ok { + return errors.New("cannot find htlc pre-image, missing metadata entry") + } + if !bytes.Equal(value, claim.Preimage) { + return errors.Errorf("invalid action, cannot match htlc pre-image with metadata [%x]!=[%x]", value, claim.Preimage) + } + + return nil +} diff --git a/token/core/zkatdlog/nogh/deserializer.go b/token/core/zkatdlog/nogh/deserializer.go index e3847a5bb..8e4694a35 100644 --- a/token/core/zkatdlog/nogh/deserializer.go +++ b/token/core/zkatdlog/nogh/deserializer.go @@ -16,7 +16,7 @@ import ( "github.com/hyperledger-labs/fabric-token-sdk/token/core/identity" "github.com/hyperledger-labs/fabric-token-sdk/token/core/identity/msp/idemix" "github.com/hyperledger-labs/fabric-token-sdk/token/core/identity/msp/x509" - "github.com/hyperledger-labs/fabric-token-sdk/token/core/interop" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/interop/htlc" "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto" "github.com/hyperledger-labs/fabric-token-sdk/token/driver" "github.com/pkg/errors" @@ -55,14 +55,14 @@ func NewDeserializer(pp *crypto.PublicParams) (*deserializer, error) { return &deserializer{ auditorDeserializer: &x509.MSPIdentityDeserializer{}, issuerDeserializer: &x509.MSPIdentityDeserializer{}, - ownerDeserializer: identity.NewRawOwnerIdentityDeserializer(idemixDes), + ownerDeserializer: htlc.NewDeserializer(identity.NewRawOwnerIdentityDeserializer(idemixDes)), auditDeserializer: idemixDes, }, nil } // GetOwnerVerifier deserializes the verifier for the passed owner identity func (d *deserializer) GetOwnerVerifier(id view.Identity) (driver.Verifier, error) { - return interop.NewDeserializer(d.ownerDeserializer).GetOwnerVerifier(id) + return d.ownerDeserializer.DeserializeVerifier(id) } // GetIssuerVerifier deserializes the verifier for the passed issuer identity @@ -127,7 +127,7 @@ func (e *enrollmentService) GetEnrollmentID(auditInfo []byte) (string, error) { } // Try to unmarshal it as ScriptInfo - si := &interop.ScriptInfo{} + si := &htlc.ScriptInfo{} err := json.Unmarshal(auditInfo, si) if err == nil && (len(si.Sender) != 0 || len(si.Recipient) != 0) { if len(si.Recipient) != 0 { diff --git a/token/core/zkatdlog/nogh/sender.go b/token/core/zkatdlog/nogh/sender.go index d8cbc081f..80088ccf7 100644 --- a/token/core/zkatdlog/nogh/sender.go +++ b/token/core/zkatdlog/nogh/sender.go @@ -9,8 +9,9 @@ package nogh import ( math "github.com/IBM/mathlib" "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/common" "github.com/hyperledger-labs/fabric-token-sdk/token/core/identity" - "github.com/hyperledger-labs/fabric-token-sdk/token/core/interop" + "github.com/hyperledger-labs/fabric-token-sdk/token/core/interop/htlc" "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto/token" "github.com/hyperledger-labs/fabric-token-sdk/token/core/zkatdlog/crypto/transfer" "github.com/hyperledger-labs/fabric-token-sdk/token/driver" @@ -69,7 +70,7 @@ func (s *Service) Transfer(txID string, wallet driver.OwnerWallet, ids []*token3 ownerIdentities = append(ownerIdentities, output.Owner.Raw) continue } - _, recipient, err := interop.GetScriptSenderAndRecipient(owner) + _, recipient, err := htlc.GetScriptSenderAndRecipient(owner) if err != nil { return nil, nil, errors.Wrap(err, "failed getting script sender and recipient") } @@ -82,6 +83,9 @@ func (s *Service) Transfer(txID string, wallet driver.OwnerWallet, ids []*token3 return nil, nil, errors.Wrapf(err, "failed to generate zkatdlog transfer action for txid [%s]", txID) } + // add transfer action's metadata + common.SetTransferActionMetadata(opts.Attributes, transfer.Metadata) + // prepare metadata var outputMetadataRaw [][]byte for _, information := range outputMetadata { @@ -94,7 +98,7 @@ func (s *Service) Transfer(txID string, wallet driver.OwnerWallet, ids []*token3 // audit info for receivers var receiverAuditInfos [][]byte for _, output := range outputTokens { - auditInfo, err := interop.GetOwnerAuditInfo(output.Owner.Raw, s.SP) + auditInfo, err := htlc.GetOwnerAuditInfo(output.Owner.Raw, s.SP) if err != nil { return nil, nil, errors.Wrapf(err, "failed getting audit info for recipient identity [%s]", view.Identity(output.Owner.Raw).String()) } @@ -104,7 +108,7 @@ func (s *Service) Transfer(txID string, wallet driver.OwnerWallet, ids []*token3 // audit info for senders var senderAuditInfos [][]byte for _, t := range tokens { - auditInfo, err := interop.GetOwnerAuditInfo(t.Owner, s.SP) + auditInfo, err := htlc.GetOwnerAuditInfo(t.Owner, s.SP) if err != nil { return nil, nil, errors.Wrapf(err, "failed getting audit info for sender identity [%s]", view.Identity(t.Owner).String()) } diff --git a/token/driver/action.go b/token/driver/action.go index 2c8f34b15..3d437d390 100644 --- a/token/driver/action.go +++ b/token/driver/action.go @@ -51,11 +51,10 @@ type TransferAction interface { IsRedeemAt(index int) bool // SerializeOutputAt returns the serialized output at the passed index SerializeOutputAt(index int) ([]byte, error) - // GetInputs returns the inputs's identifiers of the action + // GetInputs returns the identifiers of the inputs in the action. GetInputs() ([]string, error) // IsGraphHiding returns true if the action is graph hiding - // TODO: Deprecated. This should be checked using the public parameters IsGraphHiding() bool - // GetMetadata returns the metadata of the action - GetMetadata() []byte + // GetMetadata returns the action's metadata + GetMetadata() map[string][]byte } diff --git a/token/driver/validator.go b/token/driver/validator.go index 0cb7cb09b..50932407f 100644 --- a/token/driver/validator.go +++ b/token/driver/validator.go @@ -18,8 +18,8 @@ type Ledger interface { } type SignatureProvider interface { - // HasBeenSignedBy returns true if the provider contains a valid signature for the passed identity and verifier - HasBeenSignedBy(id view.Identity, verifier Verifier) error + // HasBeenSignedBy returns true and the verified signature if the provider contains a valid signature for the passed identity and verifier + HasBeenSignedBy(id view.Identity, verifier Verifier) ([]byte, error) // Signatures returns the signatures inside this provider Signatures() [][]byte } diff --git a/token/request.go b/token/request.go index 67f00f9cd..340211d02 100644 --- a/token/request.go +++ b/token/request.go @@ -8,7 +8,6 @@ package token import ( "encoding/asn1" - "fmt" view2 "github.com/hyperledger-labs/fabric-smart-client/platform/view" "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" @@ -17,6 +16,10 @@ import ( "github.com/pkg/errors" ) +const ( + TransferMetadataPrefix = "TransferMetadataPrefix" +) + // IssueOptions models the options that can be passed to the issue command type IssueOptions struct { // Attributes is a container of generic options that might be driver specific @@ -78,17 +81,9 @@ func WithTokenSelector(selector Selector) TransferOption { } } -// WithOutputMetadata sets outputs metadata -func WithOutputMetadata(metadata [][]byte) TransferOption { - return func(o *TransferOptions) error { - if o.Attributes == nil { - o.Attributes = make(map[interface{}]interface{}) - } - for i, bytes := range metadata { - o.Attributes[fmt.Sprintf("output.metadata.%d", i)] = bytes - } - return nil - } +// WithTransferMetadata sets transfer action metadata +func WithTransferMetadata(key string, value []byte) TransferOption { + return WithTransferAttribute(TransferMetadataPrefix+key, value) } // WithTokenIDs sets the tokens ids to transfer @@ -102,6 +97,9 @@ func WithTokenIDs(ids ...*token.ID) TransferOption { // WithTransferAttribute sets an attribute to be used to customize the transfer command func WithTransferAttribute(attr, value interface{}) TransferOption { return func(o *TransferOptions) error { + if o.Attributes == nil { + o.Attributes = make(map[interface{}]interface{}) + } o.Attributes[attr] = value return nil } diff --git a/token/services/interop/htlc/scanner.go b/token/services/interop/htlc/scanner.go index 0c99466bf..50f653f38 100644 --- a/token/services/interop/htlc/scanner.go +++ b/token/services/interop/htlc/scanner.go @@ -66,7 +66,7 @@ func ScanForPreImage(ctx view.Context, image []byte, hashFunc crypto.Hash, hashE if err != nil { return false, err } - if f, err := w.IsSigMetadataKey(k); err == nil && f { + if f, err := w.IsTransferMetadataKeyWithSubKey(k, ClaimPreImage); err == nil && f { // hash + encoding hash := hashFunc.New() if _, err = hash.Write(v); err != nil { diff --git a/token/services/interop/htlc/transaction.go b/token/services/interop/htlc/transaction.go index 01853a872..2e0cbff85 100644 --- a/token/services/interop/htlc/transaction.go +++ b/token/services/interop/htlc/transaction.go @@ -26,6 +26,7 @@ import ( const ( ScriptType = "htlc" // htlc script defaultDeadlineOffset = time.Hour + ClaimPreImage = "cpi" ) // WithHash sets a hash attribute to be used to customize the transfer command @@ -273,7 +274,14 @@ func (t *Transaction) Claim(wallet *token.OwnerWallet, tok *token2.UnspentToken, return err } - return t.Transfer(wallet, tok.Type, []uint64{q.ToBigInt().Uint64()}, []view.Identity{script.Recipient}, token.WithTokenIDs(tok.Id)) + return t.Transfer( + wallet, + tok.Type, + []uint64{q.ToBigInt().Uint64()}, + []view.Identity{script.Recipient}, + token.WithTokenIDs(tok.Id), + token.WithTransferMetadata(ClaimPreImage, preImage), + ) } func (t *Transaction) recipientAsScript(sender, recipient view.Identity, deadline time.Duration, h []byte, hashFunc crypto.Hash, hashEncoding encoding.Encoding) (view.Identity, []byte, error) { diff --git a/token/services/network/fabric/processor.go b/token/services/network/fabric/processor.go index a236887ab..fca836b72 100644 --- a/token/services/network/fabric/processor.go +++ b/token/services/network/fabric/processor.go @@ -198,9 +198,9 @@ func (r *RWSetProcessor) tokenRequest(req fabric.Request, tx fabric.ProcessTrans logger.Debugf("expected key without the issue action metadata, skipping") } continue - case keys.SignaturePrefix: + case keys.TransferActionMetadata: if logger.IsEnabledFor(zapcore.DebugLevel) { - logger.Debugf("expected key without the sig metadata, skipping") + logger.Debugf("expected key without the transfer action metadata, skipping") } continue } diff --git a/token/services/network/orion/processor.go b/token/services/network/orion/processor.go index a0fbc6b7c..9910b0f24 100644 --- a/token/services/network/orion/processor.go +++ b/token/services/network/orion/processor.go @@ -165,9 +165,9 @@ func (r *RWSetProcessor) tokenRequest(req orion.Request, tx orion.ProcessTransac logger.Debugf("expected key without the issue action metadata, skipping") } continue - case keys.SignaturePrefix: + case keys.TransferActionMetadata: if logger.IsEnabledFor(zapcore.DebugLevel) { - logger.Debugf("expected key without the sig metadata, skipping") + logger.Debugf("expected key without the transfer action metadata, skipping") } continue } diff --git a/token/services/vault/keys/keys.go b/token/services/vault/keys/keys.go index 299b3380e..d5318523e 100644 --- a/token/services/vault/keys/keys.go +++ b/token/services/vault/keys/keys.go @@ -3,6 +3,7 @@ Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ + package keys import ( @@ -10,8 +11,7 @@ import ( "strconv" "unicode/utf8" - token2 "github.com/hyperledger-labs/fabric-token-sdk/token/token" - + "github.com/hyperledger-labs/fabric-token-sdk/token/token" "github.com/pkg/errors" ) @@ -20,30 +20,24 @@ const ( MaxUnicodeRuneValue = utf8.MaxRune // U+10FFFF - maximum (and unallocated) code point CompositeKeyNamespace = "\x00" TokenKeyPrefix = "ztoken" - SignaturePrefix = "sig" FabTokenKeyPrefix = "token" FabTokenExtendedKeyPrefix = "etoken" AuditTokenKeyPrefix = "audittoken" TokenMineKeyPrefix = "mine" TokenSetupKeyPrefix = "setup" IssuedHistoryTokenKeyPrefix = "issued" - TokenAuditorKeyPrefix = "auditor" TokenNameSpace = "zkat" numComponentsInKey = 2 // 2 components: txid, index, excluding TokenKeyPrefix numComponentsInExtendedKey = 4 // 2 components: id, type, txid, index, excluding TokenKeyPrefix - Action = "action" - ActionIssue = "issue" - ActionTransfer = "transfer" Info = "info" IDs = "ids" TokenRequestKeyPrefix = "token_request" - OwnerSeparator = "/" SerialNumber = "sn" IssueActionMetadata = "iam" TransferActionMetadata = "tam" ) -func GetTokenIdFromKey(key string) (*token2.ID, error) { +func GetTokenIdFromKey(key string) (*token.ID, error) { _, components, err := SplitCompositeKey(key) if err != nil { return nil, errors.New(fmt.Sprintf("error splitting input composite key: '%s'", err)) @@ -60,10 +54,10 @@ func GetTokenIdFromKey(key string) (*token2.ID, error) { if err != nil { return nil, errors.New(fmt.Sprintf("error parsing output index '%s': '%s'", components[numComponentsInKey-1], err)) } - return &token2.ID{TxId: txID, Index: index}, nil + return &token.ID{TxId: txID, Index: index}, nil } -func GetTokenIdFromExtendedKey(key string) (*token2.ID, error) { +func GetTokenIdFromExtendedKey(key string) (*token.ID, error) { _, components, err := SplitCompositeKey(key) if err != nil { return nil, errors.New(fmt.Sprintf("error splitting input composite key: '%s'", err)) @@ -80,7 +74,7 @@ func GetTokenIdFromExtendedKey(key string) (*token2.ID, error) { if err != nil { return nil, errors.New(fmt.Sprintf("error parsing output index '%s': '%s'", components[numComponentsInExtendedKey-1], err)) } - return &token2.ID{TxId: txID, Index: index}, nil + return &token.ID{TxId: txID, Index: index}, nil } func SplitCompositeKey(compositeKey string) (string, []string, error) { @@ -106,10 +100,6 @@ func CreateTokenKey(txID string, index uint64) (string, error) { return CreateCompositeKey(TokenKeyPrefix, []string{txID, strconv.FormatUint(index, 10)}) } -func CreateSigMetadataKey(txID string, index uint64, subKey string) (string, error) { - return CreateCompositeKey(SignaturePrefix, []string{txID, strconv.FormatUint(index, 10), subKey}) -} - func CreateSNKey(sn string) (string, error) { return CreateCompositeKey(TokenKeyPrefix, []string{SerialNumber, sn}) } @@ -146,8 +136,28 @@ func CreateIssueActionMetadataKey(hash string) (string, error) { return CreateCompositeKey(TokenKeyPrefix, []string{IssueActionMetadata, hash}) } -func CreateTransferActionMetadataKey(hash string) (string, error) { - return CreateCompositeKey(TokenKeyPrefix, []string{TransferActionMetadata, hash}) +// CreateTransferActionMetadataKey returns the transfer action metadata key built from the passed +// transaction id, subkey, and index. Index is used to make sure the key is unique with the respect to the +// token request this key appears. +func CreateTransferActionMetadataKey(txID string, subKey string, index uint64) (string, error) { + return CreateCompositeKey(TokenKeyPrefix, []string{TransferActionMetadata, txID, subKey, strconv.FormatUint(index, 10)}) +} + +func IsTransferMetadataKeyWithSubKey(k string, subKey string) (bool, error) { + prefix, components, err := SplitCompositeKey(k) + if err != nil { + return false, errors.Wrapf(err, "failed to split composite key [%s]", k) + } + if prefix != TokenKeyPrefix { + return false, nil + } + if components[0] != TransferActionMetadata { + return false, nil + } + if len(components) != 4 { + return false, nil + } + return components[2] == subKey, nil } // CreateCompositeKey and its related functions and consts copied from core/chaincode/shim/chaincode.go diff --git a/token/services/vault/translator/action.go b/token/services/vault/translator/action.go index dad6445eb..e11b0ef15 100644 --- a/token/services/vault/translator/action.go +++ b/token/services/vault/translator/action.go @@ -3,6 +3,7 @@ Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ + package translator type SetupAction interface { @@ -22,17 +23,22 @@ type IssueAction interface { //go:generate counterfeiter -o mock/transfer_action.go -fake-name TransferAction . TransferAction +// TransferAction is the action used to transfer tokens type TransferAction interface { + // Serialize returns the serialized version of the action Serialize() ([]byte, error) + // NumOutputs returns the number of outputs of the action NumOutputs() int + // GetSerializedOutputs returns the serialized outputs of the action GetSerializedOutputs() ([][]byte, error) + // IsRedeemAt returns true if the output is a redeem output at the passed index IsRedeemAt(index int) bool + // SerializeOutputAt returns the serialized output at the passed index SerializeOutputAt(index int) ([]byte, error) + // GetInputs returns the identifiers of the inputs in the action. GetInputs() ([]string, error) + // IsGraphHiding returns true if the action is graph hiding IsGraphHiding() bool - GetMetadata() []byte -} - -type Signature interface { - Metadata() map[string][]byte + // GetMetadata returns the action's metadata + GetMetadata() map[string][]byte } diff --git a/token/services/vault/translator/mock/transfer_action.go b/token/services/vault/translator/mock/transfer_action.go index 563485a38..eb632b450 100644 --- a/token/services/vault/translator/mock/transfer_action.go +++ b/token/services/vault/translator/mock/transfer_action.go @@ -20,15 +20,15 @@ type TransferAction struct { result1 []string result2 error } - GetMetadataStub func() []byte + GetMetadataStub func() map[string][]byte getMetadataMutex sync.RWMutex getMetadataArgsForCall []struct { } getMetadataReturns struct { - result1 []byte + result1 map[string][]byte } getMetadataReturnsOnCall map[int]struct { - result1 []byte + result1 map[string][]byte } GetSerializedOutputsStub func() ([][]byte, error) getSerializedOutputsMutex sync.RWMutex @@ -158,7 +158,7 @@ func (fake *TransferAction) GetInputsReturnsOnCall(i int, result1 []string, resu }{result1, result2} } -func (fake *TransferAction) GetMetadata() []byte { +func (fake *TransferAction) GetMetadata() map[string][]byte { fake.getMetadataMutex.Lock() ret, specificReturn := fake.getMetadataReturnsOnCall[len(fake.getMetadataArgsForCall)] fake.getMetadataArgsForCall = append(fake.getMetadataArgsForCall, struct { @@ -182,32 +182,32 @@ func (fake *TransferAction) GetMetadataCallCount() int { return len(fake.getMetadataArgsForCall) } -func (fake *TransferAction) GetMetadataCalls(stub func() []byte) { +func (fake *TransferAction) GetMetadataCalls(stub func() map[string][]byte) { fake.getMetadataMutex.Lock() defer fake.getMetadataMutex.Unlock() fake.GetMetadataStub = stub } -func (fake *TransferAction) GetMetadataReturns(result1 []byte) { +func (fake *TransferAction) GetMetadataReturns(result1 map[string][]byte) { fake.getMetadataMutex.Lock() defer fake.getMetadataMutex.Unlock() fake.GetMetadataStub = nil fake.getMetadataReturns = struct { - result1 []byte + result1 map[string][]byte }{result1} } -func (fake *TransferAction) GetMetadataReturnsOnCall(i int, result1 []byte) { +func (fake *TransferAction) GetMetadataReturnsOnCall(i int, result1 map[string][]byte) { fake.getMetadataMutex.Lock() defer fake.getMetadataMutex.Unlock() fake.GetMetadataStub = nil if fake.getMetadataReturnsOnCall == nil { fake.getMetadataReturnsOnCall = make(map[int]struct { - result1 []byte + result1 map[string][]byte }) } fake.getMetadataReturnsOnCall[i] = struct { - result1 []byte + result1 map[string][]byte }{result1} } diff --git a/token/services/vault/translator/translator.go b/token/services/vault/translator/translator.go index 4769ca338..aa2d6e040 100644 --- a/token/services/vault/translator/translator.go +++ b/token/services/vault/translator/translator.go @@ -12,30 +12,29 @@ import ( "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/flogging" "github.com/hyperledger-labs/fabric-smart-client/platform/view/services/hash" - "github.com/pkg/errors" - "github.com/hyperledger-labs/fabric-token-sdk/token/services/vault/keys" - token2 "github.com/hyperledger-labs/fabric-token-sdk/token/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/token" + "github.com/pkg/errors" ) var logger = flogging.MustGetLogger("token-sdk.vault.translator") // Translator validates token requests and generates the corresponding RWSets type Translator struct { - RWSet RWSet - TxID string - counter uint64 - sigCounter uint64 - namespace string + RWSet RWSet + TxID string + counter uint64 + metadataCounter uint64 + namespace string } func New(txID string, rwSet RWSet, namespace string) *Translator { w := &Translator{ - RWSet: rwSet, - TxID: txID, - counter: 0, - sigCounter: 0, - namespace: namespace, + RWSet: rwSet, + TxID: txID, + counter: 0, + metadataCounter: 0, + namespace: namespace, } return w @@ -115,7 +114,7 @@ func (w *Translator) ReadSetupParameters() ([]byte, error) { return raw, nil } -func (w *Translator) QueryTokens(ids []*token2.ID) ([][]byte, error) { +func (w *Translator) QueryTokens(ids []*token.ID) ([][]byte, error) { var res [][]byte var errs []error for _, id := range ids { @@ -145,12 +144,8 @@ func (w *Translator) QueryTokens(ids []*token2.ID) ([][]byte, error) { return res, nil } -func (w *Translator) IsSigMetadataKey(k string) (bool, error) { - prefix, _, err := keys.SplitCompositeKey(k) - if err != nil { - return false, errors.Wrapf(err, "failed to split composite key [%s]", k) - } - return prefix == keys.SignaturePrefix, nil +func (w *Translator) IsTransferMetadataKeyWithSubKey(k string, subKey string) (bool, error) { + return keys.IsTransferMetadataKeyWithSubKey(k, subKey) } func (w *Translator) checkProcess(action interface{}) error { @@ -168,8 +163,6 @@ func (w *Translator) checkAction(tokenAction interface{}) error { return w.checkTransfer(action) case SetupAction: return nil - case Signature: - return nil default: return errors.Errorf("unknown token action: %T", action) } @@ -262,8 +255,6 @@ func (w *Translator) commitAction(tokenAction interface{}) (err error) { err = w.commitTransferAction(action) case SetupAction: err = w.commitSetupAction(action) - case Signature: - err = w.commitSignature(action) } return } @@ -325,6 +316,8 @@ func (w *Translator) commitIssueAction(issueAction IssueAction) error { // Check the owner of each output to determine how to generate the key func (w *Translator) commitTransferAction(transferAction TransferAction) error { base := w.counter + + // store outputs for i := 0; i < transferAction.NumOutputs(); i++ { if !transferAction.IsRedeemAt(i) { outputID, err := keys.CreateTokenKey(w.TxID, base+uint64(i)) @@ -342,6 +335,8 @@ func (w *Translator) commitTransferAction(transferAction TransferAction) error { } } } + + // store inputs ids, err := transferAction.GetInputs() if err != nil { return err @@ -350,39 +345,28 @@ func (w *Translator) commitTransferAction(transferAction TransferAction) error { if err != nil { return err } + + // store metadata metadata := transferAction.GetMetadata() - if len(metadata) != 0 { - key, err := keys.CreateTransferActionMetadataKey(hash.Hashable(metadata).String()) + for key, value := range metadata { + k, err := keys.CreateTransferActionMetadataKey(w.TxID, key, w.metadataCounter) if err != nil { return errors.Wrapf(err, "failed constructing metadata key") } - raw, err := w.RWSet.GetState(w.namespace, key) + raw, err := w.RWSet.GetState(w.namespace, k) if err != nil { return err } if len(raw) != 0 { return errors.Errorf("entry with transfer metadata key [%s] is already occupied by [%s]", key, string(raw)) } - if err := w.RWSet.SetState(w.namespace, key, metadata); err != nil { + if err := w.RWSet.SetState(w.namespace, k, value); err != nil { return err } + w.metadataCounter++ } - w.counter = w.counter + uint64(transferAction.NumOutputs()) - return nil -} -func (w *Translator) commitSignature(sig Signature) error { - for k, value := range sig.Metadata() { - key, err := keys.CreateSigMetadataKey(w.TxID, w.sigCounter, k) - if err != nil { - return errors.Errorf("error creating output ID: %s", err) - } - err = w.RWSet.SetState(w.namespace, key, value) - if err != nil { - return errors.Wrapf(err, "error setting state for key [%s]", key) - } - } - w.sigCounter++ + w.counter = w.counter + uint64(transferAction.NumOutputs()) return nil }