github.com/linapex/ethereum-dpos-chinese@v0.0.0-20190316121959-b78b3a4a1ece/accounts/usbwallet/ledger.go (about)

     1  
     2  //<developer>
     3  //    <name>linapex 曹一峰</name>
     4  //    <email>linapex@163.com</email>
     5  //    <wx>superexc</wx>
     6  //    <qqgroup>128148617</qqgroup>
     7  //    <url>https://jsq.ink</url>
     8  //    <role>pku engineer</role>
     9  //    <date>2019-03-16 12:09:27</date>
    10  //</624342586982666240>
    11  
    12  
    13  //此文件包含用于与分类帐硬件交互的实现
    14  //钱包。有线协议规范可在分类账Blue Github报告中找到:
    15  //https://raw.githubusercontent.com/ledgerhq/blue-app-eth/master/doc/eth app.asc
    16  
    17  package usbwallet
    18  
    19  import (
    20  	"encoding/binary"
    21  	"encoding/hex"
    22  	"errors"
    23  	"fmt"
    24  	"io"
    25  	"math/big"
    26  
    27  	"github.com/ethereum/go-ethereum/accounts"
    28  	"github.com/ethereum/go-ethereum/common"
    29  	"github.com/ethereum/go-ethereum/common/hexutil"
    30  	"github.com/ethereum/go-ethereum/core/types"
    31  	"github.com/ethereum/go-ethereum/log"
    32  	"github.com/ethereum/go-ethereum/rlp"
    33  )
    34  
    35  //LedgerProcode是对支持的分类帐操作码进行编码的枚举。
    36  type ledgerOpcode byte
    37  
    38  //LedgerParam1是一个枚举,用于编码支持的分类帐参数
    39  //特定操作码。相同的参数值可以在操作码之间重复使用。
    40  type ledgerParam1 byte
    41  
    42  //LedgerParam2是一个枚举,用于编码支持的分类帐参数
    43  //特定操作码。相同的参数值可以在操作码之间重复使用。
    44  type ledgerParam2 byte
    45  
    46  const (
    47  ledgerOpRetrieveAddress  ledgerOpcode = 0x02 //返回给定BIP 32路径的公钥和以太坊地址
    48  ledgerOpSignTransaction  ledgerOpcode = 0x04 //让用户验证参数后签署以太坊事务
    49  ledgerOpGetConfiguration ledgerOpcode = 0x06 //返回特定钱包应用程序配置
    50  
    51  ledgerP1DirectlyFetchAddress    ledgerParam1 = 0x00 //直接从钱包返回地址
    52  ledgerP1InitTransactionData     ledgerParam1 = 0x00 //用于签名的第一个事务数据块
    53  ledgerP1ContTransactionData     ledgerParam1 = 0x80 //用于签名的后续事务数据块
    54  ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 //不要随地址返回链代码
    55  )
    56  
    57  //errledgereplyinvalidheader是分类帐数据交换返回的错误消息。
    58  //如果设备回复的标题不匹配。这通常意味着设备
    59  //处于浏览器模式。
    60  var errLedgerReplyInvalidHeader = errors.New("ledger: invalid reply header")
    61  
    62  //errlegrinvalidversionreply是由分类帐版本检索返回的错误消息
    63  //当响应确实到达,但它不包含预期的数据时。
    64  var errLedgerInvalidVersionReply = errors.New("ledger: invalid version reply")
    65  
    66  //LedgerDriver实现与Ledger硬件钱包的通信。
    67  type ledgerDriver struct {
    68  device  io.ReadWriter //通过USB设备连接进行通信
    69  version [3]byte       //分类帐固件的当前版本(如果应用程序脱机,则为零)
    70  browser bool          //标记分类帐是否处于浏览器模式(答复通道不匹配)
    71  failure error         //任何使设备无法使用的故障
    72  log     log.Logger    //上下文记录器,用其ID标记分类帐
    73  }
    74  
    75  //NewledgerDriver创建LedgerUSB协议驱动程序的新实例。
    76  func newLedgerDriver(logger log.Logger) driver {
    77  	return &ledgerDriver{
    78  		log: logger,
    79  	}
    80  }
    81  
    82  //状态实现usbwallet.driver,返回分类帐可以返回的各种状态
    83  //目前在。
    84  func (w *ledgerDriver) Status() (string, error) {
    85  	if w.failure != nil {
    86  		return fmt.Sprintf("Failed: %v", w.failure), w.failure
    87  	}
    88  	if w.browser {
    89  		return "Ethereum app in browser mode", w.failure
    90  	}
    91  	if w.offline() {
    92  		return "Ethereum app offline", w.failure
    93  	}
    94  	return fmt.Sprintf("Ethereum app v%d.%d.%d online", w.version[0], w.version[1], w.version[2]), w.failure
    95  }
    96  
    97  //离线返回钱包和以太坊应用程序是否离线。
    98  //
    99  //该方法假定状态锁被保持!
   100  func (w *ledgerDriver) offline() bool {
   101  	return w.version == [3]byte{0, 0, 0}
   102  }
   103  
   104  //open实现usbwallet.driver,尝试初始化到的连接
   105  //分类帐硬件钱包。分类帐不需要用户密码,因此
   106  //参数被静默丢弃。
   107  func (w *ledgerDriver) Open(device io.ReadWriter, passphrase string) error {
   108  	w.device, w.failure = device, nil
   109  
   110  	_, err := w.ledgerDerive(accounts.DefaultBaseDerivationPath)
   111  	if err != nil {
   112  //以太坊应用程序未运行或处于浏览器模式,无需执行其他操作,返回
   113  		if err == errLedgerReplyInvalidHeader {
   114  			w.browser = true
   115  		}
   116  		return nil
   117  	}
   118  //尝试解析以太坊应用程序的版本,将在v1.0.2之前失败
   119  	if w.version, err = w.ledgerVersion(); err != nil {
   120  w.version = [3]byte{1, 0, 0} //假设最坏情况,无法验证v1.0.0或v1.0.1
   121  	}
   122  	return nil
   123  }
   124  
   125  //close实现usbwallet.driver,清理和元数据维护在
   126  //分类帐驱动程序。
   127  func (w *ledgerDriver) Close() error {
   128  	w.browser, w.version = false, [3]byte{}
   129  	return nil
   130  }
   131  
   132  //heartbeat实现usbwallet.driver,对
   133  //看它是否仍然在线。
   134  func (w *ledgerDriver) Heartbeat() error {
   135  	if _, err := w.ledgerVersion(); err != nil && err != errLedgerInvalidVersionReply {
   136  		w.failure = err
   137  		return err
   138  	}
   139  	return nil
   140  }
   141  
   142  //派生实现usbwallet.driver,向分类帐发送派生请求
   143  //并返回位于该派生路径上的以太坊地址。
   144  func (w *ledgerDriver) Derive(path accounts.DerivationPath) (common.Address, error) {
   145  	return w.ledgerDerive(path)
   146  }
   147  
   148  //signtx实现usbwallet.driver,将事务发送到分类帐并
   149  //正在等待用户确认或拒绝该事务。
   150  //
   151  //注意,如果运行在Ledger钱包上的以太坊应用程序的版本是
   152  //太旧,无法签署EIP-155交易,但要求这样做,还是有错误
   153  //将返回,而不是在宅基地模式中静默签名。
   154  func (w *ledgerDriver) SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) {
   155  //如果以太坊应用程序不运行,则中止
   156  	if w.offline() {
   157  		return common.Address{}, nil, accounts.ErrWalletClosed
   158  	}
   159  //确保钱包能够签署给定的交易
   160  	if chainID != nil && w.version[0] <= 1 && w.version[1] <= 0 && w.version[2] <= 2 {
   161  		return common.Address{}, nil, fmt.Errorf("Ledger v%d.%d.%d doesn't support signing this transaction, please update to v1.0.3 at least", w.version[0], w.version[1], w.version[2])
   162  	}
   163  //收集的所有信息和元数据均已签出,请求签名
   164  	return w.ledgerSign(path, tx, chainID)
   165  }
   166  
   167  //LedgerVersion检索正在运行的以太坊钱包应用程序的当前版本
   168  //在分类帐钱包上。
   169  //
   170  //版本检索协议定义如下:
   171  //
   172  //CLA INS P1 P2 LC LE
   173  //---+-----+---+---+---+----
   174  //e0 06 00 00 04
   175  //
   176  //没有输入数据,输出数据为:
   177  //
   178  //说明长度
   179  //——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
   180  //标志01:用户1字节启用的任意数据签名
   181  //应用程序主版本1字节
   182  //应用程序次要版本1字节
   183  //应用补丁版本1字节
   184  func (w *ledgerDriver) ledgerVersion() ([3]byte, error) {
   185  //发送请求并等待响应
   186  	reply, err := w.ledgerExchange(ledgerOpGetConfiguration, 0, 0, nil)
   187  	if err != nil {
   188  		return [3]byte{}, err
   189  	}
   190  	if len(reply) != 4 {
   191  		return [3]byte{}, errLedgerInvalidVersionReply
   192  	}
   193  //缓存版本以备将来参考
   194  	var version [3]byte
   195  	copy(version[:], reply[1:])
   196  	return version, nil
   197  }
   198  
   199  //LedgerDrive从分类帐中检索当前活动的以太坊地址
   200  //钱包在指定的衍生路径。
   201  //
   202  //地址派生协议定义如下:
   203  //
   204  //CLA INS P1 P2 LC LE
   205  //---+-----+---+---+---+----
   206  //e0 02 00返回地址
   207  //01显示地址,返回前确认
   208  //00:不返回链码
   209  //01:返回链码
   210  //γvar 00
   211  //
   212  //其中输入数据为:
   213  //
   214  //说明长度
   215  //——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
   216  //要执行的BIP 32派生数(最大10)1字节
   217  //第一个派生索引(big endian)4字节
   218  //……4字节
   219  //上次派生索引(big endian)4字节
   220  //
   221  //输出数据为:
   222  //
   223  //说明长度
   224  //——————————+———————————————————
   225  //公钥长度1字节
   226  //未压缩公钥任意
   227  //以太坊地址长度1字节
   228  //以太坊地址40字节十六进制ASCII
   229  //链码(如果要求)32字节
   230  func (w *ledgerDriver) ledgerDerive(derivationPath []uint32) (common.Address, error) {
   231  //将派生路径展平到分类帐请求中
   232  	path := make([]byte, 1+4*len(derivationPath))
   233  	path[0] = byte(len(derivationPath))
   234  	for i, component := range derivationPath {
   235  		binary.BigEndian.PutUint32(path[1+4*i:], component)
   236  	}
   237  //发送请求并等待响应
   238  	reply, err := w.ledgerExchange(ledgerOpRetrieveAddress, ledgerP1DirectlyFetchAddress, ledgerP2DiscardAddressChainCode, path)
   239  	if err != nil {
   240  		return common.Address{}, err
   241  	}
   242  //丢弃公钥,我们暂时不需要它
   243  	if len(reply) < 1 || len(reply) < 1+int(reply[0]) {
   244  		return common.Address{}, errors.New("reply lacks public key entry")
   245  	}
   246  	reply = reply[1+int(reply[0]):]
   247  
   248  //提取以太坊十六进制地址字符串
   249  	if len(reply) < 1 || len(reply) < 1+int(reply[0]) {
   250  		return common.Address{}, errors.New("reply lacks address entry")
   251  	}
   252  	hexstr := reply[1 : 1+int(reply[0])]
   253  
   254  //将十六进制sting解码为以太坊地址并返回
   255  	var address common.Address
   256  	hex.Decode(address[:], hexstr)
   257  	return address, nil
   258  }
   259  
   260  //Ledgersign将交易发送到Ledger钱包,并等待用户
   261  //确认或拒绝交易。
   262  //
   263  //事务签名协议定义如下:
   264  //
   265  //CLA INS P1 P2 LC LE
   266  //---+-----+---+---+---+----
   267  //e0 04 00:第一个事务数据块
   268  //80:后续交易数据块
   269  //00变量变量
   270  //
   271  //其中,第一个事务块(前255个字节)的输入为:
   272  //
   273  //说明长度
   274  //————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
   275  //要执行的BIP 32派生数(最大10)1字节
   276  //第一个派生索引(big endian)4字节
   277  //……4字节
   278  //上次派生索引(big endian)4字节
   279  //
   280  //
   281  //后续事务块(前255个字节)的输入为:
   282  //
   283  //说明长度
   284  //————————+——————————
   285  //RLP事务块任意
   286  //
   287  //输出数据为:
   288  //
   289  //说明长度
   290  //-----------+-----
   291  //签名v 1字节
   292  //签名R 32字节
   293  //签名S 32字节
   294  func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) {
   295  //将派生路径展平到分类帐请求中
   296  	path := make([]byte, 1+4*len(derivationPath))
   297  	path[0] = byte(len(derivationPath))
   298  	for i, component := range derivationPath {
   299  		binary.BigEndian.PutUint32(path[1+4*i:], component)
   300  	}
   301  //根据请求的是遗留签名还是EIP155签名创建事务RLP
   302  	var (
   303  		txrlp []byte
   304  		err   error
   305  	)
   306  	if chainID == nil {
   307  		if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data()}); err != nil {
   308  			return common.Address{}, nil, err
   309  		}
   310  	} else {
   311  		if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), chainID, big.NewInt(0), big.NewInt(0)}); err != nil {
   312  			return common.Address{}, nil, err
   313  		}
   314  	}
   315  	payload := append(path, txrlp...)
   316  
   317  //发送请求并等待响应
   318  	var (
   319  		op    = ledgerP1InitTransactionData
   320  		reply []byte
   321  	)
   322  	for len(payload) > 0 {
   323  //计算下一个数据块的大小
   324  		chunk := 255
   325  		if chunk > len(payload) {
   326  			chunk = len(payload)
   327  		}
   328  //发送块,确保正确处理
   329  		reply, err = w.ledgerExchange(ledgerOpSignTransaction, op, 0, payload[:chunk])
   330  		if err != nil {
   331  			return common.Address{}, nil, err
   332  		}
   333  //移动有效负载并确保后续块标记为
   334  		payload = payload[chunk:]
   335  		op = ledgerP1ContTransactionData
   336  	}
   337  //提取以太坊签名并进行健全性验证
   338  	if len(reply) != 65 {
   339  		return common.Address{}, nil, errors.New("reply lacks signature")
   340  	}
   341  	signature := append(reply[1:], reply[0])
   342  
   343  //基于链ID创建正确的签名者和签名转换
   344  	var signer types.Signer
   345  	if chainID == nil {
   346  		signer = new(types.HomesteadSigner)
   347  	} else {
   348  		signer = types.NewEIP155Signer(chainID)
   349  		signature[64] = signature[64] - byte(chainID.Uint64()*2+35)
   350  	}
   351  	signed, err := tx.WithSignature(signer, signature)
   352  	if err != nil {
   353  		return common.Address{}, nil, err
   354  	}
   355  	sender, err := types.Sender(signer, signed)
   356  	if err != nil {
   357  		return common.Address{}, nil, err
   358  	}
   359  	return sender, signed, nil
   360  }
   361  
   362  //LedgerxChange执行与Ledger钱包的数据交换,并向其发送
   363  //并检索响应。
   364  //
   365  //公共传输头定义如下:
   366  //
   367  //说明长度
   368  //————————————————————————————————————————————————————————————————————————————————————————————————————————————————
   369  //通信信道ID(big endian)2字节
   370  //命令标记1字节
   371  //包序列索引(big endian)2字节
   372  //有效载荷任意
   373  //
   374  //通信信道ID允许命令在同一个信道上复用
   375  //物理链路。暂时不使用,应设置为0101
   376  //为了避免与忽略前导00字节的实现的兼容性问题。
   377  //
   378  //命令标记描述消息内容。使用标签“apdu(0x05)”作为标准
   379  //apdu有效负载,或标记\ping(0x02),用于简单链接测试。
   380  //
   381  //包序列索引描述了分段有效负载的当前序列。
   382  //第一个片段索引是0x00。
   383  //
   384  //APDU命令有效负载编码如下:
   385  //
   386  //说明长度
   387  //—————————————————————————
   388  //apdu长度(big endian)2字节
   389  //apdu cla 1字节
   390  //apdu-ins 1字节
   391  //APDU P1 1字节
   392  //apdu p2 1字节
   393  //apdu长度1字节
   394  //可选APDU数据任意
   395  func (w *ledgerDriver) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 ledgerParam2, data []byte) ([]byte, error) {
   396  //构造消息有效负载,可能拆分为多个块
   397  	apdu := make([]byte, 2, 7+len(data))
   398  
   399  	binary.BigEndian.PutUint16(apdu, uint16(5+len(data)))
   400  	apdu = append(apdu, []byte{0xe0, byte(opcode), byte(p1), byte(p2), byte(len(data))}...)
   401  	apdu = append(apdu, data...)
   402  
   403  //将所有块流式传输到设备
   404  header := []byte{0x01, 0x01, 0x05, 0x00, 0x00} //附加了通道ID和命令标记
   405  	chunk := make([]byte, 64)
   406  	space := len(chunk) - len(header)
   407  
   408  	for i := 0; len(apdu) > 0; i++ {
   409  //构造要传输的新消息
   410  		chunk = append(chunk[:0], header...)
   411  		binary.BigEndian.PutUint16(chunk[3:], uint16(i))
   412  
   413  		if len(apdu) > space {
   414  			chunk = append(chunk, apdu[:space]...)
   415  			apdu = apdu[space:]
   416  		} else {
   417  			chunk = append(chunk, apdu...)
   418  			apdu = nil
   419  		}
   420  //发送到设备
   421  		w.log.Trace("Data chunk sent to the Ledger", "chunk", hexutil.Bytes(chunk))
   422  		if _, err := w.device.Write(chunk); err != nil {
   423  			return nil, err
   424  		}
   425  	}
   426  //将回复从钱包中以64字节的块流式返回
   427  	var reply []byte
   428  chunk = chunk[:64] //是的,我们肯定有足够的空间
   429  	for {
   430  //从分类帐钱包中读取下一块
   431  		if _, err := io.ReadFull(w.device, chunk); err != nil {
   432  			return nil, err
   433  		}
   434  		w.log.Trace("Data chunk received from the Ledger", "chunk", hexutil.Bytes(chunk))
   435  
   436  //确保传输头匹配
   437  		if chunk[0] != 0x01 || chunk[1] != 0x01 || chunk[2] != 0x05 {
   438  			return nil, errLedgerReplyInvalidHeader
   439  		}
   440  //如果是第一个块,则检索消息的总长度
   441  		var payload []byte
   442  
   443  		if chunk[3] == 0x00 && chunk[4] == 0x00 {
   444  			reply = make([]byte, 0, int(binary.BigEndian.Uint16(chunk[5:7])))
   445  			payload = chunk[7:]
   446  		} else {
   447  			payload = chunk[5:]
   448  		}
   449  //追加到答复并在填写时停止
   450  		if left := cap(reply) - len(reply); left > len(payload) {
   451  			reply = append(reply, payload...)
   452  		} else {
   453  			reply = append(reply, payload[:left]...)
   454  			break
   455  		}
   456  	}
   457  	return reply[:len(reply)-2], nil
   458  }
   459