diff --git a/Cargo.toml b/Cargo.toml index eaf3802a7..9fc4e771d 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ldk-node" -version = "0.7.0-rc.2" +version = "0.7.0-rc.3" authors = ["Elias Rohrer "] homepage = "https://lightningdevkit.org/" license = "MIT OR Apache-2.0" diff --git a/Package.swift b/Package.swift index 9e5fba9a5..1c14d0f37 100644 --- a/Package.swift +++ b/Package.swift @@ -4,7 +4,7 @@ import PackageDescription let tag = "v0.7.0-rc.2" -let checksum = "02b7edc2f3fcf2b8a1c914bd1f03e8a3bf2aca2281a2314d0d03cb7547ecd11a" +let checksum = "328ce1018daa21c07b7e9e2a6d63f3a8a3821619408f3edbcc78a448c102233b" let url = "https://github.com/synonymdev/ldk-node/releases/download/\(tag)/LDKNodeFFI.xcframework.zip" let package = Package( diff --git a/bindings/kotlin/ldk-node-android/gradle.properties b/bindings/kotlin/ldk-node-android/gradle.properties index 680b80b26..51087c7ab 100644 --- a/bindings/kotlin/ldk-node-android/gradle.properties +++ b/bindings/kotlin/ldk-node-android/gradle.properties @@ -2,4 +2,4 @@ org.gradle.jvmargs=-Xmx1536m android.useAndroidX=true android.enableJetifier=true kotlin.code.style=official -libraryVersion=0.7.0-rc.2 +libraryVersion=0.7.0-rc.3 diff --git a/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/arm64-v8a/libldk_node.so b/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/arm64-v8a/libldk_node.so index c2b4fbcd6..11dc42833 100755 Binary files a/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/arm64-v8a/libldk_node.so and b/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/arm64-v8a/libldk_node.so differ diff --git a/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/armeabi-v7a/libldk_node.so b/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/armeabi-v7a/libldk_node.so index ae9a32592..19bd69222 100755 Binary files a/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/armeabi-v7a/libldk_node.so and b/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/armeabi-v7a/libldk_node.so differ diff --git a/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/x86_64/libldk_node.so b/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/x86_64/libldk_node.so index 20665287d..1ce1536b7 100755 Binary files a/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/x86_64/libldk_node.so and b/bindings/kotlin/ldk-node-android/lib/src/main/jniLibs/x86_64/libldk_node.so differ diff --git a/bindings/kotlin/ldk-node-android/lib/src/main/kotlin/org/lightningdevkit/ldknode/ldk_node.android.kt b/bindings/kotlin/ldk-node-android/lib/src/main/kotlin/org/lightningdevkit/ldknode/ldk_node.android.kt index 609fbad1d..7ed347b0b 100644 --- a/bindings/kotlin/ldk-node-android/lib/src/main/kotlin/org/lightningdevkit/ldknode/ldk_node.android.kt +++ b/bindings/kotlin/ldk-node-android/lib/src/main/kotlin/org/lightningdevkit/ldknode/ldk_node.android.kt @@ -1487,6 +1487,8 @@ internal typealias UniffiVTableCallbackInterfaceVssHeaderProviderUniffiByValue = + + @@ -2658,6 +2660,11 @@ internal interface UniffiLib : Library { fun uniffi_ldk_node_fn_func_default_config( uniffiCallStatus: UniffiRustCallStatus, ): RustBufferByValue + fun uniffi_ldk_node_fn_func_derive_node_secret_from_mnemonic( + `mnemonic`: RustBufferByValue, + `passphrase`: RustBufferByValue, + uniffiCallStatus: UniffiRustCallStatus, + ): RustBufferByValue fun uniffi_ldk_node_fn_func_generate_entropy_mnemonic( `wordCount`: RustBufferByValue, uniffiCallStatus: UniffiRustCallStatus, @@ -2876,6 +2883,8 @@ internal interface UniffiLib : Library { ): Unit fun uniffi_ldk_node_checksum_func_default_config( ): Short + fun uniffi_ldk_node_checksum_func_derive_node_secret_from_mnemonic( + ): Short fun uniffi_ldk_node_checksum_func_generate_entropy_mnemonic( ): Short fun uniffi_ldk_node_checksum_method_bolt11invoice_amount_milli_satoshis( @@ -3268,6 +3277,9 @@ private fun uniffiCheckApiChecksums(lib: UniffiLib) { if (lib.uniffi_ldk_node_checksum_func_default_config() != 55381.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } + if (lib.uniffi_ldk_node_checksum_func_derive_node_secret_from_mnemonic() != 15067.toShort()) { + throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } if (lib.uniffi_ldk_node_checksum_func_generate_entropy_mnemonic() != 48014.toShort()) { throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project") } @@ -11054,6 +11066,7 @@ object FfiConverterTypeNodeError : FfiConverterRustBuffer { 59 -> NodeException.TransactionAlreadyConfirmed(FfiConverterString.read(buf)) 60 -> NodeException.NoSpendableOutputs(FfiConverterString.read(buf)) 61 -> NodeException.CoinSelectionFailed(FfiConverterString.read(buf)) + 62 -> NodeException.InvalidMnemonic(FfiConverterString.read(buf)) else -> throw RuntimeException("invalid error enum value, something is very wrong!!") } } @@ -11308,6 +11321,10 @@ object FfiConverterTypeNodeError : FfiConverterRustBuffer { buf.putInt(61) Unit } + is NodeException.InvalidMnemonic -> { + buf.putInt(62) + Unit + } }.let { /* this makes the `when` an expression, which ensures it is exhaustive */ } } } @@ -13772,6 +13789,17 @@ fun `defaultConfig`(): Config { }) } +@Throws(NodeException::class) +fun `deriveNodeSecretFromMnemonic`(`mnemonic`: kotlin.String, `passphrase`: kotlin.String?): List { + return FfiConverterSequenceUByte.lift(uniffiRustCallWithError(NodeExceptionErrorHandler) { uniffiRustCallStatus -> + UniffiLib.INSTANCE.uniffi_ldk_node_fn_func_derive_node_secret_from_mnemonic( + FfiConverterString.lower(`mnemonic`), + FfiConverterOptionalString.lower(`passphrase`), + uniffiRustCallStatus, + ) + }) +} + fun `generateEntropyMnemonic`(`wordCount`: WordCount?): Mnemonic { return FfiConverterTypeMnemonic.lift(uniffiRustCall { uniffiRustCallStatus -> UniffiLib.INSTANCE.uniffi_ldk_node_fn_func_generate_entropy_mnemonic( diff --git a/bindings/kotlin/ldk-node-android/lib/src/main/kotlin/org/lightningdevkit/ldknode/ldk_node.common.kt b/bindings/kotlin/ldk-node-android/lib/src/main/kotlin/org/lightningdevkit/ldknode/ldk_node.common.kt index da579dcb3..f3a2273c0 100644 --- a/bindings/kotlin/ldk-node-android/lib/src/main/kotlin/org/lightningdevkit/ldknode/ldk_node.common.kt +++ b/bindings/kotlin/ldk-node-android/lib/src/main/kotlin/org/lightningdevkit/ldknode/ldk_node.common.kt @@ -1781,6 +1781,8 @@ sealed class NodeException(message: String): kotlin.Exception(message) { class CoinSelectionFailed(message: String) : NodeException(message) + class InvalidMnemonic(message: String) : NodeException(message) + } diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 14cc2cb1d..04a5d56bb 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -1,6 +1,8 @@ namespace ldk_node { Mnemonic generate_entropy_mnemonic(WordCount? word_count); Config default_config(); + [Throws=NodeError] + sequence derive_node_secret_from_mnemonic(string mnemonic, string? passphrase); }; dictionary Config { @@ -380,6 +382,7 @@ enum NodeError { "TransactionAlreadyConfirmed", "NoSpendableOutputs", "CoinSelectionFailed", + "InvalidMnemonic", }; dictionary NodeStatus { diff --git a/bindings/python/src/ldk_node/ldk_node.py b/bindings/python/src/ldk_node/ldk_node.py index fbb2f07d6..f357f2ed0 100644 --- a/bindings/python/src/ldk_node/ldk_node.py +++ b/bindings/python/src/ldk_node/ldk_node.py @@ -463,6 +463,8 @@ def _uniffi_check_contract_api_version(lib): def _uniffi_check_api_checksums(lib): if lib.uniffi_ldk_node_checksum_func_default_config() != 55381: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + if lib.uniffi_ldk_node_checksum_func_derive_node_secret_from_mnemonic() != 15067: + raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_ldk_node_checksum_func_generate_entropy_mnemonic() != 48014: raise InternalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") if lib.uniffi_ldk_node_checksum_method_bolt11invoice_amount_milli_satoshis() != 50823: @@ -2320,6 +2322,12 @@ class _UniffiVTableCallbackInterfaceVssHeaderProvider(ctypes.Structure): ctypes.POINTER(_UniffiRustCallStatus), ) _UniffiLib.uniffi_ldk_node_fn_func_default_config.restype = _UniffiRustBuffer +_UniffiLib.uniffi_ldk_node_fn_func_derive_node_secret_from_mnemonic.argtypes = ( + _UniffiRustBuffer, + _UniffiRustBuffer, + ctypes.POINTER(_UniffiRustCallStatus), +) +_UniffiLib.uniffi_ldk_node_fn_func_derive_node_secret_from_mnemonic.restype = _UniffiRustBuffer _UniffiLib.uniffi_ldk_node_fn_func_generate_entropy_mnemonic.argtypes = ( _UniffiRustBuffer, ctypes.POINTER(_UniffiRustCallStatus), @@ -2596,6 +2604,9 @@ class _UniffiVTableCallbackInterfaceVssHeaderProvider(ctypes.Structure): _UniffiLib.uniffi_ldk_node_checksum_func_default_config.argtypes = ( ) _UniffiLib.uniffi_ldk_node_checksum_func_default_config.restype = ctypes.c_uint16 +_UniffiLib.uniffi_ldk_node_checksum_func_derive_node_secret_from_mnemonic.argtypes = ( +) +_UniffiLib.uniffi_ldk_node_checksum_func_derive_node_secret_from_mnemonic.restype = ctypes.c_uint16 _UniffiLib.uniffi_ldk_node_checksum_func_generate_entropy_mnemonic.argtypes = ( ) _UniffiLib.uniffi_ldk_node_checksum_func_generate_entropy_mnemonic.restype = ctypes.c_uint16 @@ -11996,6 +12007,11 @@ class CoinSelectionFailed(_UniffiTempNodeError): def __repr__(self): return "NodeError.CoinSelectionFailed({})".format(repr(str(self))) _UniffiTempNodeError.CoinSelectionFailed = CoinSelectionFailed # type: ignore + class InvalidMnemonic(_UniffiTempNodeError): + + def __repr__(self): + return "NodeError.InvalidMnemonic({})".format(repr(str(self))) + _UniffiTempNodeError.InvalidMnemonic = InvalidMnemonic # type: ignore NodeError = _UniffiTempNodeError # type: ignore del _UniffiTempNodeError @@ -12249,6 +12265,10 @@ def read(buf): return NodeError.CoinSelectionFailed( _UniffiConverterString.read(buf), ) + if variant == 62: + return NodeError.InvalidMnemonic( + _UniffiConverterString.read(buf), + ) raise InternalError("Raw enum value doesn't match any cases") @staticmethod @@ -12375,6 +12395,8 @@ def check_lower(value): return if isinstance(value, NodeError.CoinSelectionFailed): return + if isinstance(value, NodeError.InvalidMnemonic): + return @staticmethod def write(value, buf): @@ -12500,6 +12522,8 @@ def write(value, buf): buf.write_i32(60) if isinstance(value, NodeError.CoinSelectionFailed): buf.write_i32(61) + if isinstance(value, NodeError.InvalidMnemonic): + buf.write_i32(62) @@ -15869,6 +15893,16 @@ def default_config() -> "Config": return _UniffiConverterTypeConfig.lift(_uniffi_rust_call(_UniffiLib.uniffi_ldk_node_fn_func_default_config,)) +def derive_node_secret_from_mnemonic(mnemonic: "str",passphrase: "typing.Optional[str]") -> "typing.List[int]": + _UniffiConverterString.check_lower(mnemonic) + + _UniffiConverterOptionalString.check_lower(passphrase) + + return _UniffiConverterSequenceUInt8.lift(_uniffi_rust_call_with_error(_UniffiConverterTypeNodeError,_UniffiLib.uniffi_ldk_node_fn_func_derive_node_secret_from_mnemonic, + _UniffiConverterString.lower(mnemonic), + _UniffiConverterOptionalString.lower(passphrase))) + + def generate_entropy_mnemonic(word_count: "typing.Optional[WordCount]") -> "Mnemonic": _UniffiConverterOptionalTypeWordCount.check_lower(word_count) @@ -15939,6 +15973,7 @@ def generate_entropy_mnemonic(word_count: "typing.Optional[WordCount]") -> "Mnem "TxInput", "TxOutput", "default_config", + "derive_node_secret_from_mnemonic", "generate_entropy_mnemonic", "Bolt11Invoice", "Bolt11Payment", diff --git a/bindings/swift/Sources/LDKNode/LDKNode.swift b/bindings/swift/Sources/LDKNode/LDKNode.swift index 3ac611f7c..3b0f56064 100644 --- a/bindings/swift/Sources/LDKNode/LDKNode.swift +++ b/bindings/swift/Sources/LDKNode/LDKNode.swift @@ -8190,6 +8190,8 @@ public enum NodeError { case NoSpendableOutputs(message: String) case CoinSelectionFailed(message: String) + + case InvalidMnemonic(message: String) } #if swift(>=5.8) @@ -8445,6 +8447,10 @@ public struct FfiConverterTypeNodeError: FfiConverterRustBuffer { message: FfiConverterString.read(from: &buf) ) + case 62: return try .InvalidMnemonic( + message: FfiConverterString.read(from: &buf) + ) + default: throw UniffiInternalError.unexpectedEnumCase } } @@ -8573,6 +8579,8 @@ public struct FfiConverterTypeNodeError: FfiConverterRustBuffer { writeInt(&buf, Int32(60)) case .CoinSelectionFailed(_ /* message is ignored*/ ): writeInt(&buf, Int32(61)) + case .InvalidMnemonic(_ /* message is ignored*/ ): + writeInt(&buf, Int32(62)) } } } @@ -11679,6 +11687,15 @@ public func defaultConfig() -> Config { }) } +public func deriveNodeSecretFromMnemonic(mnemonic: String, passphrase: String?) throws -> [UInt8] { + return try FfiConverterSequenceUInt8.lift(rustCallWithError(FfiConverterTypeNodeError.lift) { + uniffi_ldk_node_fn_func_derive_node_secret_from_mnemonic( + FfiConverterString.lower(mnemonic), + FfiConverterOptionString.lower(passphrase), $0 + ) + }) +} + public func generateEntropyMnemonic(wordCount: WordCount?) -> Mnemonic { return try! FfiConverterTypeMnemonic.lift(try! rustCall { uniffi_ldk_node_fn_func_generate_entropy_mnemonic( @@ -11706,6 +11723,9 @@ private var initializationResult: InitializationResult = { if uniffi_ldk_node_checksum_func_default_config() != 55381 { return InitializationResult.apiChecksumMismatch } + if uniffi_ldk_node_checksum_func_derive_node_secret_from_mnemonic() != 15067 { + return InitializationResult.apiChecksumMismatch + } if uniffi_ldk_node_checksum_func_generate_entropy_mnemonic() != 48014 { return InitializationResult.apiChecksumMismatch } diff --git a/src/error.rs b/src/error.rs index 85f67651a..fbd44d9a2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -139,6 +139,8 @@ pub enum Error { NoSpendableOutputs, /// Coin selection failed to find suitable UTXOs. CoinSelectionFailed, + /// The given mnemonic is invalid. + InvalidMnemonic, } impl fmt::Display for Error { @@ -227,6 +229,7 @@ impl fmt::Display for Error { }, Self::NoSpendableOutputs => write!(f, "The transaction has no spendable outputs."), Self::CoinSelectionFailed => write!(f, "Coin selection failed to find suitable UTXOs."), + Self::InvalidMnemonic => write!(f, "The given mnemonic is invalid."), } } } diff --git a/src/io/utils.rs b/src/io/utils.rs index 1b4b02a82..09d8d385b 100644 --- a/src/io/utils.rs +++ b/src/io/utils.rs @@ -68,6 +68,29 @@ pub fn generate_entropy_mnemonic(word_count: Option) -> Mnemonic { Mnemonic::generate(word_count).expect("Failed to generate mnemonic") } +/// Derives the node secret key from a BIP39 mnemonic. +/// +/// This is the same key that would be used by a [`Node`] built with this mnemonic via +/// [`Builder::set_entropy_bip39_mnemonic`]. +/// +/// [`Node`]: crate::Node +/// [`Builder::set_entropy_bip39_mnemonic`]: crate::Builder::set_entropy_bip39_mnemonic +pub fn derive_node_secret_from_mnemonic( + mnemonic: String, passphrase: Option, +) -> Result, crate::Error> { + use bitcoin::bip32::Xpriv; + + let parsed_mnemonic = + Mnemonic::parse(&mnemonic).map_err(|_| crate::Error::InvalidMnemonic)?; + + let seed = parsed_mnemonic.to_seed(passphrase.as_deref().unwrap_or("")); + + let xpriv = Xpriv::new_master(Network::Bitcoin, &seed) + .map_err(|_| crate::Error::InvalidMnemonic)?; + + Ok(xpriv.private_key.secret_bytes().to_vec()) +} + pub(crate) fn read_or_generate_seed_file( keys_seed_path: &str, logger: L, ) -> std::io::Result<[u8; WALLET_KEYS_SEED_LEN]> diff --git a/src/lib.rs b/src/lib.rs index ecceae1c5..04e9df283 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,7 +131,7 @@ use fee_estimator::{ConfirmationTarget, FeeEstimator, OnchainFeeEstimator}; use ffi::*; use gossip::GossipSource; use graph::NetworkGraph; -pub use io::utils::generate_entropy_mnemonic; +pub use io::utils::{derive_node_secret_from_mnemonic, generate_entropy_mnemonic}; use io::utils::write_node_metrics; use lightning::chain::BestBlock; use lightning::events::bump_transaction::{Input, Wallet as LdkWallet};