Cosmos SDK 对所有面向用户的地址都使用 Bech32 地址格式。Bech32 编码通过校验和提供可靠的完整性检查,并包含一个人类可读前缀(HRP),用于提供地址类型的上下文信息。

地址类型

SDK 定义了三种不同的地址类型,每种类型都有各自的 Bech32 前缀:
地址类型Bech32 前缀示例用途
账户地址cosmoscosmos1r5v5sr...用户账户、余额、交易
验证者操作员地址cosmosvalopercosmosvaloper1r5v5sr...验证者操作员身份、质押操作
共识地址cosmosvalconscosmosvalcons1r5v5sr...验证者参与共识、区块签名
每种地址类型也都有对应的公钥前缀:
  • 账户公钥:cosmospub
  • 验证者公钥:cosmosvaloperpub
  • 共识公钥:cosmosvalconspub

支持的密钥方案

Cosmos SDK 支持三种密钥方案。所选方案会影响地址长度,以及它是否可用于交易或共识:
地址长度(字节)公钥长度(字节)用于交易认证用于共识(CometBFT)
secp256k12033是否
secp256r13233是否
tm-ed25519— 不使用 —32否是
secp256k1 是用户账户的默认方案。secp256r1 作为替代方案受支持,并会生成更长的地址(32 字节)。tm-ed25519 仅用于验证者共识密钥,不会生成面向用户的地址。

地址派生

地址通过对公钥进行密码学哈希得到。具体过程因密钥算法而异:

Secp256k1 密钥(账户地址)

账户地址使用类似 Bitcoin 的地址派生方式:
1. Public Key: 33 bytes (compressed secp256k1 public key)
2. SHA-256 hash of public key: 32 bytes
3. RIPEMD-160 hash of result: 20 bytes (final address)
实现: crypto/keys/secp256k1/secp256k1.go
func (pubKey *PubKey) Address() crypto.Address {
    sha := sha256.Sum256(pubKey.Key)           // Step 1: SHA-256
    hasherRIPEMD160 := ripemd160.New()
    hasherRIPEMD160.Write(sha[:])
    return hasherRIPEMD160.Sum(nil)            // Step 2: RIPEMD-160 = 20 bytes
}

Ed25519 密钥(共识地址)

共识地址使用截断的 SHA-256:
1. Public Key: 32 bytes (Ed25519 public key)
2. SHA-256 hash, truncated to first 20 bytes
实现: crypto/keys/ed25519/ed25519.go
func (pubKey *PubKey) Address() crypto.Address {
    return crypto.Address(tmhash.SumTruncated(pubKey.Key))  // SHA-256-20
}

Bech32 编码过程

在派生出地址字节后,会将其转换为 Bech32 格式: 第 1 步:从 8 位编码转换为 5 位编码
// Address bytes (20 bytes = 160 bits)
addressBytes := []byte{0x12, 0x34, ..., 0xab}  // 20 bytes

// Convert to 5-bit groups for Bech32
converted, _ := bech32.ConvertBits(addressBytes, 8, 5, true)
第 2 步:使用人类可读前缀进行编码
// Combine HRP with converted bytes
bech32Address, _ := bech32.Encode("cosmos", converted)
// Result: "cosmos1r5v5srda7xfth3uckstjst6k05kmeyzptewwdk"
实现: types/bech32/bech32.go

配置 Bech32 前缀

每个 Cosmos SDK 应用都会在启动时通过 sdk.GetConfig() 一次性设置自己的 Bech32 前缀和 SLIP-44 coin type,然后封存配置,使其在运行时无法再被修改。默认值(cosmos、cosmosvaloper 等)定义在 types/config.go 中。链开发者会在应用启动前覆盖这些值:
config := sdk.GetConfig()
config.SetBech32PrefixForAccount("cosmos", "cosmospub")
config.SetBech32PrefixForValidator("cosmosvaloper", "cosmosvaloperpub")
config.SetBech32PrefixForConsensusNode("cosmosvalcons", "cosmosvalconspub")
config.SetCoinType(118) // SLIP-44 coin type
config.Seal()

