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()) }