Skip to content

Commit

Permalink
chore: backup documentation
Browse files Browse the repository at this point in the history
chore: removed default backup server details
  • Loading branch information
Jasonvdb committed Oct 2, 2023
1 parent d3136e9 commit 8eff3dc
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 24 deletions.
5 changes: 5 additions & 0 deletions backup-server/.env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
HOST=0.0.0.0
PORT=3003
# npm run create-keypair
SECRET_KEY=
PUBLIC_KEY=
46 changes: 46 additions & 0 deletions backup-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# react-native-ldk backup server

This a server allows apps using the react-native-ldk to persist all node state remotely and can be restored only using the seed.

## Running the server
``` bash
npm i

cp .env.template .env

npm run create-keypair

#Paste new key pair in .env

npm start
```
** Remember to update wallet env with new backup server pub key

## Clients
[Swift](https://github.com/synonymdev/react-native-ldk/blob/master/lib/ios/Classes/BackupClient.swift)
[Kotlin](https://github.com/synonymdev/react-native-ldk/blob/master/lib/android/src/main/java/com/reactnativeldk/classes/BackupClient.kt)
[NodeJS](https://github.com/synonymdev/react-native-ldk/blob/master/backup-server/src/test.js)

## Persiting
All message signing/verifying is done using [LDK's node signing](https://docs.rs/lightning/latest/lightning/util/message_signing/fn.sign.html) on the client and [ln-verifymessagejs](https://github.com/SeverinAlexB/ln-verifymessagejs) on the server.

1. Payload is encrypted using using standard AES/GCM encryption with the encryption key being the node secret.
2. Client creates a hash of encrypted backup and signs it.
3. Client creates unique challenge in this format: `sha256_hash(pubkey+timestamp)`
4. Client uploads encrypted bytes along with node pubkey, signed hash and challenge in request header.
5. Server hashes received payload and validates client's signed hash was actually signed by provided pubkey.
6. Server stores encrypted bytes to disk.
7. Server signs client's challenge and returns signature in response.
8. Client validate that the bytes were stored by the correct server by checking the signature in the response was signed by the server pubkey hard coded in the client.

## Retrieving
Retieving or querying a backup requires a bearer token first done by a fairly standard challenge/response using the same node signing.

1. Client fetches challenge from server by posting timestamp (nonce) and signed (signed timestamp) in body with pubkey in the header.
2. Server validates signature and returns challenge (32 bytes hex string).
3. Client signs challenge.
4. Client posts signed challenge with pubkey in the header.
5. Server validates signature.
6. On success server returns bearer token with 5min expiry. A long expiry isn't needed as token is only used briefly to perform a restore.
7. Client uses bearer token to pull list of backed up files.
8. Client iterates through list and downloads each file and persists to disk.
4 changes: 4 additions & 0 deletions example/Dev.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,10 @@ const Dev = (): ReactElement => {
<Button
title={'Restore backup from server'}
onPress={async (): Promise<void> => {
if (!backupServerDetails) {
return setMessage('Set backupServerDetails in constants.ts');
}

setMessage('Stopping LDK...');
await ldk.stop();

Expand Down
12 changes: 7 additions & 5 deletions example/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ export const customPeers = {
bitcoinSignet: [],
};

export const backupServerDetails: TBackupServerDetails = {
host: 'https://jaybird-logical-sadly.ngrok-free.app',
serverPubKey:
'0343a6c1b7700840ac7b76372617a6e9a05cf4c9716efdc847def65360b238f243',
};
//Once local server is setup, add server details below to enable backups
export const backupServerDetails: TBackupServerDetails | undefined = undefined;
// {
// host: 'https://jaybird-logical-sadly.ngrok-free.app',
// serverPubKey:
// '0343a6c1b7700840ac7b76372617a6e9a05cf4c9716efdc847def65360b238f243',
// };
Original file line number Diff line number Diff line change
Expand Up @@ -152,20 +152,25 @@ class BackupClient {
return hash.joinToString("") { String.format("%02x", it) }
}

@Throws(BackupError::class)
private fun decrypt(blob: ByteArray): ByteArray {
if (encryptionKey == null) {
throw BackupError.requiresSetup
try {
if (encryptionKey == null) {
throw BackupError.requiresSetup
}

val nonce = blob.take(12).toByteArray()
val encrypted = blob.copyOfRange(12, blob.size)

val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val gcmSpec = GCMParameterSpec(128, nonce)

cipher.init(Cipher.DECRYPT_MODE, encryptionKey, gcmSpec)
val decryptedBytes = cipher.doFinal(encrypted)
return decryptedBytes
} catch (e: Exception) {
throw DecryptFailed(e.message ?: "")
}

val nonce = blob.take(12).toByteArray()
val encrypted = blob.copyOfRange(12, blob.size)

val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val gcmSpec = GCMParameterSpec(128, nonce)

cipher.init(Cipher.DECRYPT_MODE, encryptionKey, gcmSpec)
val decryptedBytes = cipher.doFinal(encrypted)
return decryptedBytes
}

@Throws(BackupError::class)
Expand Down
7 changes: 0 additions & 7 deletions lib/src/lightning-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,13 +355,6 @@ class LightningManager {
if (backupSetupRes.isErr()) {
return err(backupSetupRes.error);
}

//TODO remove after dev
const backupCheckRes = await ldk.backupSelfCheck();
if (backupCheckRes.isErr()) {
console.error('Backup check failed', backupCheckRes.error);
return err(backupCheckRes.error);
}
}

// Step 1: Initialize the FeeEstimator
Expand Down

0 comments on commit 8eff3dc

Please sign in to comment.