Skip to content

Tier 2: extend BitBox02 firmware simulator beyond hardware_connect_bitbox #548

@TaprootFreak

Description

@TaprootFreak

Context

.github/workflows/bitbox-simulator.yml runs the BitBox02 firmware-simulator with bitbox-testkit@v0.5.0 against the official bitbox02-firmware/simulator Docker image. Today the workflow paths: filter (lines 37-45) triggers only on:

  • lib/packages/hardware_wallet/**
  • lib/packages/wallet/**
  • lib/screens/hardware_connect_bitbox/**
  • their test mirrors
  • pubspec.yaml
  • the workflow itself

That filter is path-triggered, not opt-in (the slash-command variant bitbox-simulator-slash.yml is the opt-in path). Broadening paths: makes the workflow trigger on more PRs, not fewer.

What testkit v0.5.0 already covers

Verified from DFXswiss/bitbox-testkit:go/bitbox/simulator/scenarios.go BaselineScenarios():

  • PairAndDeviceInfo, RestoreSimulatorMnemonic, RootFingerprintDeterministic
  • ETH: EthAddressMainnet, EthAddressPolygonMultiByteV, EthSignMessageAscii, EthSignMessageBoundary, EthSignEIP1559Mainnet, EthSignTypedDataKycMultiPage, EthSignTypedDataNonAsciiRejected, EthSignLegacyPolygonMultiByteV
  • BTC: BtcXpubZpubMainnet, BtcAddressP2WPKHMainnet, BtcAddressP2TRTaproot, BtcSignMessageMainnet

Important: EthAddressMainnet already uses the path m/44'/60'/0'/0/0 — exactly realunit-app's derivation. The testkit code:

addr, err := dev.ETHPub(
    1, // mainnet
    []uint32{44 + hardened, 60 + hardened, 0 + hardened, 0, 0},
    ...
)

So "realunit-app's derivation path is uncovered" would be wrong. What EthAddressMainnet does NOT do is assert a specific address value — it only checks the returned string has the expected length/format. That's the gap.

Similarly: EthSignTypedDataKycMultiPage already feeds the realunit-app EIP-712 payload (realUnitUserKycPayload constant in scenarios.go mirrors the KYC registration shape). And EthSignMessageAscii/EthSignMessageBoundary already cover signPersonalMessage (the DFX auth signature path).

So the KYC sign ceremony and DFX auth signature are at Tier 2 today. They are NOT the gap.

Realunit-app surface

Verified from lib/packages/utils/default_assets.dart:

  • realUnitAsset — Ethereum mainnet, chainId: 1, contract 0x553C7f9C780316FC1D34b8e14ac2465Ab22a090B, ERC-20
  • realUnitTestAsset — Sepolia, chainId: 11155111, same contract symbol
  • Derivation path: m/44'/60'/0'/0/0 (lib/packages/hardware_wallet/bitbox_credentials.dart:17)

The Tier-2 matrix is exactly 2 assets, 1 derivation path, no BTC.

Scope — gaps actually worth a new testkit scenario

Each item is a candidate for a PR in DFXswiss/bitbox-testkit, followed by an action-pin bump on .github/workflows/bitbox-simulator.yml:72. This is a cross-repo workflow.

  • SellBitboxEIP1559RealUnitTx — EIP-1559 sell-BitBox transaction sign with realunit-app's exact to (the sell router/relay address), value: 0, data (ERC-20 transfer calldata produced by _sellService.createUnsignedTransactions server-side), gasLimit, maxFeePerGas, maxPriorityFeePerGas shape. EthSignEIP1559Mainnet exists but uses a generic payload — confirm whether realunit's tx bytes match or need their own baseline.
  • SellBitboxFailureBranches — assert the simulator returns the expected error class for each FakeBitboxBehavior equivalent on the firmware side (user-cancel, malformed payload, mid-stream disconnect). Today these branches are only covered at Tier 0 with the Dart fake.
  • EthAddressRealUnitMainnetExactEthAddressMainnet already uses the right derivation path but asserts only the address-string shape. The realunit-specific scenario should assert the exact address derived from the simulator's fixture mnemonic, so the testkit catches any byte-level drift in the simulator's BIP-32 implementation.
  • EthAddressRealUnitSepoliaExact — same shape for Sepolia (chainId: 11155111).

Out of scope — Tier 2 already covers these

  • Buy/Sell-Sign personal-message ceremonyBuyPaymentInfoCubit / SellPaymentInfoCubit use DfxAuthService.signMessage (auth-token JWT, personal-message sign). Already covered by EthSignMessageAscii + EthSignMessageBoundary. Only SellBitboxCubit adds a per-transaction EIP-1559 sign — that's the only real new scenario.
  • "Mnemonic-based restore" — The BitBox02 firmware simulator restores from a hard-coded fixture mnemonic ("boring mistake dish oyster …" in testkit RestoreSimulatorMnemonic); it does NOT accept user-entered mnemonic. The realunit-app RestoreWalletCubit does software-wallet BIP-39 restore (bip39.validateMnemonic in WalletService.restoreWallet) — not BitBox. The two restore paths are unrelated; nothing to add at Tier 2 for the app-side restore.

Workflow changes

  • Broaden bitbox-simulator.yml paths: (currently lines 37-45) to also include lib/screens/sell_bitbox/** + its test mirror, so PRs to the actual sell-BitBox surface trigger the simulator. Do not broaden to lib/screens/buy/** or lib/screens/sell/** — those don't call BitBox per-tx (only signMessage via DfxAuthService, which is already covered).
  • The bitbox-simulator-slash.yml workflow is issue_comment-triggered with no paths: filter — there is nothing to "expand" there. If broader opt-in coverage is wanted, update the slash-command workflow's comment block to document when maintainers should fire it.
  • Document the bitbox_flutter v0.0.7 pin (resolved-ref ebe0fb04e0fb1d56ae6fa815277598c980ac1940 in pubspec.lock:73) inside the workflow's comment header so simulator-vs-Dart-plugin compatibility is reviewable in git blame.

Cross-repo dependency

This issue cannot land here alone. The actual scenario code lives in DFXswiss/bitbox-testkit:

  1. PR in DFXswiss/bitbox-testkit adding the scenario(s) under go/bitbox/simulator/scenarios.go.
  2. Tag/release new testkit version (v0.5.1 or v0.6.0).
  3. Bump action ref pin on .github/workflows/bitbox-simulator.yml:72 (currently @45a1253d23b545d801cf5a1f42c040b85e389c7d # v0.5.0).

A tracking issue in DFXswiss/bitbox-testkit is owned by a separate decision-issue (see Related).

Acceptance criteria

  • Every new scenario runs against the upstream bitbox02-firmware/simulator Docker image via testkit
  • Failures are diff-able: scenario captures the sign payload the simulator received vs. expected baseline
  • CI cost: bitbox-simulator runs on ubuntu-latest (timeout-minutes: 15); broadening paths to lib/screens/sell_bitbox/** adds ≤ N runs per week where N = PR volume on that surface — bounded
  • Hardware variant (Phase 3 of Comprehensive BitBox testing infrastructure (Tier 0–4) #314) remains deferred; this issue is simulator-only

Estimated effort

Sub-task Days
SellBitboxEIP1559RealUnitTx scenario in bitbox-testkit (Go + payload bytes) 1.5
SellBitboxFailureBranches scenario 1.0
EthAddressRealUnitMainnetExact + EthAddressRealUnitSepoliaExact 0.5
testkit release tag + workflow action-pin bump 0.25
paths: broadening + workflow comment-block update 0.25
Cross-repo review cycle (bitbox-testkit PR) 0.5-1.0
Total ~4-5 engineer-days

Most of the effort is in bitbox-testkit (Go scenarios), not in this repo.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions