以太坊go-ethereum簽名部分源碼解析

以太坊go-ethereum簽名部分源碼解析

golang標準庫裏的crypto/ecdsa橢圓曲線加密算法所提供的函數有:git

  • ecdsa.PublicKey結構體經過持有一個elliptic,Curve接口的實現體,能夠提供橢圓曲線的全部屬性,和相關操做;PublicKey的成員(X,Y),對應於算法理論中公鑰的座標。github

  • func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error)
  • elliptic.Curve接口聲明瞭橢圓曲線的相關操做方法,其中Add()方法就是橢圓曲線點倍積中的「點相加」操做,Double()就是點倍積中的「點翻倍」操做,ScalarMult()根本就是一個點倍積運算(參數k是標量),IsOnCurve()檢查參數所表明的點是否在該橢圓曲線上;golang

  • elliptic.CurveParams結構體實現了curve接口的全部方法,另外用成員屬性定義了一個具體的橢圓曲線,好比(Gx, Gy) 表示該橢圓曲線的基點,即算法理論中的G點; N 是與基點對應的可倍積階數n;B是橢圓曲線幾何方程中的參數b,注意此處ecdsa代碼包中隱含的橢圓曲線方程爲y^2 = x^3 - 3x + b,故只需一項參數b便可。算法

  • ecdsa.PrivateKey是暴露給外部使用的主要結構體類型,它實際上是算法理論中的私鑰和公鑰的集合。它的成員D,才真正對應於算法理論中的(標量)私鑰。json

  • 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字節)app

從這個公鑰導出地址。 (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:\n%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\n", 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\n", 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\n", signedTx.Hash().String())
}
相關文章
相關標籤/搜索