Skip to content

Commit 58637ad

Browse files
committed
firmware/tests: test against bitbox02 simulators
The simulators are downloaded base on the simulators.json file if the file does not exist (or the hash is wrong). This allows us to exercise the library much more easily without needing to mock everything, as the simulator behaves like the real BitBox02 for the most part. We could automatically download all releases by using the GitHub releases API (https://api.github.com/repos/BitBoxSwiss/bitbox02-firmware/releases), but the downside is that there would be network requests each time you run the unit tests. For now we simply keep the list manually. The simulators are only released for linux-amd64 for now, so we skip tests if we are not running in this environment.
1 parent f46a769 commit 58637ad

File tree

12 files changed

+757
-4
lines changed

12 files changed

+757
-4
lines changed

api/firmware/backup_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2024 Shift Crypto AG
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package firmware
16+
17+
import (
18+
"testing"
19+
20+
"github.com/stretchr/testify/require"
21+
)
22+
23+
func TestSimulatorBackups(t *testing.T) {
24+
const seedLen = 32
25+
const testName = "test wallet name"
26+
testSimulatorsAfterPairing(t, func(t *testing.T, device *Device) {
27+
t.Helper()
28+
require.NoError(t, device.SetDeviceName(testName))
29+
30+
require.NoError(t, device.SetPassword(seedLen))
31+
require.Equal(t, StatusSeeded, device.Status())
32+
33+
list, err := device.ListBackups()
34+
require.NoError(t, err)
35+
require.Empty(t, list)
36+
37+
_, err = device.CheckBackup(true)
38+
require.Error(t, err)
39+
40+
require.NoError(t, device.CreateBackup())
41+
require.Equal(t, StatusInitialized, device.Status())
42+
43+
list, err = device.ListBackups()
44+
require.NoError(t, err)
45+
require.Len(t, list, 1)
46+
require.Equal(t, testName, list[0].Name)
47+
48+
id, err := device.CheckBackup(true)
49+
require.NoError(t, err)
50+
require.Equal(t, list[0].ID, id)
51+
52+
require.Error(t, device.RestoreBackup(list[0].ID))
53+
require.NoError(t, device.Reset())
54+
require.NoError(t, device.RestoreBackup(list[0].ID))
55+
id, err = device.CheckBackup(true)
56+
require.NoError(t, err)
57+
require.Equal(t, list[0].ID, id)
58+
})
59+
}

api/firmware/bip85_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2024 Shift Crypto AG
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package firmware
16+
17+
import (
18+
"encoding/hex"
19+
"testing"
20+
21+
"github.com/stretchr/testify/require"
22+
)
23+
24+
func TestSimulatorBIP85AppBip39(t *testing.T) {
25+
// Can't test this yet as the simulator panics at trinary_choice (12, 18, 24 word choice).
26+
t.Skip()
27+
}
28+
29+
func TestSimulatorBIP85AppLN(t *testing.T) {
30+
testInitializedSimulators(t, func(t *testing.T, device *Device) {
31+
t.Helper()
32+
entropy, err := device.BIP85AppLN()
33+
require.NoError(t, err)
34+
require.Equal(t,
35+
"d05448562b8b64994b7de7eac43cdc8a",
36+
hex.EncodeToString(entropy))
37+
})
38+
}

api/firmware/btc_test.go

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Copyright 2018-2019 Shift Cryptosecurity AG
2+
// Copyright 2024 Shift Crypto AG
23
//
34
// Licensed under the Apache License, Version 2.0 (the "License");
45
// you may not use this file except in compliance with the License.
@@ -20,12 +21,26 @@ import (
2021

2122
"github.com/BitBoxSwiss/bitbox02-api-go/api/firmware/messages"
2223
"github.com/BitBoxSwiss/bitbox02-api-go/util/semver"
24+
"github.com/btcsuite/btcd/btcec/v2"
25+
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
26+
"github.com/btcsuite/btcd/btcutil/hdkeychain"
27+
"github.com/btcsuite/btcd/chaincfg/chainhash"
2328
"github.com/stretchr/testify/require"
2429
"google.golang.org/protobuf/proto"
2530
)
2631

2732
const hardenedKeyStart = 0x80000000
2833

34+
func parseECDSASignature(t *testing.T, sig []byte) *ecdsa.Signature {
35+
t.Helper()
36+
require.Len(t, sig, 64)
37+
r := new(btcec.ModNScalar)
38+
r.SetByteSlice(sig[:32])
39+
s := new(btcec.ModNScalar)
40+
s.SetByteSlice(sig[32:])
41+
return ecdsa.NewSignature(r, s)
42+
}
43+
2944
func TestNewXPub(t *testing.T) {
3045
xpub, err := NewXPub(
3146
"xpub6FEZ9Bv73h1vnE4TJG4QFj2RPXJhhsPbnXgFyH3ErLvpcZrDcynY65bhWga8PazWHLSLi23PoBhGcLcYW6JRiJ12zXZ9Aop4LbAqsS3gtcy")
@@ -39,7 +54,79 @@ func TestNewXPub(t *testing.T) {
3954
}, xpub)
4055
}
4156

