Skip to content

Commit 9c5dd0d

Browse files
committed
Merge branch 'eth-eip712'
2 parents 30e115a + a57bf5a commit 9c5dd0d

File tree

5 files changed

+1203
-100
lines changed

5 files changed

+1203
-100
lines changed

api/firmware/eth.go

Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@
1515
package firmware
1616

1717
import (
18+
"encoding/binary"
19+
"encoding/hex"
20+
"encoding/json"
1821
"math/big"
22+
"strconv"
23+
"strings"
1924

2025
"github.com/digitalbitbox/bitbox02-api-go/api/firmware/messages"
2126
"github.com/digitalbitbox/bitbox02-api-go/util/errp"
@@ -272,3 +277,326 @@ func (device *Device) ETHSignMessage(
272277

273278
return signature, nil
274279
}
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

Comments
 (0)