Skip to content

Commit

Permalink
firmware/tests: test against bitbox02 simulator
Browse files Browse the repository at this point in the history
  • Loading branch information
benma committed Jun 3, 2024
1 parent fa7f932 commit a38c5f2
Show file tree
Hide file tree
Showing 10 changed files with 497 additions and 1 deletion.
34 changes: 34 additions & 0 deletions api/firmware/bip85_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// 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"

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

func (s *initializedSimulatorTestSuite) TestBIP85AppBip39() {
// Can't test this yet as the simulator panics at trinary_choice (12, 18, 24 word choice).
//require.NoError(s.T(), s.device.BIP85AppBip39())
}

func (s *initializedSimulatorTestSuite) TestBIP85AppLN() {
entropy, err := s.device.BIP85AppLN()
require.NoError(s.T(), err)
require.Equal(s.T(),
"d05448562b8b64994b7de7eac43cdc8a",
hex.EncodeToString(entropy))
}
78 changes: 78 additions & 0 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,6 +54,69 @@ func TestNewXPub(t *testing.T) {
}, xpub)
}

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

func (s *initializedSimulatorTestSuite) TestBTCAddress() {
address, err := s.device.BTCAddress(
messages.BTCCoin_TBTC,
[]uint32{
84 + hardenedKeyStart,
1 + hardenedKeyStart,
0 + hardenedKeyStart,
1,
10,
},
NewBTCScriptConfigSimple(messages.BTCScriptConfig_P2WPKH),
false,
)
require.NoError(s.T(), err)
require.Equal(s.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 (s *initializedSimulatorTestSuite) TestBTCSignMessage() {
coin := messages.BTCCoin_BTC
accountKeypath := []uint32{49 + hardenedKeyStart, 0 + hardenedKeyStart, 0 + hardenedKeyStart}

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

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

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

func TestBTCXPub(t *testing.T) {
testConfigurations(t, func(t *testing.T, env *testEnv) {
t.Helper()
Expand Down
66 changes: 66 additions & 0 deletions api/firmware/cardano_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// 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"

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

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

func (s *initializedSimulatorTestSuite) TestCardanoAddress() {
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 := s.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(s.T(), err)
require.Equal(s.T(),
"addr1qxdq2ez52f5gtva3m77xgf5x4a7ap78mal43e5hhszyqehaaddssj2eta30yv9chr0sf4gu0jw77gag2g464yq0c70gqks5cr4",
address,
)
}
65 changes: 65 additions & 0 deletions api/firmware/device_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 @@ -18,17 +19,81 @@ import (
"crypto/rand"
"errors"
"fmt"
"net"
"os/exec"
"testing"
"time"

"github.com/BitBoxSwiss/bitbox02-api-go/api/common"
"github.com/BitBoxSwiss/bitbox02-api-go/api/firmware/messages"
"github.com/BitBoxSwiss/bitbox02-api-go/api/firmware/mocks"
"github.com/BitBoxSwiss/bitbox02-api-go/communication/u2fhid"
"github.com/BitBoxSwiss/bitbox02-api-go/util/semver"
"github.com/flynn/noise"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"google.golang.org/protobuf/proto"
)

// Runs tests against a simulator which is not initialzed (not seeded).
type simulatorTestSuite struct {
suite.Suite

cmd *exec.Cmd
device *Device
conn net.Conn
}

func (s *simulatorTestSuite) SetupSuite() {
s.cmd = exec.Command("./testdata/simulator")
require.NoError(s.T(), s.cmd.Start())

var err error
for i := 0; i < 200; i++ {
s.conn, err = net.Dial("tcp", "localhost:15423")
if err == nil {
break
}
time.Sleep(10 * time.Millisecond)
}
require.NoError(s.T(), err)

const bitboxCMD = 0x80 + 0x40 + 0x01

communication := u2fhid.NewCommunication(s.conn, bitboxCMD)
s.device = NewDevice(nil, nil,
&mocks.Config{}, communication, &mocks.Logger{},
)
require.NoError(s.T(), s.device.Init())
s.device.ChannelHashVerify(true)

require.Equal(s.T(), common.ProductBitBox02Multi, s.device.Product())
}

func (s *simulatorTestSuite) TearDownSuite() {
require.NoError(s.T(), s.conn.Close())
require.NoError(s.T(), s.cmd.Process.Kill())
}

func TestSimulatorSuite(t *testing.T) {
suite.Run(t, &simulatorTestSuite{})
}

// Runs tests againt a simulator that is seeded with this mnemonic:
// boring mistake dish oyster truth pigeon viable emerge sort crash wire portion cannon couple enact box walk height pull today solid off enable tide
type initializedSimulatorTestSuite struct {
simulatorTestSuite
}

func (s *initializedSimulatorTestSuite) SetupSuite() {
s.simulatorTestSuite.SetupSuite()
require.NoError(s.T(), s.device.RestoreFromMnemonic())
}

func TestInitializedSimulatorSuite(t *testing.T) {
suite.Run(t, &initializedSimulatorTestSuite{})
}

// newDevice creates a device to test with, with init/pairing already processed.
func newDevice(
t *testing.T,
Expand Down
2 changes: 1 addition & 1 deletion api/firmware/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ func (device *Device) ETHSignEIP1559(
}

// ETHSignMessage signs an Ethereum message. The provided msg will be prefixed with "\x19Ethereum
// message\n" + len(msg) in the hardware, e.g. "\x19Ethereum\n5hello" (yes, the len prefix is the
// Signed Message\n" + len(msg) in the hardware, e.g. "\x19Ethereum Signed dMessage\n5hello" (yes, the len prefix is the
// ascii representation with no fixed size or delimiter, WTF).
// 27 is added to the recID to denote an uncompressed pubkey.
func (device *Device) ETHSignMessage(
Expand Down
Loading

0 comments on commit a38c5f2

Please sign in to comment.