Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 42 additions & 12 deletions pkg/cascadekit/verify.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,57 @@
package cascadekit

import (
"encoding/base64"
"fmt"

actionkeeper "github.com/LumeraProtocol/lumera/x/action/v1/keeper"
"github.com/LumeraProtocol/supernode/v2/pkg/codec"
"github.com/LumeraProtocol/supernode/v2/pkg/errors"
"github.com/LumeraProtocol/supernode/v2/pkg/utils"
)

// VerifySingleBlockIDs enforces single-block layouts and verifies that the
// symbols and block hash of ticket and local layouts match for block 0.
func VerifySingleBlockIDs(ticket, local codec.Layout) error {
if len(ticket.Blocks) != 1 || len(local.Blocks) != 1 {
return errors.New("layout must contain exactly one block")
// Verifier is a function that verifies the signature over data using the signer's on-chain pubkey.
// It should return nil if signature is valid; otherwise an error.
type Verifier func(data []byte, signature []byte) error

// VerifyStringRawOrADR36 verifies a signature over a message string in two passes:
// 1. raw: verify([]byte(message), sigRS)
// 2. ADR-36: build amino-JSON sign bytes with data = base64(message) and verify
//
// The signature is provided as base64 (DER or 64-byte r||s), and coerced to 64-byte r||s.
func VerifyStringRawOrADR36(message string, sigB64 string, signer string, verify Verifier) error {
sigRaw, err := base64.StdEncoding.DecodeString(sigB64)
if err != nil {
return fmt.Errorf("invalid base64 signature: %w", err)
}
if err := utils.EqualStrList(ticket.Blocks[0].Symbols, local.Blocks[0].Symbols); err != nil {
return errors.Errorf("symbol identifiers don't match: %w", err)
sigRS, err := actionkeeper.CoerceToRS64(sigRaw)

Check failure on line 26 in pkg/cascadekit/verify.go

View workflow job for this annotation

GitHub Actions / unit-tests

undefined: actionkeeper.CoerceToRS64

Check failure on line 26 in pkg/cascadekit/verify.go

View workflow job for this annotation

GitHub Actions / cascade-e2e-tests

undefined: actionkeeper.CoerceToRS64

Check failure on line 26 in pkg/cascadekit/verify.go

View workflow job for this annotation

GitHub Actions / build

undefined: actionkeeper.CoerceToRS64

Check failure on line 26 in pkg/cascadekit/verify.go

View workflow job for this annotation

GitHub Actions / build

undefined: actionkeeper.CoerceToRS64
if err != nil {
return fmt.Errorf("coerce signature: %w", err)
}
if ticket.Blocks[0].Hash != local.Blocks[0].Hash {
return errors.New("block hashes don't match")
if err := verify([]byte(message), sigRS); err == nil {
return nil
}
return nil
dataB64 := base64.StdEncoding.EncodeToString([]byte(message))
doc, err := actionkeeper.MakeADR36AminoSignBytes(signer, dataB64)

Check failure on line 34 in pkg/cascadekit/verify.go

View workflow job for this annotation

GitHub Actions / unit-tests

undefined: actionkeeper.MakeADR36AminoSignBytes

Check failure on line 34 in pkg/cascadekit/verify.go

View workflow job for this annotation

GitHub Actions / cascade-e2e-tests

undefined: actionkeeper.MakeADR36AminoSignBytes

Check failure on line 34 in pkg/cascadekit/verify.go

View workflow job for this annotation

GitHub Actions / build

undefined: actionkeeper.MakeADR36AminoSignBytes

Check failure on line 34 in pkg/cascadekit/verify.go

View workflow job for this annotation

GitHub Actions / build

undefined: actionkeeper.MakeADR36AminoSignBytes
if err != nil {
return fmt.Errorf("build adr36 doc: %w", err)
}
if err := verify(doc, sigRS); err == nil {
return nil
}
return fmt.Errorf("signature verification failed")
}

// VerifyIndex verifies the creator's signature over indexB64 (string), using the given verifier.
func VerifyIndex(indexB64 string, sigB64 string, signer string, verify Verifier) error {
return VerifyStringRawOrADR36(indexB64, sigB64, signer, verify)
}

// VerifyLayout verifies the layout signature over base64(JSON(layout)) bytes.
func VerifyLayout(layoutB64 []byte, sigB64 string, signer string, verify Verifier) error {
return VerifyStringRawOrADR36(string(layoutB64), sigB64, signer, verify)
}

// VerifySingleBlock checks that a layout contains exactly one block.
// VerifySingleBlock ensures the RaptorQ layout contains exactly one block.
func VerifySingleBlock(layout codec.Layout) error {
if len(layout.Blocks) != 1 {
return errors.New("layout must contain exactly one block")
Expand Down
6 changes: 4 additions & 2 deletions sdk/action/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,13 +318,15 @@ func (c *ClientImpl) BuildCascadeMetadataFromFile(ctx context.Context, filePath
// GenerateStartCascadeSignatureFromFile computes blake3(file) and signs it with the configured key.
// Returns base64-encoded signature suitable for StartCascade.
func (c *ClientImpl) GenerateStartCascadeSignatureFromFile(ctx context.Context, filePath string) (string, error) {
// Compute blake3(file), encode as base64 string, and sign the string bytes
h, err := utils.Blake3HashFile(filePath)
if err != nil {
return "", fmt.Errorf("blake3: %w", err)
}
sig, err := keyringpkg.SignBytes(c.keyring, c.config.Account.KeyName, h)
dataHashB64 := base64.StdEncoding.EncodeToString(h)
sig, err := keyringpkg.SignBytes(c.keyring, c.config.Account.KeyName, []byte(dataHashB64))
if err != nil {
return "", fmt.Errorf("sign hash: %w", err)
return "", fmt.Errorf("sign hash string: %w", err)
}
return base64.StdEncoding.EncodeToString(sig), nil
}
Expand Down
27 changes: 7 additions & 20 deletions sdk/task/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package task

import (
"context"
"encoding/base64"
"fmt"
"math/big"
"os"
"sort"

"github.com/LumeraProtocol/supernode/v2/pkg/cascadekit"
"github.com/LumeraProtocol/supernode/v2/pkg/utils"
"github.com/LumeraProtocol/supernode/v2/sdk/adapters/lumera"
)
Expand Down Expand Up @@ -74,26 +74,13 @@ func (m *ManagerImpl) validateSignature(ctx context.Context, action lumera.Actio
return fmt.Errorf("failed to decode cascade metadata: %w", err)
}

// Extract the base64-encoded data hash from the metadata
base64EnTcketDataHash := cascadeMetaData.DataHash
// Extract the base64-encoded data hash string from the metadata
dataHashB64 := cascadeMetaData.DataHash

// Decode the data hash from base64 to raw bytes
dataHashBytes, err := base64.StdEncoding.DecodeString(base64EnTcketDataHash)
if err != nil {
return fmt.Errorf("failed to decode data hash: %w", err)
}

// Decode the provided signature from base64 to raw bytes
signatureBytes, err := base64.StdEncoding.DecodeString(signature)
if err != nil {
return fmt.Errorf("failed to decode signature: %w", err)
}

// Verify the signature using the Lumera client
// This checks if the signature was produced by the action creator
// for the given data hash
err = m.lumeraClient.VerifySignature(ctx, action.Creator, dataHashBytes, signatureBytes)
if err != nil {
// Verify using cascadekit helper (raw -> ADR-36)
if err := cascadekit.VerifyStringRawOrADR36(dataHashB64, signature, action.Creator, func(data, sig []byte) error {
return m.lumeraClient.VerifySignature(ctx, action.Creator, data, sig)
}); err != nil {
m.logger.Error(ctx, "Signature validation failed", "actionID", action.ID, "error", err)
return fmt.Errorf("signature validation failed: %w", err)
}
Expand Down
9 changes: 3 additions & 6 deletions supernode/cascade/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cascade

import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"os"
Expand Down Expand Up @@ -117,11 +116,9 @@ func (task *CascadeRegistrationTask) VerifyDownloadSignature(ctx context.Context
return fmt.Errorf("get action for signature verification: %w", err)
}
creator := act.GetAction().Creator
sigBytes, err := base64.StdEncoding.DecodeString(signature)
if err != nil {
return fmt.Errorf("invalid base64 signature: %w", err)
}
if err := task.LumeraClient.Verify(ctx, creator, []byte(actionID), sigBytes); err != nil {
if err := cascadekit.VerifyStringRawOrADR36(actionID, signature, creator, func(data, sig []byte) error {
return task.LumeraClient.Verify(ctx, creator, data, sig)
}); err != nil {
return err
}
return nil
Expand Down
22 changes: 9 additions & 13 deletions supernode/cascade/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cascade

import (
"context"
"encoding/base64"
"strconv"

"cosmossdk.io/math"
Expand Down Expand Up @@ -87,11 +86,9 @@ func (task *CascadeRegistrationTask) validateIndexAndLayout(ctx context.Context,
if err != nil {
return cascadekit.IndexFile{}, nil, err
}
creatorSig, err := base64.StdEncoding.DecodeString(creatorSigB64)
if err != nil {
return cascadekit.IndexFile{}, nil, err
}
if err := task.LumeraClient.Verify(ctx, creator, []byte(indexB64), creatorSig); err != nil {
if err := cascadekit.VerifyIndex(indexB64, creatorSigB64, creator, func(data, sig []byte) error {
return task.LumeraClient.Verify(ctx, creator, data, sig)
}); err != nil {
return cascadekit.IndexFile{}, nil, err
}
// Decode index
Expand All @@ -104,14 +101,13 @@ func (task *CascadeRegistrationTask) validateIndexAndLayout(ctx context.Context,
if err != nil {
return cascadekit.IndexFile{}, nil, err
}
if err := cascadekit.VerifySingleBlock(layout); err != nil {
return cascadekit.IndexFile{}, nil, err
}
layoutSig, err := base64.StdEncoding.DecodeString(indexFile.LayoutSignature)
if err != nil {
return cascadekit.IndexFile{}, nil, err
// Enforce single-block layout for Cascade
if len(layout.Blocks) != 1 {
return cascadekit.IndexFile{}, nil, errors.New("layout must contain exactly one block")
}
if err := task.LumeraClient.Verify(ctx, creator, layoutB64, layoutSig); err != nil {
if err := cascadekit.VerifyLayout(layoutB64, indexFile.LayoutSignature, creator, func(data, sig []byte) error {
return task.LumeraClient.Verify(ctx, creator, data, sig)
}); err != nil {
return cascadekit.IndexFile{}, nil, err
}
return indexFile, layoutB64, nil
Expand Down
Loading