地址校验

SDK 会通过以下方式校验地址:
  1. 格式校验:确保是有效的 Bech32 编码
  2. 前缀校验:确认地址类型对应的 HRP 正确
  3. 长度校验:验证解码后的地址长度恰好为 20 字节
func (bc Bech32Codec) StringToBytes(text string) ([]byte, error) {
    hrp, bz, err := bech32.DecodeAndConvert(text)
    if err != nil {
        return nil, err
    }

    if hrp != bc.Bech32Prefix {
        return nil, fmt.Errorf("invalid prefix")
    }

    return bz, sdk.VerifyAddressFormat(bz)  // Checks length = 20 bytes
}

模块地址

模块账户使用 ADR-028 中定义的确定性地址派生方式:
// Module address without derivation keys
func Module(moduleName string) []byte {
    return crypto.AddressHash([]byte(moduleName))
}

// Module address with derivation keys (new method)
func Module(moduleName string, derivationKeys ...[]byte) []byte {
    mKey := append([]byte(moduleName), 0)  // Null byte separator
    addr := Hash("module", append(mKey, derivationKeys[0]...))
    return addr  // 32 bytes (not 20 bytes like user addresses)
}
模块地址更长(32 字节,而不是用户地址的 20 字节),以降低碰撞概率。

验证者地址之间的关系

一个验证者有三个相关地址:
  1. 操作员地址(cosmosvaloper1...):验证者的运营身份,由操作员账户密钥派生
  2. 共识地址(cosmosvalcons1...):由验证者的共识公钥(Ed25519)派生,用于区块签名
  3. 账户地址(cosmos1...):操作员用于接收奖励的账户
// Validator stores its consensus pubkey
type Validator struct {
    OperatorAddress string    // cosmosvaloper1... (from operator's account)
    ConsensusPubkey *Any      // Ed25519 public key for signing
    // ...
}

// Consensus address is derived from the consensus pubkey
func (v Validator) GetConsAddr() ([]byte, error) {
    pk := v.ConsensusPubkey.GetCachedValue().(cryptotypes.PubKey)
    return pk.Address().Bytes(), nil  // SHA-256-20 of Ed25519 pubkey
}

性能:地址缓存

SDK 会缓存 Bech32 编码后的地址,以优化重复转换的开销:
var (
    accAddrCache  *simplelru.LRU  // 60,000 entries
    valAddrCache  *simplelru.LRU  // 500 entries
    consAddrCache *simplelru.LRU  // 500 entries
)
当调用 Address.String() 时,SDK 会:
  1. 检查 LRU 缓存中是否已有已编码地址
  2. 如果找到则直接返回缓存值
  3. 否则执行 Bech32 编码并缓存结果
这会显著提升区块处理和状态查询期间的性能。

完整示例

下面是创建账户地址的完整流程:
// 1. Generate keypair
privKey := secp256k1.GenPrivKey()          // 32 bytes
pubKey := privKey.PubKey()                 // 33 bytes (compressed)

// 2. Derive address bytes
sha := sha256.Sum256(pubKey.Bytes())       // 32 bytes
ripemd := ripemd160.Sum(sha[:])            // 20 bytes
addrBytes := ripemd[:]

// 3. Create AccAddress type
accAddr := sdk.AccAddress(addrBytes)

// 4. Convert to Bech32 string
// Internally: bech32.ConvertAndEncode("cosmos", addrBytes)
addressStr := accAddr.String()
// Result: "cosmos1r5v5srda7xfth3uckstjst6k05kmeyzptewwdk"

// 5. Use in account
account := auth.NewBaseAccount(accAddr, pubKey, accountNumber, sequence)

