Skip to content

Commit

Permalink
firmware/tests: test against bitbox02 simulators
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
benma committed Jul 3, 2024
1 parent f46a769 commit d274b88
Show file tree
Hide file tree
Showing 12 changed files with 759 additions and 4 deletions.
59 changes: 59 additions & 0 deletions api/firmware/backup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2024 Shift Crypto AG
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package firmware

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestSimulatorBackups(t *testing.T) {
const seedLen = 32
const testName = "test wallet name"
testSimulatorsAfterPairing(t, func(t *testing.T, device *Device) {
t.Helper()
require.NoError(t, device.SetDeviceName(testName))

require.NoError(t, device.SetPassword(seedLen))
require.Equal(t, StatusSeeded, device.Status())

list, err := device.ListBackups()
require.NoError(t, err)
require.Empty(t, list)

_, err = device.CheckBackup(true)
require.Error(t, err)

require.NoError(t, device.CreateBackup())
require.Equal(t, StatusInitialized, device.Status())

list, err = device.ListBackups()
require.NoError(t, err)
require.Len(t, list, 1)
require.Equal(t, testName, list[0].Name)

id, err := device.CheckBackup(true)
require.NoError(t, err)
require.Equal(t, list[0].ID, id)

require.Error(t, device.RestoreBackup(list[0].ID))
require.NoError(t, device.Reset())
require.NoError(t, device.RestoreBackup(list[0].ID))
id, err = device.CheckBackup(true)
require.NoError(t, err)
require.Equal(t, list[0].ID, id)
})
}
38 changes: 38 additions & 0 deletions api/firmware/bip85_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2024 Shift Crypto AG
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package firmware

import (
"encoding/hex"
"testing"

"github.com/stretchr/testify/require"
)

func TestSimulatorBIP85AppBip39(t *testing.T) {
// Can't test this yet as the simulator panics at trinary_choice (12, 18, 24 word choice).
t.Skip()
}

func TestSimulatorBIP85AppLN(t *testing.T) {
testInitializedSimulators(t, func(t *testing.T, device *Device) {
t.Helper()
entropy, err := device.BIP85AppLN()
require.NoError(t, err)
require.Equal(t,
"d05448562b8b64994b7de7eac43cdc8a",
hex.EncodeToString(entropy))
})
}
91 changes: 89 additions & 2 deletions api/firmware/btc_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright 2018-2019 Shift Cryptosecurity AG
// Copyright 2024 Shift Crypto AG
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -20,12 +21,26 @@ import (

"github.com/BitBoxSwiss/bitbox02-api-go/api/firmware/messages"
"github.com/BitBoxSwiss/bitbox02-api-go/util/semver"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)

const hardenedKeyStart = 0x80000000

func parseECDSASignature(t *testing.T, sig []byte) *ecdsa.Signature {
t.Helper()
require.Len(t, sig, 64)
r := new(btcec.ModNScalar)
r.SetByteSlice(sig[:32])
s := new(btcec.ModNScalar)
s.SetByteSlice(sig[32:])
return ecdsa.NewSignature(r, s)
}

func TestNewXPub(t *testing.T) {
xpub, err := NewXPub(
"xpub6FEZ9Bv73h1vnE4TJG4QFj2RPXJhhsPbnXgFyH3ErLvpcZrDcynY65bhWga8PazWHLSLi23PoBhGcLcYW6JRiJ12zXZ9Aop4LbAqsS3gtcy")
Expand All @@ -39,7 +54,79 @@ func TestNewXPub(t *testing.T) {
}, xpub)
}

