diff --git a/plan.md b/plan.md new file mode 100644 index 0000000000..6982f67c04 --- /dev/null +++ b/plan.md @@ -0,0 +1,18 @@ +# Implementation Plan - Token Metadata Regression Pipeline + +## Goal +Implement a regression testing pipeline to ensure backward compatibility for token requests and metadata validation across protocol versions V1, V2, and V3. + +## Implementation Progress +- [x] Create fixture generator for V1/V2/V3 metadata +- [x] Implement regression test suite for `AuditCheck` +- [x] Fix type mismatches in validator test environment +- [x] Fix identity string representation in `Match` error messages +- [x] Verify all tests pass locally + +## Notes & Decisions +- Decided to use `auditor_test` package for regression tests to avoid circular dependencies. +- Added hashed/base64 identity strings to `metadata_test.go` to match the new `AuditableIdentity` behavior. +- Protocol V3 fixtures include `AuditableIdentity` for both issuer and extra signers. + +✅ COMPLETE diff --git a/token/core/fabtoken/v1/transfer.go b/token/core/fabtoken/v1/transfer.go index f6caf9c44a..da224ee851 100644 --- a/token/core/fabtoken/v1/transfer.go +++ b/token/core/fabtoken/v1/transfer.go @@ -166,7 +166,7 @@ func (s *TransferService) Transfer(ctx context.Context, anchor driver.TokenReque Inputs: transferInputsMetadata, Outputs: transferOutputsMetadata, ExtraSigners: nil, - Issuer: nil, + Issuer: driver.AuditableIdentity{}, } if isRedeem { @@ -175,7 +175,7 @@ func (s *TransferService) Transfer(ctx context.Context, anchor driver.TokenReque return nil, nil, errors.Wrap(err, "failed to select issuer for redeem") } transfer.Issuer = issuer - transferMetadata.Issuer = issuer + transferMetadata.Issuer = driver.AuditableIdentity{Identity: issuer} } return transfer, transferMetadata, nil diff --git a/token/core/zkatdlog/nogh/v1/transfer.go b/token/core/zkatdlog/nogh/v1/transfer.go index 733307cb28..7dff6f18cd 100644 --- a/token/core/zkatdlog/nogh/v1/transfer.go +++ b/token/core/zkatdlog/nogh/v1/transfer.go @@ -255,6 +255,7 @@ func (s *TransferService) Transfer(ctx context.Context, anchor driver.TokenReque Inputs: transferInputsMetadata, Outputs: transferOutputsMetadata, ExtraSigners: nil, + Issuer: driver.AuditableIdentity{}, } // 8. If this is a redeem, select an issuer who can authorize it. @@ -264,7 +265,7 @@ func (s *TransferService) Transfer(ctx context.Context, anchor driver.TokenReque return nil, nil, errors.Wrap(err, "failed to select issuer for redeem") } transfer.Issuer = issuer - transferMetadata.Issuer = issuer + transferMetadata.Issuer = driver.AuditableIdentity{Identity: issuer} } return transfer, transferMetadata, nil diff --git a/token/core/zkatdlog/nogh/v1/validator/testutils/env.go b/token/core/zkatdlog/nogh/v1/validator/testutils/env.go index f1ee37fc04..5f27dc034a 100644 --- a/token/core/zkatdlog/nogh/v1/validator/testutils/env.go +++ b/token/core/zkatdlog/nogh/v1/validator/testutils/env.go @@ -557,7 +557,7 @@ func prepareTransfer( tokns[0] = append(tokns[0], tokens...) if issuerIdentity != nil { - metadata.Issuer = issuerIdentity + metadata.Issuer = driver.AuditableIdentity{Identity: issuerIdentity} } transferMetadata := &driver.TokenRequestMetadata{Transfers: []*driver.TransferMetadata{metadata}} diff --git a/token/driver/protos-go/pp/pp.pb.go b/token/driver/protos-go/pp/pp.pb.go index d03ee4dd4c..f3782a5ed5 100644 --- a/token/driver/protos-go/pp/pp.pb.go +++ b/token/driver/protos-go/pp/pp.pb.go @@ -6,7 +6,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v6.33.4 +// protoc v5.29.3 // source: pp.proto package pp diff --git a/token/driver/protos-go/request/request.pb.go b/token/driver/protos-go/request/request.pb.go index aea2a76b28..536d547d69 100644 --- a/token/driver/protos-go/request/request.pb.go +++ b/token/driver/protos-go/request/request.pb.go @@ -6,7 +6,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v6.33.4 +// protoc v5.29.3 // source: request.proto package request @@ -408,6 +408,74 @@ func (x *TransferMetadata) GetIssuer() *Identity { return nil } +type TransferMetadataV3 struct { + state protoimpl.MessageState `protogen:"open.v1"` + Inputs []*TransferInputMetadata `protobuf:"bytes,1,rep,name=inputs,proto3" json:"inputs,omitempty"` // Inputs + Outputs []*OutputMetadata `protobuf:"bytes,2,rep,name=outputs,proto3" json:"outputs,omitempty"` // Outputs + ExtraSigners []*AuditableIdentity `protobuf:"bytes,8,rep,name=extra_signers,json=extraSigners,proto3" json:"extra_signers,omitempty"` // Additional signers for the transfer + Issuer *AuditableIdentity `protobuf:"bytes,3,opt,name=issuer,proto3" json:"issuer,omitempty"` // Issuer signer for the redeem transfer + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TransferMetadataV3) Reset() { + *x = TransferMetadataV3{} + mi := &file_request_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TransferMetadataV3) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransferMetadataV3) ProtoMessage() {} + +func (x *TransferMetadataV3) ProtoReflect() protoreflect.Message { + mi := &file_request_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TransferMetadataV3.ProtoReflect.Descriptor instead. +func (*TransferMetadataV3) Descriptor() ([]byte, []int) { + return file_request_proto_rawDescGZIP(), []int{6} +} + +func (x *TransferMetadataV3) GetInputs() []*TransferInputMetadata { + if x != nil { + return x.Inputs + } + return nil +} + +func (x *TransferMetadataV3) GetOutputs() []*OutputMetadata { + if x != nil { + return x.Outputs + } + return nil +} + +func (x *TransferMetadataV3) GetExtraSigners() []*AuditableIdentity { + if x != nil { + return x.ExtraSigners + } + return nil +} + +func (x *TransferMetadataV3) GetIssuer() *AuditableIdentity { + if x != nil { + return x.Issuer + } + return nil +} + type IssueInputMetadata struct { state protoimpl.MessageState `protogen:"open.v1"` TokenId *TokenID `protobuf:"bytes,2,opt,name=token_id,json=tokenId,proto3" json:"token_id,omitempty"` // The Token ID being consumed by the issue @@ -417,7 +485,7 @@ type IssueInputMetadata struct { func (x *IssueInputMetadata) Reset() { *x = IssueInputMetadata{} - mi := &file_request_proto_msgTypes[6] + mi := &file_request_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -429,7 +497,7 @@ func (x *IssueInputMetadata) String() string { func (*IssueInputMetadata) ProtoMessage() {} func (x *IssueInputMetadata) ProtoReflect() protoreflect.Message { - mi := &file_request_proto_msgTypes[6] + mi := &file_request_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -442,7 +510,7 @@ func (x *IssueInputMetadata) ProtoReflect() protoreflect.Message { // Deprecated: Use IssueInputMetadata.ProtoReflect.Descriptor instead. func (*IssueInputMetadata) Descriptor() ([]byte, []int) { - return file_request_proto_rawDescGZIP(), []int{6} + return file_request_proto_rawDescGZIP(), []int{7} } func (x *IssueInputMetadata) GetTokenId() *TokenID { @@ -465,7 +533,7 @@ type IssueMetadata struct { func (x *IssueMetadata) Reset() { *x = IssueMetadata{} - mi := &file_request_proto_msgTypes[7] + mi := &file_request_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -477,7 +545,7 @@ func (x *IssueMetadata) String() string { func (*IssueMetadata) ProtoMessage() {} func (x *IssueMetadata) ProtoReflect() protoreflect.Message { - mi := &file_request_proto_msgTypes[7] + mi := &file_request_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -490,7 +558,7 @@ func (x *IssueMetadata) ProtoReflect() protoreflect.Message { // Deprecated: Use IssueMetadata.ProtoReflect.Descriptor instead. func (*IssueMetadata) Descriptor() ([]byte, []int) { - return file_request_proto_rawDescGZIP(), []int{7} + return file_request_proto_rawDescGZIP(), []int{8} } func (x *IssueMetadata) GetIssuer() *AuditableIdentity { @@ -521,6 +589,74 @@ func (x *IssueMetadata) GetExtraSigners() []*Identity { return nil } +type IssueMetadataV3 struct { + state protoimpl.MessageState `protogen:"open.v1"` + Issuer *AuditableIdentity `protobuf:"bytes,1,opt,name=issuer,proto3" json:"issuer,omitempty"` // Issuer of the tokens + Inputs []*IssueInputMetadata `protobuf:"bytes,2,rep,name=inputs,proto3" json:"inputs,omitempty"` // Inputs + Outputs []*OutputMetadata `protobuf:"bytes,3,rep,name=outputs,proto3" json:"outputs,omitempty"` // Outputs + ExtraSigners []*AuditableIdentity `protobuf:"bytes,4,rep,name=extra_signers,json=extraSigners,proto3" json:"extra_signers,omitempty"` // Additional signers for the issuance + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *IssueMetadataV3) Reset() { + *x = IssueMetadataV3{} + mi := &file_request_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *IssueMetadataV3) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IssueMetadataV3) ProtoMessage() {} + +func (x *IssueMetadataV3) ProtoReflect() protoreflect.Message { + mi := &file_request_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IssueMetadataV3.ProtoReflect.Descriptor instead. +func (*IssueMetadataV3) Descriptor() ([]byte, []int) { + return file_request_proto_rawDescGZIP(), []int{9} +} + +func (x *IssueMetadataV3) GetIssuer() *AuditableIdentity { + if x != nil { + return x.Issuer + } + return nil +} + +func (x *IssueMetadataV3) GetInputs() []*IssueInputMetadata { + if x != nil { + return x.Inputs + } + return nil +} + +func (x *IssueMetadataV3) GetOutputs() []*OutputMetadata { + if x != nil { + return x.Outputs + } + return nil +} + +func (x *IssueMetadataV3) GetExtraSigners() []*AuditableIdentity { + if x != nil { + return x.ExtraSigners + } + return nil +} + // Union type containing either issue or transfer metadata type ActionMetadata struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -528,6 +664,8 @@ type ActionMetadata struct { // // *ActionMetadata_IssueMetadata // *ActionMetadata_TransferMetadata + // *ActionMetadata_IssueMetadataV3 + // *ActionMetadata_TransferMetadataV3 Metadata isActionMetadata_Metadata `protobuf_oneof:"Metadata"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -535,7 +673,7 @@ type ActionMetadata struct { func (x *ActionMetadata) Reset() { *x = ActionMetadata{} - mi := &file_request_proto_msgTypes[8] + mi := &file_request_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -547,7 +685,7 @@ func (x *ActionMetadata) String() string { func (*ActionMetadata) ProtoMessage() {} func (x *ActionMetadata) ProtoReflect() protoreflect.Message { - mi := &file_request_proto_msgTypes[8] + mi := &file_request_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -560,7 +698,7 @@ func (x *ActionMetadata) ProtoReflect() protoreflect.Message { // Deprecated: Use ActionMetadata.ProtoReflect.Descriptor instead. func (*ActionMetadata) Descriptor() ([]byte, []int) { - return file_request_proto_rawDescGZIP(), []int{8} + return file_request_proto_rawDescGZIP(), []int{10} } func (x *ActionMetadata) GetMetadata() isActionMetadata_Metadata { @@ -588,6 +726,24 @@ func (x *ActionMetadata) GetTransferMetadata() *TransferMetadata { return nil } +func (x *ActionMetadata) GetIssueMetadataV3() *IssueMetadataV3 { + if x != nil { + if x, ok := x.Metadata.(*ActionMetadata_IssueMetadataV3); ok { + return x.IssueMetadataV3 + } + } + return nil +} + +func (x *ActionMetadata) GetTransferMetadataV3() *TransferMetadataV3 { + if x != nil { + if x, ok := x.Metadata.(*ActionMetadata_TransferMetadataV3); ok { + return x.TransferMetadataV3 + } + } + return nil +} + type isActionMetadata_Metadata interface { isActionMetadata_Metadata() } @@ -600,10 +756,22 @@ type ActionMetadata_TransferMetadata struct { TransferMetadata *TransferMetadata `protobuf:"bytes,2,opt,name=transfer_metadata,json=transferMetadata,proto3,oneof"` // Transfer action metadata } +type ActionMetadata_IssueMetadataV3 struct { + IssueMetadataV3 *IssueMetadataV3 `protobuf:"bytes,3,opt,name=issue_metadata_v3,json=issueMetadataV3,proto3,oneof"` // Issue action metadata V3 +} + +type ActionMetadata_TransferMetadataV3 struct { + TransferMetadataV3 *TransferMetadataV3 `protobuf:"bytes,4,opt,name=transfer_metadata_v3,json=transferMetadataV3,proto3,oneof"` // Transfer action metadata V3 +} + func (*ActionMetadata_IssueMetadata) isActionMetadata_Metadata() {} func (*ActionMetadata_TransferMetadata) isActionMetadata_Metadata() {} +func (*ActionMetadata_IssueMetadataV3) isActionMetadata_Metadata() {} + +func (*ActionMetadata_TransferMetadataV3) isActionMetadata_Metadata() {} + // Token request metadata containing multiple actions and application-specific data type TokenRequestMetadata struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -616,7 +784,7 @@ type TokenRequestMetadata struct { func (x *TokenRequestMetadata) Reset() { *x = TokenRequestMetadata{} - mi := &file_request_proto_msgTypes[9] + mi := &file_request_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -628,7 +796,7 @@ func (x *TokenRequestMetadata) String() string { func (*TokenRequestMetadata) ProtoMessage() {} func (x *TokenRequestMetadata) ProtoReflect() protoreflect.Message { - mi := &file_request_proto_msgTypes[9] + mi := &file_request_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -641,7 +809,7 @@ func (x *TokenRequestMetadata) ProtoReflect() protoreflect.Message { // Deprecated: Use TokenRequestMetadata.ProtoReflect.Descriptor instead. func (*TokenRequestMetadata) Descriptor() ([]byte, []int) { - return file_request_proto_rawDescGZIP(), []int{9} + return file_request_proto_rawDescGZIP(), []int{11} } func (x *TokenRequestMetadata) GetVersion() uint32 { @@ -676,7 +844,7 @@ type Action struct { func (x *Action) Reset() { *x = Action{} - mi := &file_request_proto_msgTypes[10] + mi := &file_request_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -688,7 +856,7 @@ func (x *Action) String() string { func (*Action) ProtoMessage() {} func (x *Action) ProtoReflect() protoreflect.Message { - mi := &file_request_proto_msgTypes[10] + mi := &file_request_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -701,7 +869,7 @@ func (x *Action) ProtoReflect() protoreflect.Message { // Deprecated: Use Action.ProtoReflect.Descriptor instead. func (*Action) Descriptor() ([]byte, []int) { - return file_request_proto_rawDescGZIP(), []int{10} + return file_request_proto_rawDescGZIP(), []int{12} } func (x *Action) GetType() ActionType { @@ -728,7 +896,7 @@ type Signature struct { func (x *Signature) Reset() { *x = Signature{} - mi := &file_request_proto_msgTypes[11] + mi := &file_request_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -740,7 +908,7 @@ func (x *Signature) String() string { func (*Signature) ProtoMessage() {} func (x *Signature) ProtoReflect() protoreflect.Message { - mi := &file_request_proto_msgTypes[11] + mi := &file_request_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -753,7 +921,7 @@ func (x *Signature) ProtoReflect() protoreflect.Message { // Deprecated: Use Signature.ProtoReflect.Descriptor instead. func (*Signature) Descriptor() ([]byte, []int) { - return file_request_proto_rawDescGZIP(), []int{11} + return file_request_proto_rawDescGZIP(), []int{13} } func (x *Signature) GetRaw() []byte { @@ -774,7 +942,7 @@ type AuditorSignature struct { func (x *AuditorSignature) Reset() { *x = AuditorSignature{} - mi := &file_request_proto_msgTypes[12] + mi := &file_request_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -786,7 +954,7 @@ func (x *AuditorSignature) String() string { func (*AuditorSignature) ProtoMessage() {} func (x *AuditorSignature) ProtoReflect() protoreflect.Message { - mi := &file_request_proto_msgTypes[12] + mi := &file_request_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -799,7 +967,7 @@ func (x *AuditorSignature) ProtoReflect() protoreflect.Message { // Deprecated: Use AuditorSignature.ProtoReflect.Descriptor instead. func (*AuditorSignature) Descriptor() ([]byte, []int) { - return file_request_proto_rawDescGZIP(), []int{12} + return file_request_proto_rawDescGZIP(), []int{14} } func (x *AuditorSignature) GetIdentity() *Identity { @@ -826,7 +994,7 @@ type Auditing struct { func (x *Auditing) Reset() { *x = Auditing{} - mi := &file_request_proto_msgTypes[13] + mi := &file_request_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -838,7 +1006,7 @@ func (x *Auditing) String() string { func (*Auditing) ProtoMessage() {} func (x *Auditing) ProtoReflect() protoreflect.Message { - mi := &file_request_proto_msgTypes[13] + mi := &file_request_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -851,7 +1019,7 @@ func (x *Auditing) ProtoReflect() protoreflect.Message { // Deprecated: Use Auditing.ProtoReflect.Descriptor instead. func (*Auditing) Descriptor() ([]byte, []int) { - return file_request_proto_rawDescGZIP(), []int{13} + return file_request_proto_rawDescGZIP(), []int{15} } func (x *Auditing) GetSignatures() []*AuditorSignature { @@ -874,7 +1042,7 @@ type TokenRequest struct { func (x *TokenRequest) Reset() { *x = TokenRequest{} - mi := &file_request_proto_msgTypes[14] + mi := &file_request_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -886,7 +1054,7 @@ func (x *TokenRequest) String() string { func (*TokenRequest) ProtoMessage() {} func (x *TokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_request_proto_msgTypes[14] + mi := &file_request_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -899,7 +1067,7 @@ func (x *TokenRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use TokenRequest.ProtoReflect.Descriptor instead. func (*TokenRequest) Descriptor() ([]byte, []int) { - return file_request_proto_rawDescGZIP(), []int{14} + return file_request_proto_rawDescGZIP(), []int{16} } func (x *TokenRequest) GetVersion() uint32 { @@ -942,7 +1110,7 @@ type TokenRequestWithMetadata struct { func (x *TokenRequestWithMetadata) Reset() { *x = TokenRequestWithMetadata{} - mi := &file_request_proto_msgTypes[15] + mi := &file_request_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -954,7 +1122,7 @@ func (x *TokenRequestWithMetadata) String() string { func (*TokenRequestWithMetadata) ProtoMessage() {} func (x *TokenRequestWithMetadata) ProtoReflect() protoreflect.Message { - mi := &file_request_proto_msgTypes[15] + mi := &file_request_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -967,7 +1135,7 @@ func (x *TokenRequestWithMetadata) ProtoReflect() protoreflect.Message { // Deprecated: Use TokenRequestWithMetadata.ProtoReflect.Descriptor instead. func (*TokenRequestWithMetadata) Descriptor() ([]byte, []int) { - return file_request_proto_rawDescGZIP(), []int{15} + return file_request_proto_rawDescGZIP(), []int{17} } func (x *TokenRequestWithMetadata) GetVersion() uint32 { @@ -1024,17 +1192,29 @@ const file_request_proto_rawDesc = "" + "\x06inputs\x18\x01 \x03(\v2\x1d.protos.TransferInputMetadataR\x06inputs\x120\n" + "\aoutputs\x18\x02 \x03(\v2\x16.protos.OutputMetadataR\aoutputs\x125\n" + "\rextra_signers\x18\b \x03(\v2\x10.protos.IdentityR\fextraSigners\x12(\n" + - "\x06issuer\x18\x03 \x01(\v2\x10.protos.IdentityR\x06issuer\"@\n" + + "\x06issuer\x18\x03 \x01(\v2\x10.protos.IdentityR\x06issuer\"\xf0\x01\n" + + "\x12TransferMetadataV3\x125\n" + + "\x06inputs\x18\x01 \x03(\v2\x1d.protos.TransferInputMetadataR\x06inputs\x120\n" + + "\aoutputs\x18\x02 \x03(\v2\x16.protos.OutputMetadataR\aoutputs\x12>\n" + + "\rextra_signers\x18\b \x03(\v2\x19.protos.AuditableIdentityR\fextraSigners\x121\n" + + "\x06issuer\x18\x03 \x01(\v2\x19.protos.AuditableIdentityR\x06issuer\"@\n" + "\x12IssueInputMetadata\x12*\n" + "\btoken_id\x18\x02 \x01(\v2\x0f.protos.TokenIDR\atokenId\"\xdf\x01\n" + "\rIssueMetadata\x121\n" + "\x06issuer\x18\x01 \x01(\v2\x19.protos.AuditableIdentityR\x06issuer\x122\n" + "\x06inputs\x18\x02 \x03(\v2\x1a.protos.IssueInputMetadataR\x06inputs\x120\n" + "\aoutputs\x18\x03 \x03(\v2\x16.protos.OutputMetadataR\aoutputs\x125\n" + - "\rextra_signers\x18\x04 \x03(\v2\x10.protos.IdentityR\fextraSigners\"\xa5\x01\n" + + "\rextra_signers\x18\x04 \x03(\v2\x10.protos.IdentityR\fextraSigners\"\xea\x01\n" + + "\x0fIssueMetadataV3\x121\n" + + "\x06issuer\x18\x01 \x01(\v2\x19.protos.AuditableIdentityR\x06issuer\x122\n" + + "\x06inputs\x18\x02 \x03(\v2\x1a.protos.IssueInputMetadataR\x06inputs\x120\n" + + "\aoutputs\x18\x03 \x03(\v2\x16.protos.OutputMetadataR\aoutputs\x12>\n" + + "\rextra_signers\x18\x04 \x03(\v2\x19.protos.AuditableIdentityR\fextraSigners\"\xbc\x02\n" + "\x0eActionMetadata\x12>\n" + "\x0eissue_metadata\x18\x01 \x01(\v2\x15.protos.IssueMetadataH\x00R\rissueMetadata\x12G\n" + - "\x11transfer_metadata\x18\x02 \x01(\v2\x18.protos.TransferMetadataH\x00R\x10transferMetadataB\n" + + "\x11transfer_metadata\x18\x02 \x01(\v2\x18.protos.TransferMetadataH\x00R\x10transferMetadata\x12E\n" + + "\x11issue_metadata_v3\x18\x03 \x01(\v2\x17.protos.IssueMetadataV3H\x00R\x0fissueMetadataV3\x12N\n" + + "\x14transfer_metadata_v3\x18\x04 \x01(\v2\x1a.protos.TransferMetadataV3H\x00R\x12transferMetadataV3B\n" + "\n" + "\bMetadata\"\xf5\x01\n" + "\x14TokenRequestMetadata\x12\x18\n" + @@ -1086,7 +1266,7 @@ func file_request_proto_rawDescGZIP() []byte { } var file_request_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_request_proto_msgTypes = make([]protoimpl.MessageInfo, 17) +var file_request_proto_msgTypes = make([]protoimpl.MessageInfo, 19) var file_request_proto_goTypes = []any{ (ActionType)(0), // 0: protos.ActionType (*Identity)(nil), // 1: protos.Identity @@ -1095,17 +1275,19 @@ var file_request_proto_goTypes = []any{ (*TransferInputMetadata)(nil), // 4: protos.TransferInputMetadata (*OutputMetadata)(nil), // 5: protos.OutputMetadata (*TransferMetadata)(nil), // 6: protos.TransferMetadata - (*IssueInputMetadata)(nil), // 7: protos.IssueInputMetadata - (*IssueMetadata)(nil), // 8: protos.IssueMetadata - (*ActionMetadata)(nil), // 9: protos.ActionMetadata - (*TokenRequestMetadata)(nil), // 10: protos.TokenRequestMetadata - (*Action)(nil), // 11: protos.Action - (*Signature)(nil), // 12: protos.Signature - (*AuditorSignature)(nil), // 13: protos.AuditorSignature - (*Auditing)(nil), // 14: protos.Auditing - (*TokenRequest)(nil), // 15: protos.TokenRequest - (*TokenRequestWithMetadata)(nil), // 16: protos.TokenRequestWithMetadata - nil, // 17: protos.TokenRequestMetadata.ApplicationEntry + (*TransferMetadataV3)(nil), // 7: protos.TransferMetadataV3 + (*IssueInputMetadata)(nil), // 8: protos.IssueInputMetadata + (*IssueMetadata)(nil), // 9: protos.IssueMetadata + (*IssueMetadataV3)(nil), // 10: protos.IssueMetadataV3 + (*ActionMetadata)(nil), // 11: protos.ActionMetadata + (*TokenRequestMetadata)(nil), // 12: protos.TokenRequestMetadata + (*Action)(nil), // 13: protos.Action + (*Signature)(nil), // 14: protos.Signature + (*AuditorSignature)(nil), // 15: protos.AuditorSignature + (*Auditing)(nil), // 16: protos.Auditing + (*TokenRequest)(nil), // 17: protos.TokenRequest + (*TokenRequestWithMetadata)(nil), // 18: protos.TokenRequestWithMetadata + nil, // 19: protos.TokenRequestMetadata.ApplicationEntry } var file_request_proto_depIdxs = []int32{ 1, // 0: protos.AuditableIdentity.identity:type_name -> protos.Identity @@ -1116,29 +1298,39 @@ var file_request_proto_depIdxs = []int32{ 5, // 5: protos.TransferMetadata.outputs:type_name -> protos.OutputMetadata 1, // 6: protos.TransferMetadata.extra_signers:type_name -> protos.Identity 1, // 7: protos.TransferMetadata.issuer:type_name -> protos.Identity - 3, // 8: protos.IssueInputMetadata.token_id:type_name -> protos.TokenID - 2, // 9: protos.IssueMetadata.issuer:type_name -> protos.AuditableIdentity - 7, // 10: protos.IssueMetadata.inputs:type_name -> protos.IssueInputMetadata - 5, // 11: protos.IssueMetadata.outputs:type_name -> protos.OutputMetadata - 1, // 12: protos.IssueMetadata.extra_signers:type_name -> protos.Identity - 8, // 13: protos.ActionMetadata.issue_metadata:type_name -> protos.IssueMetadata - 6, // 14: protos.ActionMetadata.transfer_metadata:type_name -> protos.TransferMetadata - 9, // 15: protos.TokenRequestMetadata.metadata:type_name -> protos.ActionMetadata - 17, // 16: protos.TokenRequestMetadata.application:type_name -> protos.TokenRequestMetadata.ApplicationEntry - 0, // 17: protos.Action.type:type_name -> protos.ActionType - 1, // 18: protos.AuditorSignature.identity:type_name -> protos.Identity - 12, // 19: protos.AuditorSignature.signature:type_name -> protos.Signature - 13, // 20: protos.Auditing.signatures:type_name -> protos.AuditorSignature - 11, // 21: protos.TokenRequest.actions:type_name -> protos.Action - 12, // 22: protos.TokenRequest.signatures:type_name -> protos.Signature - 14, // 23: protos.TokenRequest.auditing:type_name -> protos.Auditing - 15, // 24: protos.TokenRequestWithMetadata.request:type_name -> protos.TokenRequest - 10, // 25: protos.TokenRequestWithMetadata.metadata:type_name -> protos.TokenRequestMetadata - 26, // [26:26] is the sub-list for method output_type - 26, // [26:26] is the sub-list for method input_type - 26, // [26:26] is the sub-list for extension type_name - 26, // [26:26] is the sub-list for extension extendee - 0, // [0:26] is the sub-list for field type_name + 4, // 8: protos.TransferMetadataV3.inputs:type_name -> protos.TransferInputMetadata + 5, // 9: protos.TransferMetadataV3.outputs:type_name -> protos.OutputMetadata + 2, // 10: protos.TransferMetadataV3.extra_signers:type_name -> protos.AuditableIdentity + 2, // 11: protos.TransferMetadataV3.issuer:type_name -> protos.AuditableIdentity + 3, // 12: protos.IssueInputMetadata.token_id:type_name -> protos.TokenID + 2, // 13: protos.IssueMetadata.issuer:type_name -> protos.AuditableIdentity + 8, // 14: protos.IssueMetadata.inputs:type_name -> protos.IssueInputMetadata + 5, // 15: protos.IssueMetadata.outputs:type_name -> protos.OutputMetadata + 1, // 16: protos.IssueMetadata.extra_signers:type_name -> protos.Identity + 2, // 17: protos.IssueMetadataV3.issuer:type_name -> protos.AuditableIdentity + 8, // 18: protos.IssueMetadataV3.inputs:type_name -> protos.IssueInputMetadata + 5, // 19: protos.IssueMetadataV3.outputs:type_name -> protos.OutputMetadata + 2, // 20: protos.IssueMetadataV3.extra_signers:type_name -> protos.AuditableIdentity + 9, // 21: protos.ActionMetadata.issue_metadata:type_name -> protos.IssueMetadata + 6, // 22: protos.ActionMetadata.transfer_metadata:type_name -> protos.TransferMetadata + 10, // 23: protos.ActionMetadata.issue_metadata_v3:type_name -> protos.IssueMetadataV3 + 7, // 24: protos.ActionMetadata.transfer_metadata_v3:type_name -> protos.TransferMetadataV3 + 11, // 25: protos.TokenRequestMetadata.metadata:type_name -> protos.ActionMetadata + 19, // 26: protos.TokenRequestMetadata.application:type_name -> protos.TokenRequestMetadata.ApplicationEntry + 0, // 27: protos.Action.type:type_name -> protos.ActionType + 1, // 28: protos.AuditorSignature.identity:type_name -> protos.Identity + 14, // 29: protos.AuditorSignature.signature:type_name -> protos.Signature + 15, // 30: protos.Auditing.signatures:type_name -> protos.AuditorSignature + 13, // 31: protos.TokenRequest.actions:type_name -> protos.Action + 14, // 32: protos.TokenRequest.signatures:type_name -> protos.Signature + 16, // 33: protos.TokenRequest.auditing:type_name -> protos.Auditing + 17, // 34: protos.TokenRequestWithMetadata.request:type_name -> protos.TokenRequest + 12, // 35: protos.TokenRequestWithMetadata.metadata:type_name -> protos.TokenRequestMetadata + 36, // [36:36] is the sub-list for method output_type + 36, // [36:36] is the sub-list for method input_type + 36, // [36:36] is the sub-list for extension type_name + 36, // [36:36] is the sub-list for extension extendee + 0, // [0:36] is the sub-list for field type_name } func init() { file_request_proto_init() } @@ -1146,9 +1338,11 @@ func file_request_proto_init() { if File_request_proto != nil { return } - file_request_proto_msgTypes[8].OneofWrappers = []any{ + file_request_proto_msgTypes[10].OneofWrappers = []any{ (*ActionMetadata_IssueMetadata)(nil), (*ActionMetadata_TransferMetadata)(nil), + (*ActionMetadata_IssueMetadataV3)(nil), + (*ActionMetadata_TransferMetadataV3)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -1156,7 +1350,7 @@ func file_request_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_request_proto_rawDesc), len(file_request_proto_rawDesc)), NumEnums: 1, - NumMessages: 17, + NumMessages: 19, NumExtensions: 0, NumServices: 0, }, diff --git a/token/driver/protos/request.proto b/token/driver/protos/request.proto index 0da66ab151..8fb6951c00 100644 --- a/token/driver/protos/request.proto +++ b/token/driver/protos/request.proto @@ -54,6 +54,13 @@ message TransferMetadata { Identity issuer = 3; // Issuer signer for the redeem transfer } +message TransferMetadataV3 { + repeated TransferInputMetadata inputs = 1; // Inputs + repeated OutputMetadata outputs = 2; // Outputs + repeated AuditableIdentity extra_signers = 8; // Additional signers for the transfer + AuditableIdentity issuer = 3; // Issuer signer for the redeem transfer +} + message IssueInputMetadata { TokenID token_id = 2; // The Token ID being consumed by the issue } @@ -66,11 +73,20 @@ message IssueMetadata { repeated Identity extra_signers = 4; // Additional signers for the issuance } +message IssueMetadataV3 { + AuditableIdentity issuer = 1; // Issuer of the tokens + repeated IssueInputMetadata inputs = 2; // Inputs + repeated OutputMetadata outputs = 3; // Outputs + repeated AuditableIdentity extra_signers = 4; // Additional signers for the issuance +} + // Union type containing either issue or transfer metadata message ActionMetadata { oneof Metadata { // Oneof field containing either issue or transfer metadata IssueMetadata issue_metadata = 1; // Issue action metadata TransferMetadata transfer_metadata = 2; // Transfer action metadata + IssueMetadataV3 issue_metadata_v3 = 3; // Issue action metadata V3 + TransferMetadataV3 transfer_metadata_v3 = 4; // Transfer action metadata V3 } } diff --git a/token/driver/request.go b/token/driver/request.go index 4e99228642..2e54b3e371 100644 --- a/token/driver/request.go +++ b/token/driver/request.go @@ -21,6 +21,7 @@ import ( const ( ProtocolV1 = 1 ProtocolV2 = 2 + ProtocolV3 = 3 // MaxAnchorSize defines the maximum allowed size for anchor parameter in bytes. // This limit prevents potential DoS attacks through excessive memory allocation. @@ -169,7 +170,6 @@ func (r *TokenRequest) FromProtos(tr *request.TokenRequest) error { r.Signatures = append(r.Signatures, signature.Raw) } if tr.Auditing != nil { - r.AuditorSignatures = make([]*AuditorSignature, len(tr.Auditing.Signatures)) r.AuditorSignatures = slices.GenericSliceOfPointers[AuditorSignature](len(tr.Auditing.Signatures)) if err := protos.FromProtosSlice(tr.Auditing.Signatures, r.AuditorSignatures); err != nil { return errors.Wrap(err, "failed converting auditor signatures") @@ -214,13 +214,13 @@ func (r *TokenRequest) MarshalToMessageToSign(anchor []byte) ([]byte, error) { // getVersion returns the protocol version of this TokenRequest. // Returns the stored version, defaulting to V2 for new requests. -func (r *TokenRequest) getVersion() int { +func (r *TokenRequest) getVersion() uint32 { if r.Version == 0 { // Default to V2 for new requests return ProtocolV2 } - return int(r.Version) + return r.Version } // marshalToMessageToSignV1 implements the V1 protocol signature message construction. @@ -385,7 +385,7 @@ type IssueMetadata struct { Outputs []*IssueOutputMetadata // ExtraSigners is the list of extra identities that are not part of the issue action per se // but needs to sign the request - ExtraSigners []Identity + ExtraSigners []*AuditableIdentity } func (i *IssueMetadata) ToProtos() (*request.IssueMetadata, error) { @@ -402,11 +402,45 @@ func (i *IssueMetadata) ToProtos() (*request.IssueMetadata, error) { return nil, errors.Wrapf(err, "failed marshalling outputs") } + extraSigners := make([]*request.Identity, len(i.ExtraSigners)) + for idx, es := range i.ExtraSigners { + if es != nil { + extraSigners[idx] = &request.Identity{Raw: es.Identity} + } + } + return &request.IssueMetadata{ Issuer: issuer, Inputs: inputs, Outputs: outputs, - ExtraSigners: ToProtoIdentitySlice(i.ExtraSigners), + ExtraSigners: extraSigners, + }, nil +} + +func (i *IssueMetadata) ToProtosV3() (*request.IssueMetadataV3, error) { + issuer, err := i.Issuer.ToProtos() + if err != nil { + return nil, errors.Wrapf(err, "failed marshalling issuer [%v]", i.Issuer) + } + inputs, err := protos.ToProtosSlice(i.Inputs) + if err != nil { + return nil, errors.Wrapf(err, "failed marshalling inputs") + } + outputs, err := protos.ToProtosSlice(i.Outputs) + if err != nil { + return nil, errors.Wrapf(err, "failed marshalling outputs") + } + + extraSigners, err := protos.ToProtosSlice[request.AuditableIdentity, *AuditableIdentity](i.ExtraSigners) + if err != nil { + return nil, errors.Wrapf(err, "failed marshalling extra signers") + } + + return &request.IssueMetadataV3{ + Issuer: issuer, + Inputs: inputs, + Outputs: outputs, + ExtraSigners: extraSigners, }, nil } @@ -426,7 +460,36 @@ func (i *IssueMetadata) FromProtos(issueMetadata *request.IssueMetadata) error { if err != nil { return errors.Wrap(err, "failed unmarshalling output metadata") } - i.ExtraSigners = FromProtoIdentitySlice(issueMetadata.ExtraSigners) + i.ExtraSigners = make([]*AuditableIdentity, len(issueMetadata.ExtraSigners)) + for idx, es := range issueMetadata.ExtraSigners { + if es != nil { + i.ExtraSigners[idx] = &AuditableIdentity{Identity: es.Raw} + } + } + + return nil +} + +func (i *IssueMetadata) FromProtosV3(issueMetadata *request.IssueMetadataV3) error { + issuer := &AuditableIdentity{} + if err := issuer.FromProtos(issueMetadata.Issuer); err != nil { + return errors.Wrapf(err, "failed unmarshalling issuer [%v]", issueMetadata.Issuer) + } + i.Issuer = *issuer + i.Inputs = slices.GenericSliceOfPointers[IssueInputMetadata](len(issueMetadata.Inputs)) + err := protos.FromProtosSlice[request.IssueInputMetadata, *IssueInputMetadata](issueMetadata.Inputs, i.Inputs) + if err != nil { + return errors.Wrap(err, "failed unmarshalling input metadata") + } + i.Outputs = slices.GenericSliceOfPointers[IssueOutputMetadata](len(issueMetadata.Outputs)) + err = protos.FromProtosSlice(issueMetadata.Outputs, i.Outputs) + if err != nil { + return errors.Wrap(err, "failed unmarshalling output metadata") + } + i.ExtraSigners = slices.GenericSliceOfPointers[AuditableIdentity](len(issueMetadata.ExtraSigners)) + if err = protos.FromProtosSlice[request.AuditableIdentity, *AuditableIdentity](issueMetadata.ExtraSigners, i.ExtraSigners); err != nil { + return errors.Wrap(err, "failed unmarshalling extra signers") + } return nil } @@ -536,9 +599,9 @@ type TransferMetadata struct { Outputs []*TransferOutputMetadata // ExtraSigners is the list of extra identities that are not part of the transfer action per se // but needs to sign the request - ExtraSigners []Identity + ExtraSigners []*AuditableIdentity // Issuer contains the identity of the issuer to sign the transfer action - Issuer Identity + Issuer AuditableIdentity } // TokenIDAt returns the TokenID at the given index. @@ -560,18 +623,51 @@ func (t *TransferMetadata) ToProtos() (*request.TransferMetadata, error) { if err != nil { return nil, errors.Wrapf(err, "failed marshalling outputs") } + extraSigners := make([]*request.Identity, len(t.ExtraSigners)) + for idx, es := range t.ExtraSigners { + if es != nil { + extraSigners[idx] = &request.Identity{Raw: es.Identity} + } + } var issuer *request.Identity - if t.Issuer != nil { + if t.Issuer.Identity != nil { issuer = &request.Identity{ - Raw: t.Issuer.Bytes(), + Raw: t.Issuer.Identity, } } return &request.TransferMetadata{ Inputs: inputs, Outputs: outputs, - ExtraSigners: ToProtoIdentitySlice(t.ExtraSigners), + ExtraSigners: extraSigners, + Issuer: issuer, + }, nil +} + +func (t *TransferMetadata) ToProtosV3() (*request.TransferMetadataV3, error) { + inputs, err := protos.ToProtosSlice[request.TransferInputMetadata, *TransferInputMetadata](t.Inputs) + if err != nil { + return nil, errors.Wrapf(err, "failed marshalling inputs") + } + outputs, err := protos.ToProtosSlice(t.Outputs) + if err != nil { + return nil, errors.Wrapf(err, "failed marshalling outputs") + } + extraSigners, err := protos.ToProtosSlice[request.AuditableIdentity, *AuditableIdentity](t.ExtraSigners) + if err != nil { + return nil, errors.Wrapf(err, "failed marshalling extra signers") + } + + issuer, err := t.Issuer.ToProtos() + if err != nil { + return nil, errors.Wrapf(err, "failed marshalling issuer [%v]", t.Issuer) + } + + return &request.TransferMetadataV3{ + Inputs: inputs, + Outputs: outputs, + ExtraSigners: extraSigners, Issuer: issuer, }, nil } @@ -585,11 +681,40 @@ func (t *TransferMetadata) FromProtos(transferMetadata *request.TransferMetadata if err := protos.FromProtosSlice(transferMetadata.Outputs, t.Outputs); err != nil { return errors.Wrap(err, "failed unmarshalling outputs") } - t.ExtraSigners = FromProtoIdentitySlice(transferMetadata.ExtraSigners) + t.ExtraSigners = make([]*AuditableIdentity, len(transferMetadata.ExtraSigners)) + for idx, es := range transferMetadata.ExtraSigners { + if es != nil { + t.ExtraSigners[idx] = &AuditableIdentity{Identity: es.Raw} + } + } + + t.Issuer = AuditableIdentity{} + if transferMetadata.Issuer != nil { + t.Issuer = AuditableIdentity{Identity: transferMetadata.Issuer.Raw} + } + + return nil +} + +func (t *TransferMetadata) FromProtosV3(transferMetadata *request.TransferMetadataV3) error { + t.Inputs = slices.GenericSliceOfPointers[TransferInputMetadata](len(transferMetadata.Inputs)) + if err := protos.FromProtosSlice(transferMetadata.Inputs, t.Inputs); err != nil { + return errors.Wrap(err, "failed unmarshalling inputs") + } + t.Outputs = slices.GenericSliceOfPointers[TransferOutputMetadata](len(transferMetadata.Outputs)) + if err := protos.FromProtosSlice(transferMetadata.Outputs, t.Outputs); err != nil { + return errors.Wrap(err, "failed unmarshalling outputs") + } + t.ExtraSigners = slices.GenericSliceOfPointers[AuditableIdentity](len(transferMetadata.ExtraSigners)) + if err := protos.FromProtosSlice[request.AuditableIdentity, *AuditableIdentity](transferMetadata.ExtraSigners, t.ExtraSigners); err != nil { + return errors.Wrap(err, "failed unmarshalling extra signers") + } - t.Issuer = nil if transferMetadata.Issuer != nil { - t.Issuer = transferMetadata.Issuer.Raw + t.Issuer = AuditableIdentity{} + if err := t.Issuer.FromProtos(transferMetadata.Issuer); err != nil { + return errors.Wrap(err, "failed unmarshalling issuer") + } } return nil @@ -669,7 +794,7 @@ func (m *TokenRequestMetadata) FromBytes(raw []byte) error { func (m *TokenRequestMetadata) ToProtos() (*request.TokenRequestMetadata, error) { trm := &request.TokenRequestMetadata{ - Version: ProtocolV1, + Version: ProtocolV3, Metadata: nil, Application: m.Application, } @@ -678,13 +803,13 @@ func (m *TokenRequestMetadata) ToProtos() (*request.TokenRequestMetadata, error) if meta == nil { return nil, errors.Errorf("failed unmarshalling issue metadata, it is nil") } - metaProto, err := meta.ToProtos() + metaProto, err := meta.ToProtosV3() if err != nil { return nil, errors.Wrapf(err, "failed marshalling issue metadata") } trm.Metadata = append(trm.Metadata, &request.ActionMetadata{ - Metadata: &request.ActionMetadata_IssueMetadata{ - IssueMetadata: metaProto, + Metadata: &request.ActionMetadata_IssueMetadataV3{ + IssueMetadataV3: metaProto, }, }) } @@ -692,13 +817,13 @@ func (m *TokenRequestMetadata) ToProtos() (*request.TokenRequestMetadata, error) if meta == nil { return nil, errors.Errorf("failed unmarshalling issue metadata, it is nil") } - metaProto, err := meta.ToProtos() + metaProto, err := meta.ToProtosV3() if err != nil { return nil, errors.Wrapf(err, "failed marshalling transfer metadata") } trm.Metadata = append(trm.Metadata, &request.ActionMetadata{ - Metadata: &request.ActionMetadata_TransferMetadata{ - TransferMetadata: metaProto, + Metadata: &request.ActionMetadata_TransferMetadataV3{ + TransferMetadataV3: metaProto, }, }) } @@ -708,14 +833,13 @@ func (m *TokenRequestMetadata) ToProtos() (*request.TokenRequestMetadata, error) func (m *TokenRequestMetadata) FromProtos(trm *request.TokenRequestMetadata) error { // assert version - if trm.Version != ProtocolV1 { - return errors.Errorf("invalid token request metadata version, expected [%d], got [%d]", ProtocolV1, trm.Version) + if trm.Version != ProtocolV1 && trm.Version != ProtocolV2 && trm.Version != ProtocolV3 { + return errors.Errorf("invalid token request metadata version, expected [%d, %d, or %d], got [%d]", ProtocolV1, ProtocolV2, ProtocolV3, trm.Version) } m.Application = trm.Application for _, meta := range trm.Metadata { - im := meta.GetIssueMetadata() - if im != nil { + if im := meta.GetIssueMetadata(); im != nil { issueMetadata := &IssueMetadata{} if err := issueMetadata.FromProtos(im); err != nil { return errors.Wrapf(err, "failed unmarshalling issue metadata") @@ -724,8 +848,16 @@ func (m *TokenRequestMetadata) FromProtos(trm *request.TokenRequestMetadata) err continue } - tm := meta.GetTransferMetadata() - if tm != nil { + if imv3 := meta.GetIssueMetadataV3(); imv3 != nil { + issueMetadata := &IssueMetadata{} + if err := issueMetadata.FromProtosV3(imv3); err != nil { + return errors.Wrapf(err, "failed unmarshalling issue metadata v3") + } + m.Issues = append(m.Issues, issueMetadata) + + continue + } + if tm := meta.GetTransferMetadata(); tm != nil { transferMetadata := &TransferMetadata{} if err := transferMetadata.FromProtos(tm); err != nil { return errors.Wrapf(err, "failed unmarshalling transfer metadata") @@ -734,6 +866,15 @@ func (m *TokenRequestMetadata) FromProtos(trm *request.TokenRequestMetadata) err continue } + if tmv3 := meta.GetTransferMetadataV3(); tmv3 != nil { + transferMetadata := &TransferMetadata{} + if err := transferMetadata.FromProtosV3(tmv3); err != nil { + return errors.Wrapf(err, "failed unmarshalling transfer metadata v3") + } + m.Transfers = append(m.Transfers, transferMetadata) + + continue + } return errors.Errorf("failed unmarshalling metadata, type not recognized") } diff --git a/token/driver/request_test.go b/token/driver/request_test.go index bf1e247a1c..2a084b28c6 100644 --- a/token/driver/request_test.go +++ b/token/driver/request_test.go @@ -387,7 +387,9 @@ func TestIssueMetadata_ToProtos(t *testing.T) { Outputs: []*IssueOutputMetadata{ {OutputMetadata: []byte("output1")}, }, - ExtraSigners: []Identity{Identity("signer1")}, + ExtraSigners: []*AuditableIdentity{ + {Identity: Identity("signer1")}, + }, } proto, err := im.ToProtos() @@ -432,7 +434,7 @@ func TestIssueMetadata_FromProtos(t *testing.T) { assert.Len(t, im.Outputs, 1) assert.Equal(t, []byte("output1"), im.Outputs[0].OutputMetadata) assert.Len(t, im.ExtraSigners, 1) - assert.Equal(t, Identity("signer1"), im.ExtraSigners[0]) + assert.Equal(t, Identity("signer1"), im.ExtraSigners[0].Identity) } // TestTransferInputMetadata_ToProtos tests conversion to protobuf @@ -648,8 +650,10 @@ func TestTransferMetadata_ToProtos(t *testing.T) { Outputs: []*TransferOutputMetadata{ {OutputMetadata: []byte("output1")}, }, - ExtraSigners: []Identity{Identity("signer1")}, - Issuer: Identity("issuer1"), + ExtraSigners: []*AuditableIdentity{ + {Identity: Identity("signer1")}, + }, + Issuer: AuditableIdentity{Identity: Identity("issuer1")}, } proto, err := tm.ToProtos() @@ -671,7 +675,7 @@ func TestTransferMetadata_ToProtos_NilIssuer(t *testing.T) { tm := &TransferMetadata{ Inputs: []*TransferInputMetadata{}, Outputs: []*TransferOutputMetadata{}, - Issuer: nil, + Issuer: AuditableIdentity{}, } proto, err := tm.ToProtos() @@ -704,8 +708,8 @@ func TestTransferMetadata_FromProtos(t *testing.T) { assert.Len(t, tm.Outputs, 1) assert.Equal(t, []byte("output1"), tm.Outputs[0].OutputMetadata) assert.Len(t, tm.ExtraSigners, 1) - assert.Equal(t, Identity("signer1"), tm.ExtraSigners[0]) - assert.Equal(t, Identity("issuer1"), tm.Issuer) + assert.Equal(t, Identity("signer1"), tm.ExtraSigners[0].Identity) + assert.Equal(t, Identity("issuer1"), tm.Issuer.Identity) } // TestTransferMetadata_FromProtos_NilIssuer tests conversion with nil issuer @@ -719,7 +723,7 @@ func TestTransferMetadata_FromProtos_NilIssuer(t *testing.T) { tm := &TransferMetadata{} err := tm.FromProtos(proto) require.NoError(t, err) - assert.Nil(t, tm.Issuer) + assert.Nil(t, tm.Issuer.Identity) } func TestTokenRequestMetadataSerialization(t *testing.T) { @@ -751,9 +755,9 @@ func TestTokenRequestMetadataSerialization(t *testing.T) { }, }, }, - ExtraSigners: []Identity{ - []byte("issue_extra_signer1"), - []byte("issue_extra_signer2"), + ExtraSigners: []*AuditableIdentity{ + {Identity: []byte("issue_extra_signer1")}, + {Identity: []byte("issue_extra_signer2")}, }, }, }, @@ -807,11 +811,11 @@ func TestTokenRequestMetadataSerialization(t *testing.T) { }, }, }, - ExtraSigners: []Identity{ - []byte("extra_signer1"), - []byte("extra_signer2"), + ExtraSigners: []*AuditableIdentity{ + {Identity: []byte("extra_signer1")}, + {Identity: []byte("extra_signer2")}, }, - Issuer: Identity([]byte("issuer")), + Issuer: AuditableIdentity{Identity: Identity([]byte("issuer"))}, }, }, Application: map[string][]byte{ @@ -1312,20 +1316,23 @@ func TestIssueMetadata_ToProtos_WithExtraSigners(t *testing.T) { Outputs: []*IssueOutputMetadata{ {OutputMetadata: []byte("output1")}, }, - ExtraSigners: []Identity{ - Identity("signer1"), - Identity("signer2"), - Identity("signer3"), + ExtraSigners: []*AuditableIdentity{ + {Identity: Identity("signer1"), AuditInfo: []byte("audit1")}, + {Identity: Identity("signer2"), AuditInfo: []byte("audit2")}, + {Identity: Identity("signer3"), AuditInfo: []byte("audit3")}, }, } - proto, err := im.ToProtos() + proto, err := im.ToProtosV3() require.NoError(t, err) assert.NotNil(t, proto) assert.Len(t, proto.ExtraSigners, 3) - assert.Equal(t, []byte("signer1"), proto.ExtraSigners[0].Raw) - assert.Equal(t, []byte("signer2"), proto.ExtraSigners[1].Raw) - assert.Equal(t, []byte("signer3"), proto.ExtraSigners[2].Raw) + assert.Equal(t, []byte("signer1"), proto.ExtraSigners[0].Identity.Raw) + assert.Equal(t, []byte("audit1"), proto.ExtraSigners[0].AuditInfo) + assert.Equal(t, []byte("signer2"), proto.ExtraSigners[1].Identity.Raw) + assert.Equal(t, []byte("audit2"), proto.ExtraSigners[1].AuditInfo) + assert.Equal(t, []byte("signer3"), proto.ExtraSigners[2].Identity.Raw) + assert.Equal(t, []byte("audit3"), proto.ExtraSigners[2].AuditInfo) } // TestTransferMetadata_ToProtos_WithExtraSigners tests ToProtos with extra signers @@ -1337,21 +1344,23 @@ func TestTransferMetadata_ToProtos_WithExtraSigners(t *testing.T) { Outputs: []*TransferOutputMetadata{ {OutputMetadata: []byte("output1")}, }, - ExtraSigners: []Identity{ - Identity("signer1"), - Identity("signer2"), + ExtraSigners: []*AuditableIdentity{ + {Identity: Identity("signer1"), AuditInfo: []byte("audit1")}, + {Identity: Identity("signer2"), AuditInfo: []byte("audit2")}, }, - Issuer: Identity("issuer1"), + Issuer: AuditableIdentity{Identity: Identity("issuer1")}, } - proto, err := tm.ToProtos() + proto, err := tm.ToProtosV3() require.NoError(t, err) assert.NotNil(t, proto) assert.Len(t, proto.ExtraSigners, 2) - assert.Equal(t, []byte("signer1"), proto.ExtraSigners[0].Raw) - assert.Equal(t, []byte("signer2"), proto.ExtraSigners[1].Raw) + assert.Equal(t, []byte("signer1"), proto.ExtraSigners[0].Identity.Raw) + assert.Equal(t, []byte("audit1"), proto.ExtraSigners[0].AuditInfo) + assert.Equal(t, []byte("signer2"), proto.ExtraSigners[1].Identity.Raw) + assert.Equal(t, []byte("audit2"), proto.ExtraSigners[1].AuditInfo) assert.NotNil(t, proto.Issuer) - assert.Equal(t, []byte("issuer1"), proto.Issuer.Raw) + assert.Equal(t, []byte("issuer1"), proto.Issuer.Identity.Raw) } // TestTokenRequest_ToProtos_EmptyAuditorSignatures tests ToProtos with empty auditor signatures @@ -1582,7 +1591,7 @@ func TestTransferMetadata_ToProtos_WithIssuer(t *testing.T) { Outputs: []*TransferOutputMetadata{ {OutputMetadata: []byte("output1")}, }, - Issuer: Identity("issuer1"), + Issuer: AuditableIdentity{Identity: Identity("issuer1")}, } proto, err := meta.ToProtos() diff --git a/token/metadata.go b/token/metadata.go index 5d3c67e2c2..1ec859dd9c 100644 --- a/token/metadata.go +++ b/token/metadata.go @@ -142,6 +142,7 @@ func (m *Metadata) filterTransfers(ctx context.Context, issues []*driver.Transfe Inputs: nil, Outputs: nil, ExtraSigners: transfer.ExtraSigners, + Issuer: transfer.Issuer, } // Filter outputs: if the receiver has the given enrollment ID, add it. Otherwise, add empty entries. @@ -246,9 +247,11 @@ func (m *IssueMetadata) Match(action *IssueAction) error { if len(m.ExtraSigners) != len(extraSigners) { return errors.Errorf("expected [%d] extra signers but got [%d]", len(extraSigners), len(m.ExtraSigners)) } - for i, signer := range extraSigners { - if !slices.ContainsFunc(m.ExtraSigners, signer.Equal) { - return errors.Errorf("expected extra signer [%s] but got [%s]", signer, m.ExtraSigners[i]) + for _, signer := range extraSigners { + if !slices.ContainsFunc(m.ExtraSigners, func(es *driver.AuditableIdentity) bool { + return es != nil && signer.Equal(es.Identity) + }) { + return errors.Errorf("expected extra signer among [%v] but got [%s]", m.ExtraSigners, signer) } } @@ -302,14 +305,14 @@ func (m *TransferMetadata) Match(action *TransferAction) error { return errors.Errorf("expected [%d] extra signers but got [%d]", len(m.ExtraSigners), len(extraSigners)) } for i, signer := range extraSigners { - if !signer.Equal(m.ExtraSigners[i]) { - return errors.Errorf("expected extra signer [%s] but got [%s]", m.ExtraSigners[i], signer) + if m.ExtraSigners[i] == nil || !signer.Equal(m.ExtraSigners[i].Identity) { + return errors.Errorf("expected extra signer [%s] but got [%s]", m.ExtraSigners[i].Identity, signer) } } // Check that the issuer identity matches, if present in the metadata. - if !m.Issuer.Equal(action.GetIssuer()) { - return errors.Errorf("expected issuer [%s] but got [%s]", m.Issuer, action.GetIssuer().Bytes()) + if !m.Issuer.Identity.Equal(action.GetIssuer()) { + return errors.Errorf("expected issuer [%s] but got [%s]", m.Issuer.Identity, action.GetIssuer()) } return nil diff --git a/token/metadata_test.go b/token/metadata_test.go index b7b90106cd..e144b56cfe 100644 --- a/token/metadata_test.go +++ b/token/metadata_test.go @@ -248,10 +248,10 @@ func TestMetadata_TestMatchTransferAction(t *testing.T) { action: &token.TransferAction{transferActionWithIssuer}, meta: &token.TransferMetadata{ &driver.TransferMetadata{ - Issuer: mockIssuer, + Issuer: driver.AuditableIdentity{Identity: mockIssuer}, Inputs: []*driver.TransferInputMetadata{{}}, Outputs: []*driver.TransferOutputMetadata{{}}, - ExtraSigners: []token.Identity{signer1}, + ExtraSigners: []*driver.AuditableIdentity{{Identity: signer1}}, }, }, wantErr: false, @@ -309,7 +309,7 @@ func TestMetadata_TestMatchTransferAction(t *testing.T) { &driver.TransferMetadata{ Inputs: []*driver.TransferInputMetadata{{}}, Outputs: []*driver.TransferOutputMetadata{{}}, - ExtraSigners: []token.Identity{}, + ExtraSigners: []*driver.AuditableIdentity{}, }, }, wantErr: true, @@ -322,7 +322,7 @@ func TestMetadata_TestMatchTransferAction(t *testing.T) { &driver.TransferMetadata{ Inputs: []*driver.TransferInputMetadata{{}}, Outputs: []*driver.TransferOutputMetadata{{}}, - ExtraSigners: []token.Identity{token.Identity("other")}, + ExtraSigners: []*driver.AuditableIdentity{{Identity: token.Identity("other")}}, }, }, wantErr: true, @@ -333,14 +333,14 @@ func TestMetadata_TestMatchTransferAction(t *testing.T) { action: &token.TransferAction{transferActionWithIssuer}, meta: &token.TransferMetadata{ &driver.TransferMetadata{ - Issuer: token.Identity("other"), + Issuer: driver.AuditableIdentity{Identity: token.Identity("other")}, Inputs: []*driver.TransferInputMetadata{{}}, Outputs: []*driver.TransferOutputMetadata{{}}, - ExtraSigners: []token.Identity{signer1}, + ExtraSigners: []*driver.AuditableIdentity{{Identity: signer1}}, }, }, wantErr: true, - expectedError: "expected issuer [2SmKENGwc1g33EvYXaxkGw887yekfl1TpU8vP1svz/o=] but got [issuer1]", + expectedError: "expected issuer [2SmKENGwc1g33EvYXaxkGw887yekfl1TpU8vP1svz/o=] but got [7yg9tam/vXldzOxgyOs2BIObcQTCxk7wTJK/CZJARw4=]", }, } @@ -525,7 +525,7 @@ func TestIssueMetadata_Match(t *testing.T) { Issuer: driver.AuditableIdentity{Identity: issuer1}, Inputs: []*driver.IssueInputMetadata{{}}, Outputs: []*driver.IssueOutputMetadata{{}}, - ExtraSigners: []token.Identity{signer1}, + ExtraSigners: []*driver.AuditableIdentity{{Identity: signer1}}, }, }, action: token.NewIssueAction(action), @@ -583,7 +583,7 @@ func TestIssueMetadata_Match(t *testing.T) { IssueMetadata: &driver.IssueMetadata{ Inputs: []*driver.IssueInputMetadata{{}}, Outputs: []*driver.IssueOutputMetadata{{}}, - ExtraSigners: []token.Identity{}, + ExtraSigners: []*driver.AuditableIdentity{}, }, }, action: token.NewIssueAction(action), @@ -596,7 +596,7 @@ func TestIssueMetadata_Match(t *testing.T) { IssueMetadata: &driver.IssueMetadata{ Inputs: []*driver.IssueInputMetadata{{}}, Outputs: []*driver.IssueOutputMetadata{{}}, - ExtraSigners: []token.Identity{token.Identity("other")}, + ExtraSigners: []*driver.AuditableIdentity{{Identity: token.Identity("other")}}, }, }, action: token.NewIssueAction(action), @@ -610,7 +610,7 @@ func TestIssueMetadata_Match(t *testing.T) { Issuer: driver.AuditableIdentity{Identity: issuer2}, Inputs: []*driver.IssueInputMetadata{{}}, Outputs: []*driver.IssueOutputMetadata{{}}, - ExtraSigners: []token.Identity{signer1}, + ExtraSigners: []*driver.AuditableIdentity{{Identity: signer1}}, }, }, action: token.NewIssueAction(action), diff --git a/token/request.go b/token/request.go index cb9628e704..c84ebe5552 100644 --- a/token/request.go +++ b/token/request.go @@ -1191,12 +1191,12 @@ func (r *Request) BindTo(ctx context.Context, binder Binder, identity Identity) // extra signers for _, eid := range r.Metadata.Transfers[i].ExtraSigners { - if w := r.TokenService.WalletManager().Wallet(ctx, eid); w != nil { + if w := r.TokenService.WalletManager().Wallet(ctx, eid.Identity); w != nil { // this is me, skip continue } - r.TokenService.logger.DebugfContext(ctx, "bind extra signer [%s] to [%s]", eid, identity) - if err := binder.Bind(ctx, identity, eid); err != nil { + r.TokenService.logger.DebugfContext(ctx, "bind extra signer [%s] to [%s]", eid.Identity, identity) + if err := binder.Bind(ctx, identity, eid.Identity); err != nil { return errors.Wrap(err, "failed binding sender identities") } } @@ -1225,10 +1225,16 @@ func (r *Request) BindTo(ctx context.Context, binder Binder, identity Identity) func (r *Request) Issues() []*Issue { var issues []*Issue for _, issue := range r.Metadata.Issues { + extraSigners := make([]Identity, 0, len(issue.ExtraSigners)) + for _, es := range issue.ExtraSigners { + if es != nil { + extraSigners = append(extraSigners, es.Identity) + } + } issues = append(issues, &Issue{ Issuer: issue.Issuer.Identity, Receivers: issue.Receivers(), - ExtraSigners: issue.ExtraSigners, + ExtraSigners: extraSigners, }) } @@ -1239,11 +1245,17 @@ func (r *Request) Issues() []*Issue { func (r *Request) Transfers() []*Transfer { var transfers []*Transfer for _, transfer := range r.Metadata.Transfers { + extraSigners := make([]Identity, 0, len(transfer.ExtraSigners)) + for _, es := range transfer.ExtraSigners { + if es != nil { + extraSigners = append(extraSigners, es.Identity) + } + } transfers = append(transfers, &Transfer{ Senders: transfer.Senders(), Receivers: transfer.Receivers(), - ExtraSigners: transfer.ExtraSigners, - Issuer: transfer.Issuer, + ExtraSigners: extraSigners, + Issuer: transfer.Issuer.Identity, }) } diff --git a/token/request_test.go b/token/request_test.go index 85b71e5993..2413424976 100644 --- a/token/request_test.go +++ b/token/request_test.go @@ -695,7 +695,7 @@ func TestRequest_Issues(t *testing.T) { }, }, }, - ExtraSigners: []Identity{Identity("signer1")}, + ExtraSigners: []*driver.AuditableIdentity{{Identity: Identity("signer1")}}, }, { Issuer: driver.AuditableIdentity{ @@ -746,8 +746,8 @@ func TestRequest_Transfers(t *testing.T) { }, }, }, - ExtraSigners: []Identity{Identity("extra1")}, - Issuer: Identity("issuer1"), + ExtraSigners: []*driver.AuditableIdentity{{Identity: Identity("extra1")}}, + Issuer: driver.AuditableIdentity{Identity: Identity("issuer1")}, }, }, }, @@ -785,8 +785,8 @@ func TestRequest_TransferSigners(t *testing.T) { }, }, }, - ExtraSigners: []Identity{Identity("extra1")}, - Issuer: Identity("issuer1"), + ExtraSigners: []*driver.AuditableIdentity{{Identity: Identity("extra1")}}, + Issuer: driver.AuditableIdentity{Identity: Identity("issuer1")}, }, }, }, @@ -809,7 +809,7 @@ func TestRequest_IssueSigners(t *testing.T) { Issuer: driver.AuditableIdentity{ Identity: Identity("issuer1"), }, - ExtraSigners: []Identity{Identity("extra1"), Identity("extra2")}, + ExtraSigners: []*driver.AuditableIdentity{{Identity: Identity("extra1")}, {Identity: Identity("extra2")}}, }, { Issuer: driver.AuditableIdentity{ diff --git a/token/services/auditor/regression_test.go b/token/services/auditor/regression_test.go new file mode 100644 index 0000000000..c2263914de --- /dev/null +++ b/token/services/auditor/regression_test.go @@ -0,0 +1,180 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package auditor_test + +import ( + "os" + "path/filepath" + "testing" + + "github.com/hyperledger-labs/fabric-token-sdk/token" + "github.com/hyperledger-labs/fabric-token-sdk/token/driver" + drivermock "github.com/hyperledger-labs/fabric-token-sdk/token/driver/mock" + tokenmock "github.com/hyperledger-labs/fabric-token-sdk/token/mock" + "github.com/hyperledger-labs/fabric-token-sdk/token/services/logging" + tokenpkg "github.com/hyperledger-labs/fabric-token-sdk/token/token" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// mockOutput is a dummy output for testing +type mockOutput struct{} + +func (m *mockOutput) Serialize() ([]byte, error) { return []byte("output"), nil } +func (m *mockOutput) IsRedeem() bool { return false } +func (m *mockOutput) GetOwner() []byte { return []byte("owner") } + +// TestMetadataRegression verifies that token requests from all supported protocol versions can be correctly unmarshalled and audited. +func TestMetadataRegression(t *testing.T) { + testcases := []struct { + name string + fixture string + version uint32 + }{ + {name: "Protocol V1", fixture: "v1.bin", version: driver.ProtocolV1}, + {name: "Protocol V2", fixture: "v2.bin", version: driver.ProtocolV2}, + {name: "Protocol V3", fixture: "v3.bin", version: driver.ProtocolV3}, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + raw, err := os.ReadFile(filepath.Join("testdata", "regression", tc.fixture)) + require.NoError(t, err) + + // Mock TMS to satisfy NewFullRequestFromBytes and AuditCheck + mockTMS := &drivermock.TokenManagerService{} + mockPPM := &drivermock.PublicParamsManager{} + mockPP := &drivermock.PublicParameters{} + mockPP.PrecisionReturns(64) + mockPP.MaxTokenValueReturns(1000000) + mockPPM.PublicParametersReturns(mockPP) + mockTMS.PublicParamsManagerReturns(mockPPM) + + mockIssueService := &drivermock.IssueService{} + mockTransferService := &drivermock.TransferService{} + mockWalletService := &drivermock.WalletService{} + mockTokensService := &drivermock.TokensService{} + mockTMS.IssueServiceReturns(mockIssueService) + mockTMS.TransferServiceReturns(mockTransferService) + mockTMS.WalletServiceReturns(mockWalletService) + mockTMS.TokensServiceReturns(mockTokensService) + mockTMS.ValidatorReturns(&drivermock.Validator{}, nil) + + // Stub deserialization to avoid nil panics + mockIssueAction := &drivermock.IssueAction{} + mockIssueService.DeserializeIssueActionReturns(mockIssueAction, nil) + mockTransferService.DeserializeTransferActionReturns(&drivermock.TransferAction{}, nil) + + // Stub Deobfuscate to return dummy data (will be updated inside loop) + mockTokensService.DeobfuscateReturns(&tokenpkg.Token{Quantity: "10"}, driver.Identity("issuer"), []driver.Identity{driver.Identity("receiver")}, "format", nil) + + mockVP := &tokenmock.VaultProvider{} + mockV := &drivermock.Vault{} + mockV.QueryEngineReturns(&drivermock.QueryEngine{}) + mockVP.VaultReturns(mockV, nil) + + tms, err := token.NewManagementService(token.TMSID{}, mockTMS, logging.MustGetLogger("test"), mockVP, nil, nil) + require.NoError(t, err) + + // Setup mock actions based on protocol version to pass Match() + if tc.version == driver.ProtocolV3 { + mockIssueAction.NumOutputsReturns(0) + mockIssueAction.ExtraSignersReturns([]driver.Identity{driver.Identity("extra-v3")}) + mockIssueAction.GetIssuerReturns([]byte("issuer-v3")) + } else { + mockIssueAction.NumOutputsReturns(1) + mockIssueAction.GetSerializedOutputsReturns([][]byte{[]byte("serialized-output")}, nil) + mockIssueAction.GetOutputsReturns([]driver.Output{&mockOutput{}}) + if tc.fixture == "v1.bin" { + mockIssueAction.GetIssuerReturns([]byte("issuer-v1")) + mockTokensService.DeobfuscateReturns(&tokenpkg.Token{Quantity: "10"}, driver.Identity("issuer-v1"), []driver.Identity{driver.Identity("receiver-v1")}, "format", nil) + } else { + mockIssueAction.GetIssuerReturns([]byte("issuer-v2")) + mockTokensService.DeobfuscateReturns(&tokenpkg.Token{Quantity: "10"}, driver.Identity("issuer-v2"), []driver.Identity{driver.Identity("receiver-v2")}, "format", nil) + } + } + + req, err := token.NewFullRequestFromBytes(tms, raw) + require.NoError(t, err) + require.NotNil(t, req) + + // Verify metadata was correctly restored + require.NotNil(t, req.Metadata) + + // Verify that the metadata version matches or is at least correctly parsed. + // The current driver.TokenRequestMetadata FromProtos doesn't store the version in the struct, + // but we can check if it loaded the issues correctly. + assert.NotEmpty(t, req.Metadata.Issues) + + // Check V3 specific field if it's V3 + if tc.version == driver.ProtocolV3 { + issue := req.Metadata.Issues[0] + assert.NotEmpty(t, issue.Issuer.AuditInfo, "V3 should have audit info") + assert.NotEmpty(t, issue.ExtraSigners[0].AuditInfo, "V3 should have extra signer audit info") + } else { + issue := req.Metadata.Issues[0] + assert.Empty(t, issue.Issuer.AuditInfo, "V1/V2 should NOT have audit info") + } + + // Verify AuditCheck calls the auditor + mockAuditor := &drivermock.AuditorService{} + mockTMS.AuditorServiceReturns(mockAuditor) + + // IsValid will be called by AuditCheck + // We need to mock Deserializers etc if we want IsValid to pass, + // or we can just mock AuditorCheck and call it. + + // For regression, the most important is that AuditorCheck is reachable and doesn't crash + err = req.AuditCheck(t.Context()) + // It might fail because of missing mocks for IsValid, but we want to see it reaches AuditorCheck + require.NoError(t, err) + + assert.Equal(t, 1, mockAuditor.AuditorCheckCallCount()) + }) + } +} + +func TestMetadataEdgeCases(t *testing.T) { + // 1. Malformed metadata + t.Run("Malformed Metadata", func(t *testing.T) { + mockTMS := &drivermock.TokenManagerService{} + mockPPM := &drivermock.PublicParamsManager{} + mockPP := &drivermock.PublicParameters{} + mockPPM.PublicParametersReturns(mockPP) + mockTMS.PublicParamsManagerReturns(mockPPM) + mockTMS.ValidatorReturns(&drivermock.Validator{}, nil) + + mockVP := &tokenmock.VaultProvider{} + mockV := &drivermock.Vault{} + mockVP.VaultReturns(mockV, nil) + tms, err := token.NewManagementService(token.TMSID{}, mockTMS, logging.MustGetLogger("test"), mockVP, nil, nil) + require.NoError(t, err) + + req := token.NewRequest(tms, "anchor") + err = req.Metadata.FromBytes([]byte("not a proto")) + assert.Error(t, err) + }) + + // 2. Missing audit info in V3 (should still pass if allowed, but here we check if it's handled) + t.Run("V3 Missing Audit Info", func(t *testing.T) { + trm := &driver.TokenRequestMetadata{ + Issues: []*driver.IssueMetadata{ + { + Issuer: driver.AuditableIdentity{Identity: driver.Identity("issuer")}, // AuditInfo is nil + }, + }, + } + raw, err := trm.Bytes() + require.NoError(t, err) + + trm2 := &driver.TokenRequestMetadata{} + err = trm2.FromBytes(raw) + require.NoError(t, err) + assert.Equal(t, driver.Identity("issuer"), trm2.Issues[0].Issuer.Identity) + assert.Empty(t, trm2.Issues[0].Issuer.AuditInfo) + }) +} diff --git a/token/services/auditor/testdata/generate/main.go b/token/services/auditor/testdata/generate/main.go new file mode 100644 index 0000000000..93305735f9 --- /dev/null +++ b/token/services/auditor/testdata/generate/main.go @@ -0,0 +1,188 @@ +/* +Copyright IBM Corp. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package main + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/hyperledger-labs/fabric-smart-client/pkg/utils/proto" + "github.com/hyperledger-labs/fabric-token-sdk/token/driver" + "github.com/hyperledger-labs/fabric-token-sdk/token/driver/protos-go/request" +) + +// main is the entry point for the fixture generator +func main() { + baseDir := "token/services/auditor/testdata/regression" + if err := os.MkdirAll(baseDir, 0755); err != nil { + fmt.Fprintf(os.Stderr, "failed to create base directory: %v\n", err) + os.Exit(1) + } + + generateV1(baseDir) + generateV2(baseDir) + generateV3(baseDir) +} + +// generateV1 produces fixtures for Protocol V1 +func generateV1(baseDir string) { + fmt.Println("Generating Protocol V1 fixtures...") + tr := &driver.TokenRequest{ + Version: driver.ProtocolV1, + Issues: [][]byte{[]byte("issue-v1")}, + } + trm := &driver.TokenRequestMetadata{ + Issues: []*driver.IssueMetadata{ + { + Issuer: driver.AuditableIdentity{Identity: driver.Identity("issuer-v1")}, + Outputs: []*driver.IssueOutputMetadata{ + { + OutputMetadata: []byte("output-v1"), + Receivers: []*driver.AuditableIdentity{ + {Identity: driver.Identity("receiver-v1")}, + }, + }, + }, + }, + }, + } + save(baseDir, "v1", tr, trm) +} + +// generateV2 produces fixtures for Protocol V2 +func generateV2(baseDir string) { + fmt.Println("Generating Protocol V2 fixtures...") + tr := &driver.TokenRequest{ + Version: driver.ProtocolV2, + Issues: [][]byte{[]byte("issue-v2")}, + } + trm := &driver.TokenRequestMetadata{ + Issues: []*driver.IssueMetadata{ + { + Issuer: driver.AuditableIdentity{Identity: driver.Identity("issuer-v2")}, + Outputs: []*driver.IssueOutputMetadata{ + { + OutputMetadata: []byte("output-v2"), + Receivers: []*driver.AuditableIdentity{ + {Identity: driver.Identity("receiver-v2")}, + }, + }, + }, + }, + }, + } + save(baseDir, "v2", tr, trm) +} + +// generateV3 produces fixtures for Protocol V3 +func generateV3(baseDir string) { + fmt.Println("Generating Protocol V3 fixtures...") + tr := &driver.TokenRequest{ + Version: driver.ProtocolV2, // TokenRequest still V2 + Issues: [][]byte{[]byte("issue-v3")}, + } + trm := &driver.TokenRequestMetadata{ + Issues: []*driver.IssueMetadata{ + { + Issuer: driver.AuditableIdentity{Identity: driver.Identity("issuer-v3"), AuditInfo: []byte("issuer-audit-v3")}, + ExtraSigners: []*driver.AuditableIdentity{ + {Identity: driver.Identity("extra-v3"), AuditInfo: []byte("extra-audit-v3")}, + }, + }, + }, + } + save(baseDir, "v3", tr, trm) +} + +// save serializes the token request and metadata to a binary file +func save(baseDir, version string, tr *driver.TokenRequest, trm *driver.TokenRequestMetadata) { + if _, err := tr.Bytes(); err != nil { + fmt.Fprintf(os.Stderr, "failed to serialize token request: %v\n", err) + os.Exit(1) + } + if _, err := trm.Bytes(); err != nil { + fmt.Fprintf(os.Stderr, "failed to serialize token request metadata: %v\n", err) + os.Exit(1) + } + + // We wrap them in TokenRequestWithMetadata for easier loading + trwm := &request.TokenRequestWithMetadata{ + Version: driver.ProtocolV1, // This version is for the wrapper + Anchor: "anchor-" + version, + Request: toProtoRequest(tr), + Metadata: toProtoMetadata(trm, version), + } + raw, err := proto.Marshal(trwm) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to marshal TokenRequestWithMetadata: %v\n", err) + os.Exit(1) + } + + if err := os.WriteFile(filepath.Join(baseDir, version+".bin"), raw, 0644); err != nil { + fmt.Fprintf(os.Stderr, "failed to write fixture file: %v\n", err) + os.Exit(1) + } +} + +// toProtoRequest converts a driver.TokenRequest to its protobuf representation +func toProtoRequest(tr *driver.TokenRequest) *request.TokenRequest { + p, err := tr.ToProtos() + if err != nil { + fmt.Fprintf(os.Stderr, "failed to convert TokenRequest to protos: %v\n", err) + os.Exit(1) + } + return p +} + +// toProtoMetadata converts a driver.TokenRequestMetadata to its protobuf representation +func toProtoMetadata(trm *driver.TokenRequestMetadata, version string) *request.TokenRequestMetadata { + if version == "v3" { + p, err := trm.ToProtos() + if err != nil { + fmt.Fprintf(os.Stderr, "failed to convert TokenRequestMetadata to protos: %v\n", err) + os.Exit(1) + } + p.Version = driver.ProtocolV3 + return p + } + // For V1/V2, we manually create it to ensure it uses old fields if needed, + // but trm.ToProtos() currently always uses V3. + // So for V1/V2 we should use the old marshaling logic. + + p := &request.TokenRequestMetadata{ + Version: driver.ProtocolV2, + } + if version == "v1" { + p.Version = driver.ProtocolV1 + } + + for _, issue := range trm.Issues { + issueProto := &request.IssueMetadata{ + Issuer: &request.AuditableIdentity{ + Identity: &request.Identity{Raw: issue.Issuer.Identity}, + }, + } + for _, out := range issue.Outputs { + outProto := &request.OutputMetadata{ + Metadata: out.OutputMetadata, + } + for _, rec := range out.Receivers { + outProto.Receivers = append(outProto.Receivers, &request.AuditableIdentity{ + Identity: &request.Identity{Raw: rec.Identity}, + }) + } + issueProto.Outputs = append(issueProto.Outputs, outProto) + } + p.Metadata = append(p.Metadata, &request.ActionMetadata{ + Metadata: &request.ActionMetadata_IssueMetadata{ + IssueMetadata: issueProto, + }, + }) + } + return p +} diff --git a/token/services/auditor/testdata/regression/v1.bin b/token/services/auditor/testdata/regression/v1.bin new file mode 100644 index 0000000000..8e349f06f7 Binary files /dev/null and b/token/services/auditor/testdata/regression/v1.bin differ diff --git a/token/services/auditor/testdata/regression/v2.bin b/token/services/auditor/testdata/regression/v2.bin new file mode 100644 index 0000000000..1e978d866f Binary files /dev/null and b/token/services/auditor/testdata/regression/v2.bin differ diff --git a/token/services/auditor/testdata/regression/v3.bin b/token/services/auditor/testdata/regression/v3.bin new file mode 100644 index 0000000000..fcd9628531 Binary files /dev/null and b/token/services/auditor/testdata/regression/v3.bin differ