相关概念

  • 账户 - 理解账户类型与管理方式
  • Store - 地址如何作为键用于状态存储
  • 交易 - 地址如何用于交易签名

The Cosmos SDK uses the Bech32 address format for all user-facing addresses. Bech32 encoding provides robust integrity checks through checksums and includes a human-readable prefix (HRP) that provides contextual information about the address type.

Address Types

The SDK defines three distinct address types, each with its own Bech32 prefix:
Address TypeBech32 PrefixExamplePurpose
Account Addresscosmoscosmos1r5v5sr...User accounts, balances, transactions
Validator Operator Addresscosmosvalopercosmosvaloper1r5v5sr...Validator operator identity, staking operations
Consensus Addresscosmosvalconscosmosvalcons1r5v5sr...Validator consensus participation, block signing
Each address type also has a corresponding public key prefix:
  • Account public keys: cosmospub
  • Validator public keys: cosmosvaloperpub
  • Consensus public keys: cosmosvalconspub

Supported Key Schemes

The Cosmos SDK supports three key schemes. The choice of scheme affects address length and whether it can be used for transactions or consensus:
Address length in bytesPublic key length in bytesUsed for transaction authenticationUsed for consensus (CometBFT)
secp256k12033yesno
secp256r13233yesno
tm-ed25519— not used —32noyes
secp256k1 is the default for user accounts. secp256r1 is supported as an alternative and produces longer addresses (32 bytes). tm-ed25519 is used exclusively for validator consensus keys and does not produce a user-facing address.

Address Derivation

Addresses are derived from public keys through cryptographic hashing. The process differs based on the key algorithm:

Secp256k1 Keys (Account Addresses)

Account addresses use Bitcoin-style address derivation:
1. Public Key: 33 bytes (compressed secp256k1 public key)
2. SHA-256 hash of public key: 32 bytes
3. RIPEMD-160 hash of result: 20 bytes (final address)
Implementation: crypto/keys/secp256k1/secp256k1.go
func (pubKey *PubKey) Address() crypto.Address {
    sha := sha256.Sum256(pubKey.Key)           // Step 1: SHA-256
    hasherRIPEMD160 := ripemd160.New()
    hasherRIPEMD160.Write(sha[:])
    return hasherRIPEMD160.Sum(nil)            // Step 2: RIPEMD-160 = 20 bytes
}

Ed25519 Keys (Consensus Addresses)

Consensus addresses use truncated SHA-256:
1. Public Key: 32 bytes (Ed25519 public key)
2. SHA-256 hash, truncated to first 20 bytes
Implementation: crypto/keys/ed25519/ed25519.go
func (pubKey *PubKey) Address() crypto.Address {
    return crypto.Address(tmhash.SumTruncated(pubKey.Key))  // SHA-256-20
}

Bech32 Encoding Process

Once address bytes are derived, they’re converted to Bech32 format: Step 1: Convert from 8-bit to 5-bit encoding
// Address bytes (20 bytes = 160 bits)
addressBytes := []byte{0x12, 0x34, ..., 0xab}  // 20 bytes

// Convert to 5-bit groups for Bech32
converted, _ := bech32.ConvertBits(addressBytes, 8, 5, true)
Step 2: Encode with Human-Readable Prefix
// Combine HRP with converted bytes
bech32Address, _ := bech32.Encode("cosmos", converted)
// Result: "cosmos1r5v5srda7xfth3uckstjst6k05kmeyzptewwdk"
Implementation: types/bech32/bech32.go

Configuring Bech32 prefixes

Every Cosmos SDK application sets its Bech32 prefixes and SLIP-44 coin type once at startup via sdk.GetConfig(), then seals the config so it cannot be changed at runtime. The defaults (cosmos, cosmosvaloper, etc.) are defined in types/config.go. Chain developers override them before the app starts:
config := sdk.GetConfig()
config.SetBech32PrefixForAccount("cosmos", "cosmospub")
config.SetBech32PrefixForValidator("cosmosvaloper", "cosmosvaloperpub")
config.SetBech32PrefixForConsensusNode("cosmosvalcons", "cosmosvalconspub")
config.SetCoinType(118) // SLIP-44 coin type
config.Seal()

