diff --git a/api/firmware/backup_test.go b/api/firmware/backup_test.go new file mode 100644 index 0000000..9d23f25 --- /dev/null +++ b/api/firmware/backup_test.go @@ -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) + }) +} diff --git a/api/firmware/bip85_test.go b/api/firmware/bip85_test.go new file mode 100644 index 0000000..37914e6 --- /dev/null +++ b/api/firmware/bip85_test.go @@ -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)) + }) +} diff --git a/api/firmware/btc_test.go b/api/firmware/btc_test.go index 2d97f30..f3b26dc 100644 --- a/api/firmware/btc_test.go +++ b/api/firmware/btc_test.go @@ -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. @@ -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") @@ -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" @@ -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" diff --git a/api/firmware/cardano_test.go b/api/firmware/cardano_test.go new file mode 100644 index 0000000..2c82bf5 --- /dev/null +++ b/api/firmware/cardano_test.go @@ -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, + ) + }) +} diff --git a/api/firmware/device_test.go b/api/firmware/device_test.go index 6d4d0f5..f90b9c8 100644 --- a/api/firmware/device_test.go +++ b/api/firmware/device_test.go @@ -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. @@ -16,19 +17,200 @@ package firmware import ( "crypto/rand" + "crypto/sha256" + "encoding/hex" + "encoding/json" "errors" "fmt" + "io" + "net" + "net/http" + "net/url" + "os" + "os/exec" + "path" + "path/filepath" + "runtime" + "sync" "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" "google.golang.org/protobuf/proto" ) +func runSimulator(filename string) (func() error, *Device, error) { + cmd := exec.Command(filename) + if err := cmd.Start(); err != nil { + return nil, nil, err + } + var conn net.Conn + var err error + for i := 0; i < 200; i++ { + conn, err = net.Dial("tcp", "localhost:15423") + if err == nil { + break + } + time.Sleep(10 * time.Millisecond) + } + if err != nil { + return nil, nil, err + } + const bitboxCMD = 0x80 + 0x40 + 0x01 + + communication := u2fhid.NewCommunication(conn, bitboxCMD) + device := NewDevice(nil, nil, + &mocks.Config{}, communication, &mocks.Logger{}, + ) + return func() error { + if err := conn.Close(); err != nil { + return err + } + return cmd.Process.Kill() + }, device, nil +} + +func downloadSimulators() ([]string, error) { + type simulator struct { + URL string `json:"url"` + Sha256 string `json:"sha256"` + } + data, err := os.ReadFile("./testdata/simulators.json") + if err != nil { + return nil, err + } + var simulators []simulator + if err := json.Unmarshal(data, &simulators); err != nil { + return nil, err + } + + fileNotExistOrHashMismatch := func(filename, expectedHash string) (bool, error) { + file, err := os.Open(filename) + if os.IsNotExist(err) { + return true, nil + } + if err != nil { + return false, err + } + defer file.Close() + + hasher := sha256.New() + if _, err := io.Copy(hasher, file); err != nil { + return false, err + } + actualHash := hex.EncodeToString(hasher.Sum(nil)) + + return actualHash != expectedHash, nil + } + + downloadFile := func(url, filename string) error { + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("bad status: %s", resp.Status) + } + + // Create the file + out, err := os.Create(filename) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, resp.Body) + return err + } + filenames := []string{} + for _, simulator := range simulators { + simUrl, err := url.Parse(simulator.URL) + if err != nil { + return nil, err + } + filename := filepath.Join("testdata", "simulators", path.Base(simUrl.Path)) + if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil { + return nil, err + } + doDownload, err := fileNotExistOrHashMismatch(filename, simulator.Sha256) + if err != nil { + return nil, err + } + if doDownload { + if err := downloadFile(simulator.URL, filename); err != nil { + return nil, err + } + if err := os.Chmod(filename, 0755); err != nil { + return nil, err + } + } + filenames = append(filenames, filename) + } + return filenames, nil +} + +var downloadSimulatorsOnce = sync.OnceValues(downloadSimulators) + +// Runs tests against a simulator which is not initialized (not paired, not seeded). +func testSimulators(t *testing.T, run func(*testing.T, *Device)) { + t.Helper() + if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" { + t.Skip("Skipping simulator tests: not running on linux-amd64") + } + + simulatorFilenames, err := downloadSimulatorsOnce() + require.NoError(t, err) + + for _, simulatorFilename := range simulatorFilenames { + t.Run(filepath.Base(simulatorFilename), func(t *testing.T) { + teardown, device, err := runSimulator(simulatorFilename) + require.NoError(t, err) + defer func() { require.NoError(t, teardown()) }() + run(t, device) + }) + } +} + +// Runs tests against a simulator which is not initialized, but paired (not seeded). +func testSimulatorsAfterPairing(t *testing.T, run func(*testing.T, *Device)) { + t.Helper() + testSimulators(t, func(t *testing.T, device *Device) { + t.Helper() + require.NoError(t, device.Init()) + device.ChannelHashVerify(true) + run(t, device) + }) +} + +// 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 +func testInitializedSimulators(t *testing.T, run func(*testing.T, *Device)) { + t.Helper() + testSimulatorsAfterPairing(t, func(t *testing.T, device *Device) { + t.Helper() + require.NoError(t, device.RestoreFromMnemonic()) + run(t, device) + }) +} + +func TestSimulatorRootFingerprint(t *testing.T) { + testInitializedSimulators(t, func(t *testing.T, device *Device) { + t.Helper() + fp, err := device.RootFingerprint() + require.NoError(t, err) + require.Equal(t, "4c00739d", hex.EncodeToString(fp)) + }) +} + // newDevice creates a device to test with, with init/pairing already processed. func newDevice( t *testing.T, @@ -296,6 +478,14 @@ func TestVersion(t *testing.T) { }) } +func TestSimulatorProduct(t *testing.T) { + testSimulators(t, func(t *testing.T, device *Device) { + t.Helper() + require.NoError(t, device.Init()) + require.Equal(t, common.ProductBitBox02Multi, device.Product()) + }) +} + func TestProduct(t *testing.T) { testConfigurations(t, func(t *testing.T, env *testEnv) { t.Helper() diff --git a/api/firmware/eth.go b/api/firmware/eth.go index 9da9a81..96b1267 100644 --- a/api/firmware/eth.go +++ b/api/firmware/eth.go @@ -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( diff --git a/api/firmware/eth_test.go b/api/firmware/eth_test.go index 693c338..77ffdba 100644 --- a/api/firmware/eth_test.go +++ b/api/firmware/eth_test.go @@ -19,8 +19,15 @@ import ( "github.com/BitBoxSwiss/bitbox02-api-go/api/firmware/messages" "github.com/stretchr/testify/require" + "golang.org/x/crypto/sha3" ) +func hashKeccak(b []byte) []byte { + h := sha3.NewLegacyKeccak256() + h.Write(b) + return h.Sum(nil) +} + func parseTypeNoErr(t *testing.T, typ string, types map[string]interface{}) *messages.ETHSignTypedMessageRequest_MemberType { t.Helper() parsed, err := parseType(typ, types) @@ -29,7 +36,6 @@ func parseTypeNoErr(t *testing.T, typ string, types map[string]interface{}) *mes } func TestParseType(t *testing.T) { - require.Equal(t, &messages.ETHSignTypedMessageRequest_MemberType{ Type: messages.ETHSignTypedMessageRequest_STRING, @@ -238,3 +244,152 @@ func TestEncodeValue(t *testing.T) { require.NoError(t, err) require.Equal(t, []byte("\x00\x00\x03\xe8"), encoded) } + +func TestSimulatorETHPub(t *testing.T) { + testInitializedSimulators(t, func(t *testing.T, device *Device) { + t.Helper() + chainID := uint64(1) + xpub, err := device.ETHPub( + chainID, + []uint32{ + 44 + hardenedKeyStart, + 60 + hardenedKeyStart, + 0 + hardenedKeyStart, + 0, + }, + messages.ETHPubRequest_XPUB, + false, + nil, + ) + require.NoError(t, err) + require.Equal(t, + "xpub6F2rrkQ947NAvxGQdZPcw1fMHdnJMxXCPtGKWdmf1aaumRkaCoJF72yFYhKRmkbat27bhDy79FWndkS3skRNLgbsuuJKqBoFyUcrp5ZgmC3", + xpub, + ) + + address, err := device.ETHPub( + chainID, + []uint32{ + 44 + hardenedKeyStart, + 60 + hardenedKeyStart, + 0 + hardenedKeyStart, + 0, + 1, + }, + messages.ETHPubRequest_ADDRESS, + false, + nil, + ) + require.NoError(t, err) + require.Equal(t, + "0x6A2A567cB891DeF8eA8C215C85f93d2f0F844ceB", + address, + ) + }) +} + +func TestSimulatorETHSignMessage(t *testing.T) { + testInitializedSimulators(t, func(t *testing.T, device *Device) { + t.Helper() + chainID := uint64(1) + xpubStr, err := device.ETHPub( + chainID, + []uint32{ + 44 + hardenedKeyStart, + 60 + hardenedKeyStart, + 0 + hardenedKeyStart, + 0, + }, + messages.ETHPubRequest_XPUB, + false, + nil, + ) + require.NoError(t, err) + + xpub := parseXPub(t, xpubStr, 10) + pubKey, err := xpub.ECPubKey() + require.NoError(t, err) + + sig, err := device.ETHSignMessage( + chainID, + []uint32{ + 44 + hardenedKeyStart, + 60 + hardenedKeyStart, + 0 + hardenedKeyStart, + 0, + 10, + }, + []byte("message"), + ) + require.NoError(t, err) + + sigHash := hashKeccak([]byte("\x19Ethereum Signed Message:\n7message")) + require.True(t, parseECDSASignature(t, sig[:64]).Verify(sigHash, pubKey)) + }) +} + +func TestSimulatorETHSignTypedMessage(t *testing.T) { + testInitializedSimulators(t, func(t *testing.T, device *Device) { + t.Helper() + msg := []byte(` +{ + "types": { + "EIP712Domain": [ + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "Attachment": [ + { "name": "contents", "type": "string" } + ], + "Person": [ + { "name": "name", "type": "string" }, + { "name": "wallet", "type": "address" }, + { "name": "age", "type": "uint8" } + ], + "Mail": [ + { "name": "from", "type": "Person" }, + { "name": "to", "type": "Person" }, + { "name": "contents", "type": "string" }, + { "name": "attachments", "type": "Attachment[]" } + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": 1, + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + "age": 20 + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + "age": "0x1e" + }, + "contents": "Hello, Bob!", + "attachments": [{ "contents": "attachment1" }, { "contents": "attachment2" }] + } +}`) + + sig, err := device.ETHSignTypedMessage( + 1, + []uint32{ + 44 + hardenedKeyStart, + 60 + hardenedKeyStart, + 0 + hardenedKeyStart, + 0, + 10, + }, + msg, + ) + require.NoError(t, err) + require.Len(t, sig, 65) + }) +} diff --git a/api/firmware/mnemonic_test.go b/api/firmware/mnemonic_test.go new file mode 100644 index 0000000..530a588 --- /dev/null +++ b/api/firmware/mnemonic_test.go @@ -0,0 +1,50 @@ +// 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 TestSimulatorShowMnemonic(t *testing.T) { + testInitializedSimulators(t, func(t *testing.T, device *Device) { + t.Helper() + require.NoError(t, device.ShowMnemonic()) + }) +} + +func TestSimulatorSetMnemonicPassphraseEnabled(t *testing.T) { + testInitializedSimulators(t, func(t *testing.T, device *Device) { + t.Helper() + info, err := device.DeviceInfo() + require.NoError(t, err) + require.False(t, info.MnemonicPassphraseEnabled) + + require.NoError(t, device.SetMnemonicPassphraseEnabled(true)) + + info, err = device.DeviceInfo() + require.NoError(t, err) + require.True(t, info.MnemonicPassphraseEnabled) + + require.NoError(t, device.SetMnemonicPassphraseEnabled(false)) + + info, err = device.DeviceInfo() + require.NoError(t, err) + require.False(t, info.MnemonicPassphraseEnabled) + }) + +} diff --git a/api/firmware/sdcard_test.go b/api/firmware/sdcard_test.go new file mode 100644 index 0000000..c8ae1f9 --- /dev/null +++ b/api/firmware/sdcard_test.go @@ -0,0 +1,42 @@ +// 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/BitBoxSwiss/bitbox02-api-go/api/firmware/messages" + "github.com/stretchr/testify/require" +) + +func TestSimulatorCheckSDCard(t *testing.T) { + testSimulatorsAfterPairing(t, func(t *testing.T, device *Device) { + t.Helper() + inserted, err := device.CheckSDCard() + require.NoError(t, err) + // Simulator always returns true. + require.True(t, inserted) + }) +} + +func TestSimutorInsertRemoveSDCard(t *testing.T) { + testSimulatorsAfterPairing(t, func(t *testing.T, device *Device) { + t.Helper() + require.NoError(t, + device.InsertRemoveSDCard(messages.InsertRemoveSDCardRequest_INSERT_CARD)) + require.NoError(t, + device.InsertRemoveSDCard(messages.InsertRemoveSDCardRequest_REMOVE_CARD)) + }) +} diff --git a/api/firmware/system_test.go b/api/firmware/system_test.go new file mode 100644 index 0000000..416fb60 --- /dev/null +++ b/api/firmware/system_test.go @@ -0,0 +1,52 @@ +// 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 ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSimulatorDeviceName(t *testing.T) { + testSimulatorsAfterPairing(t, func(t *testing.T, device *Device) { + t.Helper() + info, err := device.DeviceInfo() + require.NoError(t, err) + require.Equal(t, "My BitBox", info.Name) + + // Name too long. + require.Error(t, device.SetDeviceName( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) + + require.NoError(t, device.SetDeviceName("new name")) + info, err = device.DeviceInfo() + require.NoError(t, err) + require.Equal(t, "new name", info.Name) + }) +} + +func TestSimulatorSetPassword(t *testing.T) { + for _, seedLen := range []int{16, 32} { + t.Run(fmt.Sprintf("seedLen=%d", seedLen), func(t *testing.T) { + testSimulatorsAfterPairing(t, func(t *testing.T, device *Device) { + t.Helper() + require.NoError(t, device.SetPassword(seedLen)) + require.Equal(t, StatusSeeded, device.Status()) + }) + }) + } +} diff --git a/api/firmware/testdata/.gitignore b/api/firmware/testdata/.gitignore new file mode 100644 index 0000000..c677f0d --- /dev/null +++ b/api/firmware/testdata/.gitignore @@ -0,0 +1 @@ +/simulators/ diff --git a/api/firmware/testdata/simulators.json b/api/firmware/testdata/simulators.json new file mode 100644 index 0000000..bc8c72f --- /dev/null +++ b/api/firmware/testdata/simulators.json @@ -0,0 +1,6 @@ +[ + { + "url": "https://github.com/BitBoxSwiss/bitbox02-firmware/releases/download/firmware%2Fv9.19.0/bitbox02-multi-v9.19.0-simulator1.0.0-linux-amd64", + "sha256": "e28be3fd6c7777624ad2574546ba125b7f134f095fa951acc8fb7295f3d33931" + } +]