diff --git a/simplehash/hasher.go b/simplehash/hasher.go index 4cadef0..7a979ed 100644 --- a/simplehash/hasher.go +++ b/simplehash/hasher.go @@ -36,16 +36,16 @@ func NewEventMarshaler() *simpleoneof.Marshaler { return v2assets.NewFlatMarshalerForEvents() } -func (h *Hasher) applyEventOptions(o HashOptions, event *v2assets.EventResponse) { +func (h *Hasher) applyEventOptions(o HashOptions, event EventOptionApplier) { if o.publicFromPermissioned { - PublicFromPermissionedEvent(event) + event.ToPublicIdentity() } // force the commited time in the hash. only useful to the service that is // actually doing the committing. public consumers only ever see confirmed // events with the timestamp already in place. if o.committed != nil { - event.TimestampCommitted = o.committed + event.SetTimestampCommitted(o.committed) } } diff --git a/simplehash/options.go b/simplehash/options.go index 84b93cd..efbfc72 100644 --- a/simplehash/options.go +++ b/simplehash/options.go @@ -10,6 +10,11 @@ import ( // These options are not part of the event schema. The can be used to adjust how // the schema is applied to produce a hash for different purposes. +type EventOptionApplier interface { + ToPublicIdentity() + SetTimestampCommitted(*timestamppb.Timestamp) +} + type HashOptions struct { accumulateHash bool publicFromPermissioned bool diff --git a/simplehash/publicevent.go b/simplehash/publicevent.go deleted file mode 100644 index 0cbfb7e..0000000 --- a/simplehash/publicevent.go +++ /dev/null @@ -1,10 +0,0 @@ -package simplehash - -import v2assets "github.com/datatrails/go-datatrails-common-api-gen/assets/v2/assets" - -// PublicFromPermissionedEvent translates the permissioned event and asset identities to -// their public counter parts. -func PublicFromPermissionedEvent(event *v2assets.EventResponse) { - event.Identity = v2assets.PublicIdentityFromPermissioned(event.Identity) - event.AssetIdentity = v2assets.PublicIdentityFromPermissioned(event.AssetIdentity) -} diff --git a/simplehash/schemav2.go b/simplehash/schemav2.go index c023cb8..b5e630e 100644 --- a/simplehash/schemav2.go +++ b/simplehash/schemav2.go @@ -6,10 +6,12 @@ import ( "encoding/json" "fmt" "hash" + "time" v2assets "github.com/datatrails/go-datatrails-common-api-gen/assets/v2/assets" "github.com/datatrails/go-datatrails-common-api-gen/marshalers/simpleoneof" "github.com/zeebo/bencode" + "google.golang.org/protobuf/types/known/timestamppb" ) // V2Event is a struct that contains ONLY the event fields we want to hash for schema v2 @@ -30,6 +32,17 @@ type V2Event struct { TenantIdentity string `json:"tenant_identity"` } +// ToPublicIdentity converts the identity of the event into a public identity +func (e *V2Event) ToPublicIdentity() { + e.AssetIdentity = v2assets.PublicIdentityFromPermissioned(e.AssetIdentity) + e.Identity = v2assets.PublicIdentityFromPermissioned(e.Identity) +} + +// SetTimestampCommitted sets the timestamp committed to the given timestamp +func (e *V2Event) SetTimestampCommitted(timestamp *timestamppb.Timestamp) { + e.TimestampCommitted = timestamp.AsTime().Format(time.RFC3339Nano) +} + type HasherV2 struct { Hasher } @@ -64,8 +77,6 @@ func (h *HasherV2) HashEvent(event *v2assets.EventResponse, opts ...HashOption) opt(&o) } - h.Hasher.applyEventOptions(o, event) - // Note that we _don't_ take any notice of confirmation status. v2Event, err := V2FromEventResponse(h.marshaler, event) @@ -73,6 +84,8 @@ func (h *HasherV2) HashEvent(event *v2assets.EventResponse, opts ...HashOption) return err } + h.Hasher.applyEventOptions(o, &v2Event) + // Hash data accumulation starts here h.Hasher.applyHashingOptions(o) diff --git a/simplehash/schemav2_test.go b/simplehash/schemav2_test.go index 14af016..e2aa9ec 100644 --- a/simplehash/schemav2_test.go +++ b/simplehash/schemav2_test.go @@ -5,11 +5,13 @@ import ( "encoding/hex" "hash" "testing" + "time" v2assets "github.com/datatrails/go-datatrails-common-api-gen/assets/v2/assets" "github.com/datatrails/go-datatrails-common-api-gen/attribute/v2/attribute" "github.com/datatrails/go-datatrails-common-api-gen/marshalers/simpleoneof" "github.com/golang/protobuf/ptypes/timestamp" + "google.golang.org/protobuf/types/known/timestamppb" "gotest.tools/v3/assert" ) @@ -114,6 +116,41 @@ var ( } ) +// TestV2Event_SetTimestampCommitted tests: +// +// 1. setting the timestamp gives the correctly formatted timestamp in the v2event +func TestV2Event_SetTimestampCommitted(t *testing.T) { + type args struct { + timestamp *timestamppb.Timestamp + } + tests := []struct { + name string + originalTimestamp string + args args + expected string + }{ + { + name: "positive", + originalTimestamp: "2023-02-23T10:11:08.761Z", + args: args{ + timestamp: timestamppb.New(time.Unix(1706700559, 43000000)), + }, + expected: "2024-01-31T11:29:19.043Z", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + e := &V2Event{ + TimestampCommitted: test.originalTimestamp, + } + + e.SetTimestampCommitted(test.args.timestamp) + + assert.Equal(t, test.expected, e.TimestampCommitted) + }) + } +} + func TestEventSimpleHashV2(t *testing.T) { type args struct { hasher hash.Hash @@ -268,3 +305,41 @@ func TestHasherV2_HashEventJSON(t *testing.T) { }) } } + +// TestV2Event_ToPublicIdentity tests: +// +// 1. that both identity and asset identity convert correctly to the public identity. +func TestV2Event_ToPublicIdentity(t *testing.T) { + type fields struct { + Identity string + AssetIdentity string + } + tests := []struct { + name string + fields fields + eIdentity string + eAssetIdentity string + }{ + { + name: "positive", + fields: fields{ + Identity: "assets/9ccdc19b-44a1-434c-afab-14f8eac3405c/events/e76a03d1-19a5-4f11-bcaf-383bb4f1dfd4", + AssetIdentity: "assets/9ccdc19b-44a1-434c-afab-14f8eac3405c", + }, + eIdentity: "publicassets/9ccdc19b-44a1-434c-afab-14f8eac3405c/events/e76a03d1-19a5-4f11-bcaf-383bb4f1dfd4", + eAssetIdentity: "publicassets/9ccdc19b-44a1-434c-afab-14f8eac3405c", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + e := &V2Event{ + Identity: test.fields.Identity, + AssetIdentity: test.fields.AssetIdentity, + } + e.ToPublicIdentity() + + assert.Equal(t, test.eIdentity, e.Identity) + assert.Equal(t, test.eAssetIdentity, e.AssetIdentity) + }) + } +} diff --git a/simplehash/schemav3.go b/simplehash/schemav3.go index 0f71f21..5f568be 100644 --- a/simplehash/schemav3.go +++ b/simplehash/schemav3.go @@ -4,10 +4,12 @@ import ( "encoding/json" "fmt" "hash" + "time" v2assets "github.com/datatrails/go-datatrails-common-api-gen/assets/v2/assets" "github.com/datatrails/go-datatrails-common-api-gen/marshalers/simpleoneof" "github.com/zeebo/bencode" + "google.golang.org/protobuf/types/known/timestamppb" ) // V3Event is a struct that contains ONLY the event fields we want to hash for schema v3 @@ -25,6 +27,16 @@ type V3Event struct { TenantIdentity string `json:"tenant_identity"` } +// ToPublicIdentity converts the identity of the event into a public identity +func (e *V3Event) ToPublicIdentity() { + e.Identity = v2assets.PublicIdentityFromPermissioned(e.Identity) +} + +// SetTimestampCommitted sets the timestamp committed to the given timestamp +func (e *V3Event) SetTimestampCommitted(timestamp *timestamppb.Timestamp) { + e.TimestampCommitted = timestamp.AsTime().Format(time.RFC3339Nano) +} + func V3HashEvent(hasher hash.Hash, v3Event V3Event) error { var err error @@ -110,13 +122,47 @@ func (h *HasherV3) HashEvent(event *v2assets.EventResponse, opts ...HashOption) opt(&o) } - h.applyEventOptions(o, event) - v3Event, err := V3FromEventResponse(h.marshaler, event) if err != nil { return err } + h.applyEventOptions(o, &v3Event) + + h.applyHashingOptions(o) + + return V3HashEvent(h.hasher, v3Event) +} + +// HashEventFromJson hashes a single event according to the canonical simple hash event +// format available to api consumers. The source event is in json format. +// +// Options: +// - WithIDCommitted prefix the data to hash with the bigendian encoding of +// idtimestamp before hashing. +// - WithPrefix is used to provide domain separation, the provided bytes are +// pre-pended to the data to be hashed. Eg H(prefix || data) +// This option can be used multiple times, the prefix bytes are appended to +// any previously supplied. +// - WithAccumulate callers wishing to implement batched hashing of multiple +// events in series should set this. They should call Reset() at their batch +// boundaries. +// - WithPublicFromPermissioned should be set if the event is the +// permissioned (owner) counter part of a public attestation. +func (h *HasherV3) HashEventFromJSON(eventJson []byte, opts ...HashOption) error { + + o := HashOptions{} + for _, opt := range opts { + opt(&o) + } + + v3Event, err := V3FromEventJSON(eventJson) + if err != nil { + return err + } + + h.applyEventOptions(o, &v3Event) + h.applyHashingOptions(o) return V3HashEvent(h.hasher, v3Event) diff --git a/simplehash/schemav3_test.go b/simplehash/schemav3_test.go index 387573e..967bcc8 100644 --- a/simplehash/schemav3_test.go +++ b/simplehash/schemav3_test.go @@ -4,8 +4,10 @@ import ( "crypto/sha256" "encoding/hex" "testing" + "time" v2assets "github.com/datatrails/go-datatrails-common-api-gen/assets/v2/assets" + "google.golang.org/protobuf/types/known/timestamppb" "gotest.tools/v3/assert" ) @@ -62,3 +64,70 @@ func TestHasherV3_HashEvent(t *testing.T) { }) } } + +// TestV3Event_SetTimestampCommitted tests: +// +// 1. setting the timestamp gives the correctly formatted timestamp in the v3event +func TestV3Event_SetTimestampCommitted(t *testing.T) { + type args struct { + timestamp *timestamppb.Timestamp + } + tests := []struct { + name string + originalTimestamp string + args args + expected string + }{ + { + name: "positive", + originalTimestamp: "2023-02-23T10:11:08.761Z", + args: args{ + timestamp: timestamppb.New(time.Unix(1706700559, 43000000)), + }, + expected: "2024-01-31T11:29:19.043Z", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + e := &V3Event{ + TimestampCommitted: test.originalTimestamp, + } + + e.SetTimestampCommitted(test.args.timestamp) + + assert.Equal(t, test.expected, e.TimestampCommitted) + }) + } +} + +// TestV3Event_ToPublicIdentity tests: +// +// 1. that identity convert correctly to the public identity. +func TestV3Event_ToPublicIdentity(t *testing.T) { + type fields struct { + Identity string + } + tests := []struct { + name string + fields fields + eIdentity string + }{ + { + name: "positive", + fields: fields{ + Identity: "assets/9ccdc19b-44a1-434c-afab-14f8eac3405c/events/e76a03d1-19a5-4f11-bcaf-383bb4f1dfd4", + }, + eIdentity: "publicassets/9ccdc19b-44a1-434c-afab-14f8eac3405c/events/e76a03d1-19a5-4f11-bcaf-383bb4f1dfd4", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + e := &V2Event{ + Identity: test.fields.Identity, + } + e.ToPublicIdentity() + + assert.Equal(t, test.eIdentity, e.Identity) + }) + } +}