bhwi-async 0.1.0

Async HWI trait over the bhwi sans-IO Bitcoin hardware wallet interpreters
Documentation
use crate::{HttpClient, Transport};
use async_trait::async_trait;
use bhwi::{
    Interpreter,
    coldcard::{
        ColdcardCommand, ColdcardError, ColdcardInterpreter, ColdcardResponse, ColdcardTransmit,
        encrypt::{self, CryptoRngCore},
    },
    common,
};

/// An asynchronous Coldcard client with persistent key-agreement and cipher state.
pub struct Coldcard<T> {
    /// The transport used for device exchanges.
    pub transport: T,
    encryption: encrypt::Engine,
}

impl<T> Coldcard<T> {
    /// Creates a client with a fresh key-agreement secret generated by `rng`.
    ///
    /// Unlock the client to establish encryption before issuing encrypted commands.
    pub fn new(transport: T, rng: &mut impl CryptoRngCore) -> Self {
        Self {
            transport,
            encryption: encrypt::Engine::new(rng),
        }
    }
}

impl<C, T, R, E, F> crate::CommonInterface<C, T, R, E> for Coldcard<F>
where
    C: TryInto<ColdcardCommand, Error = ColdcardError>,
    T: From<ColdcardTransmit>,
    R: From<ColdcardResponse>,
    E: From<ColdcardError>,
    F: Transport,
{
    type TransportError = F::Error;
    type HttpClientError = ColdcardError;
    fn components(
        &mut self,
    ) -> (
        &mut (dyn Transport<Error = Self::TransportError> + '_),
        &(dyn HttpClient<Error = Self::HttpClientError> + '_),
        Option<&mut (dyn crate::HostInteraction + 'static)>,
        impl Interpreter<Command = C, Transmit = T, Response = R, Error = E>,
    ) {
        (
            &mut self.transport,
            &DummyClient {},
            None,
            ColdcardInterpreter::new(&mut self.encryption),
        )
    }
}

impl<T> crate::OnUnlock for Coldcard<T> {
    fn on_unlock(&mut self, response: bhwi::common::Response) -> Result<(), common::Error> {
        if let bhwi::common::Response::EncryptionKey(key) = response {
            self.encryption.ready(key)?;
        }
        Ok(())
    }
}

/// An unused HTTP client whose [`HttpClient::request`] implementation always panics.
///
/// Coldcard commands do not use HTTP; this is not a fallback HTTP implementation.
pub struct DummyClient;
#[async_trait(?Send)]
impl HttpClient for DummyClient {
    type Error = ColdcardError;
    async fn request(&self, _url: &str, _req: &[u8]) -> Result<Vec<u8>, Self::Error> {
        unreachable!("Coldcard does not need http client")
    }
}