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
172 changes: 172 additions & 0 deletions backend/devices/bitbox02/simulator/simulator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// Copyright 2025 Shift Devices 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 simulator

import (
"fmt"
"io"
"net"
"os/exec"
"time"

"github.com/BitBoxSwiss/bitbox-wallet-app/util/logging"
bitbox02common "github.com/BitBoxSwiss/bitbox02-api-go/api/common"
bitbox02firmware "github.com/BitBoxSwiss/bitbox02-api-go/api/firmware"
"github.com/BitBoxSwiss/bitbox02-api-go/communication/u2fhid"
"github.com/BitBoxSwiss/bitbox02-api-go/util/semver"
)

var testDeviceInfo *deviceInfo

// Init processes the simulator flags, and starts a goroutine to monitor the simulator process
// and update the testDeviceInfo accordingly.
func Init(simulatorPort int) {
go func() {
log := logging.Get().WithGroup("simulator")
for {
if !isRunning(simulatorPort) {
// Simulator not running. Unset test device info.
testDeviceInfo = nil
continue
}
// If the testdevice info is already set, do nothing.
if testDeviceInfo != nil {
continue
}

var err error
// Otherwise, set the test device info.
testDeviceInfo, err = newDeviceInfo(simulatorPort)
if err != nil {
log.WithError(err).Error("Failed to get simulator device info")
testDeviceInfo = nil
}
time.Sleep(50 * time.Millisecond)
}
}()
}

// isRunning checks if a process is listening at the provided port.
func isRunning(port int) bool {
cmd := exec.Command("lsof", "-i", fmt.Sprintf(":%d", port), "-sTCP:LISTEN")
if err := cmd.Run(); err != nil {
return false // No process listening on the port
}
return true
}

// TestDeviceInfo returns the deviceInfo of the running simulator, or nil if no simulator is running.
func TestDeviceInfo() *deviceInfo {
return testDeviceInfo
}

// deviceInfo implements usb.deviceInfo for the simulator.
type deviceInfo struct {
port int
version semver.SemVer
product bitbox02common.Product
}

func newDeviceInfo(simulatorPort int) (*deviceInfo, error) {
info := deviceInfo{
port: simulatorPort,
}
hidDevice, err := info.Open()
if err != nil {
return nil, err
}
defer func() {
if err := hidDevice.Close(); err != nil {
logging.Get().WithError(err).Error("Failed to close hidDevice")
}
}()

bitboxCMD := 0x80 + 0x40 + 0x01
version, product, _, err := bitbox02firmware.Info(u2fhid.NewCommunication(hidDevice, byte(bitboxCMD)))
if err != nil {
return nil, err
}
info.version = *version
info.product = product
return &info, nil
}

// IsBluetooth implements usb.DeviceInfo.
func (d deviceInfo) IsBluetooth() bool {
return false
}

// VendorID implements usb.DeviceInfo.
func (d deviceInfo) VendorID() int {
return 0x03eb
}

// ProductID implements usb.DeviceInfo.
func (d deviceInfo) ProductID() int {
return 0x2403
}

// UsagePage implements usb.DeviceInfo.
func (d deviceInfo) UsagePage() int {
return 0xfffff
}

// Interface implements usb.DeviceInfo.
func (d deviceInfo) Interface() int {
return 0
}

// Serial implements usb.DeviceInfo.
func (d deviceInfo) Serial() string {
return fmt.Sprintf("v%s", d.version.String())
}

// Product implements usb.DeviceInfo.
func (d deviceInfo) Product() string {
switch d.product {
case bitbox02common.ProductBitBox02Multi:
return bitbox02common.FirmwareDeviceProductStringBitBox02Multi
case bitbox02common.ProductBitBox02BTCOnly:
return bitbox02common.FirmwareDeviceProductStringBitBox02BTCOnly
case bitbox02common.ProductBitBox02PlusMulti:
return bitbox02common.FirmwareDeviceProductStringBitBox02PlusMulti
case bitbox02common.ProductBitBox02PlusBTCOnly:
return bitbox02common.FirmwareDeviceProductStringBitBox02PlusBTCOnly
default:
panic("unrecognized product")
}
}

// Identifier implements usb.DeviceInfo.
func (d deviceInfo) Identifier() string {
return "bitbox02-simulator"
}

