一. 签名机概述
签名机,在 Web3 行业通常指的是用于交易签名的软件服务与硬件设备。数字签名是通过使用私钥对数据进行加密,以确保数据的完整性、来源的真实性以及不可否认性。签名机可以帮助提高密钥的安全性,防止密钥泄露,并且确保签名操作在隔离的环境中进行。
1、签名机的基本功能
私钥管理: 签名机能够存储私钥并确保私钥的安全性。私钥不会离开签名机,所有的签名操作都在硬件中完成,不会暴露到外部环境中。
签名操作: 用户将待签名的数据传递给签名机,签名机使用内部的私钥对数据进行签名,生成签名数据,并将签名返回给用户。
安全隔离: 签名机在物理或逻辑上将私钥与主机系统隔离,防止私钥被泄露或盗用。常见的硬件签名设备包括硬件安全模块(HSM)、USB加密狗、智能卡和 TEE 等。
支持多种签名算法: 常见的数字签名算法包括 ECDSA(椭圆曲线数字签名算法)、EdDSA(Edwards曲线数字签名算法)、RSA等。签名机通常支持多种签名算法,以适应不同的应用需求。
身份认证与授权: 签名机可以作为身份验证的一部分,通过数字签名验证用户或设备的身份。这对于金融交易、加密货币支付等应用非常重要。
2、常见的签名机类型
硬件安全模块(HSM):HSM 是一种物理设备,设计用于保护和管理数字密钥。它为签名操作提供专用的硬件加速,确保私钥的安全性和计算的高效性。常用于金融、企业级应用等需要高安全性的场景。
USB 加密狗(Token:USB加密狗是常见的轻量级签名设备,通常用于身份验证和数字签名。它通过 USB 接口与计算机连接,内部存储密钥并通过加密算法进行签名。
智能卡(Smart Card): 智能卡是一种内置芯片的卡片,通常用于身份验证、银行支付、个人数字证书等场景。与USB加密狗类似,智能卡也通过物理接口进行连接和签名。
移动设备签名机: 一些移动设备(如手机、平板等)也可以作为签名机,通过使用专门的硬件模块(如TPM、Secure Enclave等)提供数字签名服务。移动签名机在支付、身份认证等场景中得到广泛应用。
软件签名机: 在某些场景下,签名操作可以通过软件模拟完成,但这通常不如硬件设备安全。软件签名机依赖计算机的操作系统和软件安全性,适用于较低安全需求的应用场景。
3、签名机的应用领域
加密货币: 在加密货币领域,签名机用于保护钱包的私钥,确保用户在发起交易时能够安全地进行签名,而不暴露私钥。
电子支付: 签名机用于保护支付交易中的私钥,确保交易的安全性和有效性。金融机构和支付平台往往使用HSM等硬件设备来签名交易信息。
身份验证与授权: 签名机广泛应用于身份验证中,通过签名操作确保用户的身份。常见应用包括银行在线服务、电子政务、数字证书签发等。
法律文件签署: 在数字化合同和电子签名的场景中,签名机用于确保签署的文件具有法律效力。签名机能够确保文件未被篡改,并且签署者的身份可以验证。
区块链与智能合约: 在区块链应用中,签名机用于生成和管理与智能合约相关的签名,确保智能合约的执行是由合法用户发起的。
二. 签名机架构设计
HSM: 支持 CloudHSM 的签名方式
SSM:软签名,签名机部署在 TEE 环境
RPC Services: RPC 接口层,签名机密钥生成,签名相关的接口封装
三. 功能模块
1、Keygen过程
2、交易签名流程
四. 核心源码解析
1、Protobuf接口定义
syntax = "proto3";
option go_package = "./protobuf/wallet";
package dapplink.wallet;
message PublicKey {
string compress_pubkey = 1;
string decompress_pubkey = 2;
}
message SupportSignWayRequest{
string consumer_token = 1;
string type = 2;
}
message SupportSignWayResponse {
string code = 1;
string msg = 2;
bool support = 3;
}
message ExportPublicKeyRequest {
string consumer_token = 1;
string type = 2;
uint64 number = 3;
}
message ExportPublicKeyResponse {
string code = 1;
string msg = 2;
repeated PublicKey public_key = 3;
}
message SignTxMessageRequest {
string consumer_token = 1;
string type = 2;
string public_key = 3;
string message_hash = 4;
}
message SignTxMessageResponse {
string code = 1;
string msg = 2;
string signature = 3;
}
service WalletService {
rpc getSupportSignWay(SupportSignWayRequest) returns (SupportSignWayResponse) {}
rpc exportPublicKeyList(ExportPublicKeyRequest) returns (ExportPublicKeyResponse) {}
rpc signTxMessage(SignTxMessageRequest) returns (SignTxMessageResponse) {}
}
1. 代码详解
1.1 文件结构
语法声明: syntax = "proto3"; 选择了 proto3,表示使用 Protocol Buffers 第三版。
选项声明: option go_package = "./protobuf/wallet"; 指定 Go 包路径为 ./protobuf/wallet,方便生成的代码直接用在指定目录。
包名: package dapplink.wallet; 定义了 Protocol Buffers 文件的包名为 dapplink.wallet,用于避免命名冲突。
1.2 消息类型解析
message PublicKey {
string compress_pubkey = 1;
string decompress_pubkey = 2;
}
作用:用于存储公钥信息。
字段解析:
compress_pubkey: 压缩形式的公钥,类型为 string。
decompress_pubkey: 解压缩形式的公钥,类型为 string。
1.3 SupportSignWayRequest
message SupportSignWayRequest {
string consumer_token = 1;
string type = 2;
}
作用:查询支持的签名方式的请求结构。
字段解析:
consumer_token: 表示消费者的身份令牌,用于认证。type: 查询的签名方式类型,具体值取决于业务需求。
1.4 SupportSignWayResponse
message SupportSignWayResponse {
string code = 1;
string msg = 2;
bool support = 3;
}
作用:查询支持签名方式的响应结构。
字段解析:
code:状态码,用于指示请求处理结果。
msg: 描述状态码的消息。
support: 是否支持指定的签名方式,布尔值。
1.5 ExportPublicKeyRequest
message ExportPublicKeyRequest {
string consumer_token = 1;
string type = 2;
uint64 number = 3;
}
作用:导出公钥列表的请求结构。
字段解析:
consumer_token: 消费者令牌。
type: 公钥类型。
number: 导出的公钥数量,类型为无符号 64 位整数。
1.6 ExportPublicKeyResponse
message ExportPublicKeyResponse {
string code = 1;
string msg = 2;
repeated PublicKey public_key = 3;
}
作用:导出公钥列表的响应结构。
字段解析:
code:状态码。
msg:响应信息。
public_key: 公钥列表,PublicKey类型的数组。
1.7 SignTxMessageRequest
message SignTxMessageRequest {
string consumer_token = 1;
string type = 2;
string public_key = 3;
string message_hash = 4;
}
作用:请求对交易消息进行签名。
字段解析:
consumer_token: 消费者令牌。
type: 签名方式。
public_key: 用于签名的公钥。
message_hash: 待签名的消息哈希值。
1.8 SignTxMessageResponse
message SignTxMessageResponse {
string code = 1;
string msg = 2;
string signature = 3;
}
作用:签名响应。
字段解析:
code:状态码。
msg:响应消息。
signature:签名结果。
1.9 服务定义
service WalletService {
rpc getSupportSignWay(SupportSignWayRequest) returns (SupportSignWayResponse) {}
rpc exportPublicKeyList(ExportPublicKeyRequest) returns (ExportPublicKeyResponse) {}
rpc signTxMessage(SignTxMessageRequest) returns (SignTxMessageResponse) {}
}
WalletService 定义了钱包服务的接口,包括以下三个 RPC方法:
getSupportSignWay:
输入: SupportSignWayRequest
输出: SupportSignWayResponse
功能: 查询是否支持指定的签名方式。
exportPublicKeyList:
输入: ExportPublicKeyRequest
输出: ExportPublicKeyResponse
功能: 导出指定类型和数量的公钥。
signTxMessage:
输入: SignTxMessageRequest
输出: SignTxMessageResponse
功能: 对交易消息进行签名
2. 总结
该 Protocol Buffers 定义文件旨在为一个钱包服务定义结构化数据类型和接口,涵盖了:
1)公钥管理(查询、导出)。
2)签名方式的支持性查询。
3)交易消息签名服务。
通过上述定义,可以生成对应的 Go 代码并实现服务逻辑。
2、RPC服务接口
1. 服务实现
package rpc
import (
"context"
"fmt"
"github.com/dapplink-labs/wallet-sign-go/hsm"
"net"
"sync/atomic"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
"github.com/ethereum/go-ethereum/log"
"github.com/dapplink-labs/wallet-sign-go/leveldb"
"github.com/dapplink-labs/wallet-sign-go/protobuf/wallet"
)
const MaxRecvMessageSize = 1024 * 1024 * 30000
type RpcServerConfig struct {
GrpcHostname string
GrpcPort int
KeyPath string
KeyName string
HsmEnable bool
}
type RpcServer struct {
*RpcServerConfig
db *leveldb.Keys
HsmClient *hsm.HsmClient
wallet.UnimplementedWalletServiceServer
stopped atomic.Bool
}
func (s *RpcServer) Stop(ctx context.Context) error {
s.stopped.Store(true)
return nil
}
func (s *RpcServer) Stopped() bool {
return s.stopped.Load()
}
func NewRpcServer(db *leveldb.Keys, config *RpcServerConfig) (*RpcServer, error) {
hsmClient, err := hsm.NewHSMClient(context.Background(), config.KeyPath, config.KeyName)
if err != nil {
log.Error("new hsm client fail", "err", err)
}
return &RpcServer{
RpcServerConfig: config,
db: db,
HsmClient: hsmClient,
}, nil
}
func (s *RpcServer) Start(ctx context.Context) error {
go func(s *RpcServer) {
addr := fmt.Sprintf("%s:%d", s.GrpcHostname, s.GrpcPort)
log.Info("start rpc services", "addr", addr)
listener, err := net.Listen("tcp", addr)
if err != nil {
log.Error("Could not start tcp listener. ")
}
opt := grpc.MaxRecvMsgSize(MaxRecvMessageSize)
gs := grpc.NewServer(
opt,
grpc.ChainUnaryInterceptor(
nil,
),
)
reflection.Register(gs)
wallet.RegisterWalletServiceServer(gs, s)
log.Info("Grpc info", "port", s.GrpcPort, "address", listener.Addr())
if err := gs.Serve(listener); err != nil {
log.Error("Could not GRPC services")
}
}(s)
return nil
}
1.1 源码解析
这段代码实现了一个基于 gRPC 的服务,负责启动和管理 WalletService RPC 服务。以下是对代码的详细解析:
常量
const MaxRecvMessageSize = 1024 * 1024 * 30000
定义了gRPC服务的最大消息接收大小(MaxRecvMessageSize)。
值为 30,000 MB,这是一个非常大的值,可能是为了处理极大数据量的场景。
RpcServerConfig 配置结构体
type RpcServerConfig struct {
GrpcHostname string
GrpcPort int
KeyPath string
KeyName string
HsmEnable bool
}
用于配置 RPC 服务。
GrpcHostname: gRPC 服务监听的主机名。
GrpcPort: gRPC 服务监听的端口。
KeyPath 和 KeyName: 配置与 HSM 客户端相关的密钥路径和名称。
HsmEnable: 标志是否启用 HSM(硬件安全模块)。
RpcServer 服务结构体
type RpcServer struct {
*RpcServerConfig
db *leveldb.Keys
HsmClient *hsm.HsmClient
wallet.UnimplementedWalletServiceServer
stopped atomic.Bool
}
RpcServerConfig: 嵌入的配置结构体,存储服务相关的配置信息。
db: 使用 LevelDB 存储密钥信息的数据库实例。
HsmClient: 与 HSM 交互的客户端实例。UnimplementedWalletServiceServer: 自动生成的 gRPC 服务接口,提供未实现的默认方法。
stopped: 原子布尔值,用于标记服务是否已停止。
停止服务方法
func (s *RpcServer) Stop(ctx context.Context) error {
s.stopped.Store(true)
return nil
}
func (s *RpcServer) Stopped() bool {
return s.stopped.Load()
}
Stop 方法:
将 stopped 设置为 true,表示服务已停止。
Stopped 方法:
检查 stopped 的当前值,返回服务是否已停止。
NewRpcServer 创建服务实例
func NewRpcServer(db *leveldb.Keys, config *RpcServerConfig) (*RpcServer, error) {
hsmClient, err := hsm.NewHSMClient(context.Background(), config.KeyPath, config.KeyName)
if err != nil {
log.Error("new hsm client fail", "err", err)
}
return &RpcServer{
RpcServerConfig: config,
db: db,
HsmClient: hsmClient,
}, nil
}
作用: 创建并初始化一个新的 RpcServer 实例。
关键点: 创建 HSM 客户端 hsm.NewHSMClient。如果创建失败,会记录错误日志,但继续返回一个 RpcServer 实例(可能需要额外处理此错误)。
Start 方法
func (s *RpcServer) Start(ctx context.Context) error {
go func(s *RpcServer) {
addr := fmt.Sprintf("%s:%d", s.GrpcHostname, s.GrpcPort)
log.Info("start rpc services", "addr", addr)
listener, err := net.Listen("tcp", addr)
if err != nil {
log.Error("Could not start tcp listener. ")
}
opt := grpc.MaxRecvMsgSize(MaxRecvMessageSize)
gs := grpc.NewServer(
opt,
grpc.ChainUnaryInterceptor(
nil,
),
)
reflection.Register(gs)
wallet.RegisterWalletServiceServer(gs, s)
log.Info("Grpc info", "port", s.GrpcPort, "address", listener.Addr())
if err := gs.Serve(listener); err != nil {
log.Error("Could not GRPC services")
}
}(s)
return nil
}
作用: 启动 gRPC 服务。
详细解析:
监听地址:通过 fmt.Sprintf("%s:%d", s.GrpcHostname, s.GrpcPort) 生成服务监听的地址。
使用 net.Listen("tcp", addr) 创建 TCP 监听器。
gRPC 服务配置:设置最大接收消息大小 grpc.MaxRecvMsgSize(MaxRecvMessageSize)。
使用 grpc.NewServer 创建 gRPC 服务实例。
服务反射: 调用 reflection.Register(gs) 注册 gRPC 反射服务(通常用于调试)。
服务注册:注册自定义的 WalletService 实现:wallet.RegisterWalletServiceServer(gs, s)。
启动服务:调用 gs.Serve(listener) 启动服务。
日志记录:输出服务启动的日志信息。
3. 接口代码
package rpc
import (
"context"
"strconv"
"github.com/ethereum/go-ethereum/log"
"github.com/pkg/errors"
"github.com/dapplink-labs/wallet-sign-go/leveldb"
"github.com/dapplink-labs/wallet-sign-go/protobuf/wallet"
"github.com/dapplink-labs/wallet-sign-go/ssm"
)
func (s *RpcServer) GetSupportSignWay(ctx context.Context, in *wallet.SupportSignWayRequest) (*wallet.SupportSignWayResponse, error) {
if in.Type == "ecdsa" || in.Type == "eddsa" {
return &wallet.SupportSignWayResponse{
Code: strconv.Itoa(1),
Msg: "Support this sign way",
Support: true,
}, nil
} else {
return &wallet.SupportSignWayResponse{
Code: strconv.Itoa(0),
Msg: "Do not support this sign way",
Support: false,
}, nil
}
}
func (s *RpcServer) ExportPublicKeyList(ctx context.Context, in *wallet.ExportPublicKeyRequest) (*wallet.ExportPublicKeyResponse, error) {
if in.Number > 10000 {
return &wallet.ExportPublicKeyResponse{
Code: strconv.Itoa(1),
Msg: "Number must be less than 100000",
}, nil
}
var keyList []leveldb.Key
var retKeyList []*wallet.PublicKey
for counter := 0; counter <= int(in.Number); counter++ {
var priKeyStr, pubKeyStr, decPubkeyStr string
var err error
switch in.Type {
case "ecdsa":
priKeyStr, pubKeyStr, decPubkeyStr, err = ssm.CreateECDSAKeyPair()
case "eddsa":
priKeyStr, pubKeyStr, err = ssm.CreateEdDSAKeyPair()
decPubkeyStr = pubKeyStr
default:
return nil, errors.New("unsupported key type")
}
if err != nil {
log.Error("create key pair fail", "err", err)
return nil, err
}
keyItem := leveldb.Key{
PrivateKey: priKeyStr,
CompressPubkey: pubKeyStr,
}
pukItem := &wallet.PublicKey{
CompressPubkey: pubKeyStr,
DecompressPubkey: decPubkeyStr,
}
retKeyList = append(retKeyList, pukItem)
keyList = append(keyList, keyItem)
}
isOk := s.db.StoreKeys(keyList)
if !isOk {
log.Error("store keys fail", "isOk", isOk)
return nil, errors.New("store keys fail")
}
return &wallet.ExportPublicKeyResponse{
Code: strconv.Itoa(1),
Msg: "create keys success",
PublicKey: retKeyList,
}, nil
}
func (s *RpcServer) SignTxMessage(ctx context.Context, in *wallet.SignTxMessageRequest) (*wallet.SignTxMessageResponse, error) {
privKey, isOk := s.db.GetPrivKey(in.PublicKey)
if !isOk {
return nil, errors.New("get private key by public key fail")
}
var signature string
var err error
switch in.Type {
case "ecdsa":
signature, err = ssm.SignECDSAMessage(privKey, in.MessageHash)
case "eddsa":
signature, err = ssm.SignEdDSAMessage(privKey, in.MessageHash)
default:
return nil, errors.New("unsupported key type")
}
if err != nil {
return nil, err
}
return &wallet.SignTxMessageResponse{
Code: strconv.Itoa(1),
Msg: "sign tx message success",
Signature: signature,
}, nil
}
3.1 源码解析
GetSupportSignWay
func (s *RpcServer) GetSupportSignWay(ctx context.Context, in *wallet.SupportSignWayRequest) (*wallet.SupportSignWayResponse, error) {
if in.Type == "ecdsa" || in.Type == "eddsa" {
return &wallet.SupportSignWayResponse{
Code: strconv.Itoa(1),
Msg: "Support this sign way",
Support: true,
}, nil
} else {
return &wallet.SupportSignWayResponse{
Code: strconv.Itoa(0),
Msg: "Do not support this sign way",
Support: false,
}, nil
}
}
功能: 判断是否支持指定的签名方式(ecdsa 或 eddsa)。
逻辑解析:
1) 检查输入的签名类型 in.Type 是否为支持的类型。
2) 如果支持:
返回响应对象,Support: true,并附带成功信息。
3) 如果不支持:
返回响应对象,Support: false,并附带错误信息。
ExportPublicKeyList
func (s *RpcServer) ExportPublicKeyList(ctx context.Context, in *wallet.ExportPublicKeyRequest) (*wallet.ExportPublicKeyResponse, error) {
if in.Number > 10000 {
return &wallet.ExportPublicKeyResponse{
Code: strconv.Itoa(1),
Msg: "Number must be less than 100000",
}, nil
}
var keyList []leveldb.Key
var retKeyList []*wallet.PublicKey
for counter := 0; counter <= int(in.Number); counter++ {
var priKeyStr, pubKeyStr, decPubkeyStr string
var err error
switch in.Type {
case "ecdsa":
priKeyStr, pubKeyStr, decPubkeyStr, err = ssm.CreateECDSAKeyPair()
case "eddsa":
priKeyStr, pubKeyStr, err = ssm.CreateEdDSAKeyPair()
decPubkeyStr = pubKeyStr
default:
return nil, errors.New("unsupported key type")
}
if err != nil {
log.Error("create key pair fail", "err", err)
return nil, err
}
keyItem := leveldb.Key{
PrivateKey: priKeyStr,
CompressPubkey: pubKeyStr,
}
pukItem := &wallet.PublicKey{
CompressPubkey: pubKeyStr,
DecompressPubkey: decPubkeyStr,
}
retKeyList = append(retKeyList, pukItem)
keyList = append(keyList, keyItem)
}
isOk := s.db.StoreKeys(keyList)
if !isOk {
log.Error("store keys fail", "isOk", isOk)
return nil, errors.New("store keys fail")
}
return &wallet.ExportPublicKeyResponse{
Code: strconv.Itoa(1),
Msg: "create keys success",
PublicKey: retKeyList,
}, nil
}
功能: 根据请求生成密钥对列表,并将结果存储到数据库。
逻辑解析:
1) 检查请求中的 Number 参数。
如果超过 10,000,直接返回错误响应。
2) 循环生成密钥对:
根据 Type 参数调用对应的方法生成密钥对(ecdsa 或 eddsa)。
将生成的公私钥信息分别存储到 keyList 和 retKeyList。ecdsa: 生成压缩和解压缩公钥。
eddsa: 仅生成压缩公钥(等于解压缩公钥)。
如果生成失败,记录日志并返回错误。
3) 调用 StoreKeys 方法将生成的密钥存储到 LevelDB。
如果存储失败,记录日志并返回错误。
4) 返回成功响应,包含生成的公钥列表。
SignTxMessage
func (s *RpcServer) SignTxMessage(ctx context.Context, in *wallet.SignTxMessageRequest) (*wallet.SignTxMessageResponse, error) {
privKey, isOk := s.db.GetPrivKey(in.PublicKey)
if !isOk {
return nil, errors.New("get private key by public key fail")
}
var signature string
var err error
switch in.Type {
case "ecdsa":
signature, err = ssm.SignECDSAMessage(privKey, in.MessageHash)
case "eddsa":
signature, err = ssm.SignEdDSAMessage(privKey, in.MessageHash)
default:
return nil, errors.New("unsupported key type")
}
if err != nil {
return nil, err
}
return &wallet.SignTxMessageResponse{
Code: strconv.Itoa(1),
Msg: "sign tx message success",
Signature: signature,
}, nil
}
功能: 根据提供的公钥和消息哈希,使用对应的私钥对消息进行签名。
逻辑解析:
1) 从 LevelDB 获取与公钥对应的私钥:
如果未找到,返回错误。
2) 根据签名类型 Type 调用对应的签名方法:
ecdsa: 使用 ECDSA 方法签名。
eddsa: 使用 EdDSA 方法签名。
如果类型不支持,返回错误。
3) 如果签名失败,返回错误。
4) 返回成功响应,包含生成的签名。
4.LevelDB 代码解析
package leveldb
import (
"encoding/hex"
"github.com/ethereum/go-ethereum/log"
"github.com/syndtr/goleveldb/leveldb"
)
type LevelStore struct {
*leveldb.DB
}
func NewLevelStore(path string) (*LevelStore, error) {
handle, err := leveldb.OpenFile(path, nil)
if err != nil {
log.Error("open level db file fail", "err", err)
return nil, err
}
return &LevelStore{handle}, nil
}
func (db *LevelStore) Put(key []byte, value []byte) error {
return db.DB.Put(key, value, nil)
}
func (db *LevelStore) Get(key []byte) ([]byte, error) {
return db.DB.Get(key, nil)
}
func (db *LevelStore) Delete(key []byte) error {
return db.DB.Delete(key, nil)
}
func toBytes(dataStr string) []byte {
dataBytes, _ := hex.DecodeString(dataStr)
return dataBytes
}
func toString(byteArr []byte) string {
return hex.EncodeToString(byteArr)
}
这段代码实现了基于 LevelDB 的一个简单键值存储库封装,提供了对数据的基本操作接口(Put、Get 和 Delete)
package leveldb
import "github.com/ethereum/go-ethereum/log"
type Keys struct {
db *LevelStore
}
func NewKeyStore(path string) (*Keys, error) {
db, err := NewLevelStore(path)
if err != nil {
log.Error("Could not create leveldb database.")
return nil, err
}
return &Keys{
db: db,
}, nil
}
func (k *Keys) GetPrivKey(publicKey string) (string, bool) {
key := []byte(publicKey)
data, err := k.db.Get(key)
if err != nil {
return "0x00", false
}
bstr := toString(data)
return bstr, true
}
func (k *Keys) StoreKeys(keyList []Key) bool {
for _, item := range keyList {
key := []byte(item.CompressPubkey)
value := toBytes(item.PrivateKey)
err := k.db.Put(key, value)
if err != nil {
log.Error("store key value fail", "err", err, "key", key, "value", value)
return false
}
}
return true
}
这段代码实现了一个密钥存储 (Keys) 模块,基于 LevelDB 提供密钥的存取功能;主要功能是实现 privateKey 的存取。
小结
DappLink的签名机支持两种主要模式:CloudHSM(云硬件安全模块)和TEE(可信执行环境)。这两种模式提供了高安全性的环境用于执行加密操作。签名机目前支持两种常见的数字签名算法:ECDSA(椭圆曲线数字签名算法)和EdDSA(Edwards曲线数字签名算法)。这些功能已经被多家厂商采用,能够提供安全、可靠的数字签名解决方案,广泛应用于加密货币、区块链、金融等领域。
声明:本网站所有相关资料如有侵权请联系站长删除,资料仅供用户学习及研究之用,不构成任何投资建议!