☰
Cosmos SDK 账户体系全解析:密钥派生、地址编码与 Keyring 自定义签名算法
2026/10/12 2:10:06 网站建设 项目流程
  • 区块链

【免费下载链接】cosmos-sdk

Framework for building performant, customizable blockchains with native interoperability

项目地址:https://gitcode.com/gh_mirrors/co/cosmos-sdk
点击查看免费下载

本指南以 Cosmos SDK 内置的账户与公钥体系为核心(对应官方文档 docs/docs/learn/beginner/03-accounts.md),系统讲解账户的构成、HD 钱包的 BIP32/BIP44 派生过程、三类链上地址的 Bech32 编码规则,以及Keyring密钥管理器的核心接口与扩展方式。读完本文,你将掌握 Cosmos SDK 中从「助记词 → 私钥 → 公钥 → 地址」的完整链路,理解各密钥方案的适用场景,并具备为 Keyring 注册自定义签名算法(如 secp256r1)的实战能力。

账户定义:公钥、私钥与地址的三角关系

在 Cosmos SDK 中,一个account(账户)本质上是一对密钥:公钥PubKey与私钥PrivKey。

  • PubKey可以被派生(哈希)出各种Addresses,这些地址用于在应用内标识用户及其他参与方。Addresses同时与 message 关联,用于标识消息的发送者。
  • PrivKey用于生成数字签名,以证明某个与该PrivKey关联的Address确实批准了给定的message。

从源码结构看,这一对密钥类型在 crypto/types/types.go 中定义:

  • PubKey接口扩展了proto.Message(因为公钥需要被持久化到 store 中),并提供Address()、Bytes()、VerifySignature(msg, sig []byte) bool、Type()等方法;
  • PrivKey接口扩展proto.Message与LedgerPrivKey,提供Sign(msg []byte) ([]byte, error)与PubKey()等方法。

HD 钱包:从助记词到多层账户的 BIP32/BIP44 派生

对于 HD(Hierarchical Deterministic,分层确定性)密钥派生,Cosmos SDK 采用 BIP32 标准。BIP32 允许用户创建 BIP44 规范所定义的 HD 钱包——即一组由同一个初始秘密种子派生出的账户集合。

一个种子通常由12 或 24 个单词的助记词(mnemonic)生成。利用单向密码学函数,同一个种子可以派生任意数量的PrivKey,再由PrivKey派生PubKey。助记词是最敏感的信息——只要助记词被妥善保存,私钥就能随时重新生成。

下图完整展示了从助记词到各个账户地址的分层派生关系:

Account 0 Account 1 Account 2 +------------------+ +------------------+ +------------------+ | | | | | | | Address 0 | | Address 1 | | Address 2 | | ^ | | ^ | | ^ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | + | | + | | + | | Public key 0 | | Public key 1 | | Public key 2 | | ^ | | ^ | | ^ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | + | | + | | + | | Private key 0 | | Private key 1 | | Private key 2 | | ^ | | ^ | | ^ | +------------------+ +------------------+ +------------------+ | | | | | | | | | +--------------------------------------------------------------------+ | | +---------+---------+ | | | Master PrivKey | | | +-------------------+ | | +---------+---------+ | | | Mnemonic (Seed) | | | +-------------------+