// Open implements usb.DeviceInfo.
func (d deviceInfo) Open() (io.ReadWriteCloser, error) {
var err error
var conn net.Conn
for range 200 {
conn, err = net.Dial("tcp", fmt.Sprintf("localhost:%d", d.port))
if err == nil {
break
}
time.Sleep(50 * time.Millisecond)
}
if err != nil {
return nil, err
}
return conn, nil
}
13 changes: 13 additions & 0 deletions cmd/servewallet/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
backendPkg "github.com/BitBoxSwiss/bitbox-wallet-app/backend"
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/arguments"
btctypes "github.com/BitBoxSwiss/bitbox-wallet-app/backend/coins/btc/types"
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/devices/bitbox02/simulator"
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/devices/usb"
backendHandlers "github.com/BitBoxSwiss/bitbox-wallet-app/backend/handlers"
"github.com/BitBoxSwiss/bitbox-wallet-app/util/config"
Expand Down Expand Up @@ -69,6 +70,11 @@ func (webdevEnvironment) NotifyUser(text string) {

// DeviceInfos implements backend.Environment.
func (webdevEnvironment) DeviceInfos() []usb.DeviceInfo {
testDeviceInfo := simulator.TestDeviceInfo()
if testDeviceInfo != nil {
// We are in "test device" mode.
return []usb.DeviceInfo{*testDeviceInfo}
}
return usb.DeviceInfos()
}

Expand Down Expand Up @@ -138,6 +144,8 @@ func main() {
devservers := flag.Bool("devservers", true, "switch to dev servers")
gapLimitsReceive := flag.Uint("gapLimitReceive", 0, "gap limit for receive addresses")
gapLimitsChange := flag.Uint("gapLimitChange", 0, "gap limit for change addresses")
simulatorPort := flag.Int("simulatorPort", 15423, "port for the BitBox02 simulator")
useSimulator := flag.Bool("simulator", false, "use the BitBox02 simulator")
flag.Parse()

var gapLimits *btctypes.GapLimits
Expand Down Expand Up @@ -177,6 +185,11 @@ func main() {
handlers := backendHandlers.NewHandlers(backend, connectionData)
log.WithFields(logrus.Fields{"address": address, "port": port}).Info("Listening for HTTP")
fmt.Printf("Listening on: http://localhost:%d\n", port)

if *useSimulator {
simulator.Init(*simulatorPort)
}

if err := http.ListenAndServe(fmt.Sprintf("%s:%d", address, port), handlers.Router); err != nil {
log.WithFields(logrus.Fields{"address": address, "port": port, "error": err.Error()}).Fatal("Failed to listen for HTTP")
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/BitBoxSwiss/bitbox-wallet-app
go 1.24

require (
github.com/BitBoxSwiss/bitbox02-api-go v0.0.0-20250916121126-b6cefdf27ff1
github.com/BitBoxSwiss/bitbox02-api-go v0.0.0-20250918124416-ba770921265e
github.com/BitBoxSwiss/block-client-go v0.0.0-20250813114605-c276f6470c3d
github.com/btcsuite/btcd v0.24.2
github.com/btcsuite/btcd/btcec/v2 v2.3.4
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/BitBoxSwiss/bitbox02-api-go v0.0.0-20250916121126-b6cefdf27ff1 h1:5FTAcNfCBBW3gnW2XSjKqohtzBNvdqU25qbknGR2YvU=
github.com/BitBoxSwiss/bitbox02-api-go v0.0.0-20250916121126-b6cefdf27ff1/go.mod h1:8yjGNQve88PvbAm8DKOVYkrmDKTxDc4rVwTll4IcH2s=
github.com/BitBoxSwiss/bitbox02-api-go v0.0.0-20250918124416-ba770921265e h1:JZP6gkSWPHhzSMB237KbbWPi4ur0DHqCBdbxGWpG7yI=
github.com/BitBoxSwiss/bitbox02-api-go v0.0.0-20250918124416-ba770921265e/go.mod h1:8yjGNQve88PvbAm8DKOVYkrmDKTxDc4rVwTll4IcH2s=
github.com/BitBoxSwiss/block-client-go v0.0.0-20250813114605-c276f6470c3d h1:HdoIlaUcNyEBajUoQgsxZtDMyCFyE7qv1kpeFe/u7nQ=
github.com/BitBoxSwiss/block-client-go v0.0.0-20250813114605-c276f6470c3d/go.mod h1:SJTiQZU9ggBzVKMni97rpNS9GddPKErndFXNSDrfEGc=
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions vendor/github.com/BitBoxSwiss/bitbox02-api-go/api/firmware/psbt.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion vendor/modules.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# github.com/BitBoxSwiss/bitbox02-api-go v0.0.0-20250916121126-b6cefdf27ff1
# github.com/BitBoxSwiss/bitbox02-api-go v0.0.0-20250918124416-ba770921265e
## explicit; go 1.22
github.com/BitBoxSwiss/bitbox02-api-go/api/bootloader
github.com/BitBoxSwiss/bitbox02-api-go/api/common
Expand Down