| name | crypto-recon-web3-cosmos |
| description | Specialist for Cosmos/IBC and Polkadot/Substrate cryptography in JS bundles. Covers CosmJS, Keplr, Amino/Protobuf encoding, Sr25519/Ed25519, SCALE encoding, and all ecosystem wallets. |
Crypto Recon — Cosmos & Polkadot Specialist
Scope
Two major non-EVM ecosystems with different serialization:
Cosmos Ecosystem:
- Cosmos Hub, Osmosis, Juno, Injective, Celestia, dYdX, Evmos
- Library:
@cosmjs/*, cosmjs-types
- Encoding: Amino (JSON) + Protobuf (binary)
- Signing: secp256k1 (same curve as EVM, different encoding) or Ed25519
Polkadot/Substrate Ecosystem:
- Polkadot, Kusama, Acala, Moonbeam, Astar, any Substrate chain
- Library:
@polkadot/api, @polkadot/keyring
- Encoding: SCALE (Simple Concatenated Aggregate Little-Endian)
- Signing: Sr25519 (Schnorrkel, NOT standard secp256k1), Ed25519, ECDSA
Cosmos secp256k1 ≠ EVM secp256k1. Same curve, DIFFERENT message format.
EVM: keccak256(message) + personal_sign prefix
Cosmos: SHA256(SHA256(amino_encoded_message)) for Amino, or Protobuf
COSMOS ECOSYSTEM
Libraries
import { SigningStargateClient, StargateClient, GasPrice } from '@cosmjs/stargate'
import { SigningCosmWasmClient, CosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { DirectSecp256k1HdWallet, DirectSecp256k1Wallet,
OfflineAminoSigner, makeCosmoshubPath } from '@cosmjs/proto-signing'
import { AminoTypes, createDefaultAminoConverters } from '@cosmjs/stargate'
import { Registry, makeAuthInfoBytes, makeSignDoc } from '@cosmjs/proto-signing'
import { coins, coin } from '@cosmjs/amino'
import { Secp256k1, sha256, ripemd160 } from '@cosmjs/crypto'
import { Bech32 } from '@cosmjs/encoding'
window.keplr.enable(chainId)
window.keplr.getOfflineSigner(chainId)
window.keplr.getOfflineSignerOnlyAmino(chainId)
window.keplr.signAmino(chainId, signer, signDoc)
window.keplr.signDirect(chainId, signer, signDoc)
window.leap.getOfflineSigner(chainId)
window.cosmostation.providers.keplr
Transaction Signing
Direct Signing (Protobuf — modern)
const client = await SigningStargateClient.connectWithSigner(rpcUrl, signer)
const msg = {
typeUrl: "/cosmos.bank.v1beta1.MsgSend",
value: { fromAddress, toAddress, amount: [{ denom: "uatom", amount: "1000" }] }
}
const result = await client.signAndBroadcast(fromAddress, [msg], fee, memo)
const signDoc = makeSignDoc(bodyBytes, authInfoBytes, chainId, accountNumber, sequence)
const { signed, signature } = await signer.signDirect(address, signDoc)
Amino Signing (legacy, still widely used)
const aminoSignDoc = {
chain_id: "cosmoshub-4",
account_number: "12345",
sequence: "0",
fee: { amount: [{ denom: "uatom", amount: "500" }], gas: "200000" },
msgs: [{ type: "cosmos-sdk/MsgSend", value: { from_address, to_address, amount: [...] } }],
memo: ""
}
const { signed, signature } = await signer.signAmino(address, aminoSignDoc)
Key Derivation (Cosmos HD Wallet)
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, {
prefix: "cosmos",
hdPaths: [makeCosmoshubPath(0)]
})
DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { prefix: "osmo" })
const wallet = await DirectSecp256k1Wallet.fromKey(privkey, "cosmos")
CosmWasm Contract Interaction
const result = await client.execute(
senderAddress,
contractAddress,
{ transfer: { recipient, amount } },
fee,
memo,
funds
)
const result = await client.queryContractSmart(contractAddress, { balance: { address } })
For each contract interaction found:
- Extract the ExecuteMsg JSON structure verbatim
- This is the equivalent of ABI encoding in Cosmos
Cosmos Address Derivation
const pubkeyHash = ripemd160(sha256(compressedPubkey))
const address = Bech32.encode("cosmos", pubkeyHash)
ArbitraryMessage / ADR-036 (off-chain signing)
await window.keplr.signArbitrary(chainId, signer, message)
const signDoc = {
chain_id: "",
account_number: "0",
sequence: "0",
fee: { gas: "0", amount: [] },
msgs: [{ type: "sign/MsgSignData", value: { signer: address, data: btoa(message) } }],
memo: ""
}
POLKADOT / SUBSTRATE ECOSYSTEM
Libraries
import { ApiPromise, WsProvider, Keyring } from '@polkadot/api'
import { web3Accounts, web3Enable, web3FromAddress } from '@polkadot/extension-dapp'
import { cryptoWaitReady, mnemonicGenerate, mnemonicToSeed } from '@polkadot/util-crypto'
import { hexToU8a, u8aToHex, stringToU8a } from '@polkadot/util'
import { TypeRegistry, Metadata } from '@polkadot/types'
import { createType, Vec, Struct, u64, u128, AccountId } from '@polkadot/types'
Transaction Signing
await web3Enable('MyApp')
const accounts = await web3Accounts()
const injector = await web3FromAddress(address)
const api = await ApiPromise.create({ provider: new WsProvider(wsUrl) })
const tx = api.tx.balances.transfer(recipient, 1000000000000)
const hash = await tx.signAndSend(address, { signer: injector.signer })
const { signature } = await injector.signer.signPayload({
address,
blockHash, blockNumber, era, genesisHash,
method: tx.toHex(),
nonce, specVersion, tip, transactionVersion
})
SCALE Encoding (Substrate-specific)
const encoded = api.createType('(u64, AccountId, u128)', [1n, address, amount]).toU8a()
const methodHex = api.tx.balances.transfer(recipient, amount).method.toHex()
Sr25519 (Schnorrkel) — Polkadot's default
const keyring = new Keyring({ type: 'sr25519' })
const pair = keyring.addFromUri('//Alice')
const pair = keyring.addFromMnemonic(mnemonic)
const pair = keyring.addFromSeed(seedHex)
const keyring = new Keyring({ type: 'ed25519' })
const keyring = new Keyring({ type: 'ecdsa' })
const signature = pair.sign(message)
const isValid = pair.verify(message, signature, pair.publicKey)
Pallet Call Decoding
api.tx.balances.transfer
api.tx.contracts.call
api.tx.staking.bond
const call = api.createType('Call', hexCallData)
console.log(call.method)
console.log(call.args)
Analysis Steps
Step 1: Identify ecosystem
@cosmjs/ → Cosmos
@polkadot/ → Polkadot/Substrate
window.keplr → Cosmos (Keplr wallet)
web3Enable → Polkadot (extension API)
Step 2: Find signing method
- Cosmos:
signDirect vs signAmino — both have different message formats
- Polkadot:
signPayload with extrinsic payload
Step 3: Extract message structure
- Cosmos Amino: extract the
msgs array verbatim — each msg has type and value
- Cosmos Protobuf: extract
typeUrl and value object
- CosmWasm: extract ExecuteMsg JSON
- Polkadot: extract the extrinsic method call and args
Step 4: Find chain configuration
const chainId = "cosmoshub-4"
const prefix = "cosmos"
const wsUrl = "wss://rpc.polkadot.io"
const specVersion
Step 5: Hardcoded credentials
Cosmos mnemonic: 12/24 BIP39 words
Polkadot mnemonic: 12/24 BIP39 words + optional "//password" suffix
Polkadot seed: "0x" + 64 hex chars
Substrate dev accounts: "//Alice", "//Bob" hardcoded → flag
Output Format
COSMOS/POLKADOT FINDINGS
=========================
[C1] Cosmos Direct Signing — HIGH
Ecosystem: Cosmos (Osmosis)
Chain ID: osmosis-1
File: chunks/swap.js:2201
Method: SigningStargateClient.signAndBroadcast
Msg types: /osmosis.gamm.v1beta1.MsgSwapExactAmountIn
Wallet: Keplr (window.keplr.signDirect)
Message structure:
tokenIn: { denom: "uosmo", amount: "1000000" }
routes: [{ poolId, tokenOutDenom }]
tokenOutMinAmount: "950000"
[C2] CosmWasm ExecuteMsg — HIGH
Ecosystem: Cosmos (Juno)
Contract: juno1abc...
File: pages/stake.js:441
Msg: { "stake": { "validator": "junovaloper1...", "amount": "1000000" } }
Callers: stakeTokens():890
[P1] Polkadot Sr25519 Extrinsic — HIGH
Ecosystem: Polkadot/Substrate
Chain: wss://rpc.polkadot.io
File: chunks/transfer.js:3301
Call: balances.transfer
Args: [dest: MultiAddress, value: Compact<u128>]
Signing: Sr25519 via Polkadot.js extension
SCALE: 0x0400 + MultiAddress_bytes + Compact(amount)
Python Reconstruction
import json, hashlib
from bech32 import bech32_encode, convertbits
from ecdsa import SigningKey, SECP256k1
def cosmos_amino_sign_doc(chain_id, account_number, sequence, msgs, fee, memo=""):
doc = {
"chain_id": chain_id,
"account_number": str(account_number),
"sequence": str(sequence),
"fee": fee,
"msgs": msgs,
"memo": memo
}
canonical = json.dumps(doc, sort_keys=True, separators=(',', ':'), ensure_ascii=False)
return canonical.encode()
def cosmos_sign(private_key_bytes: bytes, sign_bytes: bytes) -> bytes:
msg_hash = hashlib.sha256(hashlib.sha256(sign_bytes).digest()).digest()
sk = SigningKey.from_string(private_key_bytes, curve=SECP256k1)
return sk.sign_digest(msg_hash)
from substrateinterface import SubstrateInterface, Keypair
substrate = SubstrateInterface(url="wss://rpc.polkadot.io")
keypair = Keypair.create_from_mnemonic(mnemonic)
call = substrate.compose_call(
call_module='Balances',
call_function='transfer',
call_params={'dest': recipient, 'value': 1000000000000}
)
extrinsic = substrate.create_signed_extrinsic(call=call, keypair=keypair)
receipt = substrate.submit_extrinsic(extrinsic)