bhwi 0.1.0

Sans-IO Bitcoin hardware wallet interpreters and their common command model
Documentation
use crate::common::{
    Command, DeviceCode, DeviceContext, DisplayAddress, Error, MultisigAddressType,
    MultisigDisplayAddress,
};
use crate::keepkey::{
    KeepKeyCommand, KeepKeyError, KeepKeyMultisigAddress, KeepKeyMultisigAddressType,
    ManagementContext,
};
use crate::trezor::TrezorError;
use crate::trezor::interpreter::{address_n, script_type};

impl From<KeepKeyError> for Error {
    fn from(e: KeepKeyError) -> Self {
        super::trezor::engine_error(e.0, DeviceCode::KeepKey)
    }
}

impl TryFrom<Command> for KeepKeyCommand {
    type Error = TrezorError;

    fn try_from(command: Command) -> Result<Self, Self::Error> {
        Ok(match command {
            Command::Unlock { options } => Self::Initialize(options.network),
            Command::GetVersion => Self::GetFeatures,
            Command::GetMasterFingerprint => Self::GetMasterFingerprint,
            Command::GetXpub { path, display } => Self::GetXpub {
                address_n: address_n(&path),
                display,
            },
            Command::DisplayAddress(
                DisplayAddress::ByPath {
                    path,
                    display,
                    address_format,
                },
                _,
            ) => Self::GetAddress {
                address_n: address_n(&path),
                display,
                script_type: script_type(address_format, &path),
            },
            Command::DisplayAddress(DisplayAddress::ByDescriptor { .. }, _) => {
                return Err(TrezorError::UnsupportedDisplayAddress(
                    "descriptor address display is not yet supported",
                ));
            }
            Command::DisplayAddress(DisplayAddress::ByMultisig(address), _) => {
                Self::GetMultisigAddress(multisig_address(address))
            }
            Command::SignTx(psbt, context) => {
                if context.is_some() {
                    return Err(TrezorError::Unsupported(
                        "KeepKey SignTx does not support device context",
                    ));
                }
                Self::SignTx(Box::new(psbt))
            }
            Command::SignMessage { message, path } => Self::SignMessage {
                address_n: address_n(&path),
                message,
            },
            Command::RegisterWallet { .. } => {
                return Err(TrezorError::Unsupported("register_wallet is not supported"));
            }
            Command::Backup => {
                return Err(TrezorError::Unsupported(
                    "The Keepkey does not support creating a backup via software",
                ));
            }
            Command::Setup(options, context) => {
                let Some(DeviceContext::KeepKeyManagement(ManagementContext::Setup {
                    host_entropy,
                })) = context
                else {
                    return Err(TrezorError::MissingContext(
                        "KeepKey setup requires host entropy in the device context",
                    ));
                };
                Self::Setup {
                    label: (!options.label.is_empty()).then_some(options.label),
                    host_entropy,
                }
            }
            Command::Wipe => Self::Wipe,
            Command::Restore(options, context) => {
                let Some(DeviceContext::KeepKeyManagement(ManagementContext::Restore {
                    u2f_counter,
                })) = context
                else {
                    return Err(TrezorError::MissingContext(
                        "KeepKey restore requires a U2F counter in the device context",
                    ));
                };
                let word_count = u32::try_from(options.word_count).map_err(|_| {
                    TrezorError::InvalidInput("restore word count must be positive".into())
                })?;
                if !matches!(word_count, 12 | 18 | 24) {
                    return Err(TrezorError::InvalidInput(
                        "restore word count must be 12, 18, or 24".into(),
                    ));
                }
                Self::Restore {
                    label: (!options.label.is_empty()).then_some(options.label),
                    word_count,
                    u2f_counter,
                }
            }
            Command::TogglePassphrase => Self::TogglePassphrase,
            Command::PromptPin => Self::PromptPin,
            Command::SendPin(context) => {
                let Some(DeviceContext::KeepKeyManagement(ManagementContext::Pin(pin))) = context
                else {
                    return Err(TrezorError::MissingContext(
                        "KeepKey sendpin requires the PIN positions in the device context",
                    ));
                };
                Self::SendPin(pin)
            }
        })
    }
}

fn multisig_address(address: MultisigDisplayAddress) -> KeepKeyMultisigAddress {
    KeepKeyMultisigAddress {
        threshold: address.threshold,
        address_type: match address.address_type {
            MultisigAddressType::Legacy => KeepKeyMultisigAddressType::Legacy,
            MultisigAddressType::ShWit => KeepKeyMultisigAddressType::ShWit,
            MultisigAddressType::Wit => KeepKeyMultisigAddressType::Wit,
        },
        sorted: address.sorted,
        keys: address.keys,
    }
}