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