Skip to content
Open
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
121 changes: 9 additions & 112 deletions pkg/cascadekit/signatures.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ import (

"github.com/LumeraProtocol/supernode/v2/pkg/codec"
"github.com/LumeraProtocol/supernode/v2/pkg/errors"

actionkeeper "github.com/LumeraProtocol/lumera/x/action/v1/keeper"

keyringpkg "github.com/LumeraProtocol/supernode/v2/pkg/keyring"

sdkkeyring "github.com/cosmos/cosmos-sdk/crypto/keyring"
Expand Down Expand Up @@ -42,28 +39,26 @@ func SignLayoutB64(layout codec.Layout, signer Signer) (layoutB64 string, layout
}

// SignIndexB64 marshals the index to JSON, base64-encodes it, and signs the
// JSON string (not the base64), returning both the index base64 and creator-signature base64.
// base64 payload, returning both the index base64 and creator-signature base64.
//
// IMPORTANT:
// - Message signed = index JSON string (same as JS signArbitrary(indexFileString))
// - indexB64 is still base64(JSON(index)), used in metadata and RQID generation.
// - Message signed = indexB64 string (chain-compatible)
// - indexB64 is base64(JSON(index)), used in metadata and RQID generation.
func SignIndexB64(idx IndexFile, signer Signer) (indexB64 string, creatorSigB64 string, err error) {
raw, err := json.Marshal(idx)
if err != nil {
return "", "", errors.Errorf("marshal index file: %w", err)
}

indexJSON := string(raw)
indexB64 = base64.StdEncoding.EncodeToString(raw)

// Sign the JSON string (JS-style)
sig, err := signer([]byte(indexJSON))
// Sign the base64 payload (chain-compatible)
sig, err := signer([]byte(indexB64))
if err != nil {
return "", "", errors.Errorf("sign index: %w", err)
}
creatorSigB64 = base64.StdEncoding.EncodeToString(sig)

// Base64(JSON(index)) used as the first segment of indexSignatureFormat
indexB64 = base64.StdEncoding.EncodeToString(raw)
return indexB64, creatorSigB64, nil
}

Expand All @@ -73,9 +68,7 @@ func SignIndexB64(idx IndexFile, signer Signer) (indexB64 string, creatorSigB64
//
// It validates the layout has exactly one block.
//
// The "signer" can be:
// - raw: directly sign msg bytes (legacy Go path)
// - ADR-36: wrap msg into an ADR-36 sign doc, then sign (JS-compatible path)
// The signer directly signs the message bytes (raw signing).
func CreateSignatures(layout codec.Layout, signer Signer, ic, max uint32) (indexSignatureFormat string, indexIDs []string, err error) {
layoutB64, layoutSigB64, err := SignLayoutB64(layout, signer)
if err != nil {
Expand All @@ -89,7 +82,7 @@ func CreateSignatures(layout codec.Layout, signer Signer, ic, max uint32) (index
return "", nil, err
}

// Build and sign the index file (JS-style: message = index JSON string)
// Build and sign the index file (message = indexB64 string)
idx := BuildIndex(layoutIDs, layoutSigB64)
indexB64, creatorSigB64, err := SignIndexB64(idx, signer)
if err != nil {
Expand All @@ -105,12 +98,10 @@ func CreateSignatures(layout codec.Layout, signer Signer, ic, max uint32) (index
return indexSignatureFormat, indexIDs, nil
}

// CreateSignaturesWithKeyring signs layout and index using a Cosmos keyring (legacy path).
// CreateSignaturesWithKeyring signs layout and index using a Cosmos keyring.
// Message signed = raw bytes passed by SignLayoutB64 / SignIndexB64:
// - layout: layoutB64 string
// - index: index JSON string
//
// The verification pipeline already handles both raw and ADR-36, so this remains valid.
func CreateSignaturesWithKeyring(
layout codec.Layout,
kr sdkkeyring.Keyring,
Expand All @@ -122,97 +113,3 @@ func CreateSignaturesWithKeyring(
}
return CreateSignatures(layout, signer, ic, max)
}

// adr36SignerForKeyring creates a signer that signs ADR-36 doc bytes
// for the given signer address. The "msg" we pass in is the *message*
// (layoutB64, index JSON, etc.), and this helper wraps it into ADR-36.
func adr36SignerForKeyring(
kr sdkkeyring.Keyring,
keyName string,
signerAddr string,
) Signer {
return func(msg []byte) ([]byte, error) {
// msg is the cleartext message we want to sign (e.g., layoutB64 or index JSON string)
dataB64 := base64.StdEncoding.EncodeToString(msg)

// Build ADR-36 sign bytes: signerAddr + base64(message)
doc, err := actionkeeper.MakeADR36AminoSignBytes(signerAddr, dataB64)
if err != nil {
return nil, err
}

// Now sign the ADR-36 doc bytes with the keyring (direct secp256k1)
return keyringpkg.SignBytes(kr, keyName, doc)
}
}

// CreateSignaturesWithKeyringADR36WithSigner creates signatures in the SAME way as the JS SDK,
// allowing an explicit bech32 signer address override for ADR-36 sign bytes.
func CreateSignaturesWithKeyringADR36WithSigner(
layout codec.Layout,
kr sdkkeyring.Keyring,
keyName string,
signerAddr string,
ic, max uint32,
) (string, []string, error) {
if signerAddr == "" {
addr, err := keyringpkg.GetAddress(kr, keyName)
if err != nil {
return "", nil, fmt.Errorf("resolve signer address: %w", err)
}
signerAddr = addr.String()
}

signer := adr36SignerForKeyring(kr, keyName, signerAddr)

return CreateSignatures(layout, signer, ic, max)
}

// CreateSignaturesWithKeyringADR36 creates signatures in the SAME way as the JS SDK:
//
// - layout: Keplr-like ADR-36 signature over layoutB64 string
// - index: Keplr-like ADR-36 signature over index JSON string
//
// The resulting indexSignatureFormat string will match what JS produces for the same
// layout, signer, ic, and max.
func CreateSignaturesWithKeyringADR36(
layout codec.Layout,
kr sdkkeyring.Keyring,
keyName string,
ic, max uint32,
) (string, []string, error) {
return CreateSignaturesWithKeyringADR36WithSigner(layout, kr, keyName, "", ic, max)
}

// SignADR36String signs a message string using the ADR-36 scheme that Keplr uses.
// "message" must be the same string you'd pass to Keplr's signArbitrary, e.g.:
// - layoutB64
// - index JSON
// - dataHash (base64 blake3)
func SignADR36String(
kr sdkkeyring.Keyring,
keyName string,
signerAddr string,
message string,
) (string, error) {
// 1) message -> []byte
msgBytes := []byte(message)

// 2) base64(UTF-8(message))
dataB64 := base64.StdEncoding.EncodeToString(msgBytes)

// 3) Build ADR-36 sign bytes (Keplr-accurate)
docBytes, err := actionkeeper.MakeADR36AminoSignBytes(signerAddr, dataB64)
if err != nil {
return "", fmt.Errorf("build adr36 sign bytes: %w", err)
}

// 4) Sign with Cosmos keyring
sig, err := keyringpkg.SignBytes(kr, keyName, docBytes)
if err != nil {
return "", fmt.Errorf("sign adr36 doc: %w", err)
}

// 5) Wire format: base64(rsSignature)
return base64.StdEncoding.EncodeToString(sig), nil
}
26 changes: 9 additions & 17 deletions sdk/action/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type Client interface {
// and returns CascadeMetadata (with signatures) along with price and expiration time.
// Internally derives ic (random in [1..100]), max (from chain params), price (GetActionFee),
// and expiration (params duration + 1h buffer). signerAddr overrides the bech32 signer
// used in ADR-36 sign bytes; pass empty string to use the keyring address.
// used for key selection (e.g. ICA owner key); pass empty string to use the client signer.
BuildCascadeMetadataFromFile(ctx context.Context, filePath string, public bool, signerAddr string) (actiontypes.CascadeMetadata, string, string, error)
// GenerateStartCascadeSignatureFromFile computes blake3(file) and signs it with the configured key; returns base64 signature.
GenerateStartCascadeSignatureFromFile(ctx context.Context, filePath string) (string, error)
Expand Down Expand Up @@ -292,7 +292,7 @@ func (c *ClientImpl) BuildCascadeMetadataFromFile(ctx context.Context, filePath
rnd, _ := crand.Int(crand.Reader, big.NewInt(100))
ic := uint32(rnd.Int64() + 1) // 1..100

// Create signatures from the layout struct using ADR-36 scheme (JS compatible).
// Create signatures from the layout struct using raw signing.
if signerAddr == "" {
signerAddr = c.signerAddr
}
Expand All @@ -309,11 +309,10 @@ func (c *ClientImpl) BuildCascadeMetadataFromFile(ctx context.Context, filePath
event.KeyMessage: "metadata signed with ICA signer address",
}))
}
indexSignatureFormat, _, err := cascadekit.CreateSignaturesWithKeyringADR36WithSigner(
indexSignatureFormat, _, err := cascadekit.CreateSignaturesWithKeyring(
layout,
c.keyring,
keyName,
signerAddr,
ic,
max,
)
Expand Down Expand Up @@ -370,15 +369,14 @@ func (c *ClientImpl) GenerateStartCascadeSignatureFromFileDeprecated(ctx context
return base64.StdEncoding.EncodeToString(sig), nil
}

// GenerateStartCascadeSignatureFromFile computes blake3(file) and signs it with the configured key
// using the ADR-36 scheme, matching Keplr's signArbitrary(dataHash) behavior.
// Returns base64-encoded signature suitable for StartCascade.
// GenerateStartCascadeSignatureFromFile computes blake3(file) and signs the base64 hash string
// using raw signing (pure Go internal format). Returns base64-encoded signature suitable for StartCascade.
func (c *ClientImpl) GenerateStartCascadeSignatureFromFile(ctx context.Context, filePath string) (string, error) {
return c.GenerateStartCascadeSignatureFromFileWithSigner(ctx, filePath, "")
}

// GenerateStartCascadeSignatureFromFileWithSigner computes blake3(file) and signs it with the configured key,
// using the provided bech32 signer address for ADR-36 sign bytes.
// using signerAddr for key selection (e.g. ICA owner key).
func (c *ClientImpl) GenerateStartCascadeSignatureFromFileWithSigner(ctx context.Context, filePath string, signerAddr string) (string, error) {
// Compute blake3(file), encode as base64 string
h, err := utils.Blake3HashFile(filePath)
Expand All @@ -387,7 +385,6 @@ func (c *ClientImpl) GenerateStartCascadeSignatureFromFileWithSigner(ctx context
}
dataHashB64 := base64.StdEncoding.EncodeToString(h)

// Sign the dataHashB64 string using ADR-36 (same as JS / Keplr).
if signerAddr == "" {
signerAddr = c.signerAddr
}
Expand All @@ -400,17 +397,12 @@ func (c *ClientImpl) GenerateStartCascadeSignatureFromFileWithSigner(ctx context
logtrace.Info(ctx, "auth: ICA start signature", logFields)
c.logger.Info(ctx, "Signing cascade start with ICA signer", "signer", signerAddr, "key_name", keyName)
}
sigB64, err := cascadekit.SignADR36String(
c.keyring,
keyName,
signerAddr, // bech32 address for ADR-36 sign bytes
dataHashB64,
)
sig, err := snkeyring.SignBytes(c.keyring, keyName, []byte(dataHashB64))
if err != nil {
return "", fmt.Errorf("sign adr36 hash string: %w", err)
return "", fmt.Errorf("sign hash string: %w", err)
}

return sigB64, nil
return base64.StdEncoding.EncodeToString(sig), nil
}

func (c *ClientImpl) resolveSigningKeyName(signerAddr string) (string, bool) {
Expand Down