Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version-file: go/go.mod
cache: true
cache-dependency-path: go/go.sum
- run: go vet ./...
Expand Down
94 changes: 94 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Testing

## Fast PR gate

Run the same checks the `PR checks` workflow enforces. CI fails on any of
these, so it is cheaper to catch them locally first:

```sh
# Flutter
dart format --set-exit-if-changed --output=none lib test example/lib example/test
flutter analyze --no-fatal-infos
flutter test

# Go (from go/)
cd go
go vet ./...
go test -race -timeout 60s ./...
```

The Go API tests include a generic fake BitBox device. It is not
app-specific: it can simulate pairing, channel hashes, confirmations,
capabilities, ETH address lookup, ETH signing, BTC xpubs, BTC message signing,
device errors, missing devices, and recovered panics without connecting
hardware.

## Test layers

Use the lowest layer that can expose the bug:

- Official simulator: validates against BitBox firmware behavior. The upstream
simulator binaries currently referenced by `bitbox02-api-go` are Linux amd64,
so this is best suited for Linux CI or explicit hardware-wallet integration
jobs.
- U2FHID/BLE contract tests: validate framing assumptions, stale-buffer
behavior, repeated poll responses, and the iOS BLE bridge source contract.
These tests do not emulate firmware.
- Native API fake: validates gomobile-exported API behavior, zero values,
panic recovery, and BTC/ETH request plumbing without USB, BLE, or firmware.
- Flutter API fake: validates app/plugin flows through `BitboxManager` without
USB, BLE, native code, or firmware.

Keep physical BitBox smoke tests for behavior that requires the real device:
firmware UI, touch confirmation timing, pairing UX, cable/BLE hardware
instability, and secure-chip behavior.

## Reusable Flutter testkit

Flutter apps can import the standalone Dart simulator:

```dart
import 'package:bitbox_flutter/testing.dart';

final bitbox = installSimulatedBitboxPlatform(
channelHash: 'hash-shown-to-the-user',
);
```

The simulator replaces `BitboxUsbPlatform.instance`, so app tests can exercise
their real production BitboxManager flow without USB, BLE, or a physical
BitBox. Save and restore the previous platform in `setUp`/`tearDown` when a test
suite needs isolation.

The Dart simulator is deliberately not app-specific. It covers device
discovery/no-device states, permission/open/close, pairing channel hashes
including empty hashes and rejected confirmations, capability checks, BTC/ETH
signing, custom per-method delays, custom per-method errors, custom method
behavior, and a call log that tests can assert against.

## Official BitBox simulator

`github.com/BitBoxSwiss/bitbox02-api-go` also ships official `TestSimulator*`
integration tests. Their README documents:

```sh
go test -v -run TestSimulator ./...
SIMULATOR=/path/to/simulator go test -v -run TestSimulator ./...
```

The published simulator binaries referenced by the dependency are Linux amd64
binaries, so they are best suited for Linux CI or a Linux development machine.
The fast fake-device tests in this plugin remain the default local and PR gate.

## Regression coverage

The tests explicitly guard against these hardware-wallet regressions:

- gomobile-exported API functions without `recoverPanic`
- iOS BLE packet deduplication being reintroduced
- iOS BLE read timeout regressing from 60 seconds to 10 seconds
- U2FHID assumptions drifting away from the iOS BLE bridge contract
- Pairing/channel-hash behavior not being simulatable without hardware
- ETH/BTC success, error, and panic flows not being simulatable without hardware
- App-level Flutter flows not being testable with deterministic BitBox delays
and aborts
2 changes: 0 additions & 2 deletions go/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,6 @@ func (d deviceInfo) Open() (io.ReadWriteCloser, error) {
return readWriteCloser{device}, nil
}

var bitbox *firmware.Device

//export GetDevice
func GetDevice(device GoReadWriteCloserInterface) {
defer recoverPanic("GetDevice")
Expand Down
69 changes: 69 additions & 0 deletions go/api/bitbox_device.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package api

import (
"math/big"

"github.com/BitBoxSwiss/bitbox02-api-go/api/firmware"
"github.com/BitBoxSwiss/bitbox02-api-go/api/firmware/messages"
"github.com/btcsuite/btcd/btcutil/psbt"
)

type bitboxDevice interface {
Init() error
ChannelHash() (string, bool)
ChannelHashVerify(ok bool)
DeviceInfo() (*firmware.DeviceInfo, error)
RootFingerprint() ([]byte, error)
SupportsETH(chainID uint64) bool
SupportsLTC() bool
SupportsBluetooth() bool
SupportsERC20(contractAddress string) bool

ETHPub(
chainID uint64,
keypath []uint32,
outputType messages.ETHPubRequest_OutputType,
display bool,
contractAddress []byte,
) (string, error)
ETHSign(
chainID uint64,
keypath []uint32,
nonce uint64,
gasPrice *big.Int,
gasLimit uint64,
recipient [20]byte,
value *big.Int,
data []byte,
recipientAddressCase messages.ETHAddressCase,
) ([]byte, error)
ETHSignEIP1559(
chainID uint64,
keypath []uint32,
nonce uint64,
maxPriorityFeePerGas *big.Int,
maxFeePerGas *big.Int,
gasLimit uint64,
recipient [20]byte,
value *big.Int,
data []byte,
recipientAddressCase messages.ETHAddressCase,
) ([]byte, error)
ETHSignMessage(chainID uint64, keypath []uint32, msg []byte) ([]byte, error)
ETHSignTypedMessage(chainID uint64, keypath []uint32, jsonMsg []byte) ([]byte, error)

BTCXPub(
coin messages.BTCCoin,
keypath []uint32,
xpubType messages.BTCPubRequest_XPubType,
display bool,
) (string, error)
BTCSignPSBT(coin messages.BTCCoin, psbt *psbt.Packet, options *firmware.PSBTSignOptions) error
BTCSignMessage(
coin messages.BTCCoin,
scriptConfig *messages.BTCScriptConfigWithKeypath,
message []byte,
) (*firmware.BTCSignMessageResult, error)
}

var bitbox bitboxDevice
Loading
Loading