Address Validation

The SDK validates addresses through:
  1. Format validation: Ensures valid Bech32 encoding
  2. Prefix validation: Confirms correct HRP for address type
  3. Length validation: Verifies address is exactly 20 bytes when decoded
func (bc Bech32Codec) StringToBytes(text string) ([]byte, error) {
    hrp, bz, err := bech32.DecodeAndConvert(text)
    if err != nil {
        return nil, err
    }

    if hrp != bc.Bech32Prefix {
        return nil, fmt.Errorf("invalid prefix")
    }

    return bz, sdk.VerifyAddressFormat(bz)  // Checks length = 20 bytes
}

Module Addresses

Module accounts use deterministic address derivation defined in ADR-028:
// Module address without derivation keys
func Module(moduleName string) []byte {
    return crypto.AddressHash([]byte(moduleName))
}

// Module address with derivation keys (new method)
func Module(moduleName string, derivationKeys ...[]byte) []byte {
    mKey := append([]byte(moduleName), 0)  // Null byte separator
    addr := Hash("module", append(mKey, derivationKeys[0]...))
    return addr  // 32 bytes (not 20 bytes like user addresses)
}
Module addresses are longer (32 bytes vs 20 bytes) to reduce collision probability.

Validator Address Relationships

A validator has three related addresses:
  1. Operator Address (cosmosvaloper1...): The validator’s operational identity, derived from the operator’s account key
  2. Consensus Address (cosmosvalcons1...): Derived from the validator’s consensus public key (Ed25519), used for block signing
  3. Account Address (cosmos1...): The operator’s account for receiving rewards
// Validator stores its consensus pubkey
type Validator struct {
    OperatorAddress string    // cosmosvaloper1... (from operator's account)
    ConsensusPubkey *Any      // Ed25519 public key for signing
    // ...
}

// Consensus address is derived from the consensus pubkey
func (v Validator) GetConsAddr() ([]byte, error) {
    pk := v.ConsensusPubkey.GetCachedValue().(cryptotypes.PubKey)
    return pk.Address().Bytes(), nil  // SHA-256-20 of Ed25519 pubkey
}

Performance: Address Caching

The SDK caches Bech32-encoded addresses to optimize repeated conversions:
var (
    accAddrCache  *simplelru.LRU  // 60,000 entries
    valAddrCache  *simplelru.LRU  // 500 entries
    consAddrCache *simplelru.LRU  // 500 entries
)
When Address.String() is called, the SDK:
  1. Checks the LRU cache for the encoded address
  2. Returns cached value if found
  3. Otherwise, performs Bech32 encoding and caches the result
This significantly improves performance during block processing and state queries.

Complete Example

Here’s the full pipeline for creating an account address:
// 1. Generate keypair
privKey := secp256k1.GenPrivKey()          // 32 bytes
pubKey := privKey.PubKey()                 // 33 bytes (compressed)

// 2. Derive address bytes
sha := sha256.Sum256(pubKey.Bytes())       // 32 bytes
ripemd := ripemd160.Sum(sha[:])            // 20 bytes
addrBytes := ripemd[:]

// 3. Create AccAddress type
accAddr := sdk.AccAddress(addrBytes)

// 4. Convert to Bech32 string
// Internally: bech32.ConvertAndEncode("cosmos", addrBytes)
addressStr := accAddr.String()
// Result: "cosmos1r5v5srda7xfth3uckstjst6k05kmeyzptewwdk"

// 5. Use in account
account := auth.NewBaseAccount(accAddr, pubKey, accountNumber, sequence)
  • Accounts - Understanding account types and management
  • Store - How addresses are used as keys in state storage
  • Transactions - How addresses are used in transaction signing