Skip to content

Conversation

@mateeullahmalik
Copy link
Collaborator

PR: Align Go SDK Cascade Signatures with JS (Keplr) ADR-36 Format

Summary

This PR updates the Go Cascade SDK to generate signatures and metadata identical to the JS SDK (Keplr). Previously, Cascade actions created via Go (sn-api-server) produced signatures that did not match those produced by the JS SDK, even when using the same account and the same file. This caused mismatches when testing across Go and browser clients, and made it difficult to verify signatures in a unified way.

Problem

The signature mismatch came from two root causes:

1. Go signed raw bytes while JS (Keplr) signed ADR-36 documents

  • Go used:

    keyringpkg.SignBytes([]byte(message))
  • JS used:

    keplr.signArbitrary(chainId, address, messageString)

    which internally constructs:

    {
      "chain_id": "",
      "account_number": "0",
      "sequence": "0",
      "fee": {"gas":"0","amount":[]},
      "msgs":[{"type":"sign/MsgSignData","value":{"signer": "<bech32>", "data": base64(messageBytes) }}],
      "memo":""
    }

This means:

  • JS signs ADR-36 amino JSON
  • Go signed raw []byte(message)
  • → final signatures differ.

2. JS signs different message strings

  • Layout signature: JS signs layoutB64.
  • Index signature: JS signs the index JSON string, not the base64 wrapper.
  • Auth (StartCascade) signature: JS signs dataHashB64 using ADR-36.

Go previously signed:

  • raw layoutB64 bytes (not ADR-36),
  • raw indexB64,
  • raw dataHashB64.

So even if both sides used the same key, the signed payloads differed → signatures diverged.


What This PR Changes

1. Added full ADR-36 signing support in Go (SignADR36String)

Go now constructs ADR-36 canonical sign bytes identical to Keplr:

docBytes := actionkeeper.MakeADR36AminoSignBytes(signerAddr, base64(message))
keyringpkg.SignBytes(docBytes)

2. Reimplemented layout + index signature generation to match JS

Replaced raw signing with ADR-36 Keplr-compatible signing:

  • Layout: sign layoutB64 using ADR-36
  • Index: sign JSON(index) string using ADR-36
  • Final metadata signature:
Base64(indexJSON) + "." + Base64(creatorSignature)

This is now identical to JS’s indexWithSignature.

3. GenerateStartCascadeSignatureFromFile now matches keplr.signArbitrary(dataHash)

Go now produces the same auth/start-cascade signature as JS for the same file and key.

4. Kept backward compatibility

Supernode validation (VerifyIndex, VerifyLayout) already tries:

  1. RAW message
  2. ADR-36 message (JS/Keplr)

So:

  • old Go tickets remain valid,
  • new JS-compatible tickets validate correctly.

Result

After these changes:

  • Go and JS produce identical values for:

    • data_hash
    • signatures (indexSignatureFormat)
    • authSignature (StartCascade signature)
  • Cross-client Cascade actions work consistently.

  • Supernodes can process both legacy Go and modern JS signatures.


Copilot finished reviewing on behalf of a-ok123 November 18, 2025 04:18
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR aligns the Go SDK's Cascade signature generation with the JavaScript SDK (Keplr) by implementing ADR-36 signing. Previously, Go signed raw bytes while JS signed ADR-36 documents, causing signature mismatches even when using the same account and file.

Key Changes:

  • Implemented ADR-36 signing support via new SignADR36String and CreateSignaturesWithKeyringADR36 functions
  • Updated signature generation to sign layout base64 and index JSON using ADR-36 (matching Keplr behavior)
  • Enhanced verification functions (VerifyLayout, VerifyIndex) to support both legacy raw signatures and new ADR-36 signatures for backward compatibility

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
sdk/action/client.go Migrated to ADR-36 signature generation; deprecated old raw signing function; removed unused imports; changed initial counter to hardcoded value
pkg/cascadekit/signatures.go Added ADR-36 signing functions (SignADR36String, CreateSignaturesWithKeyringADR36); updated SignIndexB64 to sign JSON string instead of base64; moved CreateSignaturesWithKeyring from deleted file; improved documentation
pkg/cascadekit/verify.go Enhanced VerifyLayout and VerifyIndex to support both legacy and ADR-36 signature schemes; improved documentation formatting
pkg/cascadekit/keyring_signatures.go Removed file; functionality moved to signatures.go

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +285 to +286
//rnd, _ := crand.Int(crand.Reader, big.NewInt(100))
ic := uint32(6)
Copy link

Copilot AI Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initial counter ic is now hardcoded to 6 instead of being randomly selected from [1, 100]. This change reduces the randomness of generated RQIDs and could lead to predictable signature patterns. While the commented-out line suggests this might be temporary for testing/debugging purposes, it should either be removed or the random generation should be restored for production use.

If this is intentional for JS compatibility, please add a comment explaining why this specific value is required.

Suggested change
//rnd, _ := crand.Int(crand.Reader, big.NewInt(100))
ic := uint32(6)
rnd, err := crand.Int(crand.Reader, big.NewInt(100))
if err != nil {
return actiontypes.CascadeMetadata{}, "", "", fmt.Errorf("failed to generate random initial counter: %w", err)
}
ic := uint32(rnd.Int64() + 1)

Copilot uses AI. Check for mistakes.
@j-rafique j-rafique merged commit c362bb6 into master Nov 18, 2025
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants