|
15 | 15 | package firmware
|
16 | 16 |
|
17 | 17 | import (
|
| 18 | + "encoding/binary" |
| 19 | + "encoding/hex" |
| 20 | + "encoding/json" |
18 | 21 | "math/big"
|
| 22 | + "strconv" |
| 23 | + "strings" |
19 | 24 |
|
20 | 25 | "github.com/digitalbitbox/bitbox02-api-go/api/firmware/messages"
|
21 | 26 | "github.com/digitalbitbox/bitbox02-api-go/util/errp"
|
@@ -272,3 +277,326 @@ func (device *Device) ETHSignMessage(
|
272 | 277 |
|
273 | 278 | return signature, nil
|
274 | 279 | }
|
| 280 | + |
| 281 | +func parseType(typ string, types map[string]interface{}) (*messages.ETHSignTypedMessageRequest_MemberType, error) { |
| 282 | + if strings.HasSuffix(typ, "]") { |
| 283 | + index := strings.LastIndexByte(typ, '[') |
| 284 | + typ = typ[:len(typ)-1] |
| 285 | + rest, size := typ[:index], typ[index+1:] |
| 286 | + var sizeInt uint32 |
| 287 | + if size != "" { |
| 288 | + i, err := strconv.ParseUint(size, 10, 32) |
| 289 | + if err != nil { |
| 290 | + return nil, errp.WithStack(err) |
| 291 | + } |
| 292 | + sizeInt = uint32(i) |
| 293 | + } |
| 294 | + arrayType, err := parseType(rest, types) |
| 295 | + if err != nil { |
| 296 | + return nil, err |
| 297 | + } |
| 298 | + return &messages.ETHSignTypedMessageRequest_MemberType{ |
| 299 | + Type: messages.ETHSignTypedMessageRequest_ARRAY, |
| 300 | + Size: sizeInt, |
| 301 | + ArrayType: arrayType, |
| 302 | + }, nil |
| 303 | + } |
| 304 | + if strings.HasPrefix(typ, "bytes") { |
| 305 | + size := typ[5:] |
| 306 | + var sizeInt uint32 |
| 307 | + if size != "" { |
| 308 | + i, err := strconv.ParseUint(size, 10, 32) |
| 309 | + if err != nil { |
| 310 | + return nil, errp.WithStack(err) |
| 311 | + } |
| 312 | + sizeInt = uint32(i) |
| 313 | + } |
| 314 | + return &messages.ETHSignTypedMessageRequest_MemberType{ |
| 315 | + Type: messages.ETHSignTypedMessageRequest_BYTES, |
| 316 | + Size: sizeInt, |
| 317 | + }, nil |
| 318 | + } |
| 319 | + |
| 320 | + if strings.HasPrefix(typ, "uint") { |
| 321 | + size := typ[4:] |
| 322 | + if size == "" { |
| 323 | + return nil, errp.New("uint must be sized") |
| 324 | + } |
| 325 | + sizeInt, err := strconv.ParseUint(size, 10, 32) |
| 326 | + if err != nil { |
| 327 | + return nil, errp.WithStack(err) |
| 328 | + } |
| 329 | + return &messages.ETHSignTypedMessageRequest_MemberType{ |
| 330 | + Type: messages.ETHSignTypedMessageRequest_UINT, |
| 331 | + Size: uint32(sizeInt) / 8, |
| 332 | + }, nil |
| 333 | + } |
| 334 | + if strings.HasPrefix(typ, "int") { |
| 335 | + size := typ[3:] |
| 336 | + if size == "" { |
| 337 | + return nil, errp.New("int must be sized") |
| 338 | + } |
| 339 | + sizeInt, err := strconv.ParseUint(size, 10, 32) |
| 340 | + if err != nil { |
| 341 | + return nil, errp.WithStack(err) |
| 342 | + } |
| 343 | + return &messages.ETHSignTypedMessageRequest_MemberType{ |
| 344 | + Type: messages.ETHSignTypedMessageRequest_INT, |
| 345 | + Size: uint32(sizeInt) / 8, |
| 346 | + }, nil |
| 347 | + } |
| 348 | + if typ == "bool" { |
| 349 | + return &messages.ETHSignTypedMessageRequest_MemberType{ |
| 350 | + Type: messages.ETHSignTypedMessageRequest_BOOL, |
| 351 | + }, nil |
| 352 | + } |
| 353 | + if typ == "address" { |
| 354 | + return &messages.ETHSignTypedMessageRequest_MemberType{ |
| 355 | + Type: messages.ETHSignTypedMessageRequest_ADDRESS, |
| 356 | + }, nil |
| 357 | + } |
| 358 | + if typ == "string" { |
| 359 | + return &messages.ETHSignTypedMessageRequest_MemberType{ |
| 360 | + Type: messages.ETHSignTypedMessageRequest_STRING, |
| 361 | + }, nil |
| 362 | + } |
| 363 | + if _, ok := types[typ]; ok { |
| 364 | + return &messages.ETHSignTypedMessageRequest_MemberType{ |
| 365 | + Type: messages.ETHSignTypedMessageRequest_STRUCT, |
| 366 | + StructName: typ, |
| 367 | + }, nil |
| 368 | + } |
| 369 | + return nil, errp.Newf("Can't recognize type: %s", typ) |
| 370 | +} |
| 371 | + |
| 372 | +// Golang's stdlib doesn't support serializing signed integers in big endian (two's complement). |
| 373 | +// -x = ~x+1. |
| 374 | +func bigendianInt(integer *big.Int) []byte { |
| 375 | + if integer.Sign() >= 0 { |
| 376 | + return integer.Bytes() |
| 377 | + } |
| 378 | + bytes := append([]byte{0}, integer.Bytes()...) |
| 379 | + for i, v := range bytes { |
| 380 | + bytes[i] = ^v |
| 381 | + } |
| 382 | + return new(big.Int).Add(new(big.Int).SetBytes(bytes), big.NewInt(1)).Bytes() |
| 383 | +} |
| 384 | + |
| 385 | +// encodeValue encodes a json decoded typed data value to send to the BitBox02 as part of the |
| 386 | +// SignTypedData signing process. There is no strict error checking (e.g. that the size is correct |
| 387 | +// according to the type) as the BitBox02 checks for bad input. |
| 388 | +func encodeValue(typ *messages.ETHSignTypedMessageRequest_MemberType, value interface{}) ([]byte, error) { |
| 389 | + switch typ.Type { |
| 390 | + case messages.ETHSignTypedMessageRequest_BYTES: |
| 391 | + v := value.(string) |
| 392 | + if len(v) >= 2 && v[:2] == "0x" { |
| 393 | + return hex.DecodeString(v[2:]) |
| 394 | + } |
| 395 | + return []byte(v), nil |
| 396 | + case messages.ETHSignTypedMessageRequest_UINT: |
| 397 | + bigint := new(big.Int) |
| 398 | + switch v := value.(type) { |
| 399 | + case string: |
| 400 | + _, ok := bigint.SetString(v, 10) |
| 401 | + if !ok { |
| 402 | + return nil, errp.Newf("couldn't parse uint: %s", v) |
| 403 | + } |
| 404 | + case float64: |
| 405 | + v64 := uint64(v) |
| 406 | + if float64(v64) != v { |
| 407 | + return nil, errp.Newf("float64 is not an uint: %v", v) |
| 408 | + } |
| 409 | + bigint.SetUint64(v64) |
| 410 | + default: |
| 411 | + return nil, errp.New("wrong type for uint") |
| 412 | + } |
| 413 | + return bigint.Bytes(), nil |
| 414 | + case messages.ETHSignTypedMessageRequest_INT: |
| 415 | + bigint := new(big.Int) |
| 416 | + switch v := value.(type) { |
| 417 | + case string: |
| 418 | + _, ok := bigint.SetString(v, 10) |
| 419 | + if !ok { |
| 420 | + return nil, errp.Newf("couldn't parse uint: %s", v) |
| 421 | + } |
| 422 | + case float64: |
| 423 | + v64 := int64(v) |
| 424 | + if float64(v64) != v { |
| 425 | + return nil, errp.Newf("float64 is not an uint: %v", v) |
| 426 | + } |
| 427 | + bigint.SetInt64(v64) |
| 428 | + default: |
| 429 | + return nil, errp.New("wrong type for uint") |
| 430 | + } |
| 431 | + return bigendianInt(bigint), nil |
| 432 | + case messages.ETHSignTypedMessageRequest_BOOL: |
| 433 | + if value.(bool) { |
| 434 | + return []byte{1}, nil |
| 435 | + } |
| 436 | + return []byte{0}, nil |
| 437 | + case messages.ETHSignTypedMessageRequest_ADDRESS, messages.ETHSignTypedMessageRequest_STRING: |
| 438 | + return []byte(value.(string)), nil |
| 439 | + case messages.ETHSignTypedMessageRequest_ARRAY: |
| 440 | + size := uint32(len(value.([]interface{}))) |
| 441 | + result := make([]byte, 4) |
| 442 | + binary.BigEndian.PutUint32(result, size) |
| 443 | + return result, nil |
| 444 | + } |
| 445 | + |
| 446 | + return nil, errp.New("couldn't encode value") |
| 447 | +} |
| 448 | + |
| 449 | +func getValue(what *messages.ETHTypedMessageValueResponse, msg map[string]interface{}) ([]byte, error) { |
| 450 | + types := msg["types"].(map[string]interface{}) |
| 451 | + |
| 452 | + var value interface{} |
| 453 | + var typ *messages.ETHSignTypedMessageRequest_MemberType |
| 454 | + |
| 455 | + switch what.RootObject { |
| 456 | + case messages.ETHTypedMessageValueResponse_DOMAIN: |
| 457 | + value = msg["domain"] |
| 458 | + var err error |
| 459 | + typ, err = parseType("EIP712Domain", types) |
| 460 | + if err != nil { |
| 461 | + return nil, err |
| 462 | + } |
| 463 | + case messages.ETHTypedMessageValueResponse_MESSAGE: |
| 464 | + value = msg["message"] |
| 465 | + var err error |
| 466 | + typ, err = parseType(msg["primaryType"].(string), types) |
| 467 | + if err != nil { |
| 468 | + return nil, err |
| 469 | + } |
| 470 | + default: |
| 471 | + return nil, errp.Newf("unknown root: %v", what.RootObject) |
| 472 | + } |
| 473 | + for _, element := range what.Path { |
| 474 | + switch typ.Type { |
| 475 | + case messages.ETHSignTypedMessageRequest_STRUCT: |
| 476 | + structMember := types[typ.StructName].([]interface{})[element].(map[string]interface{}) |
| 477 | + value = value.(map[string]interface{})[structMember["name"].(string)] |
| 478 | + var err error |
| 479 | + typ, err = parseType(structMember["type"].(string), types) |
| 480 | + if err != nil { |
| 481 | + return nil, err |
| 482 | + } |
| 483 | + case messages.ETHSignTypedMessageRequest_ARRAY: |
| 484 | + value = value.([]interface{})[element] |
| 485 | + typ = typ.ArrayType |
| 486 | + default: |
| 487 | + return nil, errp.New("path element does not point to struct or array") |
| 488 | + } |
| 489 | + } |
| 490 | + return encodeValue(typ, value) |
| 491 | +} |
| 492 | + |
| 493 | +// ETHSignTypedMessage signs an Ethereum EIP-612 typed message. 27 is added to the recID to denote |
| 494 | +// an uncompressed pubkey. |
| 495 | +func (device *Device) ETHSignTypedMessage( |
| 496 | + chainID uint64, |
| 497 | + keypath []uint32, |
| 498 | + jsonMsg []byte, |
| 499 | +) ([]byte, error) { |
| 500 | + if !device.version.AtLeast(semver.NewSemVer(9, 12, 0)) { |
| 501 | + return nil, UnsupportedError("9.12.0") |
| 502 | + } |
| 503 | + |
| 504 | + var msg map[string]interface{} |
| 505 | + if err := json.Unmarshal(jsonMsg, &msg); err != nil { |
| 506 | + return nil, errp.WithStack(err) |
| 507 | + } |
| 508 | + |
| 509 | + hostNonce, err := generateHostNonce() |
| 510 | + if err != nil { |
| 511 | + return nil, err |
| 512 | + } |
| 513 | + |
| 514 | + types := msg["types"].(map[string]interface{}) |
| 515 | + var parsedTypes []*messages.ETHSignTypedMessageRequest_StructType |
| 516 | + for key, value := range types { |
| 517 | + var members []*messages.ETHSignTypedMessageRequest_Member |
| 518 | + for _, member := range value.([]interface{}) { |
| 519 | + memberS := member.(map[string]interface{}) |
| 520 | + parsedType, err := parseType(memberS["type"].(string), types) |
| 521 | + if err != nil { |
| 522 | + return nil, err |
| 523 | + } |
| 524 | + members = append(members, &messages.ETHSignTypedMessageRequest_Member{ |
| 525 | + Name: memberS["name"].(string), |
| 526 | + Type: parsedType, |
| 527 | + }) |
| 528 | + } |
| 529 | + parsedTypes = append(parsedTypes, &messages.ETHSignTypedMessageRequest_StructType{ |
| 530 | + Name: key, |
| 531 | + Members: members, |
| 532 | + }) |
| 533 | + } |
| 534 | + request := &messages.ETHRequest{ |
| 535 | + Request: &messages.ETHRequest_SignTypedMsg{ |
| 536 | + SignTypedMsg: &messages.ETHSignTypedMessageRequest{ |
| 537 | + ChainId: chainID, |
| 538 | + Keypath: keypath, |
| 539 | + Types: parsedTypes, |
| 540 | + PrimaryType: msg["primaryType"].(string), |
| 541 | + HostNonceCommitment: &messages.AntiKleptoHostNonceCommitment{ |
| 542 | + Commitment: antikleptoHostCommit(hostNonce), |
| 543 | + }, |
| 544 | + }, |
| 545 | + }, |
| 546 | + } |
| 547 | + response, err := device.queryETH(request) |
| 548 | + if err != nil { |
| 549 | + return nil, err |
| 550 | + } |
| 551 | + |
| 552 | + typedMsgValueResponse, ok := response.Response.(*messages.ETHResponse_TypedMsgValue) |
| 553 | + for ok { |
| 554 | + value, err := getValue(typedMsgValueResponse.TypedMsgValue, msg) |
| 555 | + if err != nil { |
| 556 | + return nil, err |
| 557 | + } |
| 558 | + response, err = device.queryETH(&messages.ETHRequest{ |
| 559 | + Request: &messages.ETHRequest_TypedMsgValue{ |
| 560 | + TypedMsgValue: &messages.ETHTypedMessageValueRequest{ |
| 561 | + Value: value, |
| 562 | + }, |
| 563 | + }, |
| 564 | + }) |
| 565 | + if err != nil { |
| 566 | + return nil, err |
| 567 | + } |
| 568 | + typedMsgValueResponse, ok = response.Response.(*messages.ETHResponse_TypedMsgValue) |
| 569 | + } |
| 570 | + |
| 571 | + signerCommitment, ok := response.Response.(*messages.ETHResponse_AntikleptoSignerCommitment) |
| 572 | + if !ok { |
| 573 | + return nil, errp.New("unexpected response") |
| 574 | + } |
| 575 | + response, err = device.queryETH(&messages.ETHRequest{ |
| 576 | + Request: &messages.ETHRequest_AntikleptoSignature{ |
| 577 | + AntikleptoSignature: &messages.AntiKleptoSignatureRequest{ |
| 578 | + HostNonce: hostNonce, |
| 579 | + }, |
| 580 | + }, |
| 581 | + }) |
| 582 | + if err != nil { |
| 583 | + return nil, err |
| 584 | + } |
| 585 | + |
| 586 | + signResponse, ok := response.Response.(*messages.ETHResponse_Sign) |
| 587 | + if !ok { |
| 588 | + return nil, errp.New("unexpected response") |
| 589 | + } |
| 590 | + signature := signResponse.Sign.Signature |
| 591 | + err = antikleptoVerify( |
| 592 | + hostNonce, |
| 593 | + signerCommitment.AntikleptoSignerCommitment.Commitment, |
| 594 | + signature[:64], |
| 595 | + ) |
| 596 | + if err != nil { |
| 597 | + return nil, err |
| 598 | + } |
| 599 | + // 27 is the magic constant to add to the recoverable ID to denote an uncompressed pubkey. |
| 600 | + signature[64] += 27 |
| 601 | + return signature, nil |
| 602 | +} |
0 commit comments