Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for the BitBox02 hardware wallet #117

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions .changeset/heavy-kids-confess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@caravan/wallets": minor
"caravan-coordinator": minor
---

Add support for the BitBox02 hardware wallet
3 changes: 2 additions & 1 deletion apps/coordinator/src/actions/keystoreActions.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { TREZOR, LEDGER, HERMIT, COLDCARD } from "@caravan/wallets";
import { BITBOX, TREZOR, LEDGER, HERMIT, COLDCARD } from "@caravan/wallets";

export const SET_KEYSTORE = "SET_KEYSTORE";
export const SET_KEYSTORE_NOTE = "SET_KEYSTORE_NOTE";
export const SET_KEYSTORE_STATUS = "SET_KEYSTORE_STATUS";

type KeyStoreType =
| typeof BITBOX
| typeof TREZOR
| typeof LEDGER
| typeof HERMIT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ACTIVE,
ERROR,
ExportPublicKey,
BITBOX,
TREZOR,
LEDGER,
} from "@caravan/wallets";
Expand Down Expand Up @@ -172,7 +173,7 @@ const HardwareWalletPublicKeyImporter = ({

HardwareWalletPublicKeyImporter.propTypes = {
network: PropTypes.string.isRequired,
method: PropTypes.oneOf([LEDGER, TREZOR]).isRequired,
method: PropTypes.oneOf([BITBOX, LEDGER, TREZOR]).isRequired,
defaultBIP32Path: PropTypes.string.isRequired,
validatePublicKey: PropTypes.func.isRequired,
enableChangeMethod: PropTypes.func.isRequired,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { validatePublicKey as baseValidatePublicKey } from "@caravan/bitcoin";
import { TREZOR, LEDGER } from "@caravan/wallets";
import { BITBOX, TREZOR, LEDGER } from "@caravan/wallets";

// Components
import {
Expand Down Expand Up @@ -213,6 +213,7 @@ const PublicKeyImporter = ({

const renderImportByMethod = () => {
if (
publicKeyImporter.method === BITBOX ||
publicKeyImporter.method === TREZOR ||
publicKeyImporter.method === LEDGER
) {
Expand Down Expand Up @@ -261,6 +262,7 @@ const PublicKeyImporter = ({
variant="standard"
>
<MenuItem value="">{"< Select method >"}</MenuItem>
<MenuItem value={BITBOX}>BitBox</MenuItem>
<MenuItem value={TREZOR}>Trezor</MenuItem>
<MenuItem value={LEDGER}>Ledger</MenuItem>
<MenuItem value={XPUB}>Derive from extended public key</MenuItem>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React, { useState } from "react";
import { Button } from "@mui/material";
import { useDispatch, useSelector } from "react-redux";
import { BITBOX, RegisterWalletPolicy } from "@caravan/wallets";
import { getWalletConfig } from "../../selectors/wallet";
import { setErrorNotification } from "../../actions/errorNotificationActions";

const RegisterBitBoxButton = ({ ...otherProps }) => {
const [isActive, setIsActive] = useState(false);
const walletConfig = useSelector(getWalletConfig);
const dispatch = useDispatch();

const registerWallet = async () => {
setIsActive(true);
try {
const interaction = new RegisterWalletPolicy({
keystore: BITBOX,
...walletConfig,
});
await interaction.run();
} catch (e) {
dispatch(setErrorNotification(e.message));
} finally {
setIsActive(false);
}
};
return (
<Button
variant="outlined"
onClick={registerWallet}
disabled={isActive}
{...otherProps}
>
Register w/ BitBox
</Button>
);
};

export default RegisterBitBoxButton;
2 changes: 2 additions & 0 deletions apps/coordinator/src/components/RegisterWallet/index.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import DownloadColdardConfigButton from "./DownloadColdcardConfig";
import PolicyRegistrationTable from "./PolicyRegistrationsTable";
import RegisterBitBoxButton from "./RegisterBitBoxButton";
import RegisterLedgerButton from "./RegisterLedgerButton";

export {
DownloadColdardConfigButton,
PolicyRegistrationTable,
RegisterBitBoxButton,
RegisterLedgerButton,
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import {
multisigBIP32Root,
validateBIP32Path,
getMaskedDerivation,
P2SH,
} from "@caravan/bitcoin";
import { TREZOR, LEDGER, HERMIT, COLDCARD } from "@caravan/wallets";
import { BITBOX, TREZOR, LEDGER, HERMIT, COLDCARD } from "@caravan/wallets";
import {
Card,
CardHeader,
Expand Down Expand Up @@ -93,7 +94,7 @@ class SignatureImporter extends React.Component {
};

renderImport = () => {
const { signatureImporter, number, isWallet } = this.props;
const { signatureImporter, number, isWallet, addressType } = this.props;
const currentNumber = this.getCurrent();
const notMyTurn = number > currentNumber;
const { disableChangeMethod } = this.state;
Expand All @@ -119,6 +120,7 @@ class SignatureImporter extends React.Component {
onChange={this.handleMethodChange}
>
<MenuItem value={UNKNOWN}>{"< Select method >"}</MenuItem>
{addressType != P2SH && <MenuItem value={BITBOX}>BitBox</MenuItem>}
<MenuItem value={TREZOR}>Trezor</MenuItem>
<MenuItem value={LEDGER}>Ledger</MenuItem>
<MenuItem value={COLDCARD} disabled={!isWallet}>
Expand Down Expand Up @@ -155,7 +157,7 @@ class SignatureImporter extends React.Component {
} = this.props;
const { method } = signatureImporter;

if (method === TREZOR || method === LEDGER) {
if (method === BITBOX || method === TREZOR || method === LEDGER) {
return (
<DirectSignatureImporter
network={network}
Expand Down
6 changes: 6 additions & 0 deletions apps/coordinator/src/components/Slices/ConfirmAddress.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import {
multisigAddressType,
multisigRequiredSigners,
multisigTotalSigners,
P2SH,
} from "@caravan/bitcoin";
import {
BITBOX,
TREZOR,
LEDGER,
COLDCARD,
Expand Down Expand Up @@ -138,6 +140,7 @@ const ConfirmAddress = ({ slice, network }) => {
}
// FIXME - hardcoded to just show up for trezor
if (
extendedPublicKeyImporter.method === BITBOX ||
extendedPublicKeyImporter.method === TREZOR ||
extendedPublicKeyImporter.method === LEDGER
) {
Expand Down Expand Up @@ -230,6 +233,9 @@ const ConfirmAddress = ({ slice, network }) => {
variant="standard"
>
<MenuItem value="">{"< Select method >"}</MenuItem>
{addressType != P2SH && (
<MenuItem value={BITBOX}>BitBox</MenuItem>
)}
<MenuItem value={TREZOR}>Trezor</MenuItem>
<MenuItem value={LEDGER}>Ledger</MenuItem>
<MenuItem value={COLDCARD} disabled>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react";
import { connect } from "react-redux";

import {
BITBOX,
TREZOR,
LEDGER,
HERMIT,
Expand Down Expand Up @@ -89,6 +90,7 @@ const KeystorePickerBase = ({
variant="standard"
>
<MenuItem value="">{"< Select type >"}</MenuItem>
<MenuItem value={BITBOX}>BitBox</MenuItem>
<MenuItem value={TREZOR}>Trezor</MenuItem>
<MenuItem value={LEDGER}>Ledger</MenuItem>
<MenuItem value={COLDCARD}>Coldcard</MenuItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import moment from "moment";
import { useSelector, useDispatch } from "react-redux";
import Bowser from "bowser";
import {
BITBOX,
TREZOR,
LEDGER,
HERMIT,
Expand Down Expand Up @@ -178,6 +179,8 @@ const TestSuiteRunSummaryBase = () => {

const keystoreName = (type: string) => {
switch (type) {
case BITBOX:
return "BitBox";
case TREZOR:
return "Trezor";
case LEDGER:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import {
convertExtendedPublicKey,
validateExtendedPublicKey,
Network,
P2SH,
} from "@caravan/bitcoin";
import { TREZOR, LEDGER, HERMIT, COLDCARD } from "@caravan/wallets";
import { BITBOX, TREZOR, LEDGER, HERMIT, COLDCARD } from "@caravan/wallets";
import {
Card,
CardHeader,
Expand Down Expand Up @@ -68,7 +69,7 @@ class ExtendedPublicKeyImporter extends React.Component {
};

renderImport = () => {
const { extendedPublicKeyImporter, number } = this.props;
const { extendedPublicKeyImporter, number, addressType } = this.props;
const { disableChangeMethod } = this.state;
return (
<div>
Expand All @@ -82,6 +83,7 @@ class ExtendedPublicKeyImporter extends React.Component {
variant="standard"
onChange={this.handleMethodChange}
>
{addressType != P2SH && <MenuItem value={BITBOX}>BitBox</MenuItem>}
<MenuItem value={TREZOR}>Trezor</MenuItem>
<MenuItem value={COLDCARD}>Coldcard</MenuItem>
<MenuItem value={LEDGER}>Ledger</MenuItem>
Expand All @@ -105,7 +107,7 @@ class ExtendedPublicKeyImporter extends React.Component {
} = this.props;
const { method } = extendedPublicKeyImporter;

if (method === TREZOR || method === LEDGER) {
if (method === BITBOX || method === TREZOR || method === LEDGER) {
return (
<DirectExtendedPublicKeyImporter
extendedPublicKeyImporter={extendedPublicKeyImporter}
Expand Down
4 changes: 4 additions & 0 deletions apps/coordinator/src/components/Wallet/RegisterWallet.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { getWalletConfig } from "../../selectors/wallet";
import PolicyRegistrationTable from "../RegisterWallet/PolicyRegistrationsTable";
import {
DownloadColdardConfigButton,
RegisterBitBoxButton,
RegisterLedgerButton,
} from "../RegisterWallet";

Expand Down Expand Up @@ -89,6 +90,9 @@ const WalletRegistrations = () => {
<AccordionDetails>
<Box>
<Grid container spacing={2}>
<Grid item>
<RegisterBitBoxButton />
</Grid>
<Grid item>
<RegisterLedgerButton />
</Grid>
Expand Down
1 change: 1 addition & 0 deletions apps/coordinator/src/components/Wallet/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ class CreateWallet extends React.Component {
method: (method, index) => {
if (
[
"bitbox",
"trezor",
"coldcard",
"ledger",
Expand Down
13 changes: 13 additions & 0 deletions apps/coordinator/src/tests/bitbox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { BITBOX } from "@caravan/wallets";

import publicKeyTests from "./publicKeys";
import extendedPublicKeyTests from "./extendedPublicKeys";
import { signingTests } from "./signing";
import addressTests from "./addresses";
import registrationTests from "./registration";

export default publicKeyTests(BITBOX)
.concat(extendedPublicKeyTests(BITBOX))
.concat(signingTests(BITBOX))
.concat(addressTests(BITBOX))
.concat(registrationTests(BITBOX));
4 changes: 3 additions & 1 deletion apps/coordinator/src/tests/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { TREZOR, LEDGER, HERMIT, COLDCARD } from "@caravan/wallets";
import { BITBOX, TREZOR, LEDGER, HERMIT, COLDCARD } from "@caravan/wallets";
import { TEST_FIXTURES } from "@caravan/bitcoin";

import bitboxTests from "./bitbox";
import trezorTests from "./trezor";
import ledgerTests from "./ledger";
import hermitTests from "./hermit";
import coldcardTests from "./coldcard";

const SUITE = {};

SUITE[BITBOX] = bitboxTests;
SUITE[TREZOR] = trezorTests;
SUITE[LEDGER] = ledgerTests;
SUITE[HERMIT] = hermitTests;
Expand Down
6 changes: 5 additions & 1 deletion apps/coordinator/src/tests/registration.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";

import { TEST_FIXTURES } from "@caravan/bitcoin";
import { RegisterWalletPolicy } from "@caravan/wallets";
import { BITBOX, RegisterWalletPolicy } from "@caravan/wallets";
import { Box, Table, TableBody, TableRow, TableCell } from "@mui/material";

import Test from "./Test";
Expand All @@ -13,6 +13,10 @@ class RegisterWalletPolicyTest extends Test {
}

expected() {
if (this.params.keystore === BITBOX) {
// BitBox does not use HMACs to register policies.
return undefined;
}
return this.params.policyHmac;
}

Expand Down
12 changes: 12 additions & 0 deletions apps/coordinator/src/tests/signing.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
TEST_FIXTURES,
} from "@caravan/bitcoin";
import {
BITBOX,
COLDCARD,
HERMIT,
LEDGER,
Expand Down Expand Up @@ -193,6 +194,17 @@ export function signingTests(keystore) {
}
});
return transactions;
case BITBOX:
return TEST_FIXTURES.transactions
.filter((fixture) => fixture.braidDetails)
.map(
(fixture) =>
new SignMultisigTransactionTest({
...fixture,
...{ keystore },
returnSignatureArray: true,
}),
);
case LEDGER:
return TEST_FIXTURES.transactions
.filter((fixture) => fixture.policyHmac && fixture.braidDetails)
Expand Down
1 change: 1 addition & 0 deletions apps/coordinator/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default defineConfig({
}),
],
build: {
target: "esnext", // browsers can handle the latest ES features
outDir: "build",
rollupOptions: {
onwarn(warning, warn) {
Expand Down
7 changes: 7 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions packages/caravan-wallets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"@typescript-eslint/parser": "^5.51.0",
"babel-jest": "^29.7.0",
"babel-plugin-transform-inline-environment-variables": "^0.4.3",
"bitbox-api": "^0.6.0",
"esbuild-plugin-polyfill-node": "^0.3.0",
"eslint": "^8.34.0",
"eslint-plugin-import": "^2.27.5",
Expand Down
Loading
Loading