From aefb1dfb55b1de03d260a780497235760eae7a35 Mon Sep 17 00:00:00 2001 From: Matee Ullah Malik Date: Wed, 16 Jul 2025 00:19:45 +0500 Subject: [PATCH 1/2] feat: add signature verification for cascade downloads --- gen/supernode/action/cascade/service.pb.go | 112 ++++++++++-------- proto/supernode/action/cascade/service.proto | 1 + sdk/action/client.go | 6 +- sdk/adapters/supernodeservice/adapter.go | 3 +- sdk/adapters/supernodeservice/types.go | 1 + sdk/task/download.go | 6 +- sdk/task/manager.go | 7 +- .../server/cascade/cascade_action_server.go | 34 ++++-- supernode/services/cascade/helper.go | 35 ++++++ tests/system/e2e_cascade_test.go | 11 +- 10 files changed, 145 insertions(+), 71 deletions(-) diff --git a/gen/supernode/action/cascade/service.pb.go b/gen/supernode/action/cascade/service.pb.go index ded0dd47..4eca2b5b 100644 --- a/gen/supernode/action/cascade/service.pb.go +++ b/gen/supernode/action/cascade/service.pb.go @@ -342,7 +342,8 @@ type DownloadRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - ActionId string `protobuf:"bytes,1,opt,name=action_id,json=actionId,proto3" json:"action_id,omitempty"` + ActionId string `protobuf:"bytes,1,opt,name=action_id,json=actionId,proto3" json:"action_id,omitempty"` + Signature string `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` } func (x *DownloadRequest) Reset() { @@ -382,6 +383,13 @@ func (x *DownloadRequest) GetActionId() string { return "" } +func (x *DownloadRequest) GetSignature() string { + if x != nil { + return x.Signature + } + return "" +} + type DownloadResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -542,58 +550,60 @@ var file_supernode_action_cascade_service_proto_rawDesc = []byte{ 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x22, 0x2e, 0x0a, 0x0f, 0x44, 0x6f, 0x77, 0x6e, + 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x22, 0x4c, 0x0a, 0x0f, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x7f, 0x0a, 0x10, 0x44, 0x6f, 0x77, 0x6e, - 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, - 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x61, - 0x73, 0x63, 0x61, 0x64, 0x65, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x05, - 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x61, - 0x73, 0x63, 0x61, 0x64, 0x65, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x48, - 0x00, 0x52, 0x05, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x42, 0x0f, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0x65, 0x0a, 0x0d, 0x44, 0x6f, 0x77, - 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x65, 0x76, - 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, - 0x2e, 0x63, 0x61, 0x73, 0x63, 0x61, 0x64, 0x65, 0x2e, 0x53, 0x75, 0x70, 0x65, 0x72, 0x6e, 0x6f, - 0x64, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x65, 0x76, 0x65, - 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x2a, 0xb6, 0x02, 0x0a, 0x12, 0x53, 0x75, 0x70, 0x65, 0x72, 0x6e, 0x6f, 0x64, 0x65, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, - 0x57, 0x4e, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x52, - 0x45, 0x54, 0x52, 0x49, 0x45, 0x56, 0x45, 0x44, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x41, 0x43, - 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x46, 0x45, 0x45, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46, 0x49, 0x45, - 0x44, 0x10, 0x02, 0x12, 0x1e, 0x0a, 0x1a, 0x54, 0x4f, 0x50, 0x5f, 0x53, 0x55, 0x50, 0x45, 0x52, - 0x4e, 0x4f, 0x44, 0x45, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x45, - 0x44, 0x10, 0x03, 0x12, 0x14, 0x0a, 0x10, 0x4d, 0x45, 0x54, 0x41, 0x44, 0x41, 0x54, 0x41, 0x5f, - 0x44, 0x45, 0x43, 0x4f, 0x44, 0x45, 0x44, 0x10, 0x04, 0x12, 0x16, 0x0a, 0x12, 0x44, 0x41, 0x54, - 0x41, 0x5f, 0x48, 0x41, 0x53, 0x48, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, - 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x45, 0x4e, 0x43, 0x4f, 0x44, - 0x45, 0x44, 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x49, 0x47, 0x4e, 0x41, 0x54, 0x55, 0x52, - 0x45, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x07, 0x12, 0x12, 0x0a, 0x0e, - 0x52, 0x51, 0x49, 0x44, 0x5f, 0x47, 0x45, 0x4e, 0x45, 0x52, 0x41, 0x54, 0x45, 0x44, 0x10, 0x08, - 0x12, 0x11, 0x0a, 0x0d, 0x52, 0x51, 0x49, 0x44, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46, 0x49, 0x45, - 0x44, 0x10, 0x09, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x52, 0x54, 0x45, 0x46, 0x41, 0x43, 0x54, 0x53, - 0x5f, 0x53, 0x54, 0x4f, 0x52, 0x45, 0x44, 0x10, 0x0a, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x43, 0x54, - 0x49, 0x4f, 0x4e, 0x5f, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x49, 0x5a, 0x45, 0x44, 0x10, 0x0b, 0x12, - 0x18, 0x0a, 0x14, 0x41, 0x52, 0x54, 0x45, 0x46, 0x41, 0x43, 0x54, 0x53, 0x5f, 0x44, 0x4f, 0x57, - 0x4e, 0x4c, 0x4f, 0x41, 0x44, 0x45, 0x44, 0x10, 0x0c, 0x32, 0x98, 0x01, 0x0a, 0x0e, 0x43, 0x61, - 0x73, 0x63, 0x61, 0x64, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x43, 0x0a, 0x08, - 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x18, 0x2e, 0x63, 0x61, 0x73, 0x63, 0x61, - 0x64, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x61, 0x73, 0x63, 0x61, 0x64, 0x65, 0x2e, 0x52, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, - 0x01, 0x12, 0x41, 0x0a, 0x08, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x18, 0x2e, - 0x63, 0x61, 0x73, 0x63, 0x61, 0x64, 0x65, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x61, 0x73, 0x63, 0x61, 0x64, - 0x65, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x30, 0x01, 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x4c, 0x75, 0x6d, 0x65, 0x72, 0x61, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, - 0x6c, 0x2f, 0x73, 0x75, 0x70, 0x65, 0x72, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f, - 0x73, 0x75, 0x70, 0x65, 0x72, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x2f, 0x63, 0x61, 0x73, 0x63, 0x61, 0x64, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x69, 0x67, + 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x7f, 0x0a, 0x10, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, + 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x65, 0x76, + 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x61, 0x73, 0x63, + 0x61, 0x64, 0x65, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x48, 0x00, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x05, 0x63, 0x68, + 0x75, 0x6e, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x61, 0x73, 0x63, + 0x61, 0x64, 0x65, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x48, 0x00, 0x52, + 0x05, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x42, 0x0f, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0x65, 0x0a, 0x0d, 0x44, 0x6f, 0x77, 0x6e, 0x6c, + 0x6f, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x63, + 0x61, 0x73, 0x63, 0x61, 0x64, 0x65, 0x2e, 0x53, 0x75, 0x70, 0x65, 0x72, 0x6e, 0x6f, 0x64, 0x65, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x09, 0x65, 0x76, 0x65, 0x6e, 0x74, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2a, 0xb6, + 0x02, 0x0a, 0x12, 0x53, 0x75, 0x70, 0x65, 0x72, 0x6e, 0x6f, 0x64, 0x65, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, + 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x54, + 0x52, 0x49, 0x45, 0x56, 0x45, 0x44, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x41, 0x43, 0x54, 0x49, + 0x4f, 0x4e, 0x5f, 0x46, 0x45, 0x45, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, + 0x02, 0x12, 0x1e, 0x0a, 0x1a, 0x54, 0x4f, 0x50, 0x5f, 0x53, 0x55, 0x50, 0x45, 0x52, 0x4e, 0x4f, + 0x44, 0x45, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x45, 0x44, 0x10, + 0x03, 0x12, 0x14, 0x0a, 0x10, 0x4d, 0x45, 0x54, 0x41, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x44, 0x45, + 0x43, 0x4f, 0x44, 0x45, 0x44, 0x10, 0x04, 0x12, 0x16, 0x0a, 0x12, 0x44, 0x41, 0x54, 0x41, 0x5f, + 0x48, 0x41, 0x53, 0x48, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x05, 0x12, + 0x11, 0x0a, 0x0d, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x5f, 0x45, 0x4e, 0x43, 0x4f, 0x44, 0x45, 0x44, + 0x10, 0x06, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x49, 0x47, 0x4e, 0x41, 0x54, 0x55, 0x52, 0x45, 0x5f, + 0x56, 0x45, 0x52, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x07, 0x12, 0x12, 0x0a, 0x0e, 0x52, 0x51, + 0x49, 0x44, 0x5f, 0x47, 0x45, 0x4e, 0x45, 0x52, 0x41, 0x54, 0x45, 0x44, 0x10, 0x08, 0x12, 0x11, + 0x0a, 0x0d, 0x52, 0x51, 0x49, 0x44, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, + 0x09, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x52, 0x54, 0x45, 0x46, 0x41, 0x43, 0x54, 0x53, 0x5f, 0x53, + 0x54, 0x4f, 0x52, 0x45, 0x44, 0x10, 0x0a, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x43, 0x54, 0x49, 0x4f, + 0x4e, 0x5f, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x49, 0x5a, 0x45, 0x44, 0x10, 0x0b, 0x12, 0x18, 0x0a, + 0x14, 0x41, 0x52, 0x54, 0x45, 0x46, 0x41, 0x43, 0x54, 0x53, 0x5f, 0x44, 0x4f, 0x57, 0x4e, 0x4c, + 0x4f, 0x41, 0x44, 0x45, 0x44, 0x10, 0x0c, 0x32, 0x98, 0x01, 0x0a, 0x0e, 0x43, 0x61, 0x73, 0x63, + 0x61, 0x64, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x43, 0x0a, 0x08, 0x52, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x18, 0x2e, 0x63, 0x61, 0x73, 0x63, 0x61, 0x64, 0x65, + 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x19, 0x2e, 0x63, 0x61, 0x73, 0x63, 0x61, 0x64, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x12, + 0x41, 0x0a, 0x08, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x18, 0x2e, 0x63, 0x61, + 0x73, 0x63, 0x61, 0x64, 0x65, 0x2e, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x61, 0x73, 0x63, 0x61, 0x64, 0x65, 0x2e, + 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x30, 0x01, 0x42, 0x42, 0x5a, 0x40, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x4c, 0x75, 0x6d, 0x65, 0x72, 0x61, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, + 0x73, 0x75, 0x70, 0x65, 0x72, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x73, 0x75, + 0x70, 0x65, 0x72, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x63, + 0x61, 0x73, 0x63, 0x61, 0x64, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proto/supernode/action/cascade/service.proto b/proto/supernode/action/cascade/service.proto index e8cb9a97..ad0bd47e 100644 --- a/proto/supernode/action/cascade/service.proto +++ b/proto/supernode/action/cascade/service.proto @@ -31,6 +31,7 @@ message RegisterResponse { message DownloadRequest { string action_id = 1; + string signature = 2; } message DownloadResponse { diff --git a/sdk/action/client.go b/sdk/action/client.go index 6bb3ed0a..43756305 100644 --- a/sdk/action/client.go +++ b/sdk/action/client.go @@ -28,7 +28,7 @@ type Client interface { SubscribeToAllEvents(ctx context.Context, handler event.Handler) error GetSupernodeStatus(ctx context.Context, supernodeAddress string) (*supernodeservice.SupernodeStatusresponse, error) // DownloadCascade downloads cascade to outputDir, filename determined by action ID - DownloadCascade(ctx context.Context, actionID, outputDir string) (string, error) + DownloadCascade(ctx context.Context, actionID, outputDir, signature string) (string, error) } // ClientImpl implements the Client interface @@ -205,13 +205,13 @@ func (c *ClientImpl) GetSupernodeStatus(ctx context.Context, supernodeAddress st return status, nil } -func (c *ClientImpl) DownloadCascade(ctx context.Context, actionID, outputDir string) (string, error) { +func (c *ClientImpl) DownloadCascade(ctx context.Context, actionID, outputDir, signature string) (string, error) { if actionID == "" { return "", fmt.Errorf("actionID is empty") } - taskID, err := c.taskManager.CreateDownloadTask(ctx, actionID, outputDir) + taskID, err := c.taskManager.CreateDownloadTask(ctx, actionID, outputDir, signature) if err != nil { return "", fmt.Errorf("create download task: %w", err) } diff --git a/sdk/adapters/supernodeservice/adapter.go b/sdk/adapters/supernodeservice/adapter.go index 9b400d71..8d730467 100644 --- a/sdk/adapters/supernodeservice/adapter.go +++ b/sdk/adapters/supernodeservice/adapter.go @@ -232,7 +232,8 @@ func (a *cascadeAdapter) CascadeSupernodeDownload( // 1. Open gRPC stream (server-stream) stream, err := a.client.Download(ctx, &cascade.DownloadRequest{ - ActionId: in.ActionID, + ActionId: in.ActionID, + Signature: in.Signature, }, opts...) if err != nil { a.logger.Error(ctx, "failed to create download stream", "action_id", in.ActionID, "error", err) diff --git a/sdk/adapters/supernodeservice/types.go b/sdk/adapters/supernodeservice/types.go index 79a67c6b..c6b88131 100644 --- a/sdk/adapters/supernodeservice/types.go +++ b/sdk/adapters/supernodeservice/types.go @@ -53,6 +53,7 @@ type CascadeSupernodeDownloadRequest struct { ActionID string TaskID string OutputPath string + Signature string EventLogger LoggerFunc } diff --git a/sdk/task/download.go b/sdk/task/download.go index 62caa145..10d01ee9 100644 --- a/sdk/task/download.go +++ b/sdk/task/download.go @@ -21,13 +21,15 @@ type CascadeDownloadTask struct { BaseTask actionId string outputPath string + signature string } -func NewCascadeDownloadTask(base BaseTask, actionId string, outputPath string) *CascadeDownloadTask { +func NewCascadeDownloadTask(base BaseTask, actionId string, outputPath string, signature string) *CascadeDownloadTask { return &CascadeDownloadTask{ BaseTask: base, actionId: actionId, outputPath: outputPath, + signature: signature, } } @@ -64,6 +66,7 @@ func (t *CascadeDownloadTask) downloadFromSupernodes(ctx context.Context, supern ActionID: t.actionId, TaskID: t.TaskID, OutputPath: t.outputPath, + Signature: t.signature, } // Process supernodes in pairs @@ -192,6 +195,7 @@ func (t *CascadeDownloadTask) attemptConcurrentDownload( ActionID: req.ActionID, TaskID: req.TaskID, OutputPath: req.OutputPath, + Signature: req.Signature, } err := t.attemptDownload(batchCtx, sn, factory, reqCopy) diff --git a/sdk/task/manager.go b/sdk/task/manager.go index fc25cf30..10170b30 100644 --- a/sdk/task/manager.go +++ b/sdk/task/manager.go @@ -26,7 +26,7 @@ type Manager interface { SubscribeToEvents(ctx context.Context, eventType event.EventType, handler event.Handler) SubscribeToAllEvents(ctx context.Context, handler event.Handler) - CreateDownloadTask(ctx context.Context, actionID, outputPath string) (string, error) + CreateDownloadTask(ctx context.Context, actionID, outputPath, signature string) (string, error) } type ManagerImpl struct { @@ -241,13 +241,12 @@ func (m *ManagerImpl) Close(ctx context.Context) { } } -func (m *ManagerImpl) CreateDownloadTask(ctx context.Context, actionID string, outputDir string) (string, error) { +func (m *ManagerImpl) CreateDownloadTask(ctx context.Context, actionID string, outputDir string, signature string) (string, error) { // First validate the action before creating the task action, err := m.validateDownloadAction(ctx, actionID) if err != nil { return "", err } - // Decode metadata to get the filename metadata, err := m.lumeraClient.DecodeCascadeMetadata(ctx, action) if err != nil { @@ -279,7 +278,7 @@ func (m *ManagerImpl) CreateDownloadTask(ctx context.Context, actionID string, o } // Use the final output path with the correct filename - task := NewCascadeDownloadTask(baseTask, actionID, finalOutputPath) + task := NewCascadeDownloadTask(baseTask, actionID, finalOutputPath, signature) // Store task in cache m.taskCache.Set(ctx, taskID, task, TaskTypeCascade, actionID) diff --git a/supernode/node/action/server/cascade/cascade_action_server.go b/supernode/node/action/server/cascade/cascade_action_server.go index 1f896fb1..fb3cc6f6 100644 --- a/supernode/node/action/server/cascade/cascade_action_server.go +++ b/supernode/node/action/server/cascade/cascade_action_server.go @@ -28,15 +28,15 @@ func NewCascadeActionServer(factory cascadeService.CascadeServiceFactory) *Actio // to balance throughput and memory usage func calculateOptimalChunkSize(fileSize int64) int { const ( - minChunkSize = 64 * 1024 // 64 KB minimum - maxChunkSize = 4 * 1024 * 1024 // 4 MB maximum for 1GB+ files - smallFileThreshold = 1024 * 1024 // 1 MB + minChunkSize = 64 * 1024 // 64 KB minimum + maxChunkSize = 4 * 1024 * 1024 // 4 MB maximum for 1GB+ files + smallFileThreshold = 1024 * 1024 // 1 MB mediumFileThreshold = 50 * 1024 * 1024 // 50 MB - largeFileThreshold = 500 * 1024 * 1024 // 500 MB + largeFileThreshold = 500 * 1024 * 1024 // 500 MB ) var chunkSize int - + switch { case fileSize <= smallFileThreshold: // For small files (up to 1MB), use 64KB chunks @@ -51,7 +51,7 @@ func calculateOptimalChunkSize(fileSize int64) int { // For very large files (500MB+), use 4MB chunks for optimal throughput chunkSize = maxChunkSize } - + // Ensure chunk size is within bounds if chunkSize < minChunkSize { chunkSize = minChunkSize @@ -59,7 +59,7 @@ func calculateOptimalChunkSize(fileSize int64) int { if chunkSize > maxChunkSize { chunkSize = maxChunkSize } - + return chunkSize } @@ -219,6 +219,22 @@ func (server *ActionServer) Download(req *pb.DownloadRequest, stream pb.CascadeS task := server.factory.NewCascadeRegistrationTask() + // Verify signature if provided + if req.GetSignature() != "" { + // Cast to concrete type to access helper method + if cascadeTask, ok := task.(*cascadeService.CascadeRegistrationTask); ok { + err := cascadeTask.VerifyDownloadSignature(ctx, req.GetActionId(), req.GetSignature()) + if err != nil { + fields[logtrace.FieldError] = err.Error() + logtrace.Error(ctx, "signature verification failed", fields) + return fmt.Errorf("signature verification failed: %w", err) + } + } else { + logtrace.Error(ctx, "unable to cast task to CascadeRegistrationTask", fields) + return fmt.Errorf("unable to verify signature: task type assertion failed") + } + } + var restoredFile []byte var tmpDir string @@ -257,9 +273,9 @@ func (server *ActionServer) Download(req *pb.DownloadRequest, stream pb.CascadeS // Calculate optimal chunk size based on file size chunkSize := calculateOptimalChunkSize(int64(len(restoredFile))) - + logtrace.Info(ctx, "calculated optimal chunk size for download", logtrace.Fields{ - "file_size": len(restoredFile), + "file_size": len(restoredFile), "chunk_size": chunkSize, }) diff --git a/supernode/services/cascade/helper.go b/supernode/services/cascade/helper.go index f6cf44a4..7047cb12 100644 --- a/supernode/services/cascade/helper.go +++ b/supernode/services/cascade/helper.go @@ -313,3 +313,38 @@ func decodeIndexFile(data string) (IndexFile, error) { } return indexFile, nil } + +// VerifyDownloadSignature verifies the download signature for actionID.creatorAddress +func (task *CascadeRegistrationTask) VerifyDownloadSignature(ctx context.Context, actionID, signature string) error { + fields := logtrace.Fields{ + logtrace.FieldActionID: actionID, + logtrace.FieldMethod: "VerifyDownloadSignature", + } + + // Get action details to extract creator address + actionDetails, err := task.LumeraClient.GetAction(ctx, actionID) + if err != nil { + return task.wrapErr(ctx, "failed to get action", err, fields) + } + + creatorAddress := actionDetails.GetAction().Creator + fields["creator_address"] = creatorAddress + + // Create the expected signature data: actionID.creatorAddress + signatureData := fmt.Sprintf("%s.%s", actionID, creatorAddress) + fields["signature_data"] = signatureData + + // Decode the base64 signature + signatureBytes, err := base64.StdEncoding.DecodeString(signature) + if err != nil { + return task.wrapErr(ctx, "failed to decode signature from base64", err, fields) + } + + // Verify the signature using Lumera client + if err := task.LumeraClient.Verify(ctx, creatorAddress, []byte(signatureData), signatureBytes); err != nil { + return task.wrapErr(ctx, "failed to verify download signature", err, fields) + } + + logtrace.Info(ctx, "download signature successfully verified", fields) + return nil +} diff --git a/tests/system/e2e_cascade_test.go b/tests/system/e2e_cascade_test.go index 25f7ca37..5c2dce70 100644 --- a/tests/system/e2e_cascade_test.go +++ b/tests/system/e2e_cascade_test.go @@ -623,8 +623,15 @@ func TestCascadeE2E(t *testing.T) { time.Sleep(10 * time.Second) outputFileBaseDir := filepath.Join(".") - // Try to download the file using the action ID - dtaskID, err := actionClient.DownloadCascade(context.Background(), actionID, outputFileBaseDir) + // Create signature: actionId.creatorsaddress (using the same address that was used for StartCascade) + signatureData := fmt.Sprintf("%s.%s", actionID, userAddress) + // Sign the signature data with user key + signedSignature, err := keyring.SignBytes(keplrKeyring, userKeyName, []byte(signatureData)) + require.NoError(t, err, "Failed to sign signature data") + // Base64 encode the signed signature + signature := base64.StdEncoding.EncodeToString(signedSignature) + // Try to download the file using the action ID and signature + dtaskID, err := actionClient.DownloadCascade(context.Background(), actionID, outputFileBaseDir, signature) t.Logf("Download response: %s", dtaskID) require.NoError(t, err, "Failed to download cascade data using action ID") From 1df6abfeb612d9a1dc6a2ee68e517c68e16c8844 Mon Sep 17 00:00:00 2001 From: Matee Ullah Malik Date: Wed, 16 Jul 2025 02:36:43 +0500 Subject: [PATCH 2/2] clean up signature creation --- tests/system/e2e_cascade_test.go | 91 ++------------------- tests/system/signature_utils.go | 132 +++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 86 deletions(-) create mode 100644 tests/system/signature_utils.go diff --git a/tests/system/e2e_cascade_test.go b/tests/system/e2e_cascade_test.go index 5c2dce70..130610b7 100644 --- a/tests/system/e2e_cascade_test.go +++ b/tests/system/e2e_cascade_test.go @@ -1,7 +1,6 @@ package system import ( - "bytes" "context" "crypto/sha256" "encoding/base64" @@ -18,9 +17,6 @@ import ( "github.com/LumeraProtocol/supernode/pkg/codec" "github.com/LumeraProtocol/supernode/pkg/keyring" "github.com/LumeraProtocol/supernode/pkg/lumera" - "github.com/LumeraProtocol/supernode/pkg/utils" - "github.com/cosmos/btcutil/base58" - "lukechampine.com/blake3" "github.com/LumeraProtocol/supernode/sdk/action" "github.com/LumeraProtocol/supernode/sdk/event" @@ -286,89 +282,19 @@ func TestCascadeE2E(t *testing.T) { metadataFile := encodeRes.Metadata - // Marshal metadata to JSON and convert to bytes - me, err := json.Marshal(metadataFile) - require.NoError(t, err, "Failed to marshal metadata to JSON") - - // Step 1: Encode the metadata JSON as base64 string for layout signature - layoutBase64 := base64.StdEncoding.EncodeToString(me) - t.Logf("Base64 encoded layout file length: %d", len(layoutBase64)) - - // Step 2: Sign the layout with user key (layout signature) - layoutSignature, err := keyring.SignBytes(keplrKeyring, userKeyName, []byte(layoutBase64)) - require.NoError(t, err, "Failed to sign layout") - layoutSignatureB64 := base64.StdEncoding.EncodeToString(layoutSignature) - - // Step 3: Generate real layout files and IDs (production flow) - // Create redundant layout files with counters and signatures + // Cascade signature creation process const ic = uint32(121) const maxFiles = uint32(50) - // Create layout file with signature (base64Layout.signature) - layoutWithSig := fmt.Sprintf("%s.%s", layoutBase64, layoutSignatureB64) - - // Generate layout IDs - layoutIDs := make([]string, maxFiles) - for i := range maxFiles { - counter := ic + uint32(i) - // Create layout file content: base64Layout.signature.counter - layoutFileContent := fmt.Sprintf("%s.%d", layoutWithSig, counter) - - // Compress and hash to get layout ID - compressedData, err := utils.ZstdCompress([]byte(layoutFileContent)) - require.NoError(t, err, "Failed to compress layout file") - - hash, err := utils.Blake3Hash(compressedData) - require.NoError(t, err, "Failed to hash layout file") - - layoutIDs[i] = base58.Encode(hash) - } - t.Logf("Generated %d real layout IDs", len(layoutIDs)) - - // Create index file structure with l layout IDs - indexFile := map[string]any{ - "layout_ids": layoutIDs, - "layout_signature": layoutSignatureB64, - } + // Create cascade signature format + signatureFormat, indexFileIDs, err := createCascadeLayoutSignature(metadataFile, keplrKeyring, userKeyName, ic, maxFiles) + require.NoError(t, err, "Failed to create cascade signature") - // Step 4: Marshal and encode index file - indexFileJSON, err := json.Marshal(indexFile) - require.NoError(t, err, "Failed to marshal index file") - indexFileBase64 := base64.StdEncoding.EncodeToString(indexFileJSON) - t.Logf("Base64 encoded index file length: %d", len(indexFileBase64)) - - // Step 5: Sign the index file with user key (creator signature) - creatorSignature, err := keyring.SignBytes(keplrKeyring, userKeyName, []byte(indexFileBase64)) - require.NoError(t, err, "Failed to sign index file") - creatorSignatureB64 := base64.StdEncoding.EncodeToString(creatorSignature) - - // Step 6: Format Base64(index_file).creators_signature - // The chain uses this format to regenerate and validate index IDs - signatureFormat := fmt.Sprintf("%s.%s", indexFileBase64, creatorSignatureB64) t.Logf("Signature format prepared with length: %d bytes", len(signatureFormat)) - - // Step 7: Generate index file IDs (what the supernode should send to chain) - // Supernode creates index IDs using: Base58(BLAKE3(zstd(rq_ids_signature.counter))) - // where rq_ids_signature is the signatureFormat we just created - indexFileIDs := make([]string, maxFiles) - for i := range maxFiles { - counter := ic + uint32(i) - // Create index file content: rq_ids_signature.counter - indexFileContent := fmt.Sprintf("%s.%d", signatureFormat, counter) - - // Compress and hash to get index file ID - compressedData, err := utils.ZstdCompress([]byte(indexFileContent)) - require.NoError(t, err, "Failed to compress index file") - - hash, err := utils.Blake3Hash(compressedData) - require.NoError(t, err, "Failed to hash index file") - - indexFileIDs[i] = base58.Encode(hash) - } t.Logf("Generated %d index file IDs for chain verification", len(indexFileIDs)) // Data hash with blake3 - hash, err := Blake3Hash(data) + hash, err := ComputeBlake3Hash(data) b64EncodedHash := base64.StdEncoding.EncodeToString(hash) require.NoError(t, err, "Failed to compute Blake3 hash") @@ -723,13 +649,6 @@ func TestCascadeE2E(t *testing.T) { require.NoError(t, err, "Failed to get supernode status") } -func Blake3Hash(msg []byte) ([]byte, error) { - hasher := blake3.New(32, nil) - if _, err := io.Copy(hasher, bytes.NewReader(msg)); err != nil { - return nil, err - } - return hasher.Sum(nil), nil -} // SetActionParams sets the initial parameters for the action module in genesis func SetActionParams(t *testing.T) GenesisMutator { diff --git a/tests/system/signature_utils.go b/tests/system/signature_utils.go new file mode 100644 index 00000000..916646ef --- /dev/null +++ b/tests/system/signature_utils.go @@ -0,0 +1,132 @@ +package system + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "io" + + "github.com/LumeraProtocol/supernode/pkg/codec" + "github.com/LumeraProtocol/supernode/pkg/keyring" + "github.com/LumeraProtocol/supernode/pkg/utils" + "github.com/cosmos/btcutil/base58" + cosmoskeyring "github.com/cosmos/cosmos-sdk/crypto/keyring" + "lukechampine.com/blake3" +) + +func createCascadeLayoutSignature(metadataFile codec.Layout, kr cosmoskeyring.Keyring, userKeyName string, ic uint32, maxFiles uint32) (signatureFormat string, indexFileIDs []string, err error) { + + // Step 1: Convert metadata to JSON then base64 + me, err := json.Marshal(metadataFile) + if err != nil { + return "", nil, fmt.Errorf("failed to marshal metadata: %w", err) + } + layoutBase64 := base64.StdEncoding.EncodeToString(me) + + // Step 2: Sign the layout data + layoutSignature, err := keyring.SignBytes(kr, userKeyName, []byte(layoutBase64)) + if err != nil { + return "", nil, fmt.Errorf("failed to sign layout: %w", err) + } + layoutSignatureB64 := base64.StdEncoding.EncodeToString(layoutSignature) + + // Step 3: Generate redundant layout file IDs + layoutIDs := generateLayoutIDsBatch(layoutBase64, layoutSignatureB64, ic, maxFiles) + + // Step 4: Create index file containing layout references + indexFile := map[string]interface{}{ + "layout_ids": layoutIDs, + "layout_signature": layoutSignatureB64, + } + + // Step 5: Sign the index file + indexFileJSON, err := json.Marshal(indexFile) + if err != nil { + return "", nil, fmt.Errorf("failed to marshal index file: %w", err) + } + indexFileBase64 := base64.StdEncoding.EncodeToString(indexFileJSON) + + creatorSignature, err := keyring.SignBytes(kr, userKeyName, []byte(indexFileBase64)) + if err != nil { + return "", nil, fmt.Errorf("failed to sign index file: %w", err) + } + creatorSignatureB64 := base64.StdEncoding.EncodeToString(creatorSignature) + + // Step 6: Create final signature format + signatureFormat = fmt.Sprintf("%s.%s", indexFileBase64, creatorSignatureB64) + + // Step 7: Generate final index file IDs for submission + indexFileIDs = generateIndexIDsBatch(signatureFormat, ic, maxFiles) + + return signatureFormat, indexFileIDs, nil +} + +// Process: combine data -> add counter -> compress -> hash -> Base58 encode +func generateLayoutIDsBatch(layoutBase64, layoutSignatureB64 string, ic, maxFiles uint32) []string { + layoutWithSig := fmt.Sprintf("%s.%s", layoutBase64, layoutSignatureB64) + layoutIDs := make([]string, maxFiles) + + var buffer bytes.Buffer + buffer.Grow(len(layoutWithSig) + 10) + + for i := uint32(0); i < maxFiles; i++ { + // Build unique content with counter + buffer.Reset() + buffer.WriteString(layoutWithSig) + buffer.WriteByte('.') + buffer.WriteString(fmt.Sprintf("%d", ic+i)) + + // Compress for efficiency + compressedData, err := utils.ZstdCompress(buffer.Bytes()) + if err != nil { + continue + } + + // Hash for uniqueness + hash, err := utils.Blake3Hash(compressedData) + if err != nil { + continue + } + + // Base58 encode for readable ID + layoutIDs[i] = base58.Encode(hash) + } + + return layoutIDs +} + +// generateIndexIDsBatch generates index file IDs using same process as layout IDs +func generateIndexIDsBatch(signatureFormat string, ic, maxFiles uint32) []string { + indexFileIDs := make([]string, maxFiles) + + var buffer bytes.Buffer + buffer.Grow(len(signatureFormat) + 10) + + for i := uint32(0); i < maxFiles; i++ { + buffer.Reset() + buffer.WriteString(signatureFormat) + buffer.WriteByte('.') + buffer.WriteString(fmt.Sprintf("%d", ic+i)) + + compressedData, err := utils.ZstdCompress(buffer.Bytes()) + if err != nil { + continue + } + hash, err := utils.Blake3Hash(compressedData) + if err != nil { + continue + } + indexFileIDs[i] = base58.Encode(hash) + } + return indexFileIDs +} + +// ComputeBlake3Hash computes Blake3 hash of the given message +func ComputeBlake3Hash(msg []byte) ([]byte, error) { + hasher := blake3.New(32, nil) + if _, err := io.Copy(hasher, bytes.NewReader(msg)); err != nil { + return nil, err + } + return hasher.Sum(nil), nil +}