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

时间:2022-01-09 22:20:54

以太坊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)。

  • ```go
    // 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:\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())
}