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