主页 > imtoken苹果版下载 > 为什么选择secp256k1签名算法

为什么选择secp256k1签名算法

imtoken苹果版下载 2023-10-20 05:12:16

签名和验证

本来写了一篇关于以太坊交易签名的文章,但是感觉以太坊的数字签名不够扎实。 这里我们从原理上讲一下以太坊的签名和验证。 希望这篇文章能让你一次性掌握以太坊数字签名技术。

为什么选择secp256k1签名算法

比特币于2009年1月4日成功挖出创世块,至今稳定运行。 出色的稳定运行能力让其他区块链大量借鉴了比特币技术解决方案,包括密码学领域的哈希算法和加密算法。 站在巨人的肩膀上改进技术是我们一贯的做法,以太坊也不例外。 2015年7月30日以太坊上市时,也采用了比特币的签名算法:椭圆曲线算法secp256k1。

secp256k1是由高效密码组标准(SECG)协会制定的一套高效椭圆曲线签名算法标准。 直到比特币流行起来,secp256k1 才真正被使用。 secp256k1命名由几部分组成:sec来自SECG标准,p表示曲线坐标为素数域,256表示素数长度为256位以太坊加密算法,k表示是Koblitz曲线的变体,以及1 表示它是该类型曲线的第一个标准。

SECG(Standards for Efficient Cryptography Group)成立于 1998 年,是一个从事密码标准泛化潜力研究的组织。 旨在促进高效密码学的采用并提高各种计算平台之间的互操作性。

但由于几个不错的功能,它最近越来越受欢迎。 最常用的椭圆曲线是随机结构,但 secp256k1 构建了非随机结构以提高计算效率。 因此,在充分优化算法代码实现后,其计算效率可比其他椭圆曲线算法快30%以上。 此外,与常用的NIST曲线不同,secp256k1的常数是以可预测的方式选取的,可以有效降低曲线设计者安装后门的可能性。

密码学的内容涉及到太多的数学知识,我余双琪无法在这里解释其中的一二三:)。 有兴趣的可以阅读Secp256k1算法标准文档。 这里我只画一张图,让大家了解签名算法的分类。

密码学技术分类

从图中可以看出,secp256k1是ECDSA算法中的一个标准,出现的比较晚。 为什么中本聪使用比特币secp256k1作为交易验证的签名算法? 比特币开发者社区已经讨论过 secp256k1 是否安全。 中本聪并没有解释清楚,只是说了“有根据的猜测”。 社区里的讨论无非就是安全和效率之间的权衡。 选择不受任何政府控制、没有后门的签名算法是比特币的首要考虑因素。 其次以太坊加密算法,它还需要提供计算速度。 毕竟比特币中的加密签名、签名、验证签名是不断被处理的东西(大约60%的CPU时间几乎全部花在了它们上面),具有可预测性和高计算效率的Koblitz曲线是一个不错的选择. 基于安全第一,效率第二的原则,secp256k1是一个最优解。

以太坊和比特币签名之间的差异

以太坊签名算法虽然是secp256k1,但是签名格式还是有区别的。

比特币在BIP66中对签名数据格式采用了严格的DER编码格式,其签名数据格式如下:

 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S]

这里0x30和0x02是DER数据格式定义的Tags,不同的Tags对应不同的含义。 对于 secp256k1 算法:

请注意,此处尚未包含签名内容的哈希标志信息。

例如下面的代码使用Go语言版本的Bitcoin对字符串ethereum进行签名,

