zoukankan      html  css  js  c++  java
  • 以太坊go-ethereum签名部分源码解析

    以太坊go-ethereum签名部分源码解析

    golang标准库里的crypto/ecdsa椭圆曲线加密算法所提供的函数有:

    • ecdsa.PublicKey结构体通过持有一个elliptic,Curve接口的实现体,可以提供椭圆曲线的所有属性,和相关操作;PublicKey的成员(X,Y),对应于算法理论中公钥的坐标。

    • func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error)
      
    • elliptic.Curve接口声明了椭圆曲线的相关操作方法,其中Add()方法就是椭圆曲线点倍积中的“点相加”操作,Double()就是点倍积中的“点翻倍”操作,ScalarMult()根本就是一个点倍积运算(参数k是标量),IsOnCurve()检查参数所代表的点是否在该椭圆曲线上;

    • elliptic.CurveParams结构体实现了curve接口的所有方法,另外用成员属性定义了一个具体的椭圆曲线,比如(Gx, Gy) 表示该椭圆曲线的基点,即算法理论中的G点; N 是与基点对应的可倍积阶数n;B是椭圆曲线几何方程中的参数b,注意此处ecdsa代码包中隐含的椭圆曲线方程为y^2 = x^3 - 3x + b,故只需一项参数b即可。

    • ecdsa.PrivateKey是暴露给外部使用的主要结构体类型,它其实是算法理论中的私钥和公钥的集合。它的成员D,才真正对应于算法理论中的(标量)私钥。

    • ecdsa.ecdsaSignature对应于生成的数字签名(r, s)。

    • // PublicKey represents an ECDSA public key.两个big.Int类型
      type PublicKey struct {
         elliptic.Curve
         X, Y *big.Int
      }
      
      // PrivateKey represents a ECDSA private key.
      type PrivateKey struct {
         PublicKey
         D *big.Int
      }
      
      type ecdsaSignature struct {
         R, S *big.Int
      }
      
    //@Time  : 2018/3/23 11:33
    //@Author: Greg Li
    package main
    
    import (
    	"encoding/hex"
    	"fmt"
    	"github.com/ethereum/go-ethereum/crypto"
    )
    
    func main()  {
    	// 创建账户
    	key, err := crypto.GenerateKey()
    	if err !=nil {
    		fmt.Println(err)
    	}
    
    	// 私钥:64个字符
    	privateKey := hex.EncodeToString(key.D.Bytes())
    	fmt.Println(privateKey)
    
    	// 得到地址:42个字符
    	address := crypto.PubkeyToAddress(key.PublicKey).Hex()
    	fmt.Println(address)
    }
    /*
     * 公钥:在secp256k1规范下,由私钥和规范中指定的生成点计算出的坐标(x, y)
     *      非压缩格式公钥: [前缀0x04] + x + y (65字节)
     *      压缩格式公钥:[前缀0x02或0x03] + x ,其中前缀取决于 y 的符号
     */
    //生成公钥: 输入的私钥应当是buffer
    

    从私钥获取有三个主要步骤 - >地址:
    创建一个随机私钥(64(十六进制)字符/ 256位/ 32字节)

    从该私钥导出公钥(128(十六进制)字符/ 512位/ 64字节)

    从这个公钥导出地址。 (40(十六进制)字符/ 160位/ 20字节)

    尽管很多人称这个地址为公钥,但其实在Ethereum中并非如此。有一个独立的公钥,作为一个中间人,你永远不会看到,除非你去寻找一个预售钱包JSON文件。

    1.生成私钥
    私钥是64个十六进制字符。假设每一个64字节的字符串都是一个以太坊私钥将访问一个帐户。如果计划生成一个新帐户,应确保这些帐户使用适当的RNG进行播种。

    2.私钥 - >公钥
    椭圆曲线数字签名算法(ECDSA)会得到一个64字节的公钥。

    3.公钥 - >地址
    从公钥开始(128个字符/ 64个字节)

    采用公钥的Keccak-256散列。你现在应该有一个64字符/ 32字节的字符串。 (注意:SHA3-256最终成为标准,但以太坊使用Keccak)

    取这个公钥的最后40个字符/ 20个字节(Keccak-256)。或换句话说,删除前24个字符/ 12个字节。这40个字符/ 20字节是地址。当前缀为0x时,它变为42个字符长。

    定义
    地址:以太坊地址代表一个帐户。对于EOA,地址是作为控制该账户的公钥的最后20个字节导出的,例如`cd2a3d9f938e13cd947ec05abc7fe734df8dd826。这是一个十六进制格式(基数为16的表示法),通常通过向地址附加0x来明确指出。 Web3.js和控制台函数接受有或没有这个前缀的地址,但为了透明,我们鼓励他们使用。由于地址的每个字节都用2个十六进制字符表示,所以前缀地址长度为42个字符。几个应用程序和API也是为了实现从0.5.0版本开始在Mist Ethereum钱包中引入的新校验和启用地址方案。

    私钥:在[1,secp256k1n - 1]范围内随机选择的正整数(表示为长尾为32的字节数组)。

    //@Time  : 2018/3/23 14:47
    //@Author: Greg Li
    package main
    
    import (
       "fmt"
    
       "github.com/ethereum/go-ethereum/common"
       "github.com/ethereum/go-ethereum/common/hexutil"
       "github.com/ethereum/go-ethereum/crypto"
    )
    
    func main() {
       fmt.Println(verifySig(
          "0x829814B6E4dfeC4b703F2c6fDba28F1724094D11",
          "0x53edb561b0c1719e46e1e6bbbd3d82ff798762a66d0282a9adf47a114e32cbc600c248c247ee1f0fb3a6136a05f0b776db4ac82180442d3a80f3d67dde8290811c",
          []byte("hello"),
       ))
    }
    
    func verifySig(from, sigHex string, msg []byte) bool {
       fromAddr := common.HexToAddress(from)
    
       sig := hexutil.MustDecode(sigHex)
       if sig[64] != 27 && sig[64] != 28 {
          return false
       }
       sig[64] -= 27
    
       pubKey, err := crypto.SigToPub(signHash(msg), sig)
       if err != nil {
          return false
       }
    
       recoveredAddr := crypto.PubkeyToAddress(*pubKey)
    
       return fromAddr == recoveredAddr
    }
    
    func signHash(data []byte) []byte {
       msg := fmt.Sprintf("x19Ethereum Signed Message:
    %d%s", len(data), data)
       return crypto.Keccak256([]byte(msg))
    }
    

    Ecrecover可以从签名中恢复公钥

    // Ecrecover returns the uncompressed public key that created the given signature.
    func Ecrecover(hash, sig []byte) ([]byte, error) {
       return secp256k1.RecoverPubkey(hash, sig)
    }
    // SigToPub returns the public key that created the given signature.
    func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) {
    	s, err := Ecrecover(hash, sig)
    	if err != nil {
    		return nil, err
    	}
    
    	x, y := elliptic.Unmarshal(S256(), s)
    	return &ecdsa.PublicKey{Curve: S256(), X: x, Y: y}, nil
    }
    

    这里模拟发送一笔交易

    //@Time  : 2018/3/23 11:41
    //@Author: Greg Li
    package main
    
    import (
       "fmt"
       "math/big"
       "context"
       "io/ioutil"
       "github.com/ethereum/go-ethereum/accounts"
       "github.com/ethereum/go-ethereum/accounts/keystore"
       "github.com/ethereum/go-ethereum/common"
       "github.com/ethereum/go-ethereum/core/types"
       "github.com/ethereum/go-ethereum/crypto"
       "github.com/ethereum/go-ethereum/ethclient"
    )
    
    const (
       KEYJSON_FILEDIR   = `/home/greg/.ethereum/keystore/UTC--2018-03-01T06-46-59.670564333Z--99853a4cc9257df5a7e1e276d2e44567f2172db1`
       SIGN_PASSPHRASE   = `test`
       KEYSTORE_DIR      = `UTC--2018-03-01T06-46-59.670564333Z--99853a4cc9257df5a7e1e276d2e44567f2172db1`
       COINBASE_ADDR_HEX = `0x99853a4cc9257df5a7e1e276d2e44567f2172db1`
       ALTER_ADDR_HEX    = `0x0152b5c8a375bbc305a6f285c4c26d25935d5d94`
       CHAIN_ID          = 0
    )
    
    func main() {
       // 初始化keystore
       ks := keystore.NewKeyStore(
          KEYSTORE_DIR,
          keystore.LightScryptN,
          keystore.LightScryptP)
       fmt.Println()
    
       // 创建账户
       fromAccDef := accounts.Account{
          Address: common.HexToAddress(COINBASE_ADDR_HEX),
       }
    
       toAccDef := accounts.Account{
          Address: common.HexToAddress(ALTER_ADDR_HEX),
       }
    
       // 查找将给定的帐户解析为密钥库中的唯一条目:找到签名的账户
       signAcc, err := ks.Find(fromAccDef)
       if err != nil {
          fmt.Println("account keystore find error:")
          panic(err)
       }
       fmt.Printf("account found: signAcc.addr=%s; signAcc.url=%s
    ", signAcc.Address.String(), signAcc.URL)
       fmt.Println()
    
       // 解锁签名的账户
       errUnlock := ks.Unlock(signAcc, SIGN_PASSPHRASE)
       if errUnlock != nil {
          fmt.Println("account unlock error:")
          panic(err)
       }
       fmt.Printf("account unlocked: signAcc.addr=%s; signAcc.url=%s
    ", signAcc.Address.String(), signAcc.URL)
       fmt.Println()
    
       // 建立交易
       tx := types.NewTransaction(
          0x0,
          toAccDef.Address,
          new(big.Int),
          0,
          new(big.Int),
          []byte(`cooldatahere`))
    
       // 打开账户私钥文件
       keyJson, readErr := ioutil.ReadFile(KEYJSON_FILEDIR)
       if readErr != nil {
          fmt.Println("key json read error:")
          panic(readErr)
       }
    
       // 解析私钥文件
       keyWrapper, keyErr := keystore.DecryptKey(keyJson, SIGN_PASSPHRASE)
       if keyErr != nil {
          fmt.Println("key decrypt error:")
          panic(keyErr)
       }
       fmt.Printf("key extracted: addr=%s", keyWrapper.Address.String())
    
       // Define signer and chain id
       // chainID := big.NewInt(CHAIN_ID)
       // signer := types.NewEIP155Signer(chainID)
       signer := types.HomesteadSigner{}
    
       //用私钥签署交易签名
       signature, signatureErr := crypto.Sign(tx.Hash().Bytes(), keyWrapper.PrivateKey)
       if signatureErr != nil {
          fmt.Println("signature create error:")
          panic(signatureErr)
       }
    
       signedTx, signErr := tx.WithSignature(signer, signature)
       if signErr != nil {
          fmt.Println("signer with signature error:")
          panic(signErr)
       }
    
       //连接客户端
       client, err := ethclient.Dial("http://localhost:8000") // 8000=geth RPC port
       if err != nil {
          fmt.Println("client connection error:")
          panic(err)
       }
       fmt.Println("client connected")
       fmt.Println()
    
       //发送交易到网络
       txErr := client.SendTransaction(context.Background(), signedTx)
       if txErr != nil {
          fmt.Println("send tx error:")
          panic(txErr)
       }
       fmt.Printf("send success tx.hash=%s
    ", signedTx.Hash().String())
    }
    
  • 相关阅读:
    Dev C++ 工程没有调试信息 解决办法
    写一个函数,输入一个二叉树,树中每个节点存放了一个整数值,函数返回这棵二叉树中相差最大的两个节点间的差值绝对值。请注意程序效率。
    oracle10g登录em后,提示“java.lang.Exception: Exception in sending Request :: null”
    网站登录的破解
    sql 日志恢复
    Oracle expdp/impdp 使用示例
    Oracle数据库备份和恢复的基本命令
    检索 COM 类工厂中 CLSID 为 {{10020200-E260-11CF-AE68-00AA004A34D5}} 的组件时失败解决办法
    win7访问部分win2003速度慢
    公交车路线查询系统后台数据库设计--换乘算法改进与优化
  • 原文地址:https://www.cnblogs.com/ningxin18/p/8631450.html
Copyright © 2011-2022 走看看