下面同样用大白话从头讲到尾,代码用 Go(与之前的链网关栈一致,方便组合)。警告:钱包代码涉及真实资金,本文给出工程骨架 与正确思路,生产使用前务必经过专业审计。 --- 一、这玩意到底是干啥的?为什么要做? 大白话场景 你做交易所、做 DApp、做 NFT 平台、做支付,必须帮用户「保管钱、签名、发交易」。链上世界没有「账户密码」这个东西,所有东西都靠密钥: - 助记词(mnemonic):12/24 个英文单词,是密钥的"种子"- 私钥(private key):一串随机数,能花掉钱 - 公钥(public key):私钥推导出来的 - 地址(address):公钥再哈希得到,给别人转账用 痛点1. 每条链规则不同:以太坊用 secp256k1 + Keccak256,比特币也是 secp256k1 但地址格式完全不同,Solana 用 ed25519,Aptos 又不一样。每条链都自己写一遍累死人。2. 私钥怎么存? 明文存数据库就是给攻击者送钱;存用户脑子里就忘掉再也找不回。3. 签名风险高:把私钥从存储里拿出来到签名完成的这一瞬间,是整个钱包最危险的时刻。4. 广播交易要懂每条链的 RPC:以太坊 eth_sendRawTransaction、Solana sendTransaction、Bitcoin sendrawtransaction,格式都不一样。5. HD 钱包推导:一个助记词推导出无数地址(这就是 BIP-32/39/44),不懂就实现不对,实现错了用户钱就丢了。 多链钱包后端解决什么[App/前端]│ POST /wallet/create ▼ ┌────────────────────────────────────────────────┐ │ Wallet Backend │ │ │ │ ┌──────────┐ ┌──────────┐ ┌────────────┐ │ │ │ HD 推导 │→ │ Keystore │→ │ Signer │ │ │ │ BIP-32/44│ │ AES-GCM │ │(per chain)│ │ │ └──────────┘ └──────────┘ └────────────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌─────────────────────────────────────────┐ │ │ │ Chain Adapters: ETH / BTC / SOL / TRX │ │ │ └─────────────────────────────────────────┘ │ └────────────┬───────────────────────────────────┘ ▼[Chain Gateway(上一篇)]→ 区块链节点 做这个的真实价值1. 简历/作品集硬通货:密码学 + 多链协议 + 安全工程,技术含金量极高2. 可商业化:钱包即服务(WaaS)是真实赛道:Fireblocks、Coinbase MPC、Web3Auth3. 基础设施需求:任何 Web3 团队都绕不开4. 与上一篇链网关天然组合:钱包后端发交易就用网关 --- 二、技术选型(为什么用这些) ┌──────────────┬──────────────────────────────────────────────┬───────────────────────────────────────────────────┐ │ 组件 │ 选什么 │ 大白话原因 │ ├──────────────┼──────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ 语言 │ Go1.22+ │ 性能好、并发强、密码库齐全 │ ├──────────────┼──────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ HTTP 框架 │ chi / fiber │ 同上一篇 │ ├──────────────┼──────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ 助记词 │ github.com/tyler-smith/go-bip39 │ 行业标准实现,被无数项目用过 │ ├──────────────┼──────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ HD 推导 │ github.com/tyler-smith/go-bip32 │ BIP-32 标准 │ ├──────────────┼──────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ secp256k1 │ github.com/decred/dcrd/dcrec/secp256k1/v4 │ ETH、BTC、TRX 都用这条曲线,decred 这个实现最干净 │ ├──────────────┼──────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ ed25519 │ Go 标准库 crypto/ed25519 │ Solana 用 │ ├──────────────┼──────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ Ethereum SDK │ github.com/ethereum/go-ethereum │ 官方,事实标准 │ ├──────────────┼──────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ Bitcoin SDK │ github.com/btcsuite/btcd │ 比特币事实标准 Go 库 │ ├──────────────┼──────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ Solana SDK │ github.com/gagliardetto/solana-go │ 社区最活跃 │ ├──────────────┼──────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ Tron │ github.com/fbsobreira/gotron-sdk │ │ ├──────────────┼──────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ KDF │ golang.org/x/crypto/argon2 │ Argon2id,密码哈希行业最佳 │ ├──────────────┼──────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ 对称加密 │ Go 标准库 crypto/aes(GCM 模式)│ AES-256-GCM 是钱包加密标准 │ ├──────────────┼──────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ 安全内存 │ github.com/awnumar/memguard │ 防内存被 swap 出去、自动清零 │ ├──────────────┼──────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ KMS(可选) │ AWS KMS / Google Cloud KMS / HashiCorp Vault │ 生产强烈推荐 │ ├──────────────┼──────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ 数据库 │ PostgreSQL + pgx │ 加密后的 keystore 存这里 │ ├──────────────┼──────────────────────────────────────────────┼───────────────────────────────────────────────────┤ │ 测试 │ testify + 官方 BIP-39 测试向量 │ 必须用标准测试向量验证 │ └──────────────┴──────────────────────────────────────────────┴───────────────────────────────────────────────────┘ ▎ 关键提醒:钱包后端绝对不要自己实现密码学原语。Rolling your own crypto 是工程师最危险的诱惑。永远用经过审计的库。 --- 三、完整项目结构 multichain-wallet/ ├── .github/ │ ├── workflows/ │ │ ├── ci.yml │ │ ├── release.yml │ │ ├── codeql.yml# 安全扫描必须│ │ └── gosec.yml# Go 安全扫描│ ├── ISSUE_TEMPLATE/ │ ├── PULL_REQUEST_TEMPLATE.md │ └── dependabot.yml ├── cmd/ │ └── walletd/ │ └── main.go ├── internal/ │ ├── config/ │ ├── mnemonic/# 助记词生成与校验│ │ └── mnemonic.go │ ├── hd/# HD 钱包推导(BIP-32/44)│ │ └── derive.go │ ├── keystore/# 加密存储│ │ ├── keystore.go │ │ ├── encrypt.go# AES-256-GCM│ │ └── kdf.go# Argon2id│ ├── chains/# 各链适配器│ │ ├── chain.go# 统一接口│ │ ├── ethereum/ │ │ │ ├── address.go │ │ │ ├── sign.go │ │ │ └── broadcast.go │ │ ├── bitcoin/ │ │ ├── solana/ │ │ └── tron/ │ ├── signer/# 签名编排器│ │ └── signer.go │ ├── broadcaster/# 广播编排器│ │ └── broadcaster.go │ ├── api/# HTTP API│ │ ├── server.go │ │ ├── handlers.go │ │ └── middleware.go │ ├── storage/# 数据库│ │ ├── postgres.go │ │ └── schema.sql │ └── secure/# 安全内存与工具│ └── memory.go ├── pkg/# 可对外引用│ └── walletclient/# 给业务方调用的 SDK├── configs/ │ └── config.example.yaml ├── deploy/ │ ├── Dockerfile │ ├── docker-compose.yml │ └── k8s/ ├── docs/ │ ├── architecture.md │ ├── security.md# 安全模型说明,强烈推荐写│ ├── threat-model.md# 威胁模型│ └── api.md ├── scripts/ ├── test/ │ ├── integration/ │ └── vectors/# BIP-39/32/44 官方测试向量├── .gitignore ├── .golangci.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md# 必须有└── go.mod --- 四、核心代码(能跑通的关键部分)1. go.mod module github.com/yourname/multichain-wallet go1.22require(github.com/tyler-smith/go-bip39 v1.1.0 github.com/tyler-smith/go-bip32 v1.0.0 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 github.com/ethereum/go-ethereum v1.13.14 github.com/btcsuite/btcd v0.24.0 github.com/btcsuite/btcd/btcutil v1.1.5 github.com/gagliardetto/solana-go v1.10.0 github.com/awnumar/memguard v0.22.5 github.com/go-chi/chi/v5 v5.0.12 github.com/jackc/pgx/v5 v5.5.0 golang.org/x/crypto v0.21.0 github.com/stretchr/testify v1.8.4)2. internal/mnemonic/mnemonic.go — 助记词 package mnemonicimport("github.com/tyler-smith/go-bip39")// 强度:128(12词)/160(15)/192(18)/224(21)/256(24词)// 生产推荐256(24 词),更安全 func Generate(bits int)(string, error){entropy, err :=bip39.NewEntropy(bits)iferr!=nil{return"", err}returnbip39.NewMnemonic(entropy)}func Validate(words string)bool{returnbip39.IsMnemonicValid(words)}// 助记词 + passphrase → seed(64 字节) // passphrase 是可选的「第25个词」,能在助记词被偷的情况下提供额外保护 func ToSeed(mnemonic, passphrase string)[]byte{returnbip39.NewSeed(mnemonic, passphrase)}▎ 关键点:用户备份助记词必须离线,绝对不能截图、不能发微信。文档里要写清楚。3. internal/hd/derive.go — HD 推导(BIP-32/44) BIP-44 路径格式:m/44'/coin_type'/account'/change/address_index ┌─────────────────┬───────────┐ │ 链 │ coin_type │ ├─────────────────┼───────────┤ │ Bitcoin │ 0 │ ├─────────────────┼───────────┤ │ Ethereum │ 60 │ ├─────────────────┼───────────┤ │ Tron │ 195 │ ├─────────────────┼───────────┤ │ Solana │ 501 │ ├─────────────────┼───────────┤ │ Bitcoin Testnet │ 1 │ └─────────────────┴───────────┘ package hd import ( "fmt" "github.com/tyler-smith/go-bip32" ) const ( HardenedOffset = 0x80000000 // 2^31 Purpose = 44 // BIP-44 ) type ChainCoinType uint32 const ( CoinBTC ChainCoinType = 0 CoinETH ChainCoinType = 60 CoinTRX ChainCoinType = 195 CoinSOL ChainCoinType = 501 ) func DerivePath(seed []byte, coin ChainCoinType, account, index uint32) (*bip32.Key, error) { // m master, err := bip32.NewMasterKey(seed) if err != nil { return nil, err } // m/44'purpose, err :=master.NewChildKey(Purpose + HardenedOffset)iferr!=nil{returnnil, err}// m/44'/coin'coinKey, err :=purpose.NewChildKey(uint32(coin)+ HardenedOffset)iferr!=nil{returnnil, err}// m/44'/coin'/account' accountKey, err := coinKey.NewChildKey(account + HardenedOffset) if err != nil { return nil, err } // m/44'/coin'/account'/0 (0=外部地址;1=找零地址,BTC 才用) change, err :=accountKey.NewChildKey(0)iferr!=nil{returnnil, err}// m/44'/coin'/account'/0/index return change.NewChildKey(index) } // 便捷方法 func DeriveETH(seed []byte, index uint32) (*bip32.Key, error) { return DerivePath(seed, CoinETH, 0, index) } func DeriveBTC(seed []byte, index uint32) (*bip32.Key, error) { return DerivePath(seed, CoinBTC, 0, index) } func PathString(coin ChainCoinType, account, index uint32) string { return fmt.Sprintf("m/44'/%d'/%d'/0/%d", coin, account, index) } ▎ ⚠️Solana 实际上用的是 m/44'/501'/account'/0'(所有路径都 hardened),不完全遵循 BIP-44。生产代码要为 Solana ▎ 写特殊推导。 4. internal/chains/chain.go — 统一接口 每条链通过这个接口接入,业务层不关心是哪条链。 package chains import "context" type Address string type ChainID string // 未签名交易(业务层构造) type UnsignedTx struct { ChainID ChainID From Address To Address Amount string // 用 string 避免精度丢失 Nonce uint64 GasPrice string GasLimit uint64 Data []byte Extra map[string]any // 链特定的字段 } // 已签名交易 type SignedTx struct { ChainID ChainID Hash string Raw []byte // 序列化后的字节,可直接广播 } type Chain interface { ID() ChainID // 从公钥/私钥推地址 AddressFromPubKey(pubKey []byte) (Address, error) // 签名(私钥永远只在这一刻在内存里出现) Sign(privKey []byte, tx *UnsignedTx) (*SignedTx, error) // 广播 Broadcast(ctx context.Context, raw []byte) (string, error) // 查询账户余额、nonce 等 GetNonce(ctx context.Context, addr Address) (uint64, error) GetBalance(ctx context.Context, addr Address) (string, error) } 5. internal/chains/ethereum/ — 以太坊适配器 地址生成: package ethereum import ( "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/ethereum/go-ethereum/crypto" ) // 私钥 → 地址 func AddressFromPrivateKey(privBytes []byte) (string, error) { priv, _ := secp256k1.PrivKeyFromBytes(privBytes) pub := priv.PubKey() // 取未压缩公钥的 64 字节(去掉前缀 0x04) pubBytes := pub.SerializeUncompressed()[1:] // Keccak256(pubBytes)[12:] hash := crypto.Keccak256(pubBytes) return "0x" + hex.EncodeToString(hash[12:]), nil } 签名(核心,最危险的一段代码,必须有测试): package ethereum import ( "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" ) type Signer struct { chainID *big.Int } func New(chainID int64) *Signer { return &Signer{chainID: big.NewInt(chainID)} } func (s *Signer) Sign(privBytes []byte, tx *chains.UnsignedTx) (*chains.SignedTx, error) { // 1) 把私钥转为 go-ethereum 的格式 priv, err := crypto.ToECDSA(privBytes) if err != nil { return nil, err } defer zeroize(privBytes) // 用完立刻清零 // 2) 构造原始交易(EIP-1559 类型 2) to := common.HexToAddress(string(tx.To)) amount, _ := new(big.Int).SetString(tx.Amount, 10) gasTipCap, _ := new(big.Int).SetString(tx.Extra["maxPriorityFeePerGas"].(string), 10) gasFeeCap, _ := new(big.Int).SetString(tx.Extra["maxFeePerGas"].(string), 10) rawTx := types.NewTx(&types.DynamicFeeTx{ ChainID: s.chainID, Nonce: tx.Nonce, GasTipCap: gasTipCap, GasFeeCap: gasFeeCap, Gas: tx.GasLimit, To: &to, Value: amount, Data: tx.Data, }) // 3) 签名(EIP-155,防重放攻击) signer := types.LatestSignerForChainID(s.chainID) signed, err := types.SignTx(rawTx, signer, priv) if err != nil { return nil, err } // 4) 序列化 raw, err := signed.MarshalBinary() if err != nil { return nil, err } return &chains.SignedTx{ ChainID: tx.ChainID, Hash: signed.Hash().Hex(), Raw: raw, }, nil } func zeroize(b []byte) { for i := range b { b[i] = 0 } } 广播: func (s *Signer) Broadcast(ctx context.Context, raw []byte) (string, error) { // 通过链网关或直连节点 body := fmt.Sprintf(`{"jsonrpc":"2.0","id":1,"method":"eth_sendRawTransaction","params":["0x%s"]}`, hex.EncodeToString(raw)) // ... POST 到 gateway,解析返回的 hash } 6. internal/keystore/kdf.go — 密钥派生 package keystore import ( "crypto/rand" "golang.org/x/crypto/argon2" ) type KDFParams struct { Time uint32 // 迭代次数 Memory uint32 // 内存使用 KB Threads uint8 KeyLen uint32 Salt []byte } func DefaultParams() KDFParams { salt := make([]byte, 16) rand.Read(salt) return KDFParams{ Time: 3, Memory: 64 * 1024, // 64MB Threads: 4, KeyLen: 32, // 给 AES-256 用 Salt: salt, } } // 用户密码 → 加密密钥 func DeriveKey(password []byte, p KDFParams) []byte { return argon2.IDKey(password, p.Salt, p.Time, p.Memory, p.Threads, p.KeyLen) } 7. internal/keystore/encrypt.go — AES-256-GCM package keystore import ( "crypto/aes" "crypto/cipher" "crypto/rand" ) // 加密结果在数据库里这样存 type EncryptedBlob struct { Ciphertext []byte`json:"ciphertext"`Nonce []byte`json:"nonce"`KDF KDFParams`json:"kdf"`Version int`json:"version"`// 用于未来升级算法 } func Encrypt(plaintext, password []byte) (*EncryptedBlob, error) { params := DefaultParams() key := DeriveKey(password, params) defer zeroize(key) block, err := aes.NewCipher(key) if err != nil { return nil, err } aead, err := cipher.NewGCM(block) if err != nil { return nil, err } nonce := make([]byte, aead.NonceSize()) if _, err := rand.Read(nonce); err != nil { return nil, err } ciphertext := aead.Seal(nil, nonce, plaintext, nil) return &EncryptedBlob{ Ciphertext: ciphertext, Nonce: nonce, KDF: params, Version: 1, }, nil } func Decrypt(blob *EncryptedBlob, password []byte) ([]byte, error) { key := DeriveKey(password, blob.KDF) defer zeroize(key) block, err := aes.NewCipher(key) if err != nil { return nil, err } aead, err := cipher.NewGCM(block) if err != nil { return nil, err } return aead.Open(nil, blob.Nonce, blob.Ciphertext, nil) } func zeroize(b []byte) { for i := range b { b[i] = 0 } } 8. internal/keystore/keystore.go — 对外接口 package keystore import ( "context" "github.com/yourname/multichain-wallet/internal/secure" ) type Storage interface { Save(ctx context.Context, userID, walletID string, blob *EncryptedBlob) error Load(ctx context.Context, userID, walletID string) (*EncryptedBlob, error) } type Keystore struct { storage Storage } // 创建钱包:生成助记词 → 加密 → 存 // 返回助记词给用户(仅此一次!) func (k *Keystore) Create(ctx context.Context, userID, walletID, password string) (mnemonicWords string, err error) { words, err := mnemonic.Generate(256) if err != nil { return "", err}blob, err :=Encrypt([]byte(words),[]byte(password))iferr!=nil{return"", err}iferr :=k.storage.Save(ctx, userID, walletID, blob);err!=nil{return"", err}returnwords, nil}// 用助记词执行签名:解密 → 推导 → 签名 → 立刻清零 func(k *Keystore)SignWithDerivation(ctx context.Context, userID, walletID, password string, coin hd.ChainCoinType, index uint32, signFn func(privKey[]byte)(*chains.SignedTx, error),)(*chains.SignedTx, error){blob, err :=k.storage.Load(ctx, userID, walletID)iferr!=nil{returnnil, err}words, err :=Decrypt(blob,[]byte(password))iferr!=nil{returnnil, err}defer zeroize(words)seed :=mnemonic.ToSeed(string(words),"")defer zeroize(seed)key, err :=hd.DerivePath(seed, coin,0, index)iferr!=nil{returnnil, err}defer zeroize(key.Key)returnsignFn(key.Key)}9. internal/secure/memory.go — 安全内存 package secureimport"github.com/awnumar/memguard"funcinit(){memguard.CatchInterrupt()// Ctrl+C 时自动清零所有 enclave}// 把敏感数据包进 enclave,自动加密在内存中,使用完销毁 func ProtectBytes(data[]byte)*memguard.LockedBuffer{returnmemguard.NewBufferFromBytes(data)}▎ 重要:Go 的字符串是不可变的,助记词千万不要用 string 类型贯穿整条链路,要尽早转[]byte 并在使用后清零。10. API 设计(internal/api/handlers.go 节选) // POST /api/v1/wallets //{"user_id":"u1","password":"..."}// 返回:{"wallet_id":"...","mnemonic":"abandon abandon ...","warning":"请离线备份!"}func(h *Handler)CreateWallet(w http.ResponseWriter, r *http.Request){...}// POST /api/v1/wallets/{wallet_id}/addresses //{"chain":"ethereum","index":0}// 返回:{"address":"0x...","path":"m/44'/60'/0'/0/0"}func(h *Handler)DeriveAddress(w http.ResponseWriter, r *http.Request){...}// POST /api/v1/wallets/{wallet_id}/sign //{"chain":"ethereum","index":0,"password":"...", //"tx":{"to":"0x...","amount":"1000000000000000000",...}}// 返回:{"raw":"0x...","hash":"0x..."}func(h *Handler)SignTransaction(w http.ResponseWriter, r *http.Request){...}// POST /api/v1/broadcast //{"chain":"ethereum","raw":"0x..."}// 返回:{"tx_hash":"0x..."}func(h *Handler)Broadcast(w http.ResponseWriter, r *http.Request){...}11. 测试向量(必须) test/vectors/bip39_test.go:直接用 BIP-39 官方测试向量(https://github.com/trezor/python-mnemonic/blob/master/vectors. json),把每一条助记词推出来的种子、私钥、地址都对一遍。任何一条不通过都不能合并代码。 func TestBIP39Vectors(t *testing.T){cases :=loadOfficialVectors()for_, c :=range cases{seed :=mnemonic.ToSeed(c.Mnemonic,"TREZOR")assert.Equal(t, c.Seed, hex.EncodeToString(seed))}}12. 进阶选项(要写在文档里) ┌────────────────────────────┬────────────────────────────┬────────────────────────────┐ │ 方案 │ 用途 │ 推荐场景 │ ├────────────────────────────┼────────────────────────────┼────────────────────────────┤ │ 本文实现:密码加密存数据库 │ MVP / 自托管 │ 个人项目、内部工具 │ ├────────────────────────────┼────────────────────────────┼────────────────────────────┤ │ KMS 包封一层 │ 加密密钥不落在你的服务器 │ 中小规模生产,AWS/GCP 客户 │ ├────────────────────────────┼────────────────────────────┼────────────────────────────┤ │ HSM 硬件模块 │ 私钥根本不出硬件 │ 银行级别合规 │ ├────────────────────────────┼────────────────────────────┼────────────────────────────┤ │ MPC 门限签名(TSS) │ 私钥从不在任何一处完整存在 │ Fireblocks、Web3Auth 模式 │ ├────────────────────────────┼────────────────────────────┼────────────────────────────┤ │ 冷热分离 │ 大额钱包用冷签名机离线签 │ 交易所标配 │ └────────────────────────────┴────────────────────────────┴────────────────────────────┘ 最佳实践架构(生产推荐):用户密码 → KMS 数据加密密钥 → AES-GCM 加密助记词 → 存数据库。三层防护:拿到数据库没用、拿到 KMS 没用、拿到密码没用,三个全都拿到才能动钱。 --- 五、开源项目从0到持续维护(钱包项目特别注意事项) 通用流程和上一篇一样,下面只讲钱包项目特有的额外要求。 安全是第一公民1. SECURITY.md 必须非常详细# Security Policy## 安全模型本项目的安全假设是: - 服务器可能被入侵(数据库会被拖走) - 攻击者无法同时拿到 KMS 主密钥与用户密码 - 用户负责保管助记词## 不在威胁模型内- 用户客户端被植入恶意软件 - 物理访问硬件 - 量子计算机攻击## 报告漏洞**严禁**在 GitHub Issue 公开漏洞。 发送 PGP 加密邮件至 security@yourproject.org 我们承诺: -24小时内确认收到 -7天内给出初步评估 -90天 embargo 期内修复并致谢## 已知限制列出所有已知的安全权衡。## 审计历史-2026-XX-XX: XX 公司审计,报告链接...2. 自动化安全扫描必上 .github/workflows/security.yml: name: Security on:[push, pull_request, schedule]jobs: gosec: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: securego/gosec@master govulncheck: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: goinstallgolang.org/x/vuln/cmd/govulncheck@latest - run: govulncheck ./... codeql: uses: github/codeql-action/... semgrep: runs-on: ubuntu-latest steps: - uses: returntocorp/semgrep-action@v1 with: config:'p/owasp-top-ten p/golang p/crypto'3. 测试要求比普通项目高得多 - 覆盖率 ≥85%(普通项目70% 就够) - 所有官方测试向量必须通过(BIP-39/32/44/SLIP-10) - Fuzz 测试:gotest-fuzz=FuzzSign - 关键路径(签名)必须有差分测试:拿你的输出和 ethers.js / web3.py 的输出对比4. 责任声明(很重要,避免被起诉) README.md 顶部用大字写:>⚠️**DISCLAIMER**: This software is provided"as-is"without warranty.>Cryptocurrency operations are irreversible. The authors are not liable>forany loss of funds. **Audit the code yourself before usinginproduction.**>该项目尚未经过专业安全审计,请勿在生产环境管理大额资产。 推广策略(钱包项目特殊点) 比一般项目更难推广,因为: - 用户不敢用一个没名气的钱包 - 安全声誉需要时间沉淀 应对方式:1. 先做 SDK,再做钱包服务:让开发者把你的库嵌入自己的产品,远比让他们用你的服务容易2. 公开所有审计报告(即使只是自审)3. 做出可验证的差分测试结果:在 README 放一张表,"我们与 ethers.js / web3.py / bitcoinjs 输出 100% 一致"4. 找一个真实场景对接:比如帮某个小公链做钱包基础设施,免费或低价合作,换 logo 上 README5. 写技术深度博客: -"我们怎么在 Go 里实现 secp256k1 签名"-"BIP-44 多链推导踩过的 5 个坑"-"为什么我们选择 Argon2id 而不是 scrypt"治理上的关键差异 ┌────────────────────────────┬──────────────────────────────────────┐ │ 普通项目 │ 钱包项目 │ ├────────────────────────────┼──────────────────────────────────────┤ │ 直接合 PR │ 任何涉及密码学的 PR 必须2人 review │ ├────────────────────────────┼──────────────────────────────────────┤ │ 依赖升级 dependabot 自动合 │ 加密库升级必须人工审查 changelog │ ├────────────────────────────┼──────────────────────────────────────┤ │ Bug 公开讨论 │ 安全 bug 走 embargo 流程 │ ├────────────────────────────┼──────────────────────────────────────┤ │ 主分支可以 force push │ 绝对禁止 force push 到 main │ └────────────────────────────┴──────────────────────────────────────┘ 法律与合规(提前规划) 如果你打算商业化或者面向欧美用户: - 不要内置交易所 / DEX 功能(避免被认定为 MSB) - 在文档明确写"This is not a custodial service"(如果你不托管资金) - 注意你所在国家对加密货币的法规(中国大陆个人提供钱包后端服务存在法律风险,可以做开源代码贡献者但避免提供运营服务) - 选择 Apache-2.0 协议比 MIT 更好(有专利保护条款) --- 六、最关键的7条心法(钱包项目专属)1. 永远不要相信自己写的密码学。每一行加密相关代码都先问:有没有现成的成熟库?2. 官方测试向量是底线。BIP-39/32/44 都有官方测试向量,过不了说明你写错了,就是写错了。3. 私钥在内存中存活的时间,每一毫秒都是风险。用完立刻清零,越短越好。4. 错误信息不能泄露任何关于密钥的信息。"密码错误"是 ✅,"密码错误,第 3 位应该是 X"是 ❌。5. 不要在日志里打印任何敏感字段。建立白名单机制:只有显式允许的字段才能进日志。6. 生产环境的密钥派生参数(Argon2 的 time/memory)要根据机器调到「单次解密 200ms+」,让暴力破解贵到不可行。7. 当你不确定怎么做时,问审计师,不要问 ChatGPT 也不要问 Claude。这里说的所有代码都是教学骨架,距离生产可用还差至少一轮专业审计。 --- 七、能直接开干的最小路线图 ┌──────┬──────────────────────────────────────────────────────┐ │ 周 │ 目标 │ ├──────┼──────────────────────────────────────────────────────┤ │ W1 │ 项目骨架、README、LICENSE、CI、SECURITY.md │ ├──────┼──────────────────────────────────────────────────────┤ │ W2 │ 助记词生成 + BIP-39 官方测试向量全过 │ ├──────┼──────────────────────────────────────────────────────┤ │ W3 │ BIP-32/44 推导 + 测试向量 │ ├──────┼──────────────────────────────────────────────────────┤ │ W4 │ 以太坊地址 + 签名 + 与 ethers.js 差分测试 │ ├──────┼──────────────────────────────────────────────────────┤ │ W5 │ Keystore(Argon2id + AES-GCM)│ ├──────┼──────────────────────────────────────────────────────┤ │ W6 │ HTTP API + Postgres 存储 │ ├──────┼──────────────────────────────────────────────────────┤ │ W7 │ 比特币适配器 │ ├──────┼──────────────────────────────────────────────────────┤ │ W8 │ Solana 适配器 │ ├──────┼──────────────────────────────────────────────────────┤ │ W9 │ 文档完善 + threat-model.md + 自审一次 │ ├──────┼──────────────────────────────────────────────────────┤ │ W10 │ v0.1.0 发布,但 README 顶部声明"未经审计,仅供学习"│ ├──────┼──────────────────────────────────────────────────────┤ │ W11+ │ 推广 + 接 issue + 募集审计预算(可在 Gitcoin 申请) │ └──────┴──────────────────────────────────────────────────────┘ --- 八、与上一篇链网关如何组合 钱包后端发交易时不要直连节点,走链网关:[钱包后端 walletd]│ sign 完成 ▼[链网关 chain-gateway]│ 负载均衡 + 缓存 + 限流 ▼[Infura / Alchemy / 自建节点]这两个项目天然就是一组:链网关解决「读链 + 广播」,钱包后端解决「签名 + 密钥」。你可以做成一个 monorepo 或一个 organization 下的两个项目。