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