diff --git a/directives.go b/directives.go new file mode 100644 index 0000000..cf23246 --- /dev/null +++ b/directives.go @@ -0,0 +1,45 @@ +package iden3comm + +// Iden3DirectiveType represents the type of directive +type Iden3DirectiveType string + +// Constants for Iden3DirectiveType +const ( + TransparentPaymentDirectiveType Iden3DirectiveType = "TransparentPaymentDirective" +) + +// TransparentPaymentCredential represents credential information +type TransparentPaymentCredential struct { + Type string `json:"type"` + Context string `json:"context"` +} + +// TransparentPaymentRequestData represents payment request information +type TransparentPaymentRequestData struct { + Recipient string `json:"recipient"` + Amount string `json:"amount"` + Token string `json:"token,omitempty"` + Expiration string `json:"expiration"` + Nonce string `json:"nonce"` + Metadata string `json:"metadata"` +} + +// TransparentPaymentDirectivePayload represents the payload for a transparent payment directive +type TransparentPaymentDirectivePayload struct { + Credential TransparentPaymentCredential `json:"credential"` + PaymentData TransparentPaymentRequestData `json:"paymentData"` + PermitSignature string `json:"permitSignature"` + Description string `json:"description,omitempty"` +} + +// TransparentPaymentDirective represents a complete transparent payment directive +type TransparentPaymentDirective struct { + Type Iden3DirectiveType `json:"type"` + Purpose ProtocolMessage `json:"purpose,omitempty"` + Context string `json:"context,omitempty"` + Data []TransparentPaymentDirectivePayload `json:"data"` +} + +// Iden3Directive is currently an alias for TransparentPaymentDirective +// Can be expanded to a union type using interfaces if more directive types are added +type Iden3Directive = TransparentPaymentDirective diff --git a/directives_test.go b/directives_test.go new file mode 100644 index 0000000..6ec84e3 --- /dev/null +++ b/directives_test.go @@ -0,0 +1,177 @@ +package iden3comm_test + +import ( + "reflect" + "testing" + + "github.com/iden3/iden3comm/v2" +) + +func TestExtractDirectiveFromMessage(t *testing.T) { + tests := []struct { + name string + message iden3comm.BasicMessage + expected []iden3comm.Iden3Directive + }{ + { + name: "No directive attachments", + message: iden3comm.BasicMessage{ + Attachments: iden3comm.Attachments{ + {Type: "otherType"}, + }, + }, + expected: nil, + }, + { + name: "With directive attachments", + message: iden3comm.BasicMessage{ + Attachments: iden3comm.Attachments{ + { + Type: iden3comm.Iden3DirectiveAttachmentType, + Data: []iden3comm.Iden3Directive{ + { + Type: iden3comm.TransparentPaymentDirectiveType, + }, + }, + }, + }, + }, + expected: []iden3comm.Iden3Directive{ + { + Type: iden3comm.TransparentPaymentDirectiveType, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.message.Attachments.ExtractDirectives() + if !reflect.DeepEqual(result, tt.expected) { + t.Errorf("expected %v, got %v", tt.expected, result) + } + }) + } +} + +func TestPropagateDirectiveIntoMessage(t *testing.T) { + tests := []struct { + name string + message iden3comm.BasicMessage + incomingDirective []iden3comm.Iden3Directive + expected iden3comm.BasicMessage + }{ + { + name: "No incoming directives", + message: iden3comm.BasicMessage{ + ID: "test", + }, + incomingDirective: []iden3comm.Iden3Directive{}, + expected: iden3comm.BasicMessage{ + ID: "test", + }, + }, + { + name: "No existing attachments", + message: iden3comm.BasicMessage{ + ID: "test", + }, + incomingDirective: []iden3comm.Iden3Directive{ + { + Type: iden3comm.TransparentPaymentDirectiveType, + }, + }, + expected: iden3comm.BasicMessage{ + ID: "test", + Attachments: iden3comm.Attachments{ + { + Type: iden3comm.Iden3DirectiveAttachmentType, + Data: []iden3comm.Iden3Directive{ + { + Type: iden3comm.TransparentPaymentDirectiveType, + }, + }, + }, + }, + }, + }, + { + name: "With existing directive attachments", + message: iden3comm.BasicMessage{ + ID: "test", + Attachments: iden3comm.Attachments{ + { + Type: iden3comm.Iden3DirectiveAttachmentType, + Data: []iden3comm.Iden3Directive{ + { + Type: iden3comm.TransparentPaymentDirectiveType, + }, + }, + }, + }, + }, + incomingDirective: []iden3comm.Iden3Directive{ + { + Type: iden3comm.TransparentPaymentDirectiveType, + }, + }, + expected: iden3comm.BasicMessage{ + ID: "test", + Attachments: iden3comm.Attachments{ + { + Type: iden3comm.Iden3DirectiveAttachmentType, + Data: []iden3comm.Iden3Directive{ + { + Type: iden3comm.TransparentPaymentDirectiveType, + }, + { + Type: iden3comm.TransparentPaymentDirectiveType, + }, + }, + }, + }, + }, + }, + { + name: "With existing directive attachments and other attachments", + message: iden3comm.BasicMessage{ + ID: "test", + Attachments: iden3comm.Attachments{ + { + Type: "otherType", + }, + }, + }, + incomingDirective: []iden3comm.Iden3Directive{ + { + Type: iden3comm.TransparentPaymentDirectiveType, + }, + }, + expected: iden3comm.BasicMessage{ + ID: "test", + Attachments: iden3comm.Attachments{ + { + Type: "otherType", + }, + { + Type: iden3comm.Iden3DirectiveAttachmentType, + Data: []iden3comm.Iden3Directive{ + { + Type: iden3comm.TransparentPaymentDirectiveType, + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.message.Attachments.AddDirectives(tt.incomingDirective) + if !reflect.DeepEqual(tt.message, tt.expected) { + t.Errorf("expected %v, got %v", tt.expected, tt.message) + } + }) + } +} diff --git a/message.go b/message.go index 0f43abd..96902bc 100644 --- a/message.go +++ b/message.go @@ -1,6 +1,67 @@ package iden3comm -import "encoding/json" +import ( + "encoding/json" +) + +// Iden3AttachmentType represents the type of attachment +type Iden3AttachmentType string + +// Constants for Iden3AttachmentType +const ( + Iden3DirectiveAttachmentType Iden3AttachmentType = "Iden3Directive" +) + +// Attachment is structure for message attachment +type Attachment struct { + Type Iden3AttachmentType `json:"type"` + Data any `json:"data"` +} + +// Attachments is a slice of Attachment +type Attachments []*Attachment + +// ExtractDirectives extracts directives from a given iden3comm.BasicMessage. +func (a *Attachments) ExtractDirectives() []Iden3Directive { + + if a == nil { + return nil + } + + var directives []Iden3Directive + for _, attachment := range *a { + if attachment.Type != Iden3DirectiveAttachmentType { + continue + } + + d := attachment.Data.([]Iden3Directive) + + directives = append(directives, d...) + } + + return directives +} + +// AddDirectives adds directive to attachments +func (a *Attachments) AddDirectives(d []Iden3Directive) { + + if len(d) == 0 { + return + } + + // find directive attachment + for _, attachment := range *a { + if attachment.Type == Iden3DirectiveAttachmentType { + attachment.Data = append(attachment.Data.([]Iden3Directive), d...) + return + } + } + + *a = append(*a, &Attachment{ + Type: Iden3DirectiveAttachmentType, + Data: d, + }) +} // MediaType is media type for iden3comm messages type MediaType string @@ -16,8 +77,9 @@ type BasicMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments Attachments `json:"attachments,omitempty"` } // ProtocolMessage is IDEN3Comm message diff --git a/protocol/auth.go b/protocol/auth.go index 98c6704..93d5e46 100644 --- a/protocol/auth.go +++ b/protocol/auth.go @@ -27,8 +27,9 @@ type AuthorizationResponseMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // AuthorizationMessageResponseBody is struct the represents authorization response data @@ -49,8 +50,9 @@ type AuthorizationRequestMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // AuthorizationRequestMessageBody is body for authorization request diff --git a/protocol/contract_invoke.go b/protocol/contract_invoke.go index 524aec4..bc62e7a 100644 --- a/protocol/contract_invoke.go +++ b/protocol/contract_invoke.go @@ -22,8 +22,9 @@ type ContractInvokeRequestMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // ContractInvokeRequestMessageBody is body for contract invoke request diff --git a/protocol/credentials.go b/protocol/credentials.go index 5643981..e34705a 100644 --- a/protocol/credentials.go +++ b/protocol/credentials.go @@ -99,8 +99,9 @@ type CredentialIssuanceRequestMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // CredentialIssuanceRequestMessageBody represents data for credential issuance request @@ -122,8 +123,9 @@ type CredentialsOfferMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // CredentialsOfferMessageBody is struct the represents offer message @@ -151,8 +153,9 @@ type CredentialIssuanceMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // IssuanceMessageBody is struct the represents message when credential is issued @@ -171,8 +174,9 @@ type CredentialFetchRequestMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // CredentialFetchRequestMessageBody is msg body for fetch request @@ -199,8 +203,9 @@ type CredentialStatusUpdateMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // CredentialStatusUpdateMessageBody the structure that represents the body of credential status update message @@ -221,8 +226,9 @@ type CredentialRefreshMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // CredentialRefreshMessageBody is msg body for refresh message @@ -243,8 +249,9 @@ type CredentialsOnchainOfferMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // CredentialsOnchainOfferMessageBody is struct the represents onchain offer message @@ -269,8 +276,9 @@ type CredentialsProposalRequestMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // CredentialsProposalMessage represents Iden3message for credential proposal @@ -289,8 +297,9 @@ type CredentialsProposalMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // CredentialsProposalRequestBody is msg body for proposal requests @@ -362,8 +371,9 @@ type CredentialPaymentRequestMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // CredentialPaymentRequestBody is msg body for payment requests @@ -420,8 +430,9 @@ type CredentialPaymentMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // CredentialPaymentBody is msg body for payment diff --git a/protocol/devices.go b/protocol/devices.go index 00be20a..c456a60 100644 --- a/protocol/devices.go +++ b/protocol/devices.go @@ -19,8 +19,9 @@ type DeviceRegistrationRequestMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // DeviceRegistrationRequestMessageBody is struct the represents body for register device request request diff --git a/protocol/messages.go b/protocol/messages.go index 3348c58..3a2c947 100644 --- a/protocol/messages.go +++ b/protocol/messages.go @@ -19,8 +19,9 @@ type MessageFetchRequestMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // MessageFetchRequestMessageBody is struct the represents body for message fetch request. diff --git a/protocol/payment.go b/protocol/payment.go index 22a4f92..4526d6c 100644 --- a/protocol/payment.go +++ b/protocol/payment.go @@ -55,8 +55,9 @@ type PaymentRequestMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // PaymentRequestMessageBody represents the body of the PaymentRequestMessage. @@ -282,8 +283,9 @@ type PaymentMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // PaymentMessageBody represents the body of the PaymentMessage. diff --git a/protocol/problem_report.go b/protocol/problem_report.go index 34980b1..96ac82a 100644 --- a/protocol/problem_report.go +++ b/protocol/problem_report.go @@ -59,8 +59,9 @@ type ProblemReportMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // ProblemReportMessageBody is struct the represents body for problem report diff --git a/protocol/proof.go b/protocol/proof.go index a67315a..cb0e8c3 100644 --- a/protocol/proof.go +++ b/protocol/proof.go @@ -23,8 +23,9 @@ type ProofGenerationRequestMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // ProofGenerationRequestMessageBody is struct the represents body for proof generation request @@ -44,8 +45,9 @@ type ProofGenerationResponseMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // ResponseMessageBody is struct the represents request for revocation status diff --git a/protocol/revocation.go b/protocol/revocation.go index 2f4a75a..46cf1e6 100644 --- a/protocol/revocation.go +++ b/protocol/revocation.go @@ -23,8 +23,9 @@ type RevocationStatusRequestMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // RevocationStatusRequestMessageBody is struct the represents request for revocation status @@ -44,8 +45,9 @@ type RevocationStatusResponseMessage struct { From string `json:"from,omitempty"` To string `json:"to,omitempty"` - CreatedTime *int64 `json:"created_time,omitempty"` - ExpiresTime *int64 `json:"expires_time,omitempty"` + CreatedTime *int64 `json:"created_time,omitempty"` + ExpiresTime *int64 `json:"expires_time,omitempty"` + Attachments iden3comm.Attachments `json:"attachments,omitempty"` } // RevocationStatusResponseMessageBody is struct the represents request for revocation status