-
Notifications
You must be signed in to change notification settings - Fork 32
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
Conversation
🦋 Changeset detectedLatest commit: 25b399f The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
Note for when you're ready to take it out of draft that this will need a changeset in order to bump versions. Without having reviewed yet, I'm guessing minor version bump for @caravan/wallets. |
Added the generated changeset file. Should I also bump the package.json version numbers manually? |
Ready for review 😄 |
@benma nope! The automation will take care of that |
apps/coordinator/src/components/Wallet/ExtendedPublicKeyImporter.jsx
Outdated
Show resolved
Hide resolved
return { | ||
address, | ||
serializedPath: this.bip32Path, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The BitBox02 itself does not actually support regtest right now. Adding support for it would be easy, and in fact I made a PR for it now (BitBoxSwiss/bitbox02-firmware#1302). It would take a little while until it is released and can be used here though.
Another solution here would be to convert the address to the regtest format inside Caravan after the BitBox02 returns it in the testnet format.
What do you think is the best course of action?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In favor for keeping it elegant without the regtest hack for P2WSH! Nested segwit works perfectly and that's likely sufficient for now. If your firmware PR makes it in, it will puzzle in nicely.
Thank you sharing and for suggesting the address conversion, it does work well
import { decodeSegwitAddress, encodeSegwitAddress } from "./vendor/bech32";
if (this.walletConfig.network == "regtest") {
if (address.startsWith("tb1q")) {
try {
const decoded = decodeSegwitAddress("tb", address);
if (!decoded) {
throw new Error("Could not decode address");
}
const resp = encodeSegwitAddress(
"bcrt",
decoded.version,
decoded.program
);
return {
address: resp,
serializedPath: this.bip32Path,
};
} catch (err) {
console.log("error is", err);
}
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@benma any sense of when the updated firmware might be released?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was released a few weeks ago - if you update your BitBox02 to v9.21.0 it will show the proper regtest address prefix.
You can update to this firmware most easily via BitBoxApp v4.45+.
I also added a commit here to make use of it.
I'm surprised you were able to get this to run. I had to add the following to the coordinator's vite config: export default defineConfig({
...
build: {
target: "esnext", //browsers can handle the latest ES features
...
},
} Without that, I get these failures:
There are also a couple of linting errors that need to be fixed. |
I only developed using
Fixed 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great! Just a couple of small things that maybe aren't even blockers.
I'd like us to add unit tests for the @caravan/wallets
code, but since we do have the test suite at least, we don't need to block this going in.
It would also be nice to have the regtest firmware update in so we can do more rigorous testing, but otherwise I'm excited to have this merged in soon!
} | ||
} | ||
|
||
async function convertMultisig(pairedBitBox: PairedBitBox, walletConfig: MultisigWalletConfig): Promise<{ scriptConfig: BtcScriptConfig; keypathAccount: string; }> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels like this function is doing two things and it might cleaner to separate them. Correct me if I'm wrong but what it's doing is:
- verifying that the devices root fingerprint is in a given wallet config
- converting the caravan style wallet config to bitbox style
What if the verification was a method on the interaction base class where you know you'll have a paired bitbox you can interact with and then this was kept as a simple conversion utility function?
Having the verification on the interaction would allow it to be used in other interactions as well. could even retrieve the root fingerprint presumably on startup and store it as a property on the class or something and then use it as needed. Not sure if that's strictly correct or necessary, but I did find this a little hard to grok the way it is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It feels like this function is doing two things and it might cleaner to separate them. Correct me if I'm wrong but what it's doing is:
- verifying that the devices root fingerprint is in a given wallet config
- converting the caravan style wallet config to bitbox style
It is not doing the first one. It is merely trying to identify which of the xpubs belongs to the device, as that is a required parameter in the script config passed to the BitBox (ourXpubIndex
).
return { | ||
address, | ||
serializedPath: this.bip32Path, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@benma any sense of when the updated firmware might be released?
} | ||
|
||
async maybeRegisterMultisig(pairedBitBox: PairedBitBox, walletConfig: MultisigWalletConfig): Promise<{ scriptConfig: BtcScriptConfig, keypathAccount: string; }> { | ||
const { scriptConfig, keypathAccount } = await convertMultisig(pairedBitBox, walletConfig); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could do something like:
const { scriptConfig, keypathAccount } = await convertMultisig(pairedBitBox, walletConfig); | |
const rootFingerprint = await pairedBitBox.rootFingerprint() | |
this.verifyConfig(rootFingerprint, walletConfig) | |
// or this.verifyConfig(pairedBitBox, walletConfig) which could do the fingerprint retrieval | |
const { scriptConfig, keypathAccount } = convertMultisig(walletConfig); |
); | ||
// No name means the user inputs it on the device. | ||
// eslint-disable-next-line no-undefined | ||
const name = undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a name that comes in from the wallet config actually so maybe it should just use that (same is done for ledger and coldcard)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There might be trouble as the BitBox02 has some requirements on the name - max 30 chars, only printalble ASCII chars (including space), etc. If entered on the device it cannot fail.
Using the `bitbox-api` NPM package, which loads a WASM module. Note about CJS: Currently the `bitbox-api` package is an ESM package with a `module: ...` entrypoint, so it is not compatible with the `cjs` target of caravan-wallets. The only workaround that I could find where compilation succeeds and the package works in the browser is to mark bitbox-api as external in tsup.config.ts. Note about signing tests: - The BitBox02 requires the previous transaction of each input to be present in the PSBT (`PSBT_IN_NON_WITNESS_UTXO`), so it can verify the input amount and avoid fee attacks. The signing test fixtures are missing these, so they fail. - The BitBox02 uses the anti-klepto (anti-exfil) protocol to mitigate covert nonce exfil attacks. This results in random signatures. The unit test fixtures hardcode the expected signatures, assuming they are always the same. As a result, also here the tests fail. To fix this, the tests should rather verify the ECDSA signatures against the transaction sighash for each input.
BitBox02 does not support legacy P2SH.
The BitBox, if not paired yet, will show a pairing code for confirmation. This can happen in any BitBox interaction. This commit adds a `showPairingCode` param to all BitBox interactions. If not provided, a default implementation is used which shows the pairing code in a browser popup. The current `messages()` system is not a good fit, as the client does not know when to call `messagesFor()` to display it. Having a separate UI button to pair the BitBox is not good UX (why should the user be bothered to click a "pair" button first? What if the user doesn't) and also fragile (a re-pairing could be needed at any time).
Thanks! The firmware with regtest has been released and I integrated the change in a new commit, please check it out. See #117 (comment) |
Using the
bitbox-api
NPM package, which loads a WASM module.Note about CJS:
Currently the
bitbox-api
package is an ESM package with amodule: ...
entrypoint, so it is not compatible with thecjs
target of caravan-wallets. The only workaround that I could find where compilation succeeds and the package works in the browser is to mark bitbox-api as external in tsup.config.ts.Note about signing tests:
The BitBox02 requires the previous transaction of each input to be
present in the PSBT (
PSBT_IN_NON_WITNESS_UTXO
), so it can verify theinput amount and avoid fee attacks. The signing test fixtures are
missing these, so they fail.
The BitBox02 uses the anti-klepto (anti-exfil) protocol to mitigate
covert nonce exfil attacks. This results in random signatures. The
unit test fixtures hardcode the expected signatures, assuming they are
always the same. As a result, also here the tests fail. To fix this,
the tests should rather verify the ECDSA signatures against the
transaction sighash for each input.