From 047fc1010d96cee44c46620687acc12d395d0235 Mon Sep 17 00:00:00 2001 From: a-ok123 Date: Fri, 23 Jan 2026 23:25:46 -0500 Subject: [PATCH 1/3] Added multi-chan keyring; added injective keys --- Makefile | 2 +- examples/ica-request-tx/main.go | 11 +- examples/ica-request-verify/main.go | 389 ++++++++++++++++++++++++ go.mod | 3 + pkg/crypto/ethsecp256k1/ethsecp256k1.go | 141 +++++++++ pkg/crypto/multichain_keyring.go | 53 ++++ 6 files changed, 596 insertions(+), 3 deletions(-) create mode 100644 examples/ica-request-verify/main.go create mode 100644 pkg/crypto/ethsecp256k1/ethsecp256k1.go create mode 100644 pkg/crypto/multichain_keyring.go diff --git a/Makefile b/Makefile index 1dbd3d0..f2db35c 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ GO ?= go GOLANGCI_LINT ?= golangci-lint BUILD_DIR ?= build -EXAMPLES ?= action-approve cascade-upload cascade-download query-actions claim-tokens ica-request-tx ica-approve-tx +EXAMPLES ?= action-approve cascade-upload cascade-download query-actions claim-tokens ica-request-tx ica-approve-tx ica-request-verify # Default target: build SDK and examples all: sdk examples ## Build SDK (compile packages) and all examples diff --git a/examples/ica-request-tx/main.go b/examples/ica-request-tx/main.go index f0598e1..6057478 100644 --- a/examples/ica-request-tx/main.go +++ b/examples/ica-request-tx/main.go @@ -15,6 +15,7 @@ import ( "github.com/LumeraProtocol/sdk-go/constants" "github.com/LumeraProtocol/sdk-go/ica" sdkcrypto "github.com/LumeraProtocol/sdk-go/pkg/crypto" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" ) @@ -31,6 +32,7 @@ func main() { icaAddress := flag.String("ica-address", "", "ICA address on Lumera (host chain)") grpcAddr := flag.String("grpc-addr", "", "Lumera gRPC address (host:port)") chainID := flag.String("chain-id", "", "Lumera chain ID") + keyringType := flag.String("keyring-type", "lumera", "Keyring type: lumera|injective") // IBC params connectionID := flag.String("connection-id", "connection-0", "IBC connection ID on controller chain") @@ -63,8 +65,13 @@ func main() { os.Exit(1) } - params := sdkcrypto.KeyringParams{AppName: "lumera", Backend: *keyringBackend, Dir: *keyringDir} - kr, err := sdkcrypto.NewKeyring(params) + // Choose app name based on keyring type + appName := "lumera" + if *keyringType == "injective" { + appName = "injectived" + } + + kr, err := sdkcrypto.NewMultiChainKeyring(appName, *keyringBackend, *keyringDir) if err != nil { fmt.Printf("open keyring: %v\n", err) os.Exit(1) diff --git a/examples/ica-request-verify/main.go b/examples/ica-request-verify/main.go new file mode 100644 index 0000000..e8c8188 --- /dev/null +++ b/examples/ica-request-verify/main.go @@ -0,0 +1,389 @@ +package main + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "io/fs" + "log" + "os" + "path/filepath" + "sort" + "strings" + + "encoding/asn1" + "encoding/base64" + "math/big" + + "github.com/LumeraProtocol/sdk-go/cascade" + "github.com/LumeraProtocol/sdk-go/constants" + "github.com/LumeraProtocol/sdk-go/ica" + + sdkcrypto "github.com/LumeraProtocol/sdk-go/pkg/crypto" + sdktypes "github.com/LumeraProtocol/sdk-go/types" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" +) + +// This example builds an ICS-27 MsgSendTx that executes one or more +// Lumera MsgRequestAction messages over an Interchain Account. +// It builds Cascade metadata via the sdk-go Cascade client (chain gRPC) +// but does not broadcast any transactions. +func main() { + // Keyring-only; addresses are derived from the key + keyringBackend := flag.String("keyring-backend", "os", "Keyring backend: os|file|test") + keyringDir := flag.String("keyring-dir", "~/.lumera", "Keyring base directory (actual dir appends keyring- for file/test)") + keyName := flag.String("key-name", "my-key", "Key name in the keyring") + ownerHRP := flag.String("owner-hrp", "inj", "Bech32 HRP for controller chain owner address (e.g., inj)") + icaAddress := flag.String("ica-address", "", "ICA address on Lumera (host chain)") + grpcAddr := flag.String("grpc-addr", "", "Lumera gRPC address (host:port)") + chainID := flag.String("chain-id", "", "Lumera chain ID") + keyringType := flag.String("keyring-type", "lumera", "Keyring type: lumera|injective") + + // IBC params + connectionID := flag.String("connection-id", "connection-0", "IBC connection ID on controller chain") + relTimeout := flag.Uint64("relative-timeout", 600_000_000_000, "Relative timeout nanoseconds for MsgSendTx (e.g. 10 min)") + + // Input path + path := flag.String("path", "", "Path to a single file or a directory containing files") + flag.Parse() + + if strings.TrimSpace(*path) == "" { + fmt.Println("--path is required") + os.Exit(1) + } + if strings.TrimSpace(*grpcAddr) == "" { + fmt.Println("--grpc-addr is required") + os.Exit(1) + } + if strings.TrimSpace(*chainID) == "" { + fmt.Println("--chain-id is required") + os.Exit(1) + } + + files, err := collectFiles(*path) + if err != nil { + fmt.Printf("collect files: %v\n", err) + os.Exit(1) + } + if len(files) == 0 { + fmt.Println("no files found to build messages") + os.Exit(1) + } + + // Choose app name based on keyring type + appName := "lumera" + if *keyringType == "injective" { + appName = "injectived" + } + fmt.Printf("Using keyring app name: %s\n", appName) + + kr, err := sdkcrypto.NewMultiChainKeyring(appName, *keyringBackend, *keyringDir) + if err != nil { + fmt.Printf("open keyring: %v\n", err) + os.Exit(1) + } + + lumeraAddress, err := sdkcrypto.AddressFromKey(kr, *keyName, constants.LumeraAccountHRP) + if err != nil { + log.Fatalf("derive owner address: %v\n", err) + } + fmt.Printf("Derived Lumera address for key '%s': %s\n", *keyName, lumeraAddress) + + // Derive controller owner address from the same key with the given HRP + ownerAddr, err := sdkcrypto.AddressFromKey(kr, *keyName, *ownerHRP) + if err != nil { + fmt.Printf("derive owner address: %v\n", err) + os.Exit(1) + } + fmt.Printf("Derived controller owner address for key '%s' with HRP '%s': %s\n", *keyName, *ownerHRP, ownerAddr) + + ctx := context.Background() + cascadeClient, err := cascade.New(ctx, cascade.Config{ + ChainID: *chainID, + GRPCAddr: *grpcAddr, + Address: lumeraAddress, + KeyName: *keyName, + ICAOwnerKeyName: *keyName, + ICAOwnerHRP: *ownerHRP, + }, kr) + if err != nil { + fmt.Printf("create cascade client: %v\n", err) + os.Exit(1) + } + defer func() { + _ = cascadeClient.Close() + }() + + useICA := strings.TrimSpace(*icaAddress) != "" + var appPubkey []byte + if useICA { + rec, err := kr.Key(*keyName) + if err != nil { + fmt.Printf("load key: %v\n", err) + os.Exit(1) + } + pub, err := rec.GetPubKey() + if err != nil { + fmt.Printf("get pubkey: %v\n", err) + os.Exit(1) + } + if pub == nil { + fmt.Println("nil pubkey for key") + os.Exit(1) + } + appPubkey = pub.Bytes() + fmt.Printf("Using ICA with address %s and app pubkey %X\n", *icaAddress, appPubkey) + } + + // Build one MsgRequestAction per file + var anys []*codectypes.Any + for _, f := range files { + var opts []cascade.UploadOption + if useICA { + opts = append(opts, + cascade.WithICACreatorAddress(*icaAddress), + cascade.WithAppPubkey(appPubkey), + ) + } + uploadOpts := &cascade.UploadOptions{} + for _, opt := range opts { + opt(uploadOpts) + } + msgSendTx, _, err := cascadeClient.CreateRequestActionMessage(ctx, lumeraAddress, f, uploadOpts) + if err != nil { + fmt.Printf("create request message: %v\n", err) + os.Exit(1) + } + jsonBytes, err := json.MarshalIndent(msgSendTx, "", " ") + if err != nil { + fmt.Printf("marshal MsgSendTx to JSON: %v\n", err) + os.Exit(1) + } + fmt.Println("JSON(MsgSendTx):") + fmt.Println(string(jsonBytes)) + + // --- Signature Verification --- + fmt.Printf("Verifying signature for %s...\n", f) + + // Assume secp256k1-formatted pubkey bytes for app-level signatures. + appPk := secp256k1.PubKey{Key: appPubkey} + var pubKey cryptotypes.PubKey + pubKey = &appPk + fmt.Printf("Using app pubkey: %s\n", pubKey.String()) + + // 1. Unmarshal Metadata + var meta sdktypes.CascadeMetadata + // MsgRequestAction.Metadata is a JSON string of CascadeMetadata + if err := json.Unmarshal([]byte(msgSendTx.Metadata), &meta); err != nil { + fmt.Printf("failed to unmarshal metadata: %v\n", err) + os.Exit(1) + } + + // 2. Parse Signatures field: "Base64(rq_ids).Base64(signature)" + parts := strings.Split(meta.Signatures, ".") + if len(parts) != 2 { + fmt.Printf("invalid signatures format: expected 'data.signature', got '%s'\n", meta.Signatures) + os.Exit(1) + } + dataB64 := parts[0] + sigB64 := parts[1] + fmt.Printf(" Data (Base64): %s\n", dataB64) + fmt.Printf(" Signature (Base64): %s\n", sigB64) + + // 3. Decode the base64 signature + sigRaw, err := base64.StdEncoding.DecodeString(sigB64) + if err != nil { + fmt.Printf("failed to decode signature base64: %v\n", err) + os.Exit(1) + } + + // 4. Coerce to r||s format + sigRS := CoerceToRS64(sigRaw) + + // 5. Verify the signature + valid := pubKey.VerifySignature([]byte(dataB64), sigRS) + if !valid { + fmt.Printf("Signature verification FAILED for %s\n", f) + + // 6. ADR-36 (Keplr/browser) + signBytes, err := MakeADR36AminoSignBytes(lumeraAddress, dataB64) + if err == nil && pubKey.VerifySignature(signBytes, sigRS) { + fmt.Printf("ADR-36 signature verification FAILED for %s\n", f) + os.Exit(1) + } + fmt.Printf("ADR-36 Signature Verified Successfully for %s\n", f) + } else { + fmt.Printf("Signature Verified Successfully for %s\n", f) + } + // ------------------------------ + + // Pack to Any for ICA execution + any, err := ica.PackRequestAny(msgSendTx) + if err != nil { + fmt.Printf("pack request Any: %v\n", err) + os.Exit(1) + } + anys = append(anys, any) + } + + // Build packet and MsgSendTx + packet, err := ica.BuildICAPacketData(anys) + if err != nil { + fmt.Printf("build packet: %v\n", err) + os.Exit(1) + } + + msgSendTx, err := ica.BuildMsgSendTx(ownerAddr, *connectionID, *relTimeout, packet) + if err != nil { + fmt.Printf("build MsgSendTx: %v\n", err) + os.Exit(1) + } + + // Convert to JSON + jsonBytes, err := json.MarshalIndent(msgSendTx, "", " ") + if err != nil { + fmt.Printf("marshal MsgSendTx to JSON: %v\n", err) + os.Exit(1) + } + + fmt.Println("Built ICS-27 MsgSendTx (controller message)") + fmt.Printf("Owner (controller): %s\n", msgSendTx.Owner) + fmt.Printf("Connection: %s\n", msgSendTx.ConnectionId) + fmt.Printf("RelativeTimeout: %d\n", msgSendTx.RelativeTimeout) + fmt.Printf("Included messages: %d\n", len(anys)) + fmt.Println() + fmt.Println("JSON(MsgSendTx):") + fmt.Println(string(jsonBytes)) + +} + +func collectFiles(p string) ([]string, error) { + st, err := os.Stat(p) + if err != nil { + return nil, err + } + if !st.IsDir() { + // Single file + return []string{p}, nil + } + var out []string + dirEntries, err := os.ReadDir(p) + if err != nil { + return nil, err + } + for _, de := range dirEntries { + if de.IsDir() { + continue // non-recursive + } + // Ensure it's a regular file + info, err := de.Info() + if err != nil { + continue + } + if (info.Mode() & fs.ModeType) == 0 { + out = append(out, filepath.Join(p, de.Name())) + } + } + return out, nil +} + +// CoerceToRS64 converts an ECDSA signature to 64-byte [R||S] format. +// If it's already 64 bytes, it returns it as is. +// If it's ASN.1, it parses and converts. +func CoerceToRS64(sig []byte) []byte { + if len(sig) == 64 { + return sig + } + // Try parsing as ASN.1 + var ecdsaSig struct { + R, S *big.Int + } + if _, err := asn1.Unmarshal(sig, &ecdsaSig); err != nil { + // Not ASN.1 or invalid, return original + return sig + } + + // Normalize to 32 bytes each + rBytes := ecdsaSig.R.Bytes() + sBytes := ecdsaSig.S.Bytes() + + r32 := make([]byte, 32) + s32 := make([]byte, 32) + + // Copy into the end of the buffer (big endian) + if len(rBytes) > 32 { + // Should not happen for secp256k1 unless padded with zero? + // ecdsaSig.R.Bytes() strips leading zeros, but if it was somehow larger... + // Just take last 32? No, error out or take strict? + // For robustness we just copy what fits or all if small + copy(r32[:], rBytes[len(rBytes)-32:]) + } else { + copy(r32[32-len(rBytes):], rBytes) + } + + if len(sBytes) > 32 { + copy(s32[:], sBytes[len(sBytes)-32:]) + } else { + copy(s32[32-len(sBytes):], sBytes) + } + + return append(r32, s32...) +} + +// MakeADR36AminoSignBytes returns the exact JSON bytes Keplr signs. +// signerBech32: bech32 address; dataB64: base64 STRING that was given to Keplr signArbitrary(). +func MakeADR36AminoSignBytes(signerBech32, dataB64 string) ([]byte, error) { + doc := map[string]any{ + "account_number": "0", + "chain_id": "", + "fee": map[string]any{ + "amount": []any{}, + "gas": "0", + }, + "memo": "", + "msgs": []any{ + map[string]any{ + "type": "sign/MsgSignData", + "value": map[string]any{ + "data": dataB64, // IMPORTANT: base64 STRING (do not decode) + "signer": signerBech32, // bech32 account address + }, + }, + }, + "sequence": "0", + } + + canon := sortObjectByKey(doc) + bz, err := json.Marshal(canon) + if err != nil { + return nil, fmt.Errorf("marshal adr36 doc: %w", err) + } + return bz, nil +} + +func sortObjectByKey(v any) any { + switch x := v.(type) { + case map[string]any: + keys := make([]string, 0, len(x)) + for k := range x { + keys = append(keys, k) + } + sort.Strings(keys) + out := make(map[string]any, len(x)) + for _, k := range keys { + out[k] = sortObjectByKey(x[k]) + } + return out + case []any: + out := make([]any, len(x)) + for i := range x { + out[i] = sortObjectByKey(x[i]) + } + return out + default: + return v + } +} diff --git a/go.mod b/go.mod index 99d43ee..cce8531 100644 --- a/go.mod +++ b/go.mod @@ -36,6 +36,8 @@ require ( lukechampine.com/blake3 v1.4.1 ) +require github.com/ethereum/go-ethereum v1.15.11 + require ( cosmossdk.io/collections v1.3.0 // indirect cosmossdk.io/core v0.11.3 // indirect @@ -122,6 +124,7 @@ require ( github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect + github.com/holiman/uint256 v1.3.2 // indirect github.com/huandu/skiplist v1.2.1 // indirect github.com/iancoleman/strcase v0.3.0 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect diff --git a/pkg/crypto/ethsecp256k1/ethsecp256k1.go b/pkg/crypto/ethsecp256k1/ethsecp256k1.go new file mode 100644 index 0000000..7e3a218 --- /dev/null +++ b/pkg/crypto/ethsecp256k1/ethsecp256k1.go @@ -0,0 +1,141 @@ +// sdk-go/pkg/crypto/ethsecp256k1/ethsecp256k1.go +package ethsecp256k1 + +import ( + "bytes" + "crypto/sha256" + "crypto/subtle" + "fmt" + + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/ethereum/go-ethereum/crypto" +) + +const ( + PrivKeySize = 32 + PubKeySize = 33 // compressed + KeyType = "eth_secp256k1" + + // Proto type URLs - must match Injective's registration + PubKeyName = "injective.crypto.v1beta1.ethsecp256k1.PubKey" + PrivKeyName = "injective.crypto.v1beta1.ethsecp256k1.PrivKey" +) + +var ( + _ cryptotypes.PrivKey = &PrivKey{} + _ cryptotypes.PubKey = &PubKey{} +) + +// PrivKey defines a secp256k1 private key using Ethereum's Keccak256 hashing +type PrivKey struct { + Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` +} + +func (privKey *PrivKey) Bytes() []byte { + if privKey == nil { + return nil + } + return privKey.Key +} + +func (privKey *PrivKey) PubKey() cryptotypes.PubKey { + ecdsaPrivKey, err := crypto.ToECDSA(privKey.Key) + if err != nil { + return nil + } + return &PubKey{Key: crypto.CompressPubkey(&ecdsaPrivKey.PublicKey)} +} + +func (privKey *PrivKey) Equals(other cryptotypes.LedgerPrivKey) bool { + return privKey.Type() == other.Type() && subtle.ConstantTimeCompare(privKey.Bytes(), other.Bytes()) == 1 +} + +func (privKey *PrivKey) Type() string { + return KeyType +} + +func (privKey *PrivKey) Sign(digestBz []byte) ([]byte, error) { + if len(digestBz) != crypto.DigestLength { + // Use SHA256 like standard Cosmos, NOT Keccak256 + hash := sha256.Sum256(digestBz) + digestBz = hash[:] + } + key, err := crypto.ToECDSA(privKey.Key) + if err != nil { + return nil, err + } + + sig, err := crypto.Sign(digestBz, key) + if err != nil { + return nil, err + } + + // Remove recovery ID (65 → 64 bytes) to match Cosmos format + if len(sig) == 65 { + sig = sig[:64] + } + + return sig, nil +} + +// Proto methods +func (privKey *PrivKey) Reset() { *privKey = PrivKey{} } +func (privKey *PrivKey) String() string { return fmt.Sprintf("eth_secp256k1{%X}", privKey.Key) } +func (privKey *PrivKey) ProtoMessage() {} + +// XXX_MessageName returns the proto message name for proper type URL registration +func (*PrivKey) XXX_MessageName() string { return PrivKeyName } + +// PubKey defines a secp256k1 public key using Ethereum's format +type PubKey struct { + Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` +} + +func (pubKey *PubKey) Address() cryptotypes.Address { + if len(pubKey.Key) != PubKeySize { + return nil + } + uncompressed, err := crypto.DecompressPubkey(pubKey.Key) + if err != nil { + return nil + } + return crypto.PubkeyToAddress(*uncompressed).Bytes() +} + +func (pubKey *PubKey) Bytes() []byte { + if pubKey == nil { + return nil + } + return pubKey.Key +} + +func (pubKey *PubKey) Equals(other cryptotypes.PubKey) bool { + return pubKey.Type() == other.Type() && bytes.Equal(pubKey.Bytes(), other.Bytes()) +} + +func (pubKey *PubKey) Type() string { + return KeyType +} + +func (pubKey *PubKey) VerifySignature(msg, sig []byte) bool { + if len(sig) == crypto.SignatureLength { + sig = sig[:crypto.SignatureLength-1] // remove recovery ID + } + hash := crypto.Keccak256(msg) + return crypto.VerifySignature(pubKey.Key, hash, sig) +} + +// Proto methods +func (pubKey *PubKey) Reset() { *pubKey = PubKey{} } +func (pubKey *PubKey) String() string { return fmt.Sprintf("eth_secp256k1{%X}", pubKey.Key) } +func (pubKey *PubKey) ProtoMessage() {} + +// XXX_MessageName returns the proto message name for proper type URL registration +func (*PubKey) XXX_MessageName() string { return PubKeyName } + +// RegisterInterfaces registers the ethsecp256k1 types with Injective's type URLs +func RegisterInterfaces(registry codectypes.InterfaceRegistry) { + registry.RegisterImplementations((*cryptotypes.PubKey)(nil), &PubKey{}) + registry.RegisterImplementations((*cryptotypes.PrivKey)(nil), &PrivKey{}) +} diff --git a/pkg/crypto/multichain_keyring.go b/pkg/crypto/multichain_keyring.go new file mode 100644 index 0000000..827b2e8 --- /dev/null +++ b/pkg/crypto/multichain_keyring.go @@ -0,0 +1,53 @@ +package crypto + +import ( + "bufio" + "os" + "path/filepath" + "strings" + + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cosmoscrypto "github.com/cosmos/cosmos-sdk/crypto/types" + + injethsecp256k1 "github.com/LumeraProtocol/sdk-go/pkg/crypto/ethsecp256k1" +) + +func NewMultiChainKeyring(appName, backend, dir string) (keyring.Keyring, error) { + // Expand ~ in path + if strings.HasPrefix(dir, "~/") { + home, _ := os.UserHomeDir() + dir = filepath.Join(home, dir[2:]) + } + + registry := codectypes.NewInterfaceRegistry() + + // Standard Cosmos + registry.RegisterInterface("cosmos.crypto.PubKey", (*cosmoscrypto.PubKey)(nil)) + registry.RegisterInterface("cosmos.crypto.PrivKey", (*cosmoscrypto.PrivKey)(nil)) + registry.RegisterImplementations((*cosmoscrypto.PubKey)(nil), + &secp256k1.PubKey{}, + ) + registry.RegisterImplementations((*cosmoscrypto.PrivKey)(nil), + &secp256k1.PrivKey{}, + ) + + // Injective + registry.RegisterImplementations((*cosmoscrypto.PubKey)(nil), + &injethsecp256k1.PubKey{}, + ) + registry.RegisterImplementations((*cosmoscrypto.PrivKey)(nil), + &injethsecp256k1.PrivKey{}, + ) + + cdc := codec.NewProtoCodec(registry) + // For file backend, provide stdin for password input + var userInput *bufio.Reader + if backend == keyring.BackendFile { + userInput = bufio.NewReader(os.Stdin) + } + + return keyring.New(appName, backend, dir, userInput, cdc) +} From 52a6f4e4d8d2d675fcc8cee5bac356bc97a87851 Mon Sep 17 00:00:00 2001 From: a-ok123 Date: Thu, 29 Jan 2026 15:09:14 -0500 Subject: [PATCH 2/3] bumped version of SN-SDK; fixed verify logic inthe example; fixed verify in 'modified' inj keyring --- examples/ica-request-verify/main.go | 28 ++++++++++++++++++++----- go.mod | 2 +- go.sum | 4 ++-- pkg/crypto/ethsecp256k1/ethsecp256k1.go | 7 +++++-- 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/examples/ica-request-verify/main.go b/examples/ica-request-verify/main.go index e8c8188..7400394 100644 --- a/examples/ica-request-verify/main.go +++ b/examples/ica-request-verify/main.go @@ -19,12 +19,10 @@ import ( "github.com/LumeraProtocol/sdk-go/cascade" "github.com/LumeraProtocol/sdk-go/constants" "github.com/LumeraProtocol/sdk-go/ica" - sdkcrypto "github.com/LumeraProtocol/sdk-go/pkg/crypto" sdktypes "github.com/LumeraProtocol/sdk-go/types" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" ) // This example builds an ICS-27 MsgSendTx that executes one or more @@ -136,6 +134,26 @@ func main() { } appPubkey = pub.Bytes() fmt.Printf("Using ICA with address %s and app pubkey %X\n", *icaAddress, appPubkey) + + //test + //addr, err := rec.GetAddress() + //if err != nil { + // fmt.Printf("get address: %v\n", err) + // os.Exit(1) + //} + //st := "Test string" + //b := []byte(st) + //sig, _, err := kr.SignByAddress(addr, b, signing.SignMode_SIGN_MODE_DIRECT) + //if err != nil { + // fmt.Printf("sign: %v\n", err) + // os.Exit(1) + //} + //fmt.Printf("Signature: %X\n", sig) + // + //appPk := secp256k1.PubKey{Key: appPubkey} + //pubKey := &appPk + //valid := pubKey.VerifySignature(b, sig) + //fmt.Printf("Verify Signature: %v\n", valid) } // Build one MsgRequestAction per file @@ -170,8 +188,7 @@ func main() { // Assume secp256k1-formatted pubkey bytes for app-level signatures. appPk := secp256k1.PubKey{Key: appPubkey} - var pubKey cryptotypes.PubKey - pubKey = &appPk + pubKey := &appPk fmt.Printf("Using app pubkey: %s\n", pubKey.String()) // 1. Unmarshal Metadata @@ -211,10 +228,11 @@ func main() { // 6. ADR-36 (Keplr/browser) signBytes, err := MakeADR36AminoSignBytes(lumeraAddress, dataB64) if err == nil && pubKey.VerifySignature(signBytes, sigRS) { + fmt.Printf("ADR-36 Signature Verified Successfully for %s\n", f) + } else { fmt.Printf("ADR-36 signature verification FAILED for %s\n", f) os.Exit(1) } - fmt.Printf("ADR-36 Signature Verified Successfully for %s\n", f) } else { fmt.Printf("Signature Verified Successfully for %s\n", f) } diff --git a/go.mod b/go.mod index cce8531..738a991 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/LumeraProtocol/lumera v1.9.1 // SuperNode SDK for storage operations - github.com/LumeraProtocol/supernode/v2 v2.4.23 + github.com/LumeraProtocol/supernode/v2 v2.4.26 github.com/cometbft/cometbft v0.38.18 github.com/cosmos/cosmos-sdk v0.53.0 github.com/cosmos/go-bip39 v1.0.0 diff --git a/go.sum b/go.sum index 719378d..f96f6ef 100644 --- a/go.sum +++ b/go.sum @@ -113,8 +113,8 @@ github.com/LumeraProtocol/lumera v1.9.1 h1:4hI0sHHrZOiKP+S3GpLNHYeQTatXBftmcUE3Z github.com/LumeraProtocol/lumera v1.9.1/go.mod h1:38BX04sncJe191asQ4rU/EeYyVflybkU0VN4LDvLKps= github.com/LumeraProtocol/rq-go v0.2.1 h1:8B3UzRChLsGMmvZ+UVbJsJj6JZzL9P9iYxbdUwGsQI4= github.com/LumeraProtocol/rq-go v0.2.1/go.mod h1:APnKCZRh1Es2Vtrd2w4kCLgAyaL5Bqrkz/BURoRJ+O8= -github.com/LumeraProtocol/supernode/v2 v2.4.23 h1:vkOJwVtXvuosh4q1YV2E9AVqzXzoREM5EFxh7XWEJ4Y= -github.com/LumeraProtocol/supernode/v2 v2.4.23/go.mod h1:2juzppFSk/vP0kRsROIRxqc4WHBfm3dq9twD6KndWrA= +github.com/LumeraProtocol/supernode/v2 v2.4.26 h1:u2DuX8sJoIHl2Wlr8Aa08D2prPX40hNjkvNiqZ/X+n4= +github.com/LumeraProtocol/supernode/v2 v2.4.26/go.mod h1:2juzppFSk/vP0kRsROIRxqc4WHBfm3dq9twD6KndWrA= github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= diff --git a/pkg/crypto/ethsecp256k1/ethsecp256k1.go b/pkg/crypto/ethsecp256k1/ethsecp256k1.go index 7e3a218..61a791d 100644 --- a/pkg/crypto/ethsecp256k1/ethsecp256k1.go +++ b/pkg/crypto/ethsecp256k1/ethsecp256k1.go @@ -122,8 +122,11 @@ func (pubKey *PubKey) VerifySignature(msg, sig []byte) bool { if len(sig) == crypto.SignatureLength { sig = sig[:crypto.SignatureLength-1] // remove recovery ID } - hash := crypto.Keccak256(msg) - return crypto.VerifySignature(pubKey.Key, hash, sig) + //hash := crypto.Keccak256(msg) + //return crypto.VerifySignature(pubKey.Key, hash, sig) + // Use SHA256 like standard Cosmos, NOT Keccak256 + hash := sha256.Sum256(msg) + return crypto.VerifySignature(pubKey.Key, hash[:], sig) } // Proto methods From ff9597a76754ca0b3bf88a5a6c13486a48244363 Mon Sep 17 00:00:00 2001 From: a-ok123 Date: Thu, 29 Jan 2026 23:32:32 -0500 Subject: [PATCH 3/3] test ethsecp256k1 --- examples/ica-request-verify/main.go | 43 ++++++++++++++----------- pkg/crypto/ethsecp256k1/ethsecp256k1.go | 3 -- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/examples/ica-request-verify/main.go b/examples/ica-request-verify/main.go index 7400394..439e882 100644 --- a/examples/ica-request-verify/main.go +++ b/examples/ica-request-verify/main.go @@ -20,9 +20,11 @@ import ( "github.com/LumeraProtocol/sdk-go/constants" "github.com/LumeraProtocol/sdk-go/ica" sdkcrypto "github.com/LumeraProtocol/sdk-go/pkg/crypto" + "github.com/LumeraProtocol/sdk-go/pkg/crypto/ethsecp256k1" sdktypes "github.com/LumeraProtocol/sdk-go/types" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/types/tx/signing" ) // This example builds an ICS-27 MsgSendTx that executes one or more @@ -135,25 +137,28 @@ func main() { appPubkey = pub.Bytes() fmt.Printf("Using ICA with address %s and app pubkey %X\n", *icaAddress, appPubkey) - //test - //addr, err := rec.GetAddress() - //if err != nil { - // fmt.Printf("get address: %v\n", err) - // os.Exit(1) - //} - //st := "Test string" - //b := []byte(st) - //sig, _, err := kr.SignByAddress(addr, b, signing.SignMode_SIGN_MODE_DIRECT) - //if err != nil { - // fmt.Printf("sign: %v\n", err) - // os.Exit(1) - //} - //fmt.Printf("Signature: %X\n", sig) - // - //appPk := secp256k1.PubKey{Key: appPubkey} - //pubKey := &appPk - //valid := pubKey.VerifySignature(b, sig) - //fmt.Printf("Verify Signature: %v\n", valid) + /////////////////////////////////////////////////////// + //test ethsecp256k1 + addr, err := rec.GetAddress() + if err != nil { + fmt.Printf("get address: %v\n", err) + os.Exit(1) + } + st := "Test string" + b := []byte(st) + sig, _, err := kr.SignByAddress(addr, b, signing.SignMode_SIGN_MODE_DIRECT) + if err != nil { + fmt.Printf("sign: %v\n", err) + os.Exit(1) + } + fmt.Printf("Signature: %X\n", sig) + + appPk := ethsecp256k1.PubKey{Key: appPubkey} + pubKey := &appPk + valid := pubKey.VerifySignature(b, sig) + fmt.Printf("PubKey type - %s, Key Name - %s\n", appPk.Type(), rec.Name) + fmt.Printf("Verify Signature: %v\n", valid) + /////////////////////////////////////////////////////// } // Build one MsgRequestAction per file diff --git a/pkg/crypto/ethsecp256k1/ethsecp256k1.go b/pkg/crypto/ethsecp256k1/ethsecp256k1.go index 61a791d..52701c8 100644 --- a/pkg/crypto/ethsecp256k1/ethsecp256k1.go +++ b/pkg/crypto/ethsecp256k1/ethsecp256k1.go @@ -27,7 +27,6 @@ var ( _ cryptotypes.PubKey = &PubKey{} ) -// PrivKey defines a secp256k1 private key using Ethereum's Keccak256 hashing type PrivKey struct { Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` } @@ -122,8 +121,6 @@ func (pubKey *PubKey) VerifySignature(msg, sig []byte) bool { if len(sig) == crypto.SignatureLength { sig = sig[:crypto.SignatureLength-1] // remove recovery ID } - //hash := crypto.Keccak256(msg) - //return crypto.VerifySignature(pubKey.Key, hash, sig) // Use SHA256 like standard Cosmos, NOT Keccak256 hash := sha256.Sum256(msg) return crypto.VerifySignature(pubKey.Key, hash[:], sig)