From: Matt Corallo Date: Tue, 5 Jan 2021 19:03:44 +0000 (-0500) Subject: Add a lightning-wasm-bindings crate and generation code X-Git-Url: http://git.bitcoin.ninja/?a=commitdiff_plain;h=d6ebb8af337d9e07363428d4acb7d399cbd814fe;p=rust-lightning Add a lightning-wasm-bindings crate and generation code --- diff --git a/genbindings.sh b/genbindings.sh index da8a1c15a..8ff886196 100755 --- a/genbindings.sh +++ b/genbindings.sh @@ -8,24 +8,40 @@ set -x # First build the latest c-bindings-gen binary cd c-bindings-gen && cargo build && cd .. -# Then wipe all the existing C bindings (because we're being run in the right directory) -# note that we keep the few manually-generated files first: -mv lightning-c-bindings/src/c_types/mod.rs ./ -mv lightning-c-bindings/src/bitcoin ./ - -rm -rf lightning-c-bindings/src - -mkdir -p lightning-c-bindings/src/c_types/ -mv ./mod.rs lightning-c-bindings/src/c_types/ -mv ./bitcoin lightning-c-bindings/src/ - -# Finally, run the c-bindings-gen binary, building fresh bindings. -SRC="$(pwd)/lightning/src" -OUT="$(pwd)/lightning-c-bindings/src" -OUT_TEMPL="$(pwd)/lightning-c-bindings/src/c_types/derived.rs" -OUT_F="$(pwd)/lightning-c-bindings/include/rust_types.h" -OUT_CPP="$(pwd)/lightning-c-bindings/include/lightningpp.hpp" -RUST_BACKTRACE=1 ./c-bindings-gen/target/debug/c-bindings-gen $SRC/ $OUT/ lightning $OUT_TEMPL $OUT_F $OUT_CPP "#[no_mangle]" "#[repr(C)]" +for FOLDER in "lightning-c-bindings" "lightning-wasm-bindings"; do + # Then wipe all the existing C bindings (because we're being run in the right directory) + # note that we keep the few manually-generated files first: + mv $FOLDER/src/c_types/mod.rs ./ + mv $FOLDER/src/bitcoin ./ + + rm -rf $FOLDER/src + + mkdir -p $FOLDER/src/c_types/ + mv ./mod.rs $FOLDER/src/c_types/ + mv ./bitcoin $FOLDER/src/ + + # Finally, run the c-bindings-gen binary, building fresh bindings. + SRC="$(pwd)/lightning/src" + OUT="$(pwd)/$FOLDER/src" + OUT_TEMPL="$(pwd)/$FOLDER/src/c_types/derived.rs" + if [ "$FOLDER" = "lightning-c-bindings" ]; then + OUT_F="$(pwd)/$FOLDER/include/rust_types.h" + OUT_CPP="$(pwd)/$FOLDER/include/lightningpp.hpp" + FN_ATTR="#[no_mangle]" + STRUCT_ATTR="#[repr(C)]" + else + # WASM bindings don't care about C++ headers + OUT_F="/dev/null" + OUT_CPP="/dev/null" + FN_ATTR="#[wasm_bindgen::prelude::wasm_bindgen]" + STRUCT_ATTR="#[wasm_bindgen::prelude::wasm_bindgen]" + fi + RUST_BACKTRACE=1 ./c-bindings-gen/target/debug/c-bindings-gen $SRC/ $OUT/ lightning $OUT_TEMPL $OUT_F $OUT_CPP "$FN_ATTR" "$STRUCT_ATTR" + + pushd $FOLDER + cargo build + popd +done # Now cd to lightning-c-bindings, build the generated bindings, and call cbindgen to build a C header file PATH="$PATH:~/.cargo/bin" diff --git a/lightning-wasm-bindings/Cargo.toml b/lightning-wasm-bindings/Cargo.toml new file mode 100644 index 000000000..093ef0c00 --- /dev/null +++ b/lightning-wasm-bindings/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "lightning-wasm-bindings" +version = "0.0.1" +authors = ["Matt Corallo"] +license = "Apache-2.0" +edition = "2018" +description = """ +Utilities to fetch the chain from Bitcoin Core REST/RPC Interfaces and feed them into Rust Lightning. +""" + +[lib] +name = "ldk" +crate-type = ["cdylib"] + +[dependencies] +bitcoin = "0.24" +lightning = { version = "0.0.12", path = "../lightning" } +wasm-bindgen = "0.2" + +# We eventually want to join the root workspace, but for now, the bindings generation is +# a bit brittle and we don't want to hold up other developers from making changes just +# because they break the bindings +[workspace] diff --git a/lightning-wasm-bindings/src/bitcoin/mod.rs b/lightning-wasm-bindings/src/bitcoin/mod.rs new file mode 100644 index 000000000..a61610bd4 --- /dev/null +++ b/lightning-wasm-bindings/src/bitcoin/mod.rs @@ -0,0 +1 @@ +pub mod network; diff --git a/lightning-wasm-bindings/src/bitcoin/network.rs b/lightning-wasm-bindings/src/bitcoin/network.rs new file mode 100644 index 000000000..13799c0a5 --- /dev/null +++ b/lightning-wasm-bindings/src/bitcoin/network.rs @@ -0,0 +1,18 @@ +use bitcoin::network::constants::Network as BitcoinNetwork; + +#[repr(C)] +pub enum Network { + Bitcoin, + Testnet, + Regtest, +} + +impl Network { + pub(crate) fn into_bitcoin(&self) -> BitcoinNetwork { + match self { + Network::Bitcoin => BitcoinNetwork::Bitcoin, + Network::Testnet => BitcoinNetwork::Testnet, + Network::Regtest => BitcoinNetwork::Regtest, + } + } +} diff --git a/lightning-wasm-bindings/src/c_types/mod.rs b/lightning-wasm-bindings/src/c_types/mod.rs new file mode 100644 index 000000000..4e032a05a --- /dev/null +++ b/lightning-wasm-bindings/src/c_types/mod.rs @@ -0,0 +1,315 @@ +pub mod derived; + +use bitcoin::Script as BitcoinScript; +use bitcoin::Transaction as BitcoinTransaction; +use bitcoin::hashes::Hash; +use bitcoin::secp256k1::key::PublicKey as SecpPublicKey; +use bitcoin::secp256k1::key::SecretKey as SecpSecretKey; +use bitcoin::secp256k1::Signature as SecpSignature; +use bitcoin::secp256k1::Error as SecpError; + +use std::convert::TryInto; // Bindings need at least rustc 1.34 + +use wasm_bindgen::prelude::wasm_bindgen; + +#[wasm_bindgen] +#[derive(Clone)] +pub struct PublicKey { + pub compressed_form: [u8; 33], +} +impl PublicKey { + pub(crate) fn from_rust(pk: &SecpPublicKey) -> Self { + Self { + compressed_form: pk.serialize(), + } + } + pub(crate) fn into_rust(&self) -> SecpPublicKey { + SecpPublicKey::from_slice(&self.compressed_form).unwrap() + } + pub(crate) fn is_null(&self) -> bool { self.compressed_form[..] == [0; 33][..] } + pub(crate) fn null() -> Self { Self { compressed_form: [0; 33] } } +} + +#[wasm_bindgen] +pub struct SecretKey { + pub bytes: [u8; 32], +} +impl SecretKey { + // from_rust isn't implemented for a ref since we just return byte array refs directly + pub(crate) fn from_rust(sk: SecpSecretKey) -> Self { + let mut bytes = [0; 32]; + bytes.copy_from_slice(&sk[..]); + Self { bytes } + } + pub(crate) fn into_rust(&self) -> SecpSecretKey { + SecpSecretKey::from_slice(&self.bytes).unwrap() + } +} + +#[wasm_bindgen] +pub struct Signature { + pub compact_form: [u8; 64], +} +impl Signature { + pub(crate) fn from_rust(pk: &SecpSignature) -> Self { + Self { + compact_form: pk.serialize_compact(), + } + } + pub(crate) fn into_rust(&self) -> SecpSignature { + SecpSignature::from_compact(&self.compact_form).unwrap() + } + // The following are used for Option which we support, but don't use anymore + #[allow(unused)] pub(crate) fn is_null(&self) -> bool { self.compact_form[..] == [0; 64][..] } + #[allow(unused)] pub(crate) fn null() -> Self { Self { compact_form: [0; 64] } } +} + +#[wasm_bindgen] +pub enum Secp256k1Error { + IncorrectSignature, + InvalidMessage, + InvalidPublicKey, + InvalidSignature, + InvalidSecretKey, + InvalidRecoveryId, + InvalidTweak, + NotEnoughMemory, + CallbackPanicked, +} +impl Secp256k1Error { + pub(crate) fn from_rust(err: SecpError) -> Self { + match err { + SecpError::IncorrectSignature => Secp256k1Error::IncorrectSignature, + SecpError::InvalidMessage => Secp256k1Error::InvalidMessage, + SecpError::InvalidPublicKey => Secp256k1Error::InvalidPublicKey, + SecpError::InvalidSignature => Secp256k1Error::InvalidSignature, + SecpError::InvalidSecretKey => Secp256k1Error::InvalidSecretKey, + SecpError::InvalidRecoveryId => Secp256k1Error::InvalidRecoveryId, + SecpError::InvalidTweak => Secp256k1Error::InvalidTweak, + SecpError::NotEnoughMemory => Secp256k1Error::NotEnoughMemory, + } + } +} + +#[wasm_bindgen] +/// A serialized transaction, in (pointer, length) form. +/// +/// This type optionally owns its own memory, and thus the semantics around access change based on +/// the `data_is_owned` flag. If `data_is_owned` is set, you must call `Transaction_free` to free +/// the underlying buffer before the object goes out of scope. If `data_is_owned` is not set, any +/// access to the buffer after the scope in which the object was provided to you is invalid. eg, +/// access after you return from the call in which a `!data_is_owned` `Transaction` is provided to +/// you would be invalid. +/// +/// Note that, while it may change in the future, because transactions on the Rust side are stored +/// in a deserialized form, all `Transaction`s generated on the Rust side will have `data_is_owned` +/// set. Similarly, while it may change in the future, all `Transaction`s you pass to Rust may have +/// `data_is_owned` either set or unset at your discretion. +pub struct Transaction { + /// This is non-const for your convenience, an object passed to Rust is never written to. + pub data: *mut u8, + pub datalen: usize, + pub data_is_owned: bool, +} +impl Transaction { + pub(crate) fn into_bitcoin(&self) -> BitcoinTransaction { + if self.datalen == 0 { panic!("0-length buffer can never represent a valid Transaction"); } + ::bitcoin::consensus::encode::deserialize(unsafe { std::slice::from_raw_parts(self.data, self.datalen) }).unwrap() + } + pub(crate) fn from_vec(v: Vec) -> Self { + let datalen = v.len(); + let data = Box::into_raw(v.into_boxed_slice()); + Self { + data: unsafe { (*data).as_mut_ptr() }, + datalen, + data_is_owned: true, + } + } +} +impl Drop for Transaction { + fn drop(&mut self) { + if self.data_is_owned && self.datalen != 0 { + let _ = derived::CVec_u8Z { data: self.data as *mut u8, datalen: self.datalen }; + } + } +} +#[no_mangle] +pub extern "C" fn Transaction_free(_res: Transaction) { } + +pub(crate) fn bitcoin_to_C_outpoint(outpoint: ::bitcoin::blockdata::transaction::OutPoint) -> crate::chain::transaction::OutPoint { + crate::chain::transaction::OutPoint_new(ThirtyTwoBytes { data: outpoint.txid.into_inner() }, outpoint.vout.try_into().unwrap()) +} + +#[wasm_bindgen] +#[derive(Clone)] +/// A transaction output including a scriptPubKey and value. +/// This type *does* own its own memory, so must be free'd appropriately. +pub struct TxOut { + pub script_pubkey: derived::CVec_u8Z, + pub value: u64, +} + +impl TxOut { + pub(crate) fn into_rust(mut self) -> ::bitcoin::blockdata::transaction::TxOut { + ::bitcoin::blockdata::transaction::TxOut { + script_pubkey: self.script_pubkey.into_rust().into(), + value: self.value, + } + } + pub(crate) fn from_rust(txout: ::bitcoin::blockdata::transaction::TxOut) -> Self { + Self { + script_pubkey: derived::CVec_u8Z::from(txout.script_pubkey.into_bytes()), + value: txout.value + } + } +} +#[no_mangle] +pub extern "C" fn TxOut_free(_res: TxOut) { } + +#[wasm_bindgen] +pub struct u8slice { + pub data: *const u8, + pub datalen: usize +} +impl u8slice { + pub(crate) fn from_slice(s: &[u8]) -> Self { + Self { + data: s.as_ptr(), + datalen: s.len(), + } + } + pub(crate) fn to_slice(&self) -> &[u8] { + if self.datalen == 0 { return &[]; } + unsafe { std::slice::from_raw_parts(self.data, self.datalen) } + } +} + +#[wasm_bindgen] +#[derive(Copy, Clone)] +/// Arbitrary 32 bytes, which could represent one of a few different things. You probably want to +/// look up the corresponding function in rust-lightning's docs. +pub struct ThirtyTwoBytes { + pub data: [u8; 32], +} +impl ThirtyTwoBytes { + pub(crate) fn null() -> Self { + Self { data: [0; 32] } + } +} + +#[wasm_bindgen] +pub struct ThreeBytes { pub data: [u8; 3], } +#[wasm_bindgen] +#[derive(Clone)] +pub struct FourBytes { pub data: [u8; 4], } +#[wasm_bindgen] +#[derive(Clone)] +pub struct TenBytes { pub data: [u8; 10], } +#[wasm_bindgen] +#[derive(Clone)] +pub struct SixteenBytes { pub data: [u8; 16], } + +pub(crate) struct VecWriter(pub Vec); +impl lightning::util::ser::Writer for VecWriter { + fn write_all(&mut self, buf: &[u8]) -> Result<(), ::std::io::Error> { + self.0.extend_from_slice(buf); + Ok(()) + } + fn size_hint(&mut self, size: usize) { + self.0.reserve_exact(size); + } +} +pub(crate) fn serialize_obj(i: &I) -> derived::CVec_u8Z { + let mut out = VecWriter(Vec::new()); + i.write(&mut out).unwrap(); + derived::CVec_u8Z::from(out.0) +} +pub(crate) fn deserialize_obj(s: u8slice) -> Result { + I::read(&mut s.to_slice()) +} +pub(crate) fn deserialize_obj_arg>(s: u8slice, args: A) -> Result { + I::read(&mut s.to_slice(), args) +} + +#[wasm_bindgen] +#[derive(Copy, Clone)] +/// A Rust str object, ie a reference to a UTF8-valid string. +/// This is *not* null-terminated so cannot be used directly as a C string! +pub struct Str { + pub chars: *const u8, + pub len: usize +} +impl Into for &'static str { + fn into(self) -> Str { + Str { chars: self.as_ptr(), len: self.len() } + } +} +impl Into<&'static str> for Str { + fn into(self) -> &'static str { + if self.len == 0 { return ""; } + std::str::from_utf8(unsafe { std::slice::from_raw_parts(self.chars, self.len) }).unwrap() + } +} + +// Note that the C++ headers memset(0) all the Templ types to avoid deallocation! +// Thus, they must gracefully handle being completely null in _free. + +// TODO: Integer/bool primitives should avoid the pointer indirection for underlying types +// everywhere in the containers. + +pub union CResultPtr { + pub result: *mut O, + pub err: *mut E, +} +pub struct CResultTempl { + pub contents: CResultPtr, + pub result_ok: bool, +} +impl CResultTempl { + pub(crate) extern "C" fn ok(o: O) -> Self { + CResultTempl { + contents: CResultPtr { + result: Box::into_raw(Box::new(o)), + }, + result_ok: true, + } + } + pub(crate) extern "C" fn err(e: E) -> Self { + CResultTempl { + contents: CResultPtr { + err: Box::into_raw(Box::new(e)), + }, + result_ok: false, + } + } +} +impl Drop for CResultTempl { + fn drop(&mut self) { + if self.result_ok { + if unsafe { !self.contents.result.is_null() } { + unsafe { Box::from_raw(self.contents.result) }; + } + } else if unsafe { !self.contents.err.is_null() } { + unsafe { Box::from_raw(self.contents.err) }; + } + } +} + +/// Utility to make it easy to set a pointer to null and get its original value in line. +pub(crate) trait TakePointer { + fn take_ptr(&mut self) -> T; +} +impl TakePointer<*const T> for *const T { + fn take_ptr(&mut self) -> *const T { + let ret = *self; + *self = std::ptr::null(); + ret + } +} +impl TakePointer<*mut T> for *mut T { + fn take_ptr(&mut self) -> *mut T { + let ret = *self; + *self = std::ptr::null_mut(); + ret + } +}