package main
import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"log"
	
	"github.com/btcsuite/btcd/btcec" 
)
func main()  {
	dataHash := sha256.Sum256([]byte("ethereum"))
	// 准备私钥

以太坊较其他加密货币优势_以太坊官网以太坊_以太坊加密算法

pkeyb,err :=hex.DecodeString("289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032") if err!=nil{ log.Fatalln(err) } // 基于secp256k1的私钥 privk,_:=btcec.PrivKeyFromBytes(btcec.S256(),pkeyb) // 对内容的 hash 进行签名 sigInfo,err:= privk.Sign(dataHash[:]) if err!=nil{ log.Fatal(err) } // 获得DER格式的签名 sig :=sigInfo.Serialize() fmt.Println("sig length:",len(sig)) fmt.Println("sig hex:",hex.EncodeToString(sig)) }

执行代码,输出如下:

sig length 70
sig hex: 304402207912f50819764de81ab7791ab3d62f8dabe84c2fdb2f17d76465d28f8a968f73022055fbb6cd8dfc7545b6258d4b032753b2074232b07f3911822b37f024cd101166

从下图我们可以清楚的看到,对于secp256k1的签名,比特币签名是用DER格式编码的。

比特币签名格式举例

在以太坊中对内容进行签名时,还没有进行DER格式。 同样在以太坊中,字符串 ethereum 被签名。

package main
import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"log"

以太坊加密算法_以太坊较其他加密货币优势_以太坊官网以太坊

"github.com/ethereum/go-ethereum/crypto" ) func main() { dataHash := sha256.Sum256([]byte("ethereum")) // 准备私钥 pkeyb,err :=hex.DecodeString("289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032") if err!=nil{ log.Fatalln(err) } // 基于secp256k1的私钥 pkey,err:=crypto.ToECDSA(pkeyb) if err!=nil{ log.Fatalln(err) } // 签名 sig,err:= crypto.Sign(dataHash[:],pkey) if err!=nil{ log.Fatal(err) } fmt.Println("sig length:",len(sig)) fmt.Println("sig hex:",hex.EncodeToString(sig)) }

执行代码,输出如下:

sig length: 65
sig hex: 7912f50819764de81ab7791ab3d62f8dabe84c2fdb2f17d76465d28f8a968f7355fbb6cd8dfc7545b6258d4b032753b2074232b07f3911822b37f024cd10116600

与比特币签名相比,以太坊的签名格式是r+s+v。 r和s为ECDSA签名的原始输出,最后一个字节为recovery id值,但在以太坊中用V表示,v的值为1或0。recovery id简称为recid,表示从内容和签名中成功恢复出公钥后需要搜索的次数(因为根据r值,椭圆曲线中可能有多个符合要求的坐标点),但是最大需要搜索的次数在比特币下两次。 这样,在签名验证还原公钥时,无需遍历查找,一次就可以找到公钥,加快了签名验证的速度。

以太坊中的签名代码实现如下:

以太坊官网以太坊_以太坊加密算法_以太坊较其他加密货币优势

//crypto/signature_nocgo.go:60
func Sign(hash []byte, prv *ecdsa.PrivateKey) ([]byte, error) {
	if len(hash) != 32 {//❶
		return nil, fmt.Errorf("hash is required to be exactly 32 bytes (%d)", len(hash))
	}
	if prv.Curve != btcec.S256() {//❷
		return nil, fmt.Errorf("private key curve is not secp256k1")
	}
  //❸
	sig, err := btcec.SignCompact(btcec.S256(), (*btcec.PrivateKey)(prv), hash, false)
	if err != nil {
		return nil, err
	}
	// Convert to Ethereum signature format with 'recovery id' v at the end.
	v := sig[0] - 27 //❹
	copy(sig, sig[1:])//❺
	sig[64] = v
	return sig, nil
}

下图为上述操作签名数据转换的示例流程。 第一次查找只找到合法的公钥,所以recid为零。

以太坊签名数据格式

需要注意的一点是,以太坊的crypto.Sign函数实际上使用了两个代码库,C语言版本和Go语言版本。 那么实际对外调用secp256k1时调用的是哪个语言版本呢? 这是在编译时确定的。 如下图所示,以太坊的签名函数提供了C版本的调用和纯Go的调用。 两种语言版本都会在文件开头标明编译条件和文件名,以示区分。 上面的解析代码是比特币secp256k1 Go语言版本调用的。 语言存储库是 github.com/btcsuite/btcd/btcec。

以太坊crypto签名调用提供CGo和GO调用

cgo允许Go语言跨语言调用C,可以将Go代码和C代码打包在一起。 如果想了解更多,请参考官方文章C? 去? 加油!

签名验证

使用crypto.Sign 对内容进行签名后,还可以使用crypto.VerifySignature 方法验证签名是否正确。 以下示例代码演示了对上述示例中得到的签名结果的验证。

func main()  {
	decodeHex:= func(s string) []byte {
		b,err:=hex.DecodeString(s)
		if err!=nil{

以太坊加密算法_以太坊官网以太坊_以太坊较其他加密货币优势

log.Fatal(err) } return b } dataHash := sha256.Sum256([]byte("ethereum")) sig:=decodeHex( "7912f50819764de81ab7791ab3d62f8dabe84c2fdb2f17d76465d28f8a968f7355fbb6cd8dfc7545b6258d4b032753b2074232b07f3911822b37f024cd10116600") pubkey:=decodeHex( "037db227d7094ce215c3a0f57e1bcc732551fe351f94249471934567e0f5dc1bf7") ok:=crypto.VerifySignature(pubkey,dataHash[:],sig[:len(sig)-1]) fmt.Println("verify pass?",ok) }

关键是在调用验证签名函数时,第三个参数sig用sig[:len(sig)-1]发送,去掉末尾的一个字节。 这是因为函数VerifySignature要求sig参数必须是[R][S]格式,所以需要去掉末尾的[V]。

链上数据签名与验证

以上签名只是对secp256k1的签名和验证。 但实际上,在区块链中,为了安全起见,在签名中加入了特征数据,如签名类型(环签名、单一私钥签名等)、链标识符等。在以太坊中,区块中的数据是只有交易需要签名,所以我就以交易为例来说明以太坊的链数据签名和交易。

交易数据签名

以太坊加密算法采用比特币的椭圆曲线secp256k1加密算法。 签名交易对应的代码如下:

//core/types/transaction_signing.go:56
func SignTx(tx *Transaction, s Signer, prv *ecdsa.PrivateKey) (*Transaction, error) {//❶
   h := s.Hash(tx)//❷
   sig, err := crypto.Sign(h[:], prv)//❸
   if err != nil {
      return nil, err
   }
   return tx.WithSignature(s, sig)//❹
}

以太坊交易签名内容哈希新补充

这样,一个签名的交易只能属于某个唯一的区块链。

根据以上代码逻辑,提取出如下交易签名流程。 整个过程使用了RLP编码、Keccak256哈希算法和椭圆曲线secp256k1加密算法。 从这里可以看出,密码学技术是区块链成功的最大基石。

以太坊较其他加密货币优势_以太坊加密算法_以太坊官网以太坊

以太坊交易签名流程

上图中还有一个关键数据,Signer是如何生成R、S、V值的。 从前面的签名算法过程可以知道,R和S是ECDSA签名的原始输出,V的值为recid,其值为0或1。但是当交易被签名时,V的值不再是recid,但 recid+ chainID*2+ 35。例如:

tx:=types.NewTransaction(1,
   common.HexToAddress("0x002e08000acbbae2155fab7ac01929564949070d"),
   big.NewInt(100),21000,big.NewInt(1),nil)

创建一个交易,用私钥 289c2857d4598e37fb9647507e47a309d6133539bf21a8b9cb6df88fd5232032 签名。

// 实例化一个签名器
signer:=types.NewEIP155Signer(big.NewInt(888))
tx,err=types.SignTx(tx,signer,pkey)
	if err!=nil{
		log.Fatalln(err)
	}
v,r,s:=tx.RawSignatureValues()
fmt.Printf("tx sign V=%d,R=%d,S=%d\n",v,r,s)

得到 V = 888*2+recid+35= 1812。

交易签名分析流程

签署交易后,如何获得交易签署人? 这是加密算法的逆向解signer,利用用户的签名内容和签名信息(R,S,V)得到用户私钥的公钥,从而得到signer的账户地址。 详情如下所示。

与交易签名过程相比,解决方案签名是一种反向推导。

//core/types/transaction_signing.go:127
func (s EIP155Signer) Sender(tx *Transaction) (common.Address, error) {
   if !tx.Protected() { //❶
      return HomesteadSigner{}.Sender(tx)
   }
   if tx.ChainId().Cmp(s.chainId) != 0 { //❷
      return common.Address{}, ErrInvalidChainId
   }
   V := new(big.Int).Sub(tx.data.V, s.chainIdMul)//❸ 
   V.Sub(V, big8)
   return recoverPlain(s.Hash(tx), tx.data.R, tx.data.S, V, true)
}

也就是说考虑了27或者28的老签名方式,超过了MaxUint64。 得到chainID后,判断tx.ChainID是否等于当前网络的ChainID。

至此,我们讲解了以太坊的签名以及与比特币的区别,最后讲解了以太坊中一笔交易的签名过程和验证签名过程。