钱包地址生成业务,通常指的是根据特定区块链协议规范,从一对密钥(公钥、私钥)生成唯一的钱包地址的过程。这是区块链系统中非常核心的基础设施服务之一,广泛用于交易所、托管服务、Web3项目等。
钱包地址生成业务,通常指的是根据特定区块链协议规范,从一对密钥(公钥、私钥)生成唯一的钱包地址的过程。这是区块链系统中非常核心的基础设施服务之一,广泛用于交易所、托管服务、Web3
项目等。
对于以上流程图,首先是交易所的钱包层地址生成定时任务会一直监听数据库中地址是否足够,然后去请求签名机去调用批量地址生成业务,获得公钥后,转化成地址存储在钱包层的数据库中。
对于一个新增用户而言,他所请求的业务首先会进入到交易所的业务层,业务层将这个新增用户的请求转发给到钱包层这边。钱包层收到了这个请求则回去数据库中匹配一个地址分配给到这个用户。
对于交易所而言,地址生成业务是有其用户量决定的。假设,某交易所日新增用户 100
人左右,那么一周的用户量就是 700
人。一周批量生成一次 1000
个地址是绰绰有余的(当然还会经过数据库剩余地址数量检测)
下面我们来使用 go
语言搭建一个签名机应用,提供 ECDSA
、EdDSA
地址批量生成能力,并支持消息签名。
完整代码仓库: signature-machine
在这个项目中我们使用到了
urfave/cli/v2
库来搭建控制台应用,使用 leveldb
作为嵌入式数据库,使用 Grpc
提供与钱包层应用的交互能力,使用 ecdsa
的库和 eddsa
的库来保证地址生成和签名的能力。
在这个应用中,我们编写三个重要的接口:
go mod init
一个空环境git init
托管到 github
,新建 .gitignore
文件用于排除 github
提交文件.env
文件用于环境变量读取export SIGNATURE_RPC_PORT=8983
export SIGNATURE_RPC_HOST="127.0.0.1"
export SIGNATURE_LEVEL_DB_PATH="./data"
bin/compile.sh
用于管理 protobuf
的生成命令cmd/signature
,并新建 main.go
文件,这是程序的主入口func main() {
log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true)))
app := NewCli(GitCommit, GitData)
ctx := opio.WithInterruptBlocker(context.Background())
if err := app.RunContext(ctx, os.Args); err != nil {
log.Error("Application failed")
os.Exit(1)
}
}
cli.go
,使用 urfave/cle/v2
作为命令行应用的框架func NewCli(GitCommit string, GitData string) *cli.App {
flags := flags.Flags
return &cli.App{
Version: params.VersionWithCommit(GitCommit, GitData),
Description: "Signature machine eth tool",
EnableBashCompletion: true,
Commands: []*cli.Command{
{
Name: "rpc",
Flags: flags,
Description: "Run RPC",
Action: cliapp.LifecycleCmd(runRpc),
},
},
}
}
Makefile
文件用于管理 shell
命令(build/test/lint/proto/clean
)version
命令是否正常运行(./signature version
)
proto
文件,定义好接口、输入输出消息service WalletService {
rpc getSupportSignWay(SupportSignWayRequest) returns (SupportSignWayResponse) {}
rpc exportPublicKeyList(ExportPublicKeyRequest) returns (ExportPublicKeyResponse) {}
rpc signTxMessage(SignTxMessageRequest) returns (SignTxMessageResponse) {}
}
proto
生成 go
代码,放入 protobuf
包内make proto
Grpc
的 host
、port
和 LevelDB
的路径func NewConfig(ctx *cli.Context) Config {
return Config{
LevelDbPath: ctx.String(flags.LevelDbPathFlag.Name),
RpcServer: ServerConfig{
Host: ctx.String(flags.RpcHostFlag.Name),
Port: ctx.Int(flags.RpcPortFlag.Name),
},
}
}
LevelDB
连接func NewKeyStore(path string) (*Keys, error) {
db, err := NewLevelStore(path)
if err != nil {
log.Error("Could not open leveldb", "path", path, "err", err)
return nil, err
}
return &Keys{
db: db,
}, nil
}
GRPC
服务,注册 proto
定义好的路由进去 gs := grpc.NewServer(
opt,
grpc.ChainUnaryInterceptor(nil),
)
reflection.Register(gs)
wallet.RegisterWalletServiceServer(gs, r)
ssm
包,负责封装 ecdsa
和 eddsa
的秘钥生成、消息签名创建 ECDSA 秘钥对
func CreateECDSAKeyPair() (string, string, string, error) {
privateKey, err := crypto.GenerateKey()
if err != nil {
log.Error("generate key fail", "err", err)
return EmptyHexString, EmptyHexString, EmptyHexString, err
}
priKeyStr := hex.EncodeToString(crypto.FromECDSA(privateKey))
pubKeyStr := hex.EncodeToString(crypto.FromECDSAPub(&privateKey.PublicKey))
compressPubkeyStr := hex.EncodeToString(crypto.CompressPubkey(&privateKey.PublicKey))
return priKeyStr, pubKeyStr, compressPubkeyStr, nil
}
ecdsa
、eddsa
):
返回是否支持该方式的地址生成和签名// 获取加密支持类型
func (r *RpcServer) GetSupportSignWay(ctx context.Context, request *wallet.SupportSignWayRequest) (*wallet.SupportSignWayResponse, error) {
var signWay []*wallet.SignWay
signWay = append(signWay, &wallet.SignWay{Schema: "ecdsa"})
signWay = append(signWay, &wallet.SignWay{Schema: "eddsa"})
return &wallet.SupportSignWayResponse{
Code: wallet.ReturnCode_SUCCESS,
Msg: "get sign way success",
SignWay: signWay,
}, nil
}
ecdsa
、eddsa
)的不同,分别调用 ecdsa
和 eddsa
的秘钥对生成方法,
生成出对应秘钥对。
将私钥和公钥保存到 leveldb
中,将公钥返回给调用方// 批量导出公钥
func (r *RpcServer) ExportPublicKeyList(ctx context.Context, request *wallet.ExportPublicKeyRequest) (*wallet.ExportPublicKeyResponse, error) {
resp := &wallet.ExportPublicKeyResponse{
Code: wallet.ReturnCode_SUCCESS,
}
cryptoType, err := protobuf.ParseTransactionType(request.Type)
if err != nil {
resp.Msg = "input type error"
return resp, nil
}
if request.Number > 10000 {
resp.Msg = "input number > 10000"
return resp, nil
}
var keyList []leveldb.Key
var retKeyList []*wallet.PublicKey
for counter := 0; counter < int(request.Number); counter++ {
var privateKeyStr, publicKeyStr, compressPublicKeyStr string
var err error
switch cryptoType {
case protobuf.ECDSA:
privateKeyStr, publicKeyStr, compressPublicKeyStr, err = ssm.CreateECDSAKeyPair()
case protobuf.EDDSA:
privateKeyStr, publicKeyStr, err = ssm.CreateEdDSAKeyPair()
compressPublicKeyStr = publicKeyStr
default:
return nil, errors.New("unsupported key type")
}
if err != nil {
log.Error("create key pair error", "err", err)
return nil, err
}
keyItem := leveldb.Key{
PrivateKey: privateKeyStr,
PublicKey: publicKeyStr,
}
putItem := &wallet.PublicKey{
CompressPubkey: compressPublicKeyStr,
Pubkey: publicKeyStr,
}
retKeyList = append(retKeyList, putItem)
keyList = append(keyList, keyItem)
}
isOk := r.db.StoreKeys(keyList)
if !isOk {
log.Error("db store keys error", "err", isOk)
return nil, errors.New("db store keys error")
}
resp.Code = wallet.ReturnCode_SUCCESS
resp.Msg = "create key pair success"
resp.PublicKey = retKeyList
return resp, nil
}
messageHash
、公钥,请求签名。签名机内部调用 ecdsa
或 eddsa
的
签名方法将 messageHash
签名,返回 signature
// 通过 messageHash 进行签名
func (r *RpcServer) SignTxMessage(ctx context.Context, request *wallet.SignTxMessageRequest) (*wallet.SignTxMessageResponse, error) {
resp := &wallet.SignTxMessageResponse{
Code: wallet.ReturnCode_ERROR,
}
cryptoType, err := protobuf.ParseTransactionType(request.Type)
if err != nil {
resp.Msg = "input type error"
return resp, nil
}
privateKey, isOk := r.db.GetPrivateKey(request.PublicKey)
if !isOk {
return nil, errors.New("get private key error")
}
var signature string
var err2 error
switch cryptoType {
case protobuf.ECDSA:
signature, err2 = ssm.SignECDSAMessage(privateKey, request.MessageHash)
case protobuf.EDDSA:
signature, err2 = ssm.SignEdDSAMessage(privateKey, request.MessageHash)
default:
return nil, errors.New("unsupported key type")
}
if err2 != nil {
return nil, err2
}
resp.Code = wallet.ReturnCode_SUCCESS
resp.Msg = "sign tx message success"
resp.Signature = signature
return resp, nil
}
make
source .env
./signature rpc
grpcui -plaintext 127.0.0.1:8983
grpcurl -plaintext -d '{}' 127.0.0.1:8983 wallet.WalletService.getSupportSignWay
{
"Code": "SUCCESS",
"msg": "get sign way success",
"sign_way": [
{
"schema": "ecdsa"
},
{
"schema": "eddsa"
}
]
}
grpcurl -plaintext -d '{
"type": "ecdsa",
"number": "3"
}' 127.0.0.1:8983 wallet.WalletService.exportPublicKeyList
{
"Code": "SUCCESS",
"msg": "create key pair success",
"public_key": [
{
"compress_pubkey": "0287916d6ff3cc4312c8e996bfbff4f8d2f7cf44da480f3a2e70be64bdebaa68f0",
"pubkey": "0487916d6ff3cc4312c8e996bfbff4f8d2f7cf44da480f3a2e70be64bdebaa68f0e7ca2c7409fc0edb4d5f0496c3a9de1a0904deb48fe329f9a9130e5a70f35e76"
},
{
"compress_pubkey": "031427c8d6062b79072a0b941a87f776f4b862e6ab9f9b7fa5a61f5b7b3722e9ab",
"pubkey": "041427c8d6062b79072a0b941a87f776f4b862e6ab9f9b7fa5a61f5b7b3722e9ab1b3ec09bf22c123f2398bab7ecab516f7508c25bc0d506c52ee6924403eb701b"
},
{
"compress_pubkey": "03ed79ebca46736aa456729d38d00f87d5c435987adcebc2d6404f4fefc13bb72a",
"pubkey": "04ed79ebca46736aa456729d38d00f87d5c435987adcebc2d6404f4fefc13bb72a6811f36574c2fcc42c66903c9309a94dadc4d2e74d39b4c58e5979fca189c905"
}
]
}
grpcurl -plaintext -d '{
"type": "ecdsa",
"publicKey": "04ed79ebca46736aa456729d38d00f87d5c435987adcebc2d6404f4fefc13bb72a6811f36574c2fcc42c66903c9309a94dadc4d2e74d39b4c58e5979fca189c905",
"messageHash": "0x9ca77bd43a45da2399da96159b554bebdd89839eec73a8ff0626abfb2fb4b538"
}' 127.0.0.1:8983 wallet.WalletService.signTxMessage
{
"Code": "SUCCESS",
"msg": "sign tx message success",
"signature": "d703844145ef3277b9c8abf6e878590b69122684b2985bb75fd5454177a9ccfb19efcca6c063ce94ca71f3164d9d83d082fe7df73a6afbde579ff29fce4738e800"
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!