以太坊技术与实现

imToken 是一款全球领先的区块链数字资产管理工具[ZB],帮助你安全管理BTC, ETH, ATOM, EOS, TRX, CKB, BCH, LTC, DOT, KSM, FIL, XTZ 资产,同时支持去中心化币币兑换功能 ...

签名与校验

区块链比特币以太坊

原本写有以太坊交易签名的文章,但觉得对以太坊的数字签名还讲得不够夯实。这里从原理上聊聊以太坊签名与校验,希望这篇文章让你一次性掌握以太坊数字签名技术。

比特币钱包和以太坊钱包

为何选择签名算法

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

是高效密码组标准(SECG) 协会开发的一套高效的椭圆曲线签名算法标准。 在比特币流行之前,并未真正使用过。 命名由几部分组成:sec来自SECG标准,p表示曲线坐标是素数域,256表示素数是256位长,k 表示它是 曲线的变体,1表示它是第一个标准中该类型的曲线。

SECG( for Group) 成立于1998年,一个从事密码标准通用性潜力研究的组织。旨在促进在各种计算平台上采用高效加密和提高互操作性。

但因具有几个不错的特性,现在它越来越受欢迎。大多数常用的椭圆曲线是随机结构,但 是为了更有效率的计算而构造了一个非随机结构。因此经过充分地优化算法代码实现,其计算效率可以比其他椭圆曲线算法快30%以上。此外,与常用的NIST曲线不同, 的常量是以可预测的方式挑选的,这可以有效降低曲线设计者安置后门的可能性。

密码学内容涉及太多数学知识,这里我虞双齐还没能力说清这里面的一二三 :)。有兴趣的可以看 算法标准文档,这里我只画一张图,让大家对签名算法归类所有了解。

密码学技术分类

从图中看到, 是 ECDSA 算法中的一个标准,出现的也比较晚。为何中本聪为比特币作为交易验证的签名算法?比特币开发者社区曾讨论过 是否安全。中本聪没有明确解释,只是说道”有根据的推测”。社区的讨论不外乎是在安全和效率上做权衡,选择一个不受任何政府控制、无后门的签名算法是比特币的首要考虑因素,其次,也需要提供计算速度,毕竟在比特币中加密、签名、校验签名是不断在处理的事情(60%左右的CPU时间几乎全用在这上面),而具有可预测性、高计算效率特性的曲线是不错的选择。基于安全第一,效率第二原则imToken钱包, 就是一个最优解。

以太坊与比特币签名的差异化

虽然以太坊签名算法是 ,但是在签名的格式有所差异。

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

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

这里的 0x30 、0x02 是DER数据格式中定义的Tag,不同Tag对应不同含义。以 算法来说:

注意,这里还尚未包含签名内容的哈希标志信息。

例如,如下代码是利用Go语言版的比特币对字符串签名,

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

我们从下图中可以清晰地看到,比特币签名是对的签名进行DER格式编码处理。

比特币签名格式举例

而以太坊中对内容签名时,尚未进行DER格式。同样在以太坊中对字符串签名。

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签名的原始输出,而末尾的一个字节为 id 值,但在以太坊中用V表示,v 值为1或者0。 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 为零。

以太坊签名数据格式

有一点需要注意,以太坊的 .Sign 函数实际是采用两个代码库,C语言版和Go语言版。那么在外部在实际调用时调用的是哪个语言版本的呢?这在编译期由决定。如下图以太坊的签名函数提供了C版调用和纯Go调用,两个语言版本在文件开头会标记编译条件和文件名上做区分,上面的解析代码属比特币 Go语言版调用,其Go语言库是 //btcd/btcec 。

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

cgo 能让Go语言跨语言调用 C ,可以将Go代码和C代码打包到一起,想了解更多可参加官方文章 C? Go? Cgo!

签名校验

使用使用 .Sign 对内容签名后,同样可以使用 . 方法校验签名是否正确。下面示例代码演示将上面示例中获得的签名结果进行验证。

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] 去掉了末尾的一个字节。这是因为函数要求 sig参数必须是[R] [S]格式,因此需要去除末尾的[V]。

链数据签名与校验

上面的签名仅仅是 的签名与校验。但实际在区块链中,为了安全性签名中加入了特性数据,比如签名类型(环签、单私钥签名等)、链标识符等。在以太坊中区块中的数据需要签名的仅有交易,因此下面我以交易为示例讲解以太坊的链数据签名和交易。

交易数据签名

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

//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编码、哈希算法和椭圆曲线 加密算法。从这里可以看出,密码学技术是区块链成功的最大基石。

以太坊交易签名流程

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

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

创建一笔交易,使用私钥 2032 进行签名。

// 实例化一个签名器
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。

交易签名解析流程

签名交易后,如何才能获得交易签名者呢?这个是加密算法的逆向解签名者,是利用用户签名内容以及签名信息(R、S、V)得到用户私钥的公钥,从而得到签名者账户地址。具体细节如下。

对比交易签名流程,解签名是逆向推导。

//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的旧签名方式。拿到 后,则判断tx. 是否等于当前的网络的。

至此,我们说明了以太坊的签名以及和比特币的差异,最后还讲解了以太坊中一笔交易的签名流程和校验签名过程。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

返回顶部
跳到底部

Copyright © 2002-2024 imToken钱包下载官网 Rights Reserved.
备案号:晋ICP备13003952号

谷歌地图 | 百度地图
Powered by Z-BlogPHP Theme By open开发