42-
func TestBTCXPub(t *testing.T) {
57+
func TestBTCXpub(t *testing.T) {
58+
testInitializedSimulators(t, func(t *testing.T, device *Device) {
59+
t.Helper()
60+
xpub, err := device.BTCXPub(messages.BTCCoin_TBTC, []uint32{
61+
49 + hardenedKeyStart,
62+
1 + hardenedKeyStart,
63+
0 + hardenedKeyStart,
64+
}, messages.BTCPubRequest_YPUB, false)
65+
require.NoError(t, err)
66+
require.Equal(t, "ypub6WqXiL3fbDK5QNPe3hN4uSVkEvuE8wXoNCcecgggSuKVpU3Kc4fTvhuLgUhtnbAdaTb9gpz5PQdvzcsKPTLgW2CPkF5ZNRzQeKFT4NSc1xN", xpub)
67+
})
68+
}
69+
70+
func TestBTCAddress(t *testing.T) {
71+
testInitializedSimulators(t, func(t *testing.T, device *Device) {
72+
t.Helper()
73+
address, err := device.BTCAddress(
74+
messages.BTCCoin_TBTC,
75+
[]uint32{
76+
84 + hardenedKeyStart,
77+
1 + hardenedKeyStart,
78+
0 + hardenedKeyStart,
79+
1,
80+
10,
81+
},
82+
NewBTCScriptConfigSimple(messages.BTCScriptConfig_P2WPKH),
83+
false,
84+
)
85+
require.NoError(t, err)
86+
require.Equal(t, "tb1qq064dxjgl9h9wzgsmzy6t6306qew42w9ka02u3", address)
87+
})
88+
}
89+
90+
func parseXPub(t *testing.T, xpubStr string, keypath ...uint32) *hdkeychain.ExtendedKey {
91+
t.Helper()
92+
xpub, err := hdkeychain.NewKeyFromString(xpubStr)
93+
require.NoError(t, err)
94+
95+
for _, child := range keypath {
96+
xpub, err = xpub.Derive(child)
97+
require.NoError(t, err)
98+
}
99+
return xpub
100+
}
101+
102+
func TestSimulatorBTCSignMessage(t *testing.T) {
103+
testInitializedSimulators(t, func(t *testing.T, device *Device) {
104+
t.Helper()
105+
coin := messages.BTCCoin_BTC
106+
accountKeypath := []uint32{49 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart}
107+
108+
xpubStr, err := device.BTCXPub(coin, accountKeypath, messages.BTCPubRequest_XPUB, false)
109+
require.NoError(t, err)
110+
111+
xpub := parseXPub(t, xpubStr, 0, 10)
112+
pubKey, err := xpub.ECPubKey()
113+
require.NoError(t, err)
114+
115+
sig, _, _, err := device.BTCSignMessage(
116+
coin,
117+
&messages.BTCScriptConfigWithKeypath{
118+
ScriptConfig: NewBTCScriptConfigSimple(messages.BTCScriptConfig_P2WPKH_P2SH),
119+
Keypath: append(accountKeypath, 0, 10),
120+
},
121+
[]byte("message"),
122+
)
123+
require.NoError(t, err)
124+
sigHash := chainhash.DoubleHashB([]byte("\x18Bitcoin Signed Message:\n\x07message"))
125+
require.True(t, parseECDSASignature(t, sig).Verify(sigHash, pubKey))
126+
})
127+
}
128+
129+
func TestSimulatorBTCXPub(t *testing.T) {
43130
testConfigurations(t, func(t *testing.T, env *testEnv) {
44131
t.Helper()
45132
expected := "mocked-xpub"
@@ -110,7 +197,7 @@ func TestBTCXPub(t *testing.T) {
110197
})
111198
}
112199

113-
func TestBTCAddress(t *testing.T) {
200+
func TestSimulatorBTCAddress(t *testing.T) {
114201
testConfigurations(t, func(t *testing.T, env *testEnv) {
115202
t.Helper()
116203
expected := "mocked-address"

api/firmware/cardano_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2024 Shift Crypto AG
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package firmware
16+
17+
import (
18+
"encoding/hex"
19+
"testing"
20+
21+
"github.com/BitBoxSwiss/bitbox02-api-go/api/firmware/messages"
22+
"github.com/stretchr/testify/require"
23+
)
24+
25+
func TestSimulatorCardanoXPubs(t *testing.T) {
26+
testInitializedSimulators(t, func(t *testing.T, device *Device) {
27+
t.Helper()
28+
xpubs, err := device.CardanoXPubs(
29+
[][]uint32{
30+
{1852 + hardenedKeyStart, 1815 + hardenedKeyStart, hardenedKeyStart},
31+
{1852 + hardenedKeyStart, 1815 + hardenedKeyStart, hardenedKeyStart + 1},
32+
},
33+
)
34+
require.NoError(t, err)
35+
require.Len(t, xpubs, 2)
36+
require.Equal(t,
37+
"9fc9550e8379cb97c2d2557d89574207c6cf4d4ff62b37e377f2b3b3c284935b677f0fe5a4a6928c7b982c0c149f140c26c0930b73c2fe16feddfa21625e0316",
38+
hex.EncodeToString(xpubs[0]),
39+
)
40+
require.Equal(t,
41+
"7ffd0bd7d54f1648ac59a357d3eb27b878c2f7c09739d3b7c7e6662d496dea16f10ef525258833d37db047cd530bf373ebcb283495aa4c768424a2af37cee661",
42+
hex.EncodeToString(xpubs[1]),
43+
)
44+
})
45+
}
46+
47+
func TestSimulatorCardanoAddress(t *testing.T) {
48+
testInitializedSimulators(t, func(t *testing.T, device *Device) {
49+
t.Helper()
50+
const account = uint32(1)
51+
const rolePayment = uint32(0) // receive
52+
const roleStake = uint32(2) // stake role must be 2
53+
const addressIdx = uint32(10) // address index
54+
const stakeAddressIdx = uint32(0) // stake addr idx must be 0
55+
address, err := device.CardanoAddress(
56+
messages.CardanoNetwork_CardanoMainnet,
57+
&messages.CardanoScriptConfig{
58+
Config: &messages.CardanoScriptConfig_PkhSkh_{
59+
PkhSkh: &messages.CardanoScriptConfig_PkhSkh{
60+
KeypathPayment: []uint32{1852 + hardenedKeyStart, 1815 + hardenedKeyStart, account + hardenedKeyStart, rolePayment, addressIdx},
61+
KeypathStake: []uint32{1852 + hardenedKeyStart, 1815 + hardenedKeyStart, account + hardenedKeyStart, roleStake, stakeAddressIdx},
62+
},
63+
},
64+
},
65+
false,
66+
)
67+
require.NoError(t, err)
68+
require.Equal(t,
69+
"addr1qxdq2ez52f5gtva3m77xgf5x4a7ap78mal43e5hhszyqehaaddssj2eta30yv9chr0sf4gu0jw77gag2g464yq0c70gqks5cr4",
70+
address,
71+
)
72+
})
73+
}

0 commit comments

Comments
 (0)