Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom keys manager #196

Merged
merged 7 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -94,7 +94,7 @@
// @ts-ignore
paymentSubscription = ldk.onEvent(
EEventTypes.channel_manager_payment_claimed,
(res: TChannelManagerClaim) => alert(`Received ${res.amount_sat} sats`),

Check warning on line 97 in example/Dev.tsx

View workflow job for this annotation

GitHub Actions / Run lint check

Unexpected alert
);
}

Expand Down Expand Up @@ -125,7 +125,7 @@
onChannelSubscription = ldk.onEvent(
EEventTypes.new_channel,
(res: TChannelUpdate) =>
alert(

Check warning on line 128 in example/Dev.tsx

View workflow job for this annotation

GitHub Actions / Run lint check

Unexpected alert
`Channel received from ${res.counterparty_node_id} Channel ${res.channel_id}`,
),
);
Expand Down Expand Up @@ -367,12 +367,14 @@
/>

<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))
}

ovitrif marked this conversation as resolved.
Show resolved Hide resolved
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)
ovitrif marked this conversation as resolved.
Show resolved Hide resolved
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
Loading