From 698c84523db788e4f17f69aee38464ee2aeeae91 Mon Sep 17 00:00:00 2001 From: Evanfeenstra Date: Tue, 14 Apr 2026 20:43:00 -0700 Subject: [PATCH 1/8] add build-all workflow for CI testing --- .github/workflows/build-all.yml | 81 +++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 .github/workflows/build-all.yml diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml new file mode 100644 index 0000000..1f2dbac --- /dev/null +++ b/.github/workflows/build-all.yml @@ -0,0 +1,81 @@ +name: Build All FFI + +on: + pull_request: + branches: [master, main] + +jobs: + build: + runs-on: macos-latest + defaults: + run: + working-directory: sphinx-ffi + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + + - name: Install cross + run: cargo install cross --git https://github.com/cross-rs/cross + + - name: Install Rust targets (Apple) + run: | + rustup target add x86_64-apple-ios + rustup target add aarch64-apple-ios + rustup target add x86_64-apple-darwin + rustup target add aarch64-apple-darwin + + - name: Build iOS + run: bash build-ios.sh + + - name: Build macOS + run: bash build-mac.sh + + - name: Build Android + run: bash build-android.sh + + - name: Build Windows + run: bash build-windows.sh + + - name: Upload iOS artifacts + uses: actions/upload-artifact@v4 + with: + name: ios-universal + path: sphinx-ffi/target/universal-sphinxrs.a + + - name: Upload macOS artifacts + uses: actions/upload-artifact@v4 + with: + name: macos-universal + path: sphinx-ffi/target/universal-sphinxrs-mac.a + + - name: Upload Android artifacts + uses: actions/upload-artifact@v4 + with: + name: android-libraries + path: sphinx-ffi/target/kotlin-libraries.zip + + - name: Upload Windows artifacts + uses: actions/upload-artifact@v4 + with: + name: windows-libraries + path: sphinx-ffi/target/windows-libraries.zip + + - name: Upload Swift bindings + uses: actions/upload-artifact@v4 + with: + name: swift-bindings + path: | + sphinx-ffi/src/sphinxrs.swift + sphinx-ffi/src/sphinxrsFFI.modulemap + sphinx-ffi/src/sphinxrsFFI.h + + - name: Upload Kotlin bindings + uses: actions/upload-artifact@v4 + with: + name: kotlin-bindings + path: sphinx-ffi/src/uniffi/sphinxrs/sphinxrs.kt + + From b1c5ebe59a5046b5a697822a2da378b6520fa112 Mon Sep 17 00:00:00 2001 From: Evanfeenstra Date: Tue, 14 Apr 2026 20:57:50 -0700 Subject: [PATCH 2/8] configure PAT for private dependency access --- .github/workflows/build-all.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 1f2dbac..8435380 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -14,6 +14,9 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Configure git for private repos + run: git config --global url."https://x-access-token:${{ secrets.PRIVATE_REPO_PAT }}@github.com/".insteadOf "https://github.com/" + - name: Install Rust stable uses: dtolnay/rust-toolchain@stable From 865adcc4cb29d96512847525e010bd5946f5d081 Mon Sep 17 00:00:00 2001 From: Evanfeenstra Date: Tue, 14 Apr 2026 21:01:17 -0700 Subject: [PATCH 3/8] split workflow into macOS + Linux jobs, use --locked for yanked crates --- .github/workflows/build-all.yml | 158 ++++++++++++++++++++++++++------ 1 file changed, 130 insertions(+), 28 deletions(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 8435380..94985c9 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -4,8 +4,11 @@ on: pull_request: branches: [master, main] +env: + CARGO_NET_GIT_FETCH_WITH_CLI: "true" + jobs: - build: + build-apple: runs-on: macos-latest defaults: run: @@ -19,52 +22,55 @@ jobs: - name: Install Rust stable uses: dtolnay/rust-toolchain@stable + with: + targets: x86_64-apple-ios,aarch64-apple-ios,x86_64-apple-darwin,aarch64-apple-darwin - - name: Install cross - run: cargo install cross --git https://github.com/cross-rs/cross - - - name: Install Rust targets (Apple) + - name: Generate Swift bindings run: | - rustup target add x86_64-apple-ios - rustup target add aarch64-apple-ios - rustup target add x86_64-apple-darwin - rustup target add aarch64-apple-darwin + cargo run --features=uniffi/cli --bin uniffi-bindgen generate src/sphinxrs.udl --language swift + sed -i '' 's/module\ sphinxrsFFI/framework\ module\ sphinxrsFFI/' src/sphinxrsFFI.modulemap - - name: Build iOS - run: bash build-ios.sh + - name: Build iOS (x86_64) + run: cargo build --locked --features=uniffi/cli --target=x86_64-apple-ios --release - - name: Build macOS - run: bash build-mac.sh + - name: Build iOS (aarch64) + run: cargo build --locked --features=uniffi/cli --target=aarch64-apple-ios --release - - name: Build Android - run: bash build-android.sh + - name: Create iOS universal lib + run: lipo -create target/x86_64-apple-ios/release/libsphinxrs.a target/aarch64-apple-ios/release/libsphinxrs.a -output target/universal-sphinxrs.a - - name: Build Windows - run: bash build-windows.sh + - name: Build macOS (x86_64) + run: cargo build --locked --features=uniffi/cli --target=x86_64-apple-darwin --release - - name: Upload iOS artifacts + - name: Build macOS (aarch64) + run: cargo build --locked --features=uniffi/cli --target=aarch64-apple-darwin --release + + - name: Create macOS universal lib + run: lipo -create target/x86_64-apple-darwin/release/libsphinxrs.a target/aarch64-apple-darwin/release/libsphinxrs.a -output target/universal-sphinxrs-mac.a + + - name: Create macOS dylib universal + run: | + lipo -create target/x86_64-apple-darwin/release/libsphinxrs.dylib target/aarch64-apple-darwin/release/libsphinxrs.dylib -output target/universal-sphinxrs-mac.dylib || true + + - name: Upload iOS universal lib uses: actions/upload-artifact@v4 with: name: ios-universal path: sphinx-ffi/target/universal-sphinxrs.a - - name: Upload macOS artifacts + - name: Upload macOS universal lib uses: actions/upload-artifact@v4 with: name: macos-universal path: sphinx-ffi/target/universal-sphinxrs-mac.a - - name: Upload Android artifacts + - name: Upload macOS dylibs uses: actions/upload-artifact@v4 with: - name: android-libraries - path: sphinx-ffi/target/kotlin-libraries.zip - - - name: Upload Windows artifacts - uses: actions/upload-artifact@v4 - with: - name: windows-libraries - path: sphinx-ffi/target/windows-libraries.zip + name: macos-dylibs + path: | + sphinx-ffi/target/x86_64-apple-darwin/release/libsphinxrs.dylib + sphinx-ffi/target/aarch64-apple-darwin/release/libsphinxrs.dylib - name: Upload Swift bindings uses: actions/upload-artifact@v4 @@ -75,10 +81,106 @@ jobs: sphinx-ffi/src/sphinxrsFFI.modulemap sphinx-ffi/src/sphinxrsFFI.h + build-android: + runs-on: ubuntu-latest + defaults: + run: + working-directory: sphinx-ffi + + steps: + - uses: actions/checkout@v4 + + - name: Configure git for private repos + run: git config --global url."https://x-access-token:${{ secrets.PRIVATE_REPO_PAT }}@github.com/".insteadOf "https://github.com/" + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + + - name: Install cross + run: cargo install cross --git https://github.com/cross-rs/cross + + - name: Generate Kotlin bindings + run: | + cargo run --features=uniffi/cli --bin uniffi-bindgen generate src/sphinxrs.udl --language kotlin + sed -i 's/return "uniffi_sphinxrs"/return "sphinxrs"/' src/uniffi/sphinxrs/sphinxrs.kt + + - name: Build Android targets + run: | + cross build --locked --features=uniffi/cli --target i686-linux-android --release + cross build --locked --features=uniffi/cli --target aarch64-linux-android --release + cross build --locked --features=uniffi/cli --target arm-linux-androideabi --release + cross build --locked --features=uniffi/cli --target armv7-linux-androideabi --release + cross build --locked --features=uniffi/cli --target x86_64-linux-android --release + + - name: Package Android libraries + run: | + mkdir -p target/out/x86 + mkdir -p target/out/arm64-v8a + mkdir -p target/out/armeabi + mkdir -p target/out/armeabi-v7a + mkdir -p target/out/x86_64 + mv target/i686-linux-android/release/libsphinxrs.so target/out/x86/libsphinxrs.so + mv target/aarch64-linux-android/release/libsphinxrs.so target/out/arm64-v8a/libsphinxrs.so + mv target/arm-linux-androideabi/release/libsphinxrs.so target/out/armeabi/libsphinxrs.so + mv target/armv7-linux-androideabi/release/libsphinxrs.so target/out/armeabi-v7a/libsphinxrs.so + mv target/x86_64-linux-android/release/libsphinxrs.so target/out/x86_64/libsphinxrs.so + cd target && zip -r kotlin-libraries.zip out + + - name: Upload Android artifacts + uses: actions/upload-artifact@v4 + with: + name: android-libraries + path: sphinx-ffi/target/kotlin-libraries.zip + - name: Upload Kotlin bindings uses: actions/upload-artifact@v4 with: name: kotlin-bindings path: sphinx-ffi/src/uniffi/sphinxrs/sphinxrs.kt + build-windows: + runs-on: ubuntu-latest + needs: build-apple + defaults: + run: + working-directory: sphinx-ffi + + steps: + - uses: actions/checkout@v4 + + - name: Configure git for private repos + run: git config --global url."https://x-access-token:${{ secrets.PRIVATE_REPO_PAT }}@github.com/".insteadOf "https://github.com/" + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + + - name: Install cross + run: cargo install cross --git https://github.com/cross-rs/cross + + - name: Build Windows targets + run: | + cross build --locked --features=uniffi/cli --target x86_64-pc-windows-gnu --release + cross build --locked --features=uniffi/cli --target i686-pc-windows-gnu --release + + - name: Download macOS dylibs + uses: actions/download-artifact@v4 + with: + name: macos-dylibs + path: sphinx-ffi/macos-dylibs + - name: Package Windows libraries + run: | + mkdir -p target/windows/x86_64 + mkdir -p target/windows/i686 + mkdir -p target/windows/aarch64 + mv target/x86_64-pc-windows-gnu/release/sphinxrs.dll target/windows/x86_64/sphinxrs.dll + mv target/i686-pc-windows-gnu/release/sphinxrs.dll target/windows/i686/sphinxrs.dll + mv macos-dylibs/sphinx-ffi/target/x86_64-apple-darwin/release/libsphinxrs.dylib target/windows/x86_64/sphinxrs.dylib + mv macos-dylibs/sphinx-ffi/target/aarch64-apple-darwin/release/libsphinxrs.dylib target/windows/aarch64/sphinxrs.dylib + cd target && zip -r windows-libraries.zip windows + + - name: Upload Windows artifacts + uses: actions/upload-artifact@v4 + with: + name: windows-libraries + path: sphinx-ffi/target/windows-libraries.zip From 14f2dc72be916b670c7ba81d3a45cf919d4552cf Mon Sep 17 00:00:00 2001 From: Evanfeenstra Date: Tue, 14 Apr 2026 21:04:11 -0700 Subject: [PATCH 4/8] patch core2 to git source (all 0.3.x yanked from crates.io) --- sphinx-ffi/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/sphinx-ffi/Cargo.toml b/sphinx-ffi/Cargo.toml index eb1c6f4..4d360e0 100644 --- a/sphinx-ffi/Cargo.toml +++ b/sphinx-ffi/Cargo.toml @@ -17,6 +17,7 @@ wasm = [] [patch.crates-io] lightning = { git = "https://github.com/Evanfeenstra/rust-lightning", rev = "3f562dd5c6feb8413cf9a1ac02caef2b8ef59a84" } +core2 = { git = "https://github.com/bbqsrc/core2", rev = "7bf2611bd9a5" } [dependencies] sphinx-crypter = { path = "../crypter" } From 49f0c00af88ca2333cf1c40ac631467f433a4aa9 Mon Sep 17 00:00:00 2001 From: Evanfeenstra Date: Tue, 14 Apr 2026 21:07:06 -0700 Subject: [PATCH 5/8] pin Rust 1.82.0 for vls-protocol-signer compat --- .github/workflows/build-all.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 94985c9..acf0052 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -6,6 +6,7 @@ on: env: CARGO_NET_GIT_FETCH_WITH_CLI: "true" + RUST_VERSION: "1.82.0" jobs: build-apple: @@ -20,9 +21,10 @@ jobs: - name: Configure git for private repos run: git config --global url."https://x-access-token:${{ secrets.PRIVATE_REPO_PAT }}@github.com/".insteadOf "https://github.com/" - - name: Install Rust stable - uses: dtolnay/rust-toolchain@stable + - name: Install Rust + uses: dtolnay/rust-toolchain@master with: + toolchain: ${{ env.RUST_VERSION }} targets: x86_64-apple-ios,aarch64-apple-ios,x86_64-apple-darwin,aarch64-apple-darwin - name: Generate Swift bindings @@ -93,8 +95,10 @@ jobs: - name: Configure git for private repos run: git config --global url."https://x-access-token:${{ secrets.PRIVATE_REPO_PAT }}@github.com/".insteadOf "https://github.com/" - - name: Install Rust stable - uses: dtolnay/rust-toolchain@stable + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_VERSION }} - name: Install cross run: cargo install cross --git https://github.com/cross-rs/cross @@ -151,8 +155,10 @@ jobs: - name: Configure git for private repos run: git config --global url."https://x-access-token:${{ secrets.PRIVATE_REPO_PAT }}@github.com/".insteadOf "https://github.com/" - - name: Install Rust stable - uses: dtolnay/rust-toolchain@stable + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_VERSION }} - name: Install cross run: cargo install cross --git https://github.com/cross-rs/cross From 6e7a76db77a2b9ef8ce555da9023b8e9d0b68fb5 Mon Sep 17 00:00:00 2001 From: Evanfeenstra Date: Tue, 14 Apr 2026 21:36:05 -0700 Subject: [PATCH 6/8] remove VLS dependency: add sphinx-derive crate, update sphinx-ffi to use it --- .github/workflows/build-all.yml | 34 +++---- Cargo.toml | 1 + derive/Cargo.toml | 17 ++++ derive/src/lib.rs | 157 ++++++++++++++++++++++++++++++++ sphinx-ffi/Cargo.toml | 18 +--- sphinx-ffi/src/auto.rs | 13 +++ sphinx-ffi/src/lib.rs | 27 ++---- sphinx-ffi/src/parse.rs | 2 +- sphinx-ffi/src/sphinxrs.udl | 18 +--- 9 files changed, 215 insertions(+), 72 deletions(-) create mode 100644 derive/Cargo.toml create mode 100644 derive/src/lib.rs diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index acf0052..947c895 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -6,7 +6,6 @@ on: env: CARGO_NET_GIT_FETCH_WITH_CLI: "true" - RUST_VERSION: "1.82.0" jobs: build-apple: @@ -22,9 +21,8 @@ jobs: run: git config --global url."https://x-access-token:${{ secrets.PRIVATE_REPO_PAT }}@github.com/".insteadOf "https://github.com/" - name: Install Rust - uses: dtolnay/rust-toolchain@master + uses: dtolnay/rust-toolchain@stable with: - toolchain: ${{ env.RUST_VERSION }} targets: x86_64-apple-ios,aarch64-apple-ios,x86_64-apple-darwin,aarch64-apple-darwin - name: Generate Swift bindings @@ -33,19 +31,19 @@ jobs: sed -i '' 's/module\ sphinxrsFFI/framework\ module\ sphinxrsFFI/' src/sphinxrsFFI.modulemap - name: Build iOS (x86_64) - run: cargo build --locked --features=uniffi/cli --target=x86_64-apple-ios --release + run: cargo build --features=uniffi/cli --target=x86_64-apple-ios --release - name: Build iOS (aarch64) - run: cargo build --locked --features=uniffi/cli --target=aarch64-apple-ios --release + run: cargo build --features=uniffi/cli --target=aarch64-apple-ios --release - name: Create iOS universal lib run: lipo -create target/x86_64-apple-ios/release/libsphinxrs.a target/aarch64-apple-ios/release/libsphinxrs.a -output target/universal-sphinxrs.a - name: Build macOS (x86_64) - run: cargo build --locked --features=uniffi/cli --target=x86_64-apple-darwin --release + run: cargo build --features=uniffi/cli --target=x86_64-apple-darwin --release - name: Build macOS (aarch64) - run: cargo build --locked --features=uniffi/cli --target=aarch64-apple-darwin --release + run: cargo build --features=uniffi/cli --target=aarch64-apple-darwin --release - name: Create macOS universal lib run: lipo -create target/x86_64-apple-darwin/release/libsphinxrs.a target/aarch64-apple-darwin/release/libsphinxrs.a -output target/universal-sphinxrs-mac.a @@ -96,9 +94,7 @@ jobs: run: git config --global url."https://x-access-token:${{ secrets.PRIVATE_REPO_PAT }}@github.com/".insteadOf "https://github.com/" - name: Install Rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ env.RUST_VERSION }} + uses: dtolnay/rust-toolchain@stable - name: Install cross run: cargo install cross --git https://github.com/cross-rs/cross @@ -110,11 +106,11 @@ jobs: - name: Build Android targets run: | - cross build --locked --features=uniffi/cli --target i686-linux-android --release - cross build --locked --features=uniffi/cli --target aarch64-linux-android --release - cross build --locked --features=uniffi/cli --target arm-linux-androideabi --release - cross build --locked --features=uniffi/cli --target armv7-linux-androideabi --release - cross build --locked --features=uniffi/cli --target x86_64-linux-android --release + cross build --features=uniffi/cli --target i686-linux-android --release + cross build --features=uniffi/cli --target aarch64-linux-android --release + cross build --features=uniffi/cli --target arm-linux-androideabi --release + cross build --features=uniffi/cli --target armv7-linux-androideabi --release + cross build --features=uniffi/cli --target x86_64-linux-android --release - name: Package Android libraries run: | @@ -156,17 +152,15 @@ jobs: run: git config --global url."https://x-access-token:${{ secrets.PRIVATE_REPO_PAT }}@github.com/".insteadOf "https://github.com/" - name: Install Rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ env.RUST_VERSION }} + uses: dtolnay/rust-toolchain@stable - name: Install cross run: cargo install cross --git https://github.com/cross-rs/cross - name: Build Windows targets run: | - cross build --locked --features=uniffi/cli --target x86_64-pc-windows-gnu --release - cross build --locked --features=uniffi/cli --target i686-pc-windows-gnu --release + cross build --features=uniffi/cli --target x86_64-pc-windows-gnu --release + cross build --features=uniffi/cli --target i686-pc-windows-gnu --release - name: Download macOS dylibs uses: actions/download-artifact@v4 diff --git a/Cargo.toml b/Cargo.toml index 542873b..6cf381d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,5 @@ exclude = [ "sphinx-frost", "sphinx-wasm", "rmp-utils", + "derive", ] diff --git a/derive/Cargo.toml b/derive/Cargo.toml new file mode 100644 index 0000000..c28ccc0 --- /dev/null +++ b/derive/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "sphinx-derive" +version = "0.1.0" +authors = ["Evan Feenstra "] +edition = "2018" +description = "Sphinx key derivation utils" +repository = "https://github.com/stakwork/sphinx-rs" +license = "MIT" + +[lib] +doctest = false + +[dependencies] +bitcoin = "0.30.2" +bip39 = { version = "1.0.1", default-features = false } +anyhow = { version = "1", default-features = false } + diff --git a/derive/src/lib.rs b/derive/src/lib.rs new file mode 100644 index 0000000..8c6c063 --- /dev/null +++ b/derive/src/lib.rs @@ -0,0 +1,157 @@ +use bitcoin::hashes::sha256::Hash as BitcoinSha256; +use bitcoin::hashes::{Hash, HashEngine, Hmac, HmacEngine}; +use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; +use bitcoin::Network; + +pub const ENTROPY_LEN: usize = 16; + +/// derive a secret from another secret using HKDF-SHA256 +pub fn hkdf_sha256(secret: &[u8], info: &[u8], salt: &[u8]) -> [u8; 32] { + let mut result = [0u8; 32]; + hkdf_extract_expand(salt, secret, info, &mut result); + result +} + +fn hkdf_extract_expand(salt: &[u8], secret: &[u8], info: &[u8], output: &mut [u8]) { + let mut hmac = HmacEngine::::new(salt); + hmac.input(secret); + let prk = Hmac::from_engine(hmac).to_byte_array(); + + let mut t = [0; 32]; + let mut n: u8 = 0; + + for chunk in output.chunks_mut(32) { + let mut hmac = HmacEngine::::new(&prk[..]); + n = n.checked_add(1).expect("HKDF size limit exceeded."); + if n != 1 { + hmac.input(&t); + } + hmac.input(&info); + hmac.input(&[n]); + t = Hmac::from_engine(hmac).to_byte_array(); + chunk.copy_from_slice(&t); + } +} + +/// CLN compatible node key derivation +pub fn node_keys(network: &Network, seed: &[u8]) -> (PublicKey, SecretKey) { + let _ = network; // CLN native derivation doesn't use network for node keys + let secp_ctx = Secp256k1::new(); + let node_private_bytes = hkdf_sha256(seed, "nodeid".as_bytes(), &[]); + let node_secret_key = SecretKey::from_slice(&node_private_bytes).unwrap(); + let node_id = PublicKey::from_secret_key(&secp_ctx, &node_secret_key); + (node_id, node_secret_key) +} + +pub fn mnemonic_from_entropy(entropy: &[u8]) -> anyhow::Result { + let mn = bip39::Mnemonic::from_entropy(entropy) + .map_err(|e| anyhow::anyhow!("Mnemonic::from_entropy failed {:?}", e))?; + let mut ret = Vec::new(); + mn.word_iter().for_each(|w| ret.push(w.to_string())); + Ok(ret.join(" ")) +} + +pub fn entropy_from_mnemonic(mn: &str) -> anyhow::Result> { + let mn = bip39::Mnemonic::parse_normalized(mn) + .map_err(|e| anyhow::anyhow!("Mnemonic::parse_normalized failed {:?}", e))?; + match mn.word_count() { + 12 => (), + len => { + return Err(anyhow::anyhow!( + "Mnemonic is length {}, should be 12 words long.", + len + )) + } + } + let (array, len) = mn.to_entropy_array(); + if len != 16 { + return Err(anyhow::anyhow!("Should never happen, 12 words didn't convert to 16 bytes of entropy. Please try again.")); + } + Ok(array[..len].to_vec()) +} + +pub fn mnemonic_to_seed(mn: &str) -> anyhow::Result> { + let mn = bip39::Mnemonic::parse_normalized(mn) + .map_err(|e| anyhow::anyhow!("Mnemonic::parse_normalized failed {:?}", e))?; + match mn.word_count() { + 12 => (), + len => { + return Err(anyhow::anyhow!( + "Mnemonic is length {}, should be 12 words long.", + len + )) + } + } + // BIP39 seed is 64 bytes. Do like CLN does, chop off the last 32 bytes. + let e = mn.to_seed_normalized("")[..32].to_vec(); + Ok(e) +} + +pub fn entropy_to_seed(entropy: &[u8]) -> anyhow::Result> { + match entropy.len() { + 16 => (), + len => { + return Err(anyhow::anyhow!( + "Entropy is length {}, should be 16 bytes.", + len + )) + } + } + let mn = bip39::Mnemonic::from_entropy(entropy) + .map_err(|e| anyhow::anyhow!("Mnemonic::from_entropy failed {:?}", e))?; + if mn.word_count() != 12 { + return Err(anyhow::anyhow!("Should never happen, 16 bytes of entropy didn't convert to 12 words. Please try again.")); + } + // Do like CLN does, chop off the last 32 bytes + let e = mn.to_seed_normalized("")[..32].to_vec(); + Ok(e) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn entropy() -> [u8; 16] { + [1; 16] + } + + fn seed() -> [u8; 32] { + [1; 32] + } + + #[test] + fn test_mnemonic() { + let entropy = entropy(); + let mn = mnemonic_from_entropy(&entropy).expect("nope"); + assert_eq!( + mn, + "absurd amount doctor acoustic avoid letter advice cage absurd amount doctor adjust" + ); + let en = entropy_from_mnemonic(&mn).expect("fail"); + assert_eq!(en, entropy); + } + + #[test] + fn test_mnemonic_to_seed() { + let seed = mnemonic_to_seed( + "absurd amount doctor acoustic avoid letter advice cage absurd amount doctor adjust", + ) + .expect("fail"); + let vector = [ + 2, 89, 45, 66, 60, 78, 124, 109, 24, 148, 119, 19, 180, 127, 121, 87, 201, 241, 221, + 208, 161, 150, 214, 73, 215, 119, 205, 145, 70, 156, 15, 179, + ]; + assert_eq!(seed, vector); + } + + #[test] + fn test_derive() { + let net = Network::Regtest; + let ks = node_keys(&net, &seed()); + let hexpk = ks.0.to_string(); + assert_eq!( + hexpk, + "026f61d7ee82f937f9697f4f3e44bfaaa25849cc4f526b3a57326130eba6346002" + ); + } +} diff --git a/sphinx-ffi/Cargo.toml b/sphinx-ffi/Cargo.toml index 4d360e0..3731631 100644 --- a/sphinx-ffi/Cargo.toml +++ b/sphinx-ffi/Cargo.toml @@ -9,24 +9,15 @@ name = "sphinxrs" crate-type = ["staticlib", "cdylib"] [features] -default = ["ffi", "std"] +default = ["ffi"] ffi = ["uniffi"] -std = ["sphinx-signer/std", "sphinx-glyph/std"] -no-std = ["sphinx-signer/no-std", "sphinx-glyph/no-std"] wasm = [] -[patch.crates-io] -lightning = { git = "https://github.com/Evanfeenstra/rust-lightning", rev = "3f562dd5c6feb8413cf9a1ac02caef2b8ef59a84" } -core2 = { git = "https://github.com/bbqsrc/core2", rev = "7bf2611bd9a5" } - [dependencies] sphinx-crypter = { path = "../crypter" } -sphinx-signer = { path = "../signer", default-features = false, features = [ - "persist", - "no-native", -] } -sphinx-glyph = { path = "../glyph", default-features = false } -sphinx = { git = "https://github.com/stakwork/sphinx", rev = "ba1b5e31e4e79f64bf8936a9247e352b01766a43", features = [ +sphinx-derive = { path = "../derive" } +sphinx-glyph = { path = "../glyph", default-features = false, features = ["std"] } +sphinx = { git = "https://github.com/stakwork/sphinx", rev = "a1bdba9bfd9b", features = [ "msg", "bindings", "macaroon", @@ -35,7 +26,6 @@ sphinx = { git = "https://github.com/stakwork/sphinx", rev = "ba1b5e31e4e79f64bf uniffi = { version = "0.24.1", optional = true } hex = { version = "0.4.3", default-features = false } thiserror = "1.0.31" -rmp-utils = { version = "0.1.0", path = "../rmp-utils" } time = "0.3.37" [build-dependencies] diff --git a/sphinx-ffi/src/auto.rs b/sphinx-ffi/src/auto.rs index 91e56c8..f55e238 100644 --- a/sphinx-ffi/src/auto.rs +++ b/sphinx-ffi/src/auto.rs @@ -387,6 +387,19 @@ pub fn make_media_token_with_price( .into()) } +pub fn request_invoice( + seed: String, + unique_time: String, + full_state: Vec, + amt_msat: u64, +) -> Result { + Ok( + bindings::request_invoice(&seed, &unique_time, &full_state, amt_msat) + .map_err(|e| SphinxError::SendFailed { r: e.to_string() })? + .into(), + ) +} + pub fn make_invoice( seed: String, unique_time: String, diff --git a/sphinx-ffi/src/lib.rs b/sphinx-ffi/src/lib.rs index 7372c1d..1a0883d 100644 --- a/sphinx-ffi/src/lib.rs +++ b/sphinx-ffi/src/lib.rs @@ -2,12 +2,9 @@ mod auto; mod control; mod onion; mod parse; -mod signer; pub use control::*; -pub use signer::*; - pub use onion::*; pub use auto::*; @@ -15,7 +12,7 @@ pub use auto::*; use sphinx_crypter::chacha::{decrypt as chacha_decrypt, encrypt as chacha_encrypt}; use sphinx_crypter::ecdh::derive_shared_secret_from_slice; use sphinx_crypter::secp256k1::{PublicKey, Secp256k1, SecretKey}; -use sphinx_signer::{derive, lightning_signer::bitcoin::Network}; +use sphinx::Network; use std::str::FromStr; #[cfg(not(feature = "wasm"))] @@ -47,20 +44,8 @@ pub enum SphinxError { BadRequest { r: String }, #[error("Bad Response: {r}")] BadResponse { r: String }, - #[error("Bad Topic: {r}")] - BadTopic { r: String }, #[error("Bad Args: {r}")] BadArgs { r: String }, - #[error("Bad State: {r}")] - BadState { r: String }, - #[error("Bad Velocity: {r}")] - BadVelocity { r: String }, - #[error("Init Failed: {r}")] - InitFailed { r: String }, - #[error("LSS Failed: {r}")] - LssFailed { r: String }, - #[error("VLS Failed: {r}")] - VlsFailed { r: String }, #[error("Bad Child Index: {r}")] BadChildIndex { r: String }, #[error("Bad Msg: {r}")] @@ -151,7 +136,7 @@ pub fn node_keys(net: String, seed: String) -> Result { let network: Network = Network::from_str(&net).map_err(|e| SphinxError::InvalidNetwork { r: format!("{:?}", e), })?; - let ks = derive::node_keys(&network, &seed[..]); + let ks = sphinx_derive::node_keys(&network, &seed[..]); Ok(Keys { secret: hex::encode(ks.1.secret_bytes()), pubkey: ks.0.to_string(), @@ -160,21 +145,21 @@ pub fn node_keys(net: String, seed: String) -> Result { pub fn mnemonic_from_entropy(entropy: String) -> Result { let entropy = parse::parse_entropy_string(entropy)?; - let ret = derive::mnemonic_from_entropy(&entropy[..]).map_err(|e| SphinxError::BadSecret { + let ret = sphinx_derive::mnemonic_from_entropy(&entropy[..]).map_err(|e| SphinxError::BadSecret { r: format!("{:?}", e), })?; Ok(ret) } pub fn entropy_from_mnemonic(mnemonic: String) -> Result { - let m = derive::entropy_from_mnemonic(&mnemonic).map_err(|e| SphinxError::BadSecret { + let m = sphinx_derive::entropy_from_mnemonic(&mnemonic).map_err(|e| SphinxError::BadSecret { r: format!("{:?}", e), })?; Ok(hex::encode(m)) } pub fn mnemonic_to_seed(mnemonic: String) -> Result { - let m = derive::mnemonic_to_seed(&mnemonic).map_err(|e| SphinxError::BadSecret { + let m = sphinx_derive::mnemonic_to_seed(&mnemonic).map_err(|e| SphinxError::BadSecret { r: format!("{:?}", e), })?; Ok(hex::encode(m)) @@ -182,7 +167,7 @@ pub fn mnemonic_to_seed(mnemonic: String) -> Result { pub fn entropy_to_seed(entropy: String) -> Result { let entropy = parse::parse_entropy_string(entropy)?; - let m = derive::entropy_to_seed(&entropy[..]).map_err(|e| SphinxError::BadSecret { + let m = sphinx_derive::entropy_to_seed(&entropy[..]).map_err(|e| SphinxError::BadSecret { r: format!("{:?}", e), })?; Ok(hex::encode(m)) diff --git a/sphinx-ffi/src/parse.rs b/sphinx-ffi/src/parse.rs index aaa1d27..76e05c1 100644 --- a/sphinx-ffi/src/parse.rs +++ b/sphinx-ffi/src/parse.rs @@ -2,7 +2,7 @@ use crate::{Result, SphinxError}; use sphinx_crypter::chacha::{KEY_LEN, NONCE_LEN, PAYLOAD_LEN}; use sphinx_crypter::ecdh::PUBLIC_KEY_LEN; -use sphinx_signer::derive::ENTROPY_LEN; +use sphinx_derive::ENTROPY_LEN; use std::convert::TryInto; pub(crate) fn parse_secret_string(sk: String) -> Result<[u8; KEY_LEN]> { diff --git a/sphinx-ffi/src/sphinxrs.udl b/sphinx-ffi/src/sphinxrs.udl index 537cd3c..eac3407 100644 --- a/sphinx-ffi/src/sphinxrs.udl +++ b/sphinx-ffi/src/sphinxrs.udl @@ -11,13 +11,7 @@ interface SphinxError { InvalidNetwork(string r); BadRequest(string r); BadResponse(string r); - BadTopic(string r); BadArgs(string r); - BadState(string r); - BadVelocity(string r); - InitFailed(string r); - LssFailed(string r); - VlsFailed(string r); BadChildIndex(string r); BadMsg(string r); AddContactFailed(string r); @@ -35,14 +29,6 @@ dictionary Keys { string pubkey; }; -dictionary VlsResponse { - string topic; - bytes bytes; - u16 sequence; - string cmd; - bytes state; -}; - dictionary Msg { string? message; u8? type; @@ -133,8 +119,6 @@ namespace sphinxrs { string parse_response(string res); [Throws=SphinxError] string make_auth_token(u32 ts, string secret); - [Throws=SphinxError] - VlsResponse run(string topic, string args, bytes state, bytes msg1, u16? expected_sequence); string sha_256(bytes msg); [Throws=SphinxError] bytes create_onion(string seed, u64 idx, string time, string network, string hops, bytes payload); @@ -205,6 +189,8 @@ namespace sphinxrs { [Throws=SphinxError] string make_media_token_with_price(string seed, string unique_time, bytes state, string host, string muid, string to, u32 expiry, u64 price); [Throws=SphinxError] + RunReturn request_invoice(string seed, string unique_time, bytes state, u64 amt_msat); + [Throws=SphinxError] RunReturn make_invoice(string seed, string unique_time, bytes state, u64 amt_msat, string description); [Throws=SphinxError] RunReturn pay_invoice(string seed, string unique_time, bytes state, string bolt11, u64? overpay_msat); From 6fd5c2776d313c3b33352054148ae3ed1ed87109 Mon Sep 17 00:00:00 2001 From: Evanfeenstra Date: Wed, 15 Apr 2026 09:44:27 -0700 Subject: [PATCH 7/8] fix dylib artifact paths in windows job --- .github/workflows/build-all.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 947c895..146d346 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -64,13 +64,18 @@ jobs: name: macos-universal path: sphinx-ffi/target/universal-sphinxrs-mac.a + - name: Stage macOS dylibs + run: | + mkdir -p target/dylibs/x86_64 + mkdir -p target/dylibs/aarch64 + cp target/x86_64-apple-darwin/release/libsphinxrs.dylib target/dylibs/x86_64/libsphinxrs.dylib + cp target/aarch64-apple-darwin/release/libsphinxrs.dylib target/dylibs/aarch64/libsphinxrs.dylib + - name: Upload macOS dylibs uses: actions/upload-artifact@v4 with: name: macos-dylibs - path: | - sphinx-ffi/target/x86_64-apple-darwin/release/libsphinxrs.dylib - sphinx-ffi/target/aarch64-apple-darwin/release/libsphinxrs.dylib + path: sphinx-ffi/target/dylibs - name: Upload Swift bindings uses: actions/upload-artifact@v4 @@ -175,8 +180,8 @@ jobs: mkdir -p target/windows/aarch64 mv target/x86_64-pc-windows-gnu/release/sphinxrs.dll target/windows/x86_64/sphinxrs.dll mv target/i686-pc-windows-gnu/release/sphinxrs.dll target/windows/i686/sphinxrs.dll - mv macos-dylibs/sphinx-ffi/target/x86_64-apple-darwin/release/libsphinxrs.dylib target/windows/x86_64/sphinxrs.dylib - mv macos-dylibs/sphinx-ffi/target/aarch64-apple-darwin/release/libsphinxrs.dylib target/windows/aarch64/sphinxrs.dylib + mv macos-dylibs/x86_64/libsphinxrs.dylib target/windows/x86_64/sphinxrs.dylib + mv macos-dylibs/aarch64/libsphinxrs.dylib target/windows/aarch64/sphinxrs.dylib cd target && zip -r windows-libraries.zip windows - name: Upload Windows artifacts From 90c292dfd624c216e1afc5ac616073af3faa3c50 Mon Sep 17 00:00:00 2001 From: Evanfeenstra Date: Wed, 15 Apr 2026 09:56:39 -0700 Subject: [PATCH 8/8] switch workflow trigger to release, add release asset upload --- .github/workflows/build-all.yml | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 146d346..1cdf2f0 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -1,8 +1,8 @@ name: Build All FFI on: - pull_request: - branches: [master, main] + release: + types: [published] env: CARGO_NET_GIT_FETCH_WITH_CLI: "true" @@ -189,3 +189,21 @@ jobs: with: name: windows-libraries path: sphinx-ffi/target/windows-libraries.zip + + upload-release: + runs-on: ubuntu-latest + needs: [build-apple, build-android, build-windows] + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Upload artifacts to release + uses: softprops/action-gh-release@v2 + with: + files: | + artifacts/ios-universal/universal-sphinxrs.a + artifacts/macos-universal/universal-sphinxrs-mac.a + artifacts/android-libraries/kotlin-libraries.zip + artifacts/windows-libraries/windows-libraries.zip