func TestBTCXPub(t *testing.T) {
func TestBTCXpub(t *testing.T) {
testInitializedSimulators(t, func(t *testing.T, device *Device) {
t.Helper()
xpub, err := device.BTCXPub(messages.BTCCoin_TBTC, []uint32{
49 + hardenedKeyStart,
1 + hardenedKeyStart,
0 + hardenedKeyStart,
}, messages.BTCPubRequest_YPUB, false)
require.NoError(t, err)
require.Equal(t, "ypub6WqXiL3fbDK5QNPe3hN4uSVkEvuE8wXoNCcecgggSuKVpU3Kc4fTvhuLgUhtnbAdaTb9gpz5PQdvzcsKPTLgW2CPkF5ZNRzQeKFT4NSc1xN", xpub)
})
}

func TestBTCAddress(t *testing.T) {
testInitializedSimulators(t, func(t *testing.T, device *Device) {
t.Helper()
address, err := device.BTCAddress(
messages.BTCCoin_TBTC,
[]uint32{
84 + hardenedKeyStart,
1 + hardenedKeyStart,
0 + hardenedKeyStart,
1,
10,
},
NewBTCScriptConfigSimple(messages.BTCScriptConfig_P2WPKH),
false,
)
require.NoError(t, err)
require.Equal(t, "tb1qq064dxjgl9h9wzgsmzy6t6306qew42w9ka02u3", address)
})
}

func parseXPub(t *testing.T, xpubStr string, keypath ...uint32) *hdkeychain.ExtendedKey {
t.Helper()
xpub, err := hdkeychain.NewKeyFromString(xpubStr)
require.NoError(t, err)

for _, child := range keypath {
xpub, err = xpub.Derive(child)
require.NoError(t, err)
}
return xpub
}

func TestSimulatorBTCSignMessage(t *testing.T) {
testInitializedSimulators(t, func(t *testing.T, device *Device) {
t.Helper()
coin := messages.BTCCoin_BTC
accountKeypath := []uint32{49 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart}

xpubStr, err := device.BTCXPub(coin, accountKeypath, messages.BTCPubRequest_XPUB, false)
require.NoError(t, err)

xpub := parseXPub(t, xpubStr, 0, 10)
pubKey, err := xpub.ECPubKey()
require.NoError(t, err)

sig, _, _, err := device.BTCSignMessage(
coin,
&messages.BTCScriptConfigWithKeypath{
ScriptConfig: NewBTCScriptConfigSimple(messages.BTCScriptConfig_P2WPKH_P2SH),
Keypath: append(accountKeypath, 0, 10),
},
[]byte("message"),
)
require.NoError(t, err)
sigHash := chainhash.DoubleHashB([]byte("\x18Bitcoin Signed Message:\n\x07message"))
require.True(t, parseECDSASignature(t, sig).Verify(sigHash, pubKey))
})
}

func TestSimulatorBTCXPub(t *testing.T) {
testConfigurations(t, func(t *testing.T, env *testEnv) {
t.Helper()
expected := "mocked-xpub"
Expand Down Expand Up @@ -110,7 +197,7 @@ func TestBTCXPub(t *testing.T) {
})
}

func TestBTCAddress(t *testing.T) {
func TestSimulatorBTCAddress(t *testing.T) {
testConfigurations(t, func(t *testing.T, env *testEnv) {
t.Helper()
expected := "mocked-address"
Expand Down
73 changes: 73 additions & 0 deletions api/firmware/cardano_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright 2024 Shift Crypto AG
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package firmware

import (
"encoding/hex"
"testing"

"github.com/BitBoxSwiss/bitbox02-api-go/api/firmware/messages"
"github.com/stretchr/testify/require"
)

func TestSimulatorCardanoXPubs(t *testing.T) {
testInitializedSimulators(t, func(t *testing.T, device *Device) {
t.Helper()
xpubs, err := device.CardanoXPubs(
[][]uint32{
{1852 + hardenedKeyStart, 1815 + hardenedKeyStart, hardenedKeyStart},
{1852 + hardenedKeyStart, 1815 + hardenedKeyStart, hardenedKeyStart + 1},
},
)
require.NoError(t, err)
require.Len(t, xpubs, 2)
require.Equal(t,
"9fc9550e8379cb97c2d2557d89574207c6cf4d4ff62b37e377f2b3b3c284935b677f0fe5a4a6928c7b982c0c149f140c26c0930b73c2fe16feddfa21625e0316",
hex.EncodeToString(xpubs[0]),
)
require.Equal(t,
"7ffd0bd7d54f1648ac59a357d3eb27b878c2f7c09739d3b7c7e6662d496dea16f10ef525258833d37db047cd530bf373ebcb283495aa4c768424a2af37cee661",
hex.EncodeToString(xpubs[1]),
)
})
}

func TestSimulatorCardanoAddress(t *testing.T) {
testInitializedSimulators(t, func(t *testing.T, device *Device) {
t.Helper()
const account = uint32(1)
const rolePayment = uint32(0) // receive
const roleStake = uint32(2) // stake role must be 2
const addressIdx = uint32(10) // address index
const stakeAddressIdx = uint32(0) // stake addr idx must be 0
address, err := device.CardanoAddress(
messages.CardanoNetwork_CardanoMainnet,
&messages.CardanoScriptConfig{
Config: &messages.CardanoScriptConfig_PkhSkh_{
PkhSkh: &messages.CardanoScriptConfig_PkhSkh{
KeypathPayment: []uint32{1852 + hardenedKeyStart, 1815 + hardenedKeyStart, account + hardenedKeyStart, rolePayment, addressIdx},
KeypathStake: []uint32{1852 + hardenedKeyStart, 1815 + hardenedKeyStart, account + hardenedKeyStart, roleStake, stakeAddressIdx},
},
},
},
false,
)
require.NoError(t, err)
require.Equal(t,
"addr1qxdq2ez52f5gtva3m77xgf5x4a7ap78mal43e5hhszyqehaaddssj2eta30yv9chr0sf4gu0jw77gag2g464yq0c70gqks5cr4",
address,
)
})
}
Loading

0 comments on commit d274b88

Please sign in to comment.