Skip to content

Commit c72b981

Browse files
Refactor cascade service to use base64 for data hashes and improve signature verification logic
1 parent 5a78aeb commit c72b981

File tree

7 files changed

+113
-115
lines changed

7 files changed

+113
-115
lines changed

pkg/logtrace/fields.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@ const (
1919
FieldTaskID = "task_id"
2020
FieldActionID = "action_id"
2121
FieldHashHex = "hash_hex"
22+
FieldExpected = "expected"
23+
FieldActual = "actual"
2224
)

supernode/node/action/server/cascade/cascade_action_server.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
package cascade
22

33
import (
4-
"encoding/hex"
4+
"encoding/base64"
55
"fmt"
6-
"github.com/LumeraProtocol/supernode/pkg/errors"
7-
"google.golang.org/grpc"
86
"io"
9-
"lukechampine.com/blake3"
107
"os"
118
"path/filepath"
129

10+
"github.com/LumeraProtocol/supernode/pkg/errors"
11+
"google.golang.org/grpc"
12+
"lukechampine.com/blake3"
13+
1314
pb "github.com/LumeraProtocol/supernode/gen/supernode/action/cascade"
1415
"github.com/LumeraProtocol/supernode/pkg/logtrace"
1516
cascadeService "github.com/LumeraProtocol/supernode/supernode/services/cascade"
@@ -117,8 +118,8 @@ func (server *ActionServer) Register(stream pb.CascadeService_RegisterServer) er
117118
logtrace.Info(ctx, "metadata received from action-sdk", fields)
118119

119120
hash := hasher.Sum(nil)
120-
hashHex := hex.EncodeToString(hash)
121-
fields[logtrace.FieldHashHex] = hashHex
121+
122+
b64Hash := base64.StdEncoding.EncodeToString(hash)
122123
logtrace.Info(ctx, "final BLAKE3 hash generated", fields)
123124

124125
targetPath, err := replaceTempDirWithTaskDir(metadata.GetTaskId(), tempFilePath, tempFile)
@@ -133,7 +134,7 @@ func (server *ActionServer) Register(stream pb.CascadeService_RegisterServer) er
133134
err = task.Register(ctx, &cascadeService.RegisterRequest{
134135
TaskID: metadata.TaskId,
135136
ActionID: metadata.ActionId,
136-
DataHash: hash,
137+
DataHash: b64Hash,
137138
DataSize: totalSize,
138139
FilePath: targetPath,
139140
}, func(resp *cascadeService.RegisterResponse) error {

supernode/services/cascade/helper.go

Lines changed: 27 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -87,49 +87,52 @@ func (task *CascadeRegistrationTask) encodeInput(ctx context.Context, path strin
8787
return &resp, nil
8888
}
8989

90-
func (task *CascadeRegistrationTask) verifySignatureAndDecodeLayout(ctx context.Context, encoded string, creator string,
91-
encodedMeta codec.Layout, f logtrace.Fields) (codec.Layout, string, error) {
90+
func (task *CascadeRegistrationTask) verifySignatures(ctx context.Context, signaturePayload string, layout codec.Layout, creator string, f logtrace.Fields) error {
9291

93-
file, sig, err := extractSignatureAndFirstPart(encoded)
92+
data, signature, err := extractSignatureAndFirstPart(signaturePayload)
9493
if err != nil {
95-
return codec.Layout{}, "", task.wrapErr(ctx, "failed to extract signature and first part", err, f)
94+
return task.wrapErr(ctx, "failed to extract signature and first part", err, f)
9695
}
97-
logtrace.Info(ctx, "signature and first part have been extracted", f)
9896

99-
// Decode the base64-encoded signature
100-
sigBytes, err := base64.StdEncoding.DecodeString(sig)
97+
signatureBytes, err := base64.StdEncoding.DecodeString(signature)
98+
10199
if err != nil {
102-
return codec.Layout{}, "", task.wrapErr(ctx, "failed to decode signature from base64", err, f)
100+
return task.wrapErr(ctx, "failed to decode base64 signature", err, f)
103101
}
104102

105-
// Log the verification attempt for the node creator
106-
logtrace.Info(ctx, "verifying signature from node creator", logtrace.Fields{
107-
"creator": creator,
108-
"taskID": task.ID(),
109-
})
103+
// Calculate the hash of the layout we created and verify it against the provided hash
104+
layoutJson, err := json.Marshal(layout)
105+
if err != nil {
106+
return task.wrapErr(ctx, "failed to marshal layout", err, f)
107+
}
110108

111-
// Pass the decoded signature bytes for verification
112-
if err := task.LumeraClient.Verify(ctx, creator, []byte(file), sigBytes); err != nil {
113-
return codec.Layout{}, "", task.wrapErr(ctx, "failed to verify node creator signature", err, f)
109+
layoutHash, err := utils.Blake3Hash(layoutJson)
110+
if err != nil {
111+
return task.wrapErr(ctx, "failed to calculate layout hash", err, f)
114112
}
113+
b64LayoutHash := base64.StdEncoding.EncodeToString(layoutHash)
115114

116-
logtrace.Info(ctx, "node creator signature successfully verified", f)
115+
// First Check
116+
if b64LayoutHash != data {
117+
return task.wrapErr(ctx, "layout hash mismatch", errors.Errorf("expected %s, got %s", b64LayoutHash, data), f)
118+
}
117119

118-
layout, err := decodeMetadataFile(file)
119-
if err != nil {
120-
return codec.Layout{}, "", task.wrapErr(ctx, "failed to decode metadata file", err, f)
120+
// Second Check
121+
if err := task.LumeraClient.Verify(ctx, creator, layoutHash, signatureBytes); err != nil {
122+
return task.wrapErr(ctx, "failed to verify node creator signature", err, f)
121123
}
122124

123-
return layout, sig, nil
125+
logtrace.Info(ctx, "node creator signature successfully verified", f)
126+
127+
return nil
124128
}
125129

126-
func (task *CascadeRegistrationTask) generateRQIDFiles(ctx context.Context, meta actiontypes.CascadeMetadata,
127-
sig, creator string, encodedMeta codec.Layout, f logtrace.Fields) (GenRQIdentifiersFilesResponse, error) {
130+
func (task *CascadeRegistrationTask) generateRQIDFiles(ctx context.Context, meta actiontypes.CascadeMetadata, creator string, encodedMeta codec.Layout, f logtrace.Fields) (GenRQIdentifiersFilesResponse, error) {
128131
res, err := GenRQIdentifiersFiles(ctx, GenRQIdentifiersFilesRequest{
129132
Metadata: encodedMeta,
130133
CreatorSNAddress: creator,
131134
RqMax: uint32(meta.RqIdsMax),
132-
Signature: sig,
135+
Signature: meta.Signatures, // Since these are already verified, we can use them directly
133136
IC: uint32(meta.RqIdsIc),
134137
})
135138
if err != nil {
@@ -139,7 +142,6 @@ func (task *CascadeRegistrationTask) generateRQIDFiles(ctx context.Context, meta
139142
logtrace.Info(ctx, "rq symbols, rq-ids and rqid-files have been generated", f)
140143
return res, nil
141144
}
142-
143145
func (task *CascadeRegistrationTask) storeArtefacts(ctx context.Context, actionID string, idFiles [][]byte, symbolsDir string, f logtrace.Fields) error {
144146
return task.P2P.StoreArtefacts(ctx, adaptors.StoreArtefactsRequest{
145147
IDFiles: idFiles,
@@ -159,14 +161,12 @@ func (task *CascadeRegistrationTask) wrapErr(ctx context.Context, msg string, er
159161
}
160162

161163
// extractSignatureAndFirstPart extracts the signature and first part from the encoded data
162-
// data is expected to be in format: b64(JSON(Layout)).Signature
163164
func extractSignatureAndFirstPart(data string) (encodedMetadata string, signature string, err error) {
164165
parts := strings.Split(data, ".")
165166
if len(parts) < 2 {
166167
return "", "", errors.New("invalid data format")
167168
}
168169

169-
// The first part is the base64 encoded data
170170
return parts[0], parts[1], nil
171171
}
172172

@@ -185,20 +185,6 @@ func decodeMetadataFile(data string) (layout codec.Layout, err error) {
185185
return layout, nil
186186
}
187187

188-
func verifyIDs(ticketMetadata, metadata codec.Layout) error {
189-
// Verify that the symbol identifiers match between versions
190-
if err := utils.EqualStrList(ticketMetadata.Blocks[0].Symbols, metadata.Blocks[0].Symbols); err != nil {
191-
return errors.Errorf("symbol identifiers don't match: %w", err)
192-
}
193-
194-
// Verify that the block hashes match
195-
if ticketMetadata.Blocks[0].Hash != metadata.Blocks[0].Hash {
196-
return errors.New("block hashes don't match")
197-
}
198-
199-
return nil
200-
}
201-
202188
// verifyActionFee checks if the action fee is sufficient for the given data size
203189
// It fetches action parameters, calculates the required fee, and compares it with the action price
204190
func (task *CascadeRegistrationTask) verifyActionFee(ctx context.Context, action *actiontypes.Action, dataSize int, fields logtrace.Fields) error {

supernode/services/cascade/helper_test.go

Lines changed: 62 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package cascade
22

33
import (
4+
"encoding/base64"
45
"encoding/json"
56
"testing"
67

78
"github.com/LumeraProtocol/supernode/pkg/codec"
89
"github.com/LumeraProtocol/supernode/pkg/utils"
910
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
1012
)
1113

1214
func Test_extractSignatureAndFirstPart(t *testing.T) {
@@ -66,49 +68,78 @@ func Test_decodeMetadataFile(t *testing.T) {
6668
}
6769
}
6870

69-
func Test_verifyIDs(t *testing.T) {
71+
func Test_verifyLayoutHash(t *testing.T) {
72+
// Create a sample layout
73+
layout := codec.Layout{
74+
Blocks: []codec.Block{
75+
{
76+
BlockID: 1,
77+
Hash: "sample_hash",
78+
Symbols: []string{"symbol1", "symbol2"},
79+
},
80+
},
81+
}
82+
83+
// Marshal to JSON and calculate the Blake3 hash
84+
jsonBytes, err := json.Marshal(layout)
85+
require.NoError(t, err)
86+
87+
blake3Hash, err := utils.Blake3Hash(jsonBytes)
88+
require.NoError(t, err)
89+
90+
validB64Hash := base64.StdEncoding.EncodeToString(blake3Hash)
91+
7092
tests := []struct {
71-
name string
72-
ticket codec.Layout
73-
metadata codec.Layout
74-
expectErr string
93+
name string
94+
b64EncodedHash string
95+
metadata codec.Layout
96+
expectErr bool
97+
expectedErrSubstr string
7598
}{
7699
{
77-
name: "success match",
78-
ticket: codec.Layout{Blocks: []codec.Block{
79-
{Symbols: []string{"A"}, Hash: "abc"},
80-
}},
81-
metadata: codec.Layout{Blocks: []codec.Block{
82-
{Symbols: []string{"A"}, Hash: "abc"},
83-
}},
100+
name: "valid hash match",
101+
b64EncodedHash: validB64Hash,
102+
metadata: layout,
103+
expectErr: false,
104+
},
105+
{
106+
name: "hash mismatch",
107+
b64EncodedHash: "invalid_hash_that_wont_match",
108+
metadata: layout,
109+
expectErr: true,
110+
expectedErrSubstr: "layout hash mismatch",
84111
},
85112
{
86-
name: "symbol mismatch",
87-
ticket: codec.Layout{Blocks: []codec.Block{
88-
{Symbols: []string{"A"}},
89-
}},
90-
metadata: codec.Layout{Blocks: []codec.Block{
91-
{Symbols: []string{"B"}},
92-
}},
93-
expectErr: "symbol identifiers don't match",
113+
name: "different metadata",
114+
b64EncodedHash: validB64Hash,
115+
metadata: codec.Layout{
116+
Blocks: []codec.Block{
117+
{
118+
BlockID: 2,
119+
Hash: "different_hash",
120+
Symbols: []string{"different_symbol"},
121+
},
122+
},
123+
},
124+
expectErr: true,
125+
expectedErrSubstr: "layout hash mismatch",
94126
},
95127
{
96-
name: "hash mismatch",
97-
ticket: codec.Layout{Blocks: []codec.Block{
98-
{Symbols: []string{"A"}, Hash: "a"},
99-
}},
100-
metadata: codec.Layout{Blocks: []codec.Block{
101-
{Symbols: []string{"A"}, Hash: "b"},
102-
}},
103-
expectErr: "block hashes don't match",
128+
name: "empty hash",
129+
b64EncodedHash: "",
130+
metadata: layout,
131+
expectErr: true,
132+
expectedErrSubstr: "layout hash mismatch",
104133
},
105134
}
106135

107136
for _, tt := range tests {
108137
t.Run(tt.name, func(t *testing.T) {
109-
err := verifyIDs(tt.ticket, tt.metadata)
110-
if tt.expectErr != "" {
111-
assert.ErrorContains(t, err, tt.expectErr)
138+
if tt.expectErr {
139+
assert.Error(t, err)
140+
if tt.expectedErrSubstr != "" {
141+
assert.Contains(t, err.Error(), tt.expectedErrSubstr)
142+
}
112143
} else {
113144
assert.NoError(t, err)
114145
}

supernode/services/cascade/metadata.go

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"github.com/LumeraProtocol/supernode/pkg/errors"
1212
"github.com/LumeraProtocol/supernode/pkg/utils"
1313
"github.com/cosmos/btcutil/base58"
14-
json "github.com/json-iterator/go"
1514
)
1615

1716
const (
@@ -33,21 +32,10 @@ type GenRQIdentifiersFilesResponse struct {
3332
RedundantMetadataFiles [][]byte
3433
}
3534

36-
// GenRQIdentifiersFiles generates Redundant Metadata Files and IDs
3735
func GenRQIdentifiersFiles(ctx context.Context, req GenRQIdentifiersFilesRequest) (resp GenRQIdentifiersFilesResponse, err error) {
38-
metadataFile, err := json.Marshal(req.Metadata)
39-
if err != nil {
40-
return resp, errors.Errorf("marshal rqID file: %w", err)
41-
}
42-
b64EncodedMetadataFile := utils.B64Encode(metadataFile)
43-
44-
// Create the RQID file by combining the encoded file with the signature
45-
var buffer bytes.Buffer
46-
buffer.Write(b64EncodedMetadataFile)
47-
buffer.WriteByte(SeparatorByte)
48-
buffer.Write([]byte(req.Signature))
49-
encMetadataFileWithSignature := buffer.Bytes()
36+
// Since verifySignatures confirms that the signature is valid, we can use the signature from ticket directly
5037

38+
encMetadataFileWithSignature := []byte(req.Signature)
5139
// Generate the specified number of variant IDs
5240
rqIdIds, rqIDsFiles, err := GetIDFiles(ctx, encMetadataFileWithSignature, req.IC, req.RqMax)
5341
if err != nil {

supernode/services/cascade/register.go

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
type RegisterRequest struct {
1212
TaskID string
1313
ActionID string
14-
DataHash []byte
14+
DataHash string // base64 encoded blake3 hash of the data
1515
DataSize int
1616
FilePath string
1717
}
@@ -83,8 +83,11 @@ func (task *CascadeRegistrationTask) Register(
8383
task.streamEvent(SupernodeEventTypeMetadataDecoded, "cascade metadata has been decoded", "", send)
8484

8585
/* 5. Verify data hash --------------------------------------------------------- */
86-
if err := task.verifyDataHash(ctx, req.DataHash, cascadeMeta.DataHash, fields); err != nil {
87-
return err
86+
if req.DataHash != cascadeMeta.DataHash {
87+
return task.wrapErr(ctx, "data hash mismatch", nil, logtrace.Fields{
88+
logtrace.FieldExpected: req.DataHash,
89+
logtrace.FieldActual: cascadeMeta.DataHash,
90+
})
8891
}
8992
logtrace.Info(ctx, "data-hash has been verified", fields)
9093
task.streamEvent(SupernodeEventTypeDataHashVerified, "data-hash has been verified", "", send)
@@ -97,28 +100,24 @@ func (task *CascadeRegistrationTask) Register(
97100
logtrace.Info(ctx, "input-data has been encoded", fields)
98101
task.streamEvent(SupernodeEventTypeInputEncoded, "input data has been encoded", "", send)
99102

100-
/* 7. Signature verification + layout decode ---------------------------------- */
101-
layout, signature, err := task.verifySignatureAndDecodeLayout(
102-
ctx, cascadeMeta.Signatures, action.Creator, encResp.Metadata, fields,
103-
)
103+
/* 7. Signature verification */
104+
err = task.verifySignatures(ctx, cascadeMeta.Signatures, encResp.Metadata, action.Creator, fields)
104105
if err != nil {
105106
return err
106107
}
107108
logtrace.Info(ctx, "signature has been verified", fields)
108109
task.streamEvent(SupernodeEventTypeSignatureVerified, "signature has been verified", "", send)
109110

110111
/* 8. Generate RQ-ID files ----------------------------------------------------- */
111-
rqidResp, err := task.generateRQIDFiles(ctx, cascadeMeta, signature, action.Creator, encResp.Metadata, fields)
112+
// Cascade metadata from action ticket
113+
//
114+
rqidResp, err := task.generateRQIDFiles(ctx, cascadeMeta, action.Creator, encResp.Metadata, fields)
112115
if err != nil {
113116
return err
114117
}
115118
logtrace.Info(ctx, "rq-id files have been generated", fields)
116119
task.streamEvent(SupernodeEventTypeRQIDsGenerated, "rq-id files have been generated", "", send)
117120

118-
/* 9. Consistency checks ------------------------------------------------------- */
119-
if err := verifyIDs(layout, encResp.Metadata); err != nil {
120-
return task.wrapErr(ctx, "failed to verify IDs", err, fields)
121-
}
122121
logtrace.Info(ctx, "rq-ids have been verified", fields)
123122
task.streamEvent(SupernodeEventTypeRqIDsVerified, "rq-ids have been verified", "", send)
124123

0 commit comments

Comments
 (0)