Skip to content

Commit c31e782

Browse files
committed
firmware/eth: add option to disable antiklepto in ETHSignTypedMessage
1 parent 87b5585 commit c31e782

3 files changed

Lines changed: 154 additions & 79 deletions

File tree

api/firmware/eth.go

Lines changed: 53 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -574,24 +574,37 @@ func getValue(what *messages.ETHTypedMessageValueResponse, msg map[string]interf
574574
}
575575

576576
// ETHSignTypedMessage signs an Ethereum EIP-712 typed message. 27 is added to the recID to denote
577-
// an uncompressed pubkey.
577+
// an uncompressed pubkey. If useAntiklepto is false, signing is deterministic and requires
578+
// firmware >= 9.26.0.
578579
func (device *Device) ETHSignTypedMessage(
579580
chainID uint64,
580581
keypath []uint32,
581582
jsonMsg []byte,
583+
useAntiklepto bool,
582584
) ([]byte, error) {
583585
if !device.version.AtLeast(semver.NewSemVer(9, 12, 0)) {
584586
return nil, UnsupportedError("9.12.0")
585587
}
588+
if !useAntiklepto && !device.version.AtLeast(semver.NewSemVer(9, 26, 0)) {
589+
return nil, UnsupportedError("9.26.0")
590+
}
586591

587592
var msg map[string]interface{}
588593
if err := json.Unmarshal(jsonMsg, &msg); err != nil {
589594
return nil, errp.WithStack(err)
590595
}
591596

592-
hostNonce, err := generateHostNonce()
593-
if err != nil {
594-
return nil, err
597+
var hostNonce []byte
598+
var hostNonceCommitment *messages.AntiKleptoHostNonceCommitment
599+
if useAntiklepto {
600+
var err error
601+
hostNonce, err = generateHostNonce()
602+
if err != nil {
603+
return nil, err
604+
}
605+
hostNonceCommitment = &messages.AntiKleptoHostNonceCommitment{
606+
Commitment: antikleptoHostCommit(hostNonce),
607+
}
595608
}
596609

597610
types := msg["types"].(map[string]interface{})
@@ -617,13 +630,11 @@ func (device *Device) ETHSignTypedMessage(
617630
request := &messages.ETHRequest{
618631
Request: &messages.ETHRequest_SignTypedMsg{
619632
SignTypedMsg: &messages.ETHSignTypedMessageRequest{
620-
ChainId: chainID,
621-
Keypath: keypath,
622-
Types: parsedTypes,
623-
PrimaryType: msg["primaryType"].(string),
624-
HostNonceCommitment: &messages.AntiKleptoHostNonceCommitment{
625-
Commitment: antikleptoHostCommit(hostNonce),
626-
},
633+
ChainId: chainID,
634+
Keypath: keypath,
635+
Types: parsedTypes,
636+
PrimaryType: msg["primaryType"].(string),
637+
HostNonceCommitment: hostNonceCommitment,
627638
},
628639
},
629640
}
@@ -651,34 +662,45 @@ func (device *Device) ETHSignTypedMessage(
651662
typedMsgValueResponse, ok = response.Response.(*messages.ETHResponse_TypedMsgValue)
652663
}
653664

654-
signerCommitment, ok := response.Response.(*messages.ETHResponse_AntikleptoSignerCommitment)
655-
if !ok {
656-
return nil, errp.New("unexpected response")
657-
}
658-
response, err = device.queryETH(&messages.ETHRequest{
659-
Request: &messages.ETHRequest_AntikleptoSignature{
660-
AntikleptoSignature: &messages.AntiKleptoSignatureRequest{
661-
HostNonce: hostNonce,
665+
if useAntiklepto {
666+
signerCommitment, ok := response.Response.(*messages.ETHResponse_AntikleptoSignerCommitment)
667+
if !ok {
668+
return nil, errp.New("unexpected response")
669+
}
670+
response, err = device.queryETH(&messages.ETHRequest{
671+
Request: &messages.ETHRequest_AntikleptoSignature{
672+
AntikleptoSignature: &messages.AntiKleptoSignatureRequest{
673+
HostNonce: hostNonce,
674+
},
662675
},
663-
},
664-
})
665-
if err != nil {
666-
return nil, err
676+
})
677+
if err != nil {
678+
return nil, err
679+
}
680+
681+
signResponse, ok := response.Response.(*messages.ETHResponse_Sign)
682+
if !ok {
683+
return nil, errp.New("unexpected response")
684+
}
685+
signature := signResponse.Sign.Signature
686+
err = antikleptoVerify(
687+
hostNonce,
688+
signerCommitment.AntikleptoSignerCommitment.Commitment,
689+
signature[:64],
690+
)
691+
if err != nil {
692+
return nil, err
693+
}
694+
// 27 is the magic constant to add to the recoverable ID to denote an uncompressed pubkey.
695+
signature[64] += 27
696+
return signature, nil
667697
}
668698

669699
signResponse, ok := response.Response.(*messages.ETHResponse_Sign)
670700
if !ok {
671701
return nil, errp.New("unexpected response")
672702
}
673703
signature := signResponse.Sign.Signature
674-
err = antikleptoVerify(
675-
hostNonce,
676-
signerCommitment.AntikleptoSignerCommitment.Commitment,
677-
signature[:64],
678-
)
679-
if err != nil {
680-
return nil, err
681-
}
682704
// 27 is the magic constant to add to the recoverable ID to denote an uncompressed pubkey.
683705
signature[64] += 27
684706
return signature, nil

api/firmware/eth_test.go

Lines changed: 100 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"testing"
99

1010
"github.com/BitBoxSwiss/bitbox02-api-go/api/firmware/messages"
11+
"github.com/BitBoxSwiss/bitbox02-api-go/util/semver"
1112
"github.com/stretchr/testify/require"
1213
"golang.org/x/crypto/sha3"
1314
)
@@ -18,6 +19,53 @@ func hashKeccak(b []byte) []byte {
1819
return h.Sum(nil)
1920
}
2021

22+
var eip712Msg = []byte(`
23+
{
24+
"types": {
25+
"EIP712Domain": [
26+
{ "name": "name", "type": "string" },
27+
{ "name": "version", "type": "string" },
28+
{ "name": "chainId", "type": "uint256" },
29+
{ "name": "verifyingContract", "type": "address" }
30+
],
31+
"Attachment": [
32+
{ "name": "contents", "type": "string" }
33+
],
34+
"Person": [
35+
{ "name": "name", "type": "string" },
36+
{ "name": "wallet", "type": "address" },
37+
{ "name": "age", "type": "uint8" }
38+
],
39+
"Mail": [
40+
{ "name": "from", "type": "Person" },
41+
{ "name": "to", "type": "Person" },
42+
{ "name": "contents", "type": "string" },
43+
{ "name": "attachments", "type": "Attachment[]" }
44+
]
45+
},
46+
"primaryType": "Mail",
47+
"domain": {
48+
"name": "Ether Mail",
49+
"version": "1",
50+
"chainId": 1,
51+
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
52+
},
53+
"message": {
54+
"from": {
55+
"name": "Cow",
56+
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
57+
"age": 20
58+
},
59+
"to": {
60+
"name": "Bob",
61+
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
62+
"age": "0x1e"
63+
},
64+
"contents": "Hello, Bob!",
65+
"attachments": [{ "contents": "attachment1" }, { "contents": "attachment2" }]
66+
}
67+
}`)
68+
2169
func parseTypeNoErr(t *testing.T, typ string, types map[string]interface{}) *messages.ETHSignTypedMessageRequest_MemberType {
2270
t.Helper()
2371
parsed, err := parseType(typ, types)
@@ -306,53 +354,6 @@ func TestSimulatorETHSignMessage(t *testing.T) {
306354
func TestSimulatorETHSignTypedMessage(t *testing.T) {
307355
testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) {
308356
t.Helper()
309-
msg := []byte(`
310-
{
311-
"types": {
312-
"EIP712Domain": [
313-
{ "name": "name", "type": "string" },
314-
{ "name": "version", "type": "string" },
315-
{ "name": "chainId", "type": "uint256" },
316-
{ "name": "verifyingContract", "type": "address" }
317-
],
318-
"Attachment": [
319-
{ "name": "contents", "type": "string" }
320-
],
321-
"Person": [
322-
{ "name": "name", "type": "string" },
323-
{ "name": "wallet", "type": "address" },
324-
{ "name": "age", "type": "uint8" }
325-
],
326-
"Mail": [
327-
{ "name": "from", "type": "Person" },
328-
{ "name": "to", "type": "Person" },
329-
{ "name": "contents", "type": "string" },
330-
{ "name": "attachments", "type": "Attachment[]" }
331-
]
332-
},
333-
"primaryType": "Mail",
334-
"domain": {
335-
"name": "Ether Mail",
336-
"version": "1",
337-
"chainId": 1,
338-
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
339-
},
340-
"message": {
341-
"from": {
342-
"name": "Cow",
343-
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
344-
"age": 20
345-
},
346-
"to": {
347-
"name": "Bob",
348-
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
349-
"age": "0x1e"
350-
},
351-
"contents": "Hello, Bob!",
352-
"attachments": [{ "contents": "attachment1" }, { "contents": "attachment2" }]
353-
}
354-
}`)
355-
356357
sig, err := device.ETHSignTypedMessage(
357358
1,
358359
[]uint32{
@@ -362,13 +363,64 @@ func TestSimulatorETHSignTypedMessage(t *testing.T) {
362363
0,
363364
10,
364365
},
365-
msg,
366+
eip712Msg,
367+
true,
366368
)
367369
require.NoError(t, err)
368370
require.Len(t, sig, 65)
369371
})
370372
}
371373

374+
func TestSimulatorETHSignTypedMessageAntikleptoEnabled(t *testing.T) {
375+
testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) {
376+
t.Helper()
377+
keypath := []uint32{
378+
44 + hardenedKeyStart,
379+
60 + hardenedKeyStart,
380+
0 + hardenedKeyStart,
381+
0,
382+
10,
383+
}
384+
385+
sig1, err := device.ETHSignTypedMessage(1, keypath, eip712Msg, true)
386+
require.NoError(t, err)
387+
sig2, err := device.ETHSignTypedMessage(1, keypath, eip712Msg, true)
388+
require.NoError(t, err)
389+
390+
require.Len(t, sig1, 65)
391+
require.Len(t, sig2, 65)
392+
require.NotEqual(t, sig1, sig2)
393+
})
394+
}
395+
396+
func TestSimulatorETHSignTypedMessageAntikleptoDisabled(t *testing.T) {
397+
testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) {
398+
t.Helper()
399+
keypath := []uint32{
400+
44 + hardenedKeyStart,
401+
60 + hardenedKeyStart,
402+
0 + hardenedKeyStart,
403+
0,
404+
10,
405+
}
406+
407+
if device.Version().AtLeast(semver.NewSemVer(9, 26, 0)) {
408+
sig1, err := device.ETHSignTypedMessage(1, keypath, eip712Msg, false)
409+
require.NoError(t, err)
410+
sig2, err := device.ETHSignTypedMessage(1, keypath, eip712Msg, false)
411+
require.NoError(t, err)
412+
413+
require.Len(t, sig1, 65)
414+
require.Len(t, sig2, 65)
415+
require.Equal(t, sig1, sig2)
416+
return
417+
}
418+
419+
_, err := device.ETHSignTypedMessage(1, keypath, eip712Msg, false)
420+
require.EqualError(t, err, UnsupportedError("9.26.0").Error())
421+
})
422+
}
423+
372424
func TestSimulatorETHSign(t *testing.T) {
373425
testInitializedSimulators(t, func(t *testing.T, device *Device, stdOut *bytes.Buffer) {
374426
t.Helper()

cmd/playground/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ func main() {
267267
"attachments": [{ "contents": "attachment1" }, { "contents": "attachment2" }]
268268
}
269269
}`),
270+
true,
270271
)
271272
errpanic(err)
272273
fmt.Println(sig)

0 commit comments

Comments
 (0)