LedgerHQ / app-bitcoin-new

Modern Bitcoin Application based on PSBT and Descriptors
Apache License 2.0
94 stars 72 forks source link

Create JS/Kotlin/etc. bindings for bitcoin_client_rs #111

Open bigspider opened 1 year ago

bigspider commented 1 year ago

It would be very useful to create bindings from the rust library to various targets that developers need - especially JS and JVM-based.

bigspider commented 1 year ago

I did some experiments in trying to create JS bindings with wasm-bindgen. Unsuccessful so far, leaving some notes for future reference.

===

I did manage to use the transport object from JS code(from @ledgerhq/hw-transport-node-hid in a Rust class, in turn exported to JS run in node.

===

A limitation of wasm-bindgen is that it cannot export objects with type parameters; therefore, a wrapper of BitcoinClient<Transport> seems necessary, e.g. a struct like:

#[wasm_bindgen]
pub struct BitcoinClientJS {
    client: BitcoinClient<Transport>,
}

I tried to import a transport object from JS and implement the Transport trait

#[wasm_bindgen]
extern "C" {
    pub type TransportJS;

    #[wasm_bindgen(method)]
    pub async fn send(this: &TransportJS, cla: u8, ins: u8, p1: u8, p2: u8, data: &[u8])
        -> JsValue;
}

#[async_trait]
impl Transport for TransportJS {
    type Error = Box<dyn Error>;

    async fn exchange(&self, command: &APDUCommand) -> Result<(StatusWord, Vec<u8>), Self::Error> {
        todo!()
    }
}

That should be possible as per the initial experiment above, but I couldn't make it work; apart from some painful limitations of wasm-bindgen on the allowed returned types for the public members, I hit a roadblock when I started getting errors when trying to implement exchange for TransportJS, like:

55 |       async fn exchange(&self, command: &APDUCommand) -> Result<(StatusWord, Vec<u8>), Self::Error> {
   |  ___________________________________________________________________________________________________^
56 | |         let result = self
57 | |             .send(command.cla, command.ins, command.p1, command.p2, &vec![])
58 | |             .await;
...  |
63 | |         }
64 | |     }
   | |_____^ future created by async block is not `Send`

I don't know how to fix this; attempts to wrap things in Arc or Mutex failed, I couldn't get it to compile.

===

A second attempt to avoid having to call JS code from Rust world initially seemed promising, but the existing implementation of the Transport class depend on the ledger-transport-hid crate, which in turn depends on hidapi that does not compile to the wasm32-unknown-unknown; not sure if this is solvable at all, so this approach is probably not worth pursuing.


error: failed to run custom build command for `hidapi v1.5.0`

...

Caused by: failed to execute `cargo build`: exited with exit status: 101
  full command: "cargo" "build" "--lib" "--release" "--target" "wasm32-unknown-unknown"
edouardparis commented 1 year ago

I found this: https://stackoverflow.com/questions/71217860/how-to-call-an-async-javascript-import-function-from-webassembly-rust-in-a-nod Maybe it can help you solve your problem. (Sharing here because I cannot post links on the Ledger discord)

bigspider commented 1 year ago

Thanks @edouardparis, I didn't manage even in that way, either − but it's a useful reference for this issue.