Skip to content

Commit

Permalink
Merge pull request #196 from synonymdev/custom-keys-manager
Browse files Browse the repository at this point in the history
Custom keys manager
  • Loading branch information
Jasonvdb authored Jan 4, 2024
2 parents 529c1f2 + 7961273 commit 06b2692
Show file tree
Hide file tree
Showing 18 changed files with 518 additions and 60 deletions.
1 change: 1 addition & 0 deletions backup-server/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
engine-strict=true
5 changes: 5 additions & 0 deletions backup-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
"version": "1.0.0",
"description": "",
"main": "index.js",
"engines": {
"npm": ">=8.0.0 <9.0.0",
"node": ">=19.0.0"
},
"engineStrict" : true,
"scripts": {
"start": "node index.js",
"create-keypair": "node src/create-keypair.js",
Expand Down
8 changes: 5 additions & 3 deletions example/Dev.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -367,12 +367,14 @@ const Dev = (): ReactElement => {
/>

<Button
title={'Get Address Balance'}
title={'🤑Get Address Balance'}
onPress={async (): Promise<void> => {
setMessage('Getting Address Balance...');
const address = await getAddress();
const { address, publicKey } = await getAddress();
const balance = await getAddressBalance(address);
setMessage(`Balance: ${balance}`);
setMessage(
`address ${address}\npublicKey ${publicKey}\nBalance: ${balance}`,
);
}}
/>

Expand Down
4 changes: 2 additions & 2 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ PODS:
- React-jsinspector (0.72.4)
- React-logger (0.72.4):
- glog
- react-native-ldk (0.0.123):
- react-native-ldk (0.0.124):
- React
- react-native-randombytes (3.6.1):
- React-Core
Expand Down Expand Up @@ -723,7 +723,7 @@ SPEC CHECKSUMS:
React-jsiexecutor: c7f826e40fa9cab5d37cab6130b1af237332b594
React-jsinspector: aaed4cf551c4a1c98092436518c2d267b13a673f
React-logger: da1ebe05ae06eb6db4b162202faeafac4b435e77
react-native-ldk: e242bbc0c8ca5356409e2e065b80364dadb270b6
react-native-ldk: 5ac636bea5e24c687a4a009a339d5ee82e8c8d0f
react-native-randombytes: 421f1c7d48c0af8dbcd471b0324393ebf8fe7846
react-native-tcp-socket: c1b7297619616b4c9caae6889bcb0aba78086989
React-NativeModulesApple: edb5ace14f73f4969df6e7b1f3e41bef0012740f
Expand Down
3 changes: 3 additions & 0 deletions example/ldk/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,9 @@ export const getTransactionData = async (
export const getTransactionPosition = async ({
tx_hash,
height,
}: {
tx_hash: string;
height: number;
}): Promise<TTransactionPosition> => {
const response = await electrum.getTransactionMerkle({
tx_hash,
Expand Down
23 changes: 17 additions & 6 deletions example/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import Keychain from 'react-native-keychain';
import { TAccount, TAvailableNetworks } from '@synonymdev/react-native-ldk';
import {
TAccount,
TAvailableNetworks,
IAddress,
} from '@synonymdev/react-native-ldk';
import { getItem, setItem } from '../ldk';
import { EAccount } from './types';
import { err, ok, Result } from './result';
Expand Down Expand Up @@ -184,18 +188,25 @@ export const getMnemonicPhraseFromSeed = (accountSeed: string): string => {
* Returns a single test address used for channel closures.
* @returns {Promise<string>}
*/
export const getAddress = async (): Promise<string> => {
export const getAddress = async (): Promise<IAddress> => {
const network = getNetwork(selectedNetwork);

const { seed: accountSeed } = await getAccount();
const mnemonic = getMnemonicPhraseFromSeed(accountSeed);
const mnemonicSeed = await bip39.mnemonicToSeed(mnemonic);
const root = bip32.fromSeed(mnemonicSeed, network);
const keyPair = root.derivePath("m/84'/1'/0'/0/0");
return (
bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey, network }).address ??
''
);
const publicKey = keyPair.publicKey.toString('hex');
const address =
bitcoin.payments.p2wpkh({
pubkey: keyPair.publicKey,
network,
}).address ?? '';

return {
address,
publicKey,
};
};

export const ldkNetwork = (network: TAvailableNetworks): ENetworks => {
Expand Down
38 changes: 26 additions & 12 deletions lib/android/src/main/java/com/reactnativeldk/LdkModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ enum class LdkFileNames(val fileName: String) {
scorer("scorer.bin"),
paymentsClaimed("payments_claimed.json"),
paymentsSent("payments_sent.json"),
channelsOpenedWithCustomKeysManager("channels_opened_with_custom_keys_manager.json"),
}

class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
Expand All @@ -152,7 +153,7 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod
private val channelManagerPersister: LdkChannelManagerPersister by lazy { LdkChannelManagerPersister() }

//Config required to setup below objects
private var keysManager: KeysManager? = null
private var keysManager: CustomKeysManager? = null
private var channelManager: ChannelManager? = null
private var userConfig: UserConfig? = null
private var networkGraph: NetworkGraph? = null
Expand Down Expand Up @@ -229,7 +230,7 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod
}

@ReactMethod
fun initKeysManager(seed: String, promise: Promise) {
fun initKeysManager(seed: String, destinationScriptPublicKey: String, witnessProgram: String, witnessProgramVersion: Double, promise: Promise) {
if (keysManager !== null) {
return handleResolve(promise, LdkCallbackResponses.keys_manager_init_success)
}
Expand All @@ -242,7 +243,15 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod
return handleReject(promise, LdkErrors.invalid_seed_hex)
}

keysManager = KeysManager.of(seedBytes, seconds, nanoSeconds.toInt())
keysManager = CustomKeysManager(
seedBytes,
seconds,
nanoSeconds.toInt(),
destinationScriptPublicKey.hexa(),
witnessProgram.hexa(),
witnessProgramVersion.toInt().toByte()
)
//keysManager = KeysManager.of(seedBytes, seconds, nanoSeconds.toInt())

handleResolve(promise, LdkCallbackResponses.keys_manager_init_success)
}
Expand Down Expand Up @@ -438,9 +447,9 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod
channelManagerSerialized,
channelMonitors.toTypedArray(),
userConfig!!,
keysManager!!.as_EntropySource(),
keysManager!!.as_NodeSigner(),
keysManager!!.as_SignerProvider(),
keysManager!!.inner.as_EntropySource(),
keysManager!!.inner.as_NodeSigner(),
SignerProvider.new_impl(keysManager!!.signerProvider),
feeEstimator.feeEstimator,
chainMonitor!!,
filter.filter,
Expand All @@ -460,9 +469,9 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod
userConfig,
blockHash.hexa().reversedArray(),
blockHeight.toInt(),
keysManager!!.as_EntropySource(),
keysManager!!.as_NodeSigner(),
keysManager!!.as_SignerProvider(),
keysManager!!.inner.as_EntropySource(),
keysManager!!.inner.as_NodeSigner(),
SignerProvider.new_impl(keysManager!!.signerProvider),
feeEstimator.feeEstimator,
chainMonitor,
networkGraph!!,
Expand Down Expand Up @@ -653,6 +662,10 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod
return handleReject(promise, LdkErrors.channel_accept_fail, Error((error.err as APIError.APIMisuseError).err))
}

if (error.err is APIError.ChannelUnavailable) {
return handleReject(promise, LdkErrors.channel_accept_fail, Error((error.err as APIError.ChannelUnavailable).err))
}

return handleReject(promise, LdkErrors.channel_accept_fail, Error(error.err.toString()))
}

Expand Down Expand Up @@ -821,7 +834,7 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod

val res = UtilMethods.create_invoice_from_channelmanager(
channelManager,
keysManager!!.as_NodeSigner(),
keysManager!!.inner.as_NodeSigner(),
logger.logger,
ldkCurrency,
if (amountSats == 0.0) Option_u64Z.none() else Option_u64Z.some((amountSats * 1000).toLong()),
Expand Down Expand Up @@ -1096,7 +1109,7 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod
fun nodeSign(message: String, promise: Promise) {
keysManager ?: return handleReject(promise, LdkErrors.init_keys_manager)

val res = UtilMethods.sign(message.toByteArray(Charsets.UTF_8), keysManager!!._node_secret_key)
val res = UtilMethods.sign(message.toByteArray(Charsets.UTF_8), keysManager!!.inner._node_secret_key)

if (!res.is_ok) {
return handleReject(promise, LdkErrors.failed_signing_request)
Expand All @@ -1109,7 +1122,8 @@ class LdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMod
fun nodeStateDump(promise: Promise) {
val logDump: MutableList<String> = mutableListOf()

keysManager?.as_NodeSigner()?.get_node_id(Recipient.LDKRecipient_Node)?.let { pubKeyRes ->
keysManager?.inner?.as_NodeSigner()
?.get_node_id(Recipient.LDKRecipient_Node)?.let { pubKeyRes ->
if (pubKeyRes.is_ok) {
logDump.add("NodeID: ${(pubKeyRes as Result_PublicKeyNoneZ_OK).res.hexEncodedString()}")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.reactnativeldk.classes

import org.ldk.structs.KeysManager
import org.ldk.structs.Option_u32Z
import org.ldk.structs.Result_CVec_u8ZNoneZ
import org.ldk.structs.Result_ShutdownScriptInvalidShutdownScriptZ
import org.ldk.structs.Result_ShutdownScriptNoneZ
import org.ldk.structs.Result_TransactionNoneZ
import org.ldk.structs.Result_WriteableEcdsaChannelSignerDecodeErrorZ
import org.ldk.structs.ShutdownScript
import org.ldk.structs.SignerProvider
import org.ldk.structs.SignerProvider.SignerProviderInterface
import org.ldk.structs.SpendableOutputDescriptor
import org.ldk.structs.TxOut
import org.ldk.structs.WriteableEcdsaChannelSigner
import org.ldk.util.UInt128
import org.ldk.util.WitnessVersion

class CustomKeysManager(
seed: ByteArray,
startingTimeSecs: Long,
startingTimeNanos: Int,
val destinationScriptPublicKey: ByteArray,
val witnessProgram: ByteArray,
val witnessProgramVersion: Byte
) {
val inner: KeysManager = KeysManager.of(seed, startingTimeSecs, startingTimeNanos)
val signerProvider = CustomSignerProvider()

init {
signerProvider.customKeysManager = this
}

fun spend_spendable_outputs(
descriptors: Array<SpendableOutputDescriptor>,
outputs: Array<TxOut>,
changeDestinationScript: ByteArray,
feerateSatPer1000Weight: Int,
locktime: Option_u32Z
): Result_TransactionNoneZ {
val onlyNonStatic: Array<SpendableOutputDescriptor> = descriptors.filter {
it as? SpendableOutputDescriptor.StaticOutput == null
}.toTypedArray()

return inner.spend_spendable_outputs(
onlyNonStatic,
outputs,
changeDestinationScript,
feerateSatPer1000Weight,
locktime
)
}
}

class CustomSignerProvider : SignerProviderInterface {
lateinit var customKeysManager: CustomKeysManager

override fun get_destination_script(): Result_CVec_u8ZNoneZ {
return Result_CVec_u8ZNoneZ.ok(customKeysManager.destinationScriptPublicKey)
}

override fun get_shutdown_scriptpubkey(): Result_ShutdownScriptNoneZ {
val res = ShutdownScript.new_witness_program(
WitnessVersion(customKeysManager.witnessProgramVersion),
customKeysManager.witnessProgram
)

return if (res.is_ok) {
Result_ShutdownScriptNoneZ.ok((res as Result_ShutdownScriptInvalidShutdownScriptZ.Result_ShutdownScriptInvalidShutdownScriptZ_OK).res)
} else {
Result_ShutdownScriptNoneZ.err()
}
}

override fun derive_channel_signer(
channel_value_satoshis: Long,
channel_keys_id: ByteArray?
): WriteableEcdsaChannelSigner {
return customKeysManager.inner.as_SignerProvider().derive_channel_signer(channel_value_satoshis, channel_keys_id)
}

override fun generate_channel_keys_id(p0: Boolean, p1: Long, p2: UInt128?): ByteArray {
return customKeysManager.inner.as_SignerProvider().generate_channel_keys_id(p0, p1, p2)
}

override fun read_chan_signer(p0: ByteArray?): Result_WriteableEcdsaChannelSignerDecodeErrorZ {
return customKeysManager.inner.as_SignerProvider().read_chan_signer(p0!!)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ class LdkChannelManagerPersister: ChannelManagerConstructor.EventHandler {
}

(event as? Event.SpendableOutputs)?.let { spendableOutputs ->
if (channelWasOpenedWithNewCustomKeysManager((spendableOutputs.channel_id as Option_ThirtyTwoBytesZ.Some).some)) {
return
}

val body = Arguments.createMap()
val outputs = Arguments.createArray()
spendableOutputs.outputs.iterator().forEach {
Expand Down Expand Up @@ -169,6 +173,14 @@ class LdkChannelManagerPersister: ChannelManagerConstructor.EventHandler {
persistPaymentClaimed(body)
return LdkEventEmitter.send(EventTypes.channel_manager_payment_claimed, body)
}

(event as? Event.ChannelReady)?.let { channelReady ->
persistChannelOpenedWithNewCustomKeysManager(channelReady.channel_id)
}

(event as? Event.ChannelPending)?.let { channelPending ->
persistChannelOpenedWithNewCustomKeysManager(channelPending.channel_id)
}
}

override fun persist_manager(channel_manager_bytes: ByteArray?) {
Expand Down Expand Up @@ -284,4 +296,58 @@ class LdkChannelManagerPersister: ChannelManagerConstructor.EventHandler {

File(LdkModule.accountStoragePath + "/" + LdkFileNames.paymentsSent.fileName).writeText(JSONArray(payments).toString())
}

// If a channel was opened with the new custom keys manager then spendable outputs from a channel close will already be spendable by the on chain wallet and there is no need to sweep.
// TODO remove all these checks at some point in the future once certain all old channels opened prior to this update have been long closed.
private fun persistChannelOpenedWithNewCustomKeysManager(channelId: ByteArray) {
if (LdkModule.accountStoragePath == "") {
LdkEventEmitter.send(EventTypes.native_log, "Error. Failed to persist channel opened with new custom keys manager to disk (No set storage)")
return
}

val id = channelId.hexEncodedString()
val existingIds = ArrayList<String>()
try {
if (File(LdkModule.accountStoragePath + "/" + LdkFileNames.channelsOpenedWithCustomKeysManager.fileName).exists()) {
val data = File(LdkModule.accountStoragePath + "/" + LdkFileNames.channelsOpenedWithCustomKeysManager.fileName).readBytes()
val existingIdsArray = JSONArray(String(data))
for (i in 0 until existingIdsArray.length()) {
existingIds.add(existingIdsArray.getString(i))
}
}

if (!existingIds.contains(id)) {
existingIds.add(id)

File(LdkModule.accountStoragePath + "/" + LdkFileNames.channelsOpenedWithCustomKeysManager.fileName).writeText(JSONArray(existingIds).toString())
}
} catch (e: Exception) {
LdkEventEmitter.send(EventTypes.native_log, "Error could not read existing ChannelOpenedWithNewCustomKeysManager")
}

println("**** existingIds: $existingIds")
}

private fun channelWasOpenedWithNewCustomKeysManager(channelId: ByteArray): Boolean {
if (LdkModule.accountStoragePath == "") {
LdkEventEmitter.send(EventTypes.native_log, "Error. Failed to check if channel was opened with new custom keys manager (No set storage)")
return false
}

val id = channelId.hexEncodedString()
val existingIds = ArrayList<String>()
try {
if (File(LdkModule.accountStoragePath + "/" + LdkFileNames.channelsOpenedWithCustomKeysManager.fileName).exists()) {
val data = File(LdkModule.accountStoragePath + "/" + LdkFileNames.channelsOpenedWithCustomKeysManager.fileName).readBytes()
val existingIdsArray = JSONArray(String(data))
for (i in 0 until existingIdsArray.length()) {
existingIds.add(existingIdsArray.getString(i))
}
}
} catch (e: Exception) {
LdkEventEmitter.send(EventTypes.native_log, "Error could not read existing ChannelOpenedWithNewCustomKeysManager")
}

return existingIds.contains(id)
}
}
Loading

0 comments on commit 06b2692

Please sign in to comment.