从源码层面看,BIP44 路径的解析与派生实现在 crypto/hd/hdpath.go:

  • NewParamsFromPath(crypto/hd/hdpath.go#L30-L101)将形如m/44'/118'/0'/0/0的路径拆分为 purpose、coinType、account、change、addressIndex 五个字段,并严格校验:首字段必须为44',第二、三字段必须是 hardened(带'后缀),第四、五字段不能 hardened,change 只能是 0 或 1;
  • ComputeMastersFromSeed使用 HMAC-SHA512 与"Bitcoin seed"曲线标识符从种子计算主私钥与链码;
  • DerivePrivateKeyForPath沿路径逐级执行derivePrivateKey,其中 hardened 派生在索引上加上0x80000000并使用私钥本身参与 HMAC,非 hardened 派生则使用压缩公钥参与。

在 Cosmos SDK 中,密钥由名为Keyring的对象统一存储与管理。

数字签名与支持的密钥方案

用户认证的主要方式是数字签名:用户用自己的私钥签署交易,验证方用对应的公钥验签。为了在链上进行签名验证,公钥会随同其他交易校验所需数据一起存储在一个Account对象中。在节点中,所有数据均使用 Protocol Buffers 序列化存储。

Cosmos SDK 支持以下用于创建数字签名的密钥方案:

地址长度(字节)公钥长度(字节)用于交易认证用于共识(CometBFT)
secp256k12033yesno
secp256r13233yesno
tm-ed25519-- 不使用 --32noyes

几点说明:

  • secp256k1实现位于 crypto/keys/secp256k1,是最常用的账户签名算法;
  • secp256r1(NIST P-256 曲线)实现位于 crypto/keys/secp256r1,同样可用于交易认证;
  • tm-ed25519实现位于 crypto/keys/ed25519,仅用于共识验证,不作为普通用户交易签名方案。

需要留意版本差异:当前仓库的 crypto/hd/algo.go#L13-L29 还定义了bls12_381与ml_dsa_65(NIST ML-DSA-65,FIPS 204 后量子签名方案)等PubKeyType,其中 ML-DSA-65 已可用于软件 Keyring 的账户密钥生成。

地址体系:AccAddress、ValAddress 与 ConsAddress

Addresses与PubKey都是标识应用中参与方的公开信息,而Account用于存储认证信息,其基本实现是BaseAccount对象。每个账户用Address(一串从公钥派生的字节序列)来标识。Cosmos SDK 定义了3 种地址类型,用于指明账户的使用场景:

  • AccAddress:标识用户(即message的发送者);
  • ValAddress:标识验证人操作者(validator operator);
  • ConsAddress:标识参与共识的验证人节点,验证人节点使用ed25519曲线派生。

这三种类型都实现Address接口(见 types/address.go#L126-L134):

// Address is a common interface for different types of addresses used by the SDK type Address interface { Equals(Address) bool Empty() bool Marshal() ([]byte, error) MarshalJSON() ([]byte, error) Bytes() []byte String() string Format(s fmt.State, verb rune) }

在 types/address.go#L136-L141 中,AccAddress、ValAddress、ConsAddress三个类型均被断言实现了该接口。地址构造算法定义于 ADR-28,从公钥pub获取账户地址的标准写法是:

sdk.AccAddress(pub.Address().Bytes())

值得注意的是,Marshal()与Bytes()返回的都是地址的原始[]byte形式——Marshal()的存在只是为了满足 Protobuf 兼容性要求。

Bech32 编码与地址前缀

在与用户交互时,地址使用 Bech32)。Bech32 是与区块链交互时唯一支持的地址格式,其人类可读部分(HRP,即 Bech32 前缀)用于标识地址类型:

地址 Bech32 前缀
Accountscosmos
Validator Operatorcosmosvaloper
Consensus Nodescosmosvalcons

从前缀定义源码(types/address.go#L38-L76)可以看到这些前缀的拼接规则:主前缀cosmos(Bech32MainPrefix)加上valoper/valcons等子前缀,例如Bech32PrefixValAddr = Bech32MainPrefix + PrefixValidator + PrefixOperator。同时 SDK 还定义了对应的公钥前缀(cosmospub、cosmosvaloperpub、cosmosvalconspub),用于 Bech32 编码公钥。

AccAddressFromBech32(types/address.go#L193-L212)则是反向解析:先用当前配置中的账户前缀通过GetFromBech32解码,再调用VerifyAddressFormat校验长度(不能为空、不能超过address.MaxAddrLen)。此外 types/address.go#L100-L123 显示 SDK 为三类地址分别维护了 LRU 缓存(账户地址 60000 条、验证人与共识地址各 500 条),用于加速高频的地址字符串化操作。

公钥(Public Keys)

Cosmos SDK 中的公钥由cryptotypes.PubKey接口定义。由于公钥需要存入 store,cryptotypes.PubKey扩展了proto.Message接口(见 crypto/types/types.go#L8-L17):

// PubKey defines a public key and extends proto.Message. type PubKey interface { proto.Message Address() Address Bytes() []byte VerifySignature(msg, sig []byte) bool Equals(PubKey) bool Type() string }

secp256k1与secp256r1使用压缩格式序列化:

  • 若y坐标是两个与x坐标相关联的值中字典序较大的那个,首字节为0x02;
  • 否则首字节为0x03;
  • 该前缀之后紧跟x坐标。

公钥不用于引用账户(或用户),一般来说也不在构造交易消息时使用(少数例外:MsgCreateValidator、Validator与Multisig消息)。在与用户交互时,PubKey使用 Protobuf JSON 格式化(ProtoMarshalJSON函数,见 codec/json.go)。CLI 中的实际输出结构定义在 client/keys/output.go#L23-L39,即KeyOutput结构体,它包含name、type、address、pubkey(以及可选的mnemonic)字段,其中pubkey通过codectypes.NewAnyWithValue(pk)与codec.ProtoMarshalJSON编码为 JSON。

Keyring:密钥管理器

Keyring是存储与管理账户的对象,其实现遵循Keyring接口(见 crypto/keyring/keyring.go#L58-L106)。默认实现来自第三方库99designs/keyring。

从 crypto/keyring/keyring.go#L31-L39 可以看到,Keyring 支持多种后端(backend):

后端常量值说明
BackendFilefile文件系统存储
BackendOSos操作系统原生密钥环
BackendKWalletkwalletKDE Wallet 服务
BackendPasspasspass 密码管理器
BackendTesttest测试用后端
BackendMemorymemory内存后端(NewInMemory,适合测试)

核心方法剖析

Sign(uid string, msg []byte, signMode signing.SignMode) ([]byte, types.PubKey, error)只负责对msg字节进行签名。调用方必须先把交易准备并编码为规范的[]byte形式。由于 Protobuf 本身不确定,ADR-020 决定:用于签名的规范payload是SignDoc结构体,并使用 ADR-027 所述方法做确定性编码。signMode参数控制使用哪种签名模式(例如SIGN_MODE_DIRECT或SIGN_MODE_LEGACY_AMINO_JSON),从而决定msg字节如何产生。SignDoc的定义见 proto/cosmos/tx/v1beta1/tx.proto#L50-L67:

// SignDoc is the type used for generating sign bytes for SIGN_MODE_DIRECT. message SignDoc { // body_bytes is protobuf serialization of a TxBody that matches the // representation in TxRaw. bytes body_bytes = 1; // auth_info_bytes is a protobuf serialization of an AuthInfo that matches the // representation in TxRaw. bytes auth_info_bytes = 2; // chain_id is the unique identifier of the chain this transaction targets. // It prevents signed transactions from being used on another chain by an // attacker string chain_id = 3; // account_number is the account number of the account in state uint64 account_number = 4; }

注意:Cosmos SDK 默认不实现签名验证,验签被推迟到anteHandler中执行。

从 crypto/keyring/keyring.go#L398-L429 的实现可以看到Sign的三种分支:local类型密钥直接调用priv.Sign(msg);ledger类型调用SignWithLedger交给硬件钱包;multi/offline类型无本地私钥,返回ErrOfflineSign。

NewAccount(uid, mnemonic, bip39Passphrase, hdPath string, algo SignatureAlgo) (*Record, error)基于 BIP44 路径创建新账户并持久化到磁盘。私钥绝不会以明文存储,而是先用口令(passphrase)加密(实现见 crypto/armor.go,其中EncryptArmorPrivKey使用 bcrypt 或 argon2 等 KDF 派生密钥后加密为 ASCII armor 格式)。在该方法中,密钥类型与序号指的是 BIP44 派生路径中用于从助记词派生出私钥/公钥的分段(例如0、1、2……)。使用相同的助记词与派生路径,必然生成相同的PrivKey、PubKey与Address。

Keyring 支持以下密钥算法(以当前仓库 crypto/keyring/keyring.go#L209-L215 的默认注册为准):

  • secp256k1
  • ml_dsa_65(后量子签名方案 ML-DSA-65,软件 Keyring 可用;Ledger 等硬件钱包暂不支持)

说明:原文档(基于 v0.53.0)所列的默认算法为secp256k1与ed25519;当前仓库默认注册表为hd.Secp256k1与hd.MlDsa65,且 Ledger 后端仅支持secp256k1。请以你所基于的 SDK 版本实际代码为准。

ExportPrivKeyArmor(uid, encryptPassphrase string) (armor string, err error)使用给定口令将私钥导出为 ASCII-armored 加密格式。之后既可以用ImportPrivKey(uid, armor, passphrase string)重新导入 Keyring,也可以调用UnarmorDecryptPrivKey(armorStr string, passphrase string)解密得到原始私钥。实现见 crypto/keyring/keyring.go#L283-L291:先通过ExportPrivateKeyObject取出私钥对象,再调用crypto.EncryptArmorPrivKey加密。

另外,NewMnemonic(crypto/keyring/keyring.go#L558-L589)会直接从系统熵生成默认 24 词助记词(defaultEntropySize),再由助记词派生并持久化密钥;若bip39Passphrase为空,则使用DefaultBIP39Passphrase。

为 Keyring 创建新的密钥类型

要新增一种可供 Keyring 使用的密钥类型,必须实现keyring.SignatureAlgo接口(见 crypto/keyring/signing_algorithms.go#L11-L16):

// SignatureAlgo defines the interface for a keyring supported algorithm. type SignatureAlgo interface { Name() hd.PubKeyType Derive() hd.DeriveFn Generate() hd.GenerateFn }

其中Name()以hd.PubKeyType返回算法名,Derive()与Generate()必须分别返回如下类型的函数(见 crypto/hd/algo.go#L28-L37):

type ( DeriveFn func(mnemonic, bip39Passphrase, hdPath string) ([]byte, error) GenerateFn func(bz []byte) types.PrivKey )

实现好keyring.SignatureAlgo后,需要把它加入 Keyring 的 supported algos 列表。为了简洁,新密钥类型的实现应放在crypto/hd包内;可参考 crypto/hd/algo.go 中secp256k1Algo的现成实现(如Secp256k1 = secp256k1Algo{}与Derive/Generate方法)。

以 secp256r1 为例的完整实现

以下演示如何实现 secp256r1 算法(来自原文档的官方示例,路径标注亦沿用文档)。

首先,需要在 secp256r1 包中新增一个从秘密数值创建私钥的函数:

// cosmos-sdk/crypto/keys/secp256r1/privkey.go // NewPrivKeyFromSecret creates a private key derived from the secret number // represented in big-endian. The `secret` must be a valid ECDSA field element. func NewPrivKeyFromSecret(secret []byte) (*PrivKey, error) { var d = new(big.Int).SetBytes(secret) if d.Cmp(secp256r1.Params().N) >= 1 { return nil, errorsmod.Wrap(errors.ErrInvalidRequest, "secret not in the curve base field") } sk := new(ecdsa.PrivKey) return &PrivKey{&ecdsaSK{*sk}}, nil }

然后实现secp256r1Algo:

// cosmos-sdk/crypto/hd/secp256r1Algo.go package hd import ( "github.com/cosmos/go-bip39" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256r1" "github.com/cosmos/cosmos-sdk/crypto/types" ) // Secp256r1Type uses the secp256r1 ECDSA parameters. const Secp256r1Type = PubKeyType("secp256r1") var Secp256r1 = secp256r1Algo{} type secp256r1Algo struct{} func (s secp256r1Algo) Name() PubKeyType { return Secp256r1Type } // Derive derives and returns the secp256r1 private key for the given seed and HD path. func (s secp256r1Algo) Derive() DeriveFn { return func(mnemonic string, bip39Passphrase, hdPath string) ([]byte, error) { seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase) if err != nil { return nil, err } masterPriv, ch := ComputeMastersFromSeed(seed) if len(hdPath) == 0 { return masterPriv[:], nil } derivedKey, err := DerivePrivateKeyForPath(masterPriv, ch, hdPath) return derivedKey, err } } // Generate generates a secp256r1 private key from the given bytes. func (s secp256r1Algo) Generate() GenerateFn { return func(bz []byte) types.PrivKey { key, err := secp256r1.NewPrivKeyFromSecret(bz) if err != nil { panic(err) } return key } }

最后,把该算法注册进 Keyring 的 supported algos 列表:

// cosmos-sdk/crypto/keyring/keyring.go func newKeystore(kr keyring.Keyring, cdc codec.Codec, backend string, opts ...Option) keystore { // Default options for keybase, these can be overwritten using the // Option function options := Options{ SupportedAlgos: SigningAlgoList{hd.Secp256k1, hd.Secp256r1}, // added here SupportedAlgosLedger: SigningAlgoList{hd.Secp256k1}, } ...

注册完成后,创建密钥时用--algo标志指定算法即可:

simd keys add myKey --algo secp256r1

对应的 CLI 标志定义在 client/flags/flags.go#L77(FlagKeyType = "key-type"),默认值为secp256k1(见 client/keys/add.go#L94),用户在keys add时通过--key-type/--algo选择算法,SDK 再用keyring.NewSigningAlgoFromString从受支持算法列表中解析(client/keys/add.go#L151-L152)。

小结

  • 账户 = 一对密钥:PubKey派生地址并标识消息发送者,PrivKey生成数字签名完成认证,两者由 crypto/types/types.go 中接口统一定义。
  • HD 派生:助记词 → 种子 → BIP44 路径逐级派生私钥/公钥/地址,路径解析与派生逻辑见 crypto/hd/hdpath.go。
  • 三类地址:AccAddress(用户)、ValAddress(验证人操作者)、ConsAddress(共识节点),统一实现 types/address.go 中的Address接口,交互时以 Bech32 编码并携带不同前缀。
  • Keyring 管理密钥:支持file/os/memory/test等多种后端,Sign处理规范化的SignDoc字节,私钥以口令加密后落盘;验签则交由anteHandler在链上完成。
  • 可扩展性:实现keyring.SignatureAlgo(Name/Derive/Generate三方法)并注册到 supported algos,即可通过 CLI 的--algo标志启用新的签名算法。

建议进阶阅读:Anatomy of a Cosmos SDK Application 了解应用整体结构,messages 概念 理解地址在消息路由中的作用,ADR-028 掌握地址构造算法的完整规范。

  • 区块链

【免费下载链接】cosmos-sdk

Framework for building performant, customizable blockchains with native interoperability

项目地址:https://gitcode.com/gh_mirrors/co/cosmos-sdk
点击查看免费下载
上一篇:GTA5防护增强工具YimMenu:新手安全使用完全指南
下一篇:Duktape 贡献指南:从 Fork 分支到合并的完整实践流程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询