以太坊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:\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())
}
以太坊go-ethereum签名部分源码解析的更多相关文章
- [中文] 以太坊(Ethereum )白皮书
以太坊(Ethereum ):下一代智能合约和去中心化应用平台 翻译|巨蟹 .少平 译者注|中文读者可以到以太坊爱好者社区(www.ethfans.org)获取最新的以太坊信息. 当中本聪在2009年 ...
- 以太坊客户端Ethereum Wallet与Geth区别简介
以太坊客户端Ethereum Wallet与Geth区别简介 最近有不少朋友在搭建交易平台,在咨询和技术交流的过程中发现很多朋友不太清楚Ethereum Wallet和Geth区别.甚至有朋友使用Ge ...
- 阿里云视频直播API签名机制源码
阿里云视频直播API签名机制源码 本文展示:通过代码实现下阿里视频直播签名处理规则 阿里云视频直播签名机制,官方文档链接:https://help.aliyun.com/document_detail ...
- bitcoin 源码解析 - 交易 Transaction(三) - Script
bitcoin 源码解析 - 交易 Transaction(三) - Script 之前的章节已经比较粗略的解释了在Transaction体系当中的整体运作原理.接下来的章节会对这个体系进行分解,比较 ...
- 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新
[原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...
- EventBus源码解析 源码阅读记录
EventBus源码阅读记录 repo地址: greenrobot/EventBus EventBus的构造 双重加锁的单例. static volatile EventBus defaultInst ...
- 【转】Java HashMap 源码解析(好文章)
.fluid-width-video-wrapper { width: 100%; position: relative; padding: 0; } .fluid-width-video-wra ...
- SpringMVC源码解析- HandlerAdapter - ModelFactory(转)
ModelFactory主要是两个职责: 1. 初始化model 2. 处理器执行后将modle中相应参数设置到SessionAttributes中 我们来看看具体的处理逻辑(直接充当分析目录): 1 ...
- RTKLIB源码解析(一)——单点定位(pntpos.c)
RTKLIB源码解析(一)--单点定位(pntpos.c) 标签: GNSS RTKLIB 单点定位 [TOC] pntpos int pntpos (const obsd_t *obs, int n ...
随机推荐
- Service IP 原理 - 每天5分钟玩转 Docker 容器技术(137)
Service Cluster IP 是一个虚拟 IP,是由 Kubernetes 节点上的 iptables 规则管理的. 可以通过 iptables-save 命令打印出当前节点的 iptable ...
- java编程思想第四版第二章要点总结
1. 基本类型 基本类型 二进制位数 包装器类 boolean - Boolean byte 8 Byte char 16 Character short 16 Short int 32 Intege ...
- 老机器安装Centos
512M内存,80G硬盘 ubuntu-server没有压力,但是因为很多平台只支持centos服务器linux系统 内存要超过1G默认才能用图形界面安装.装好之后即使256M内存都可以跑起来.lin ...
- file_get_contents函数不能使用的解决方法
今天开发微信公众平台的时候 使用file_get_contents 去获得token 结果一直返回false.百度了一下,大部分都是说用curl 偶然发现可能是openssl没有开启的问题,开启ope ...
- SQL注入攻击三部曲之高级篇
SQL注入攻击三部曲之高级篇 经过了入门篇和进阶篇的学习,相信诸位想要破解一般的网站是没有什么问题了,但是先别得意.正所谓学海无涯,技术的进步也是没有止境的.SQL注入是一个看起来简单,但是变数很多的 ...
- Java之List排序出错
Java之List排序出错 Bound mismatch: The generic method sort(List<T>) of type Collections is not appl ...
- 表格布局----基于bootstrap样式 布局
在实际开发中,我们通过菜鸟教程复制的表格往往不能满足我们的开发需求,样式很难看,而且不能自适应,尤其是需要到处Excel的样式,感觉非常糟糕,这次我就写了一个表单,不足之处,希望大神们多多指教: 代码 ...
- 如何使用jQuery-ContextMenu实现右击菜单
最近在做项目中,遇到一个棘手的问题,页面上有很多功能需要实现,每个功能需要绑定一个按钮.如果一个功能绑定一个按钮,那么将会占用页面很大的空间,而且可能会使页面变得不美观.思前想后,决定将所有按钮做成右 ...
- WPF自学入门(三)WPF路由事件之内置路由事件
有没有想过在.NET中已经有了事件机制,为什么在WPF中不直接使用.NET事件要加入路由事件来取代事件呢?最直观的原因就是典型的WPF应用程序使用很多元素关联和组合起来,是否还记得在WPF自学入门(一 ...
- 洛谷P4180 [Beijing2010组队]次小生成树Tree(最小生成树,LCT,主席树,倍增LCA,倍增,树链剖分)
洛谷题目传送门 %%%TPLY巨佬和ysner巨佬%%% 他们的题解 思路分析 具体思路都在各位巨佬的题解中.这题做法挺多的,我就不对每个都详细讲了,泛泛而谈吧. 大多数算法都要用kruskal把最小 ...