Skip to content
This repository has been archived by the owner on Nov 22, 2022. It is now read-only.

Commit

Permalink
Added support for restoring backups generated by iOS (#35)
Browse files Browse the repository at this point in the history
* added support for private keys systemwide

* version bump

* build fix

* added tests
  • Loading branch information
samdowd authored Jan 10, 2022
1 parent c586fba commit ca2310d
Show file tree
Hide file tree
Showing 16 changed files with 167 additions and 47 deletions.
1 change: 1 addition & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ buildscript {
ext {
versions = [:]
versions.kin = "2.0.0"
versions.kin = "2.1.0"
}
}
dependencies {
Expand All @@ -38,7 +38,7 @@ dependencies {

## Demo App

> :warning: **As Of October 7th, 2021:** the Demo app is being updated to use v2.0.0 of the Kin Android SDK and is not fully functioning. The [demo](demo) directory includes a demo application, showcasing a Kin wallet.
The [demo](demo) directory includes a demo application, showcasing a Kin wallet.

## Design Showcase App

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,13 @@ public void nextButtonClicked(String confirmPassword, String password) {

private void exportAccount(String password) {
try {
final String accountKey = backupRestore.exportWallet(KeyPair.fromSecretSeed(privateKey.stellarBase32Encode()), password);
backupNavigator.navigateToSaveAndSharePage(accountKey);
if (privateKey.getValue().length == 32) {
final String accountKey = backupRestore.exportWallet(KeyPair.fromSecretSeed(privateKey.stellarBase32Encode()), password);
backupNavigator.navigateToSaveAndSharePage(accountKey);
} else {
final String accountKey = backupRestore.exportWallet(KeyPair.fromPrivateKey(privateKey.getValue()), password);
backupNavigator.navigateToSaveAndSharePage(accountKey);
}
} catch (org.kin.sdk.base.tools.CryptoException e) {
if (view != null) {
view.showBackupFailed();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import org.kin.sdk.base.models.Key;
import org.kin.sdk.base.tools.BackupRestore;
import org.kin.sdk.base.tools.BackupRestoreImpl;
import org.kin.sdk.base.tools.CorruptedDataException;
import org.kin.sdk.base.tools.CryptoException;
import org.kin.stellarfork.KeyPair;
Expand All @@ -17,7 +16,6 @@
import kin.backupandrestore.events.CallbackManager;
import kin.backupandrestore.exception.BackupAndRestoreException;
import kin.backupandrestore.restore.view.RestoreEnterPasswordView;
import kin.sdk.exception.CreateAccountException;

import static kin.backupandrestore.events.RestoreEventCode.RESTORE_PASSWORD_DONE_TAPPED;
import static kin.backupandrestore.events.RestoreEventCode.RESTORE_PASSWORD_ENTRY_PAGE_BACK_TAPPED;
Expand Down Expand Up @@ -59,7 +57,6 @@ public void restoreClicked(String password) {
KeyPair kinAccount = importAccount(keystoreData, password);
getParentPresenter().navigateToRestoreCompletedPage(kinAccount);
} catch (BackupAndRestoreException e) {
// Logger.e("RestoreEnterPasswordPresenterImpl - restore failed.", e);
RestoreEnterPasswordView view = getView();
if (view != null) {
if (e.getCode() == CODE_RESTORE_INVALID_KEYSTORE_FORMAT) {
Expand All @@ -76,20 +73,23 @@ private KeyPair importAccount(@NonNull final String keystore, @NonNull final Str
Validator.checkNotNull(keystore, "keystore");
Validator.checkNotNull(keystore, "password");
KeyPair importedAccount;
byte[] pk;
try {
importedAccount = backupRestore.importWallet(keystore, password);
if (importedAccount.getRawSecretSeed() != null) {
pk = importedAccount.getRawSecretSeed();
} else {
pk = importedAccount.getPrivateKey();
}
if(BackupAndRestoreManager.instance() != null) {
BackupAndRestoreManager.instance().
getEnvironment()
.importPrivateKey(new Key.PrivateKey(importedAccount.getRawSecretSeed()))
.resolve();
BackupAndRestoreManager.instance()
.getEnvironment()
.importPrivateKey(new Key.PrivateKey(pk))
.resolve();
}
} catch (CryptoException e) {
throw new BackupAndRestoreException(CODE_RESTORE_FAILED, "Could not import the account");
}
// catch (CreateAccountException e) {
// throw new BackupAndRestoreException(CODE_RESTORE_FAILED, "Could not create the account");
// }
catch (CorruptedDataException e) {
throw new BackupAndRestoreException(CODE_RESTORE_INVALID_KEYSTORE_FORMAT,
"The keystore is invalid - wrong format");
Expand Down
2 changes: 1 addition & 1 deletion base/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ dependencies {
repositories {
// ...
jcenter()
maven { url "https://jitpack.io/" } // Jitpack is used for OkSSE fork only
maven { url "https://jitpack.io/" }
}
```

Expand Down
1 change: 0 additions & 1 deletion base/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ dependencies {
implementation deps.kotlin_stdlib
implementation deps.gson
implementation deps.okhttp
implementation deps.oksse
implementation deps.i2p_crypto_eddsa
implementation deps.slf4j
implementation deps.grpc_stub
Expand Down
20 changes: 12 additions & 8 deletions base/src/main/java/org/kin/sdk/base/models/Key.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.kin.sdk.base.models

import org.kin.sdk.base.tools.Base58
import org.kin.stellarfork.KeyPair
import org.kin.stellarfork.StrKey

sealed class Key {
abstract val value: ByteArray
Expand Down Expand Up @@ -50,14 +51,13 @@ sealed class Key {
}

data class PrivateKey constructor(override val value: ByteArray) : Key() {

constructor(privateKeyString: String) : this({
constructor(privateKeyString: String) : this(
try {
KeyPair.fromSecretSeed(privateKeyString).rawSecretSeed!!
} catch (t: Throwable) {
Base58.decode(privateKeyString)
}
}())
)

override fun equals(other: Any?): Boolean {
if (this === other) return true
Expand All @@ -78,20 +78,24 @@ sealed class Key {
return "Key.PrivateKey(value=XXXXXXXX<Private>XXXXXXXX)"
}

fun stellarBase32Encode(): String = String(KeyPair.fromSecretSeed(value).secretSeed)
fun stellarBase32Encode(): String {
if (value.size == 32) { return String(KeyPair.fromSecretSeed(value).secretSeed) }
return String(StrKey.encodeStellarSecretSeed(KeyPair.fromPrivateKey(value).privateKey!!))
}

fun base58Encode(): String = Base58.encode(value)

fun sign(data: ByteArray): ByteArray = KeyPair.fromSecretSeed(value).sign(data)!!
fun sign(data: ByteArray): ByteArray {
if (value.size == 32) { return KeyPair.fromSecretSeed(value).sign(data)!! }
return KeyPair.fromPrivateKey(value).sign(data)!!
}

companion object {
@JvmStatic
fun decode(value: String): PrivateKey = KeyPair.fromSecretSeed(value).asPrivateKey()

@JvmStatic
fun random(): PrivateKey {
return PrivateKey(KeyPair.random().rawSecretSeed!!)
}
fun random(): PrivateKey = PrivateKey(KeyPair.random().rawSecretSeed!!)
}
}
}
2 changes: 1 addition & 1 deletion base/src/main/java/org/kin/sdk/base/models/SDKConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ package org.kin.sdk.base.models

object SDKConfig {
const val platform = "JVM"
const val versionString = "2.0.0"
const val versionString = "2.1.0"
val systemUserAgent by lazy { System.getProperty("http.agent") ?: "JVM/unspecified" }
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import java.text.SimpleDateFormat
import java.util.Date

fun KeyPair.asPrivateKey(): Key.PrivateKey =
rawSecretSeed?.let { Key.PrivateKey(it) } ?: throw Exception("No Private Key Found")
rawSecretSeed?.let { Key.PrivateKey(it) } ?: privateKey?.let { Key.PrivateKey(it) } ?: throw Exception("No Private Key Found")

fun KeyPair.asPublicKey(): Key.PublicKey =
Key.PublicKey(publicKey)
Expand All @@ -21,17 +21,21 @@ fun Key.asKinAccountId(): KinAccount.Id =
KinAccount.Id(asPublicKey().value)

fun Key.asPublicKey(): Key.PublicKey =
if (this is Key.PrivateKey) KeyPair.fromSecretSeed(value).asPublicKey()
if (this is Key.PrivateKey && value.size == 32) KeyPair.fromSecretSeed(value).asPublicKey()
else if (this is Key.PrivateKey) KeyPair.fromPrivateKey(value).asPublicKey()
else this as Key.PublicKey

fun KinAccount.Id.toKeyPair(): KeyPair = KeyPair.fromPublicKey(value)

fun KinAccount.toSigningKeyPair(): KeyPair =
(key as? Key.PrivateKey)?.let { KeyPair.fromSecretSeed(it.value) }
?: throw Exception("Cannot get a signing KeyPair")
(key as? Key.PrivateKey)?.let {
if (it.value.size == 32) KeyPair.fromSecretSeed(it.value)
else KeyPair.fromPrivateKey(it.value)
} ?: throw Exception("Cannot get a signing KeyPair")

fun Key.PrivateKey.toSigningKeyPair(): KeyPair =
KeyPair.fromSecretSeed(value) ?: throw Exception("Cannot get a signing KeyPair")
if (value.size == 32) KeyPair.fromSecretSeed(value)
else KeyPair.fromPrivateKey(value)

fun String.toUTF8Bytes(): ByteArray = toByteArray(Charsets.UTF_8)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,11 @@ internal fun KinTransactionApiV4.SubmitTransactionRequest.toGrpcRequest(): Trans
} catch (t: Throwable) {
KinAmount.ONE
}
val commitment= when {
amount.value < BigDecimal(50000) -> { // ~1 $USD
val commitment = when {
amount.value < BigDecimal(50000) -> {
Model.Commitment.RECENT
}
amount.value < BigDecimal(500000) -> { // ~10 $USD
amount.value < BigDecimal(500000) -> {
Model.Commitment.SINGLE
}
else -> {
Expand Down
34 changes: 29 additions & 5 deletions base/src/main/java/org/kin/sdk/base/tools/BackupRestoreImpl.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package org.kin.sdk.base.tools

import net.i2p.crypto.eddsa.EdDSAPrivateKey
import net.i2p.crypto.eddsa.EdDSAPublicKey
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
import org.json.JSONException
import org.json.JSONObject
import org.kin.sdk.base.models.asPublicKey
import org.kin.stellarfork.KeyPair
import org.kin.stellarfork.KeyPairJvmImpl
import org.libsodium.jni.NaCl
import org.libsodium.jni.Sodium
import java.io.UnsupportedEncodingException
Expand Down Expand Up @@ -111,16 +117,24 @@ class BackupRestoreImpl : BackupRestore {
@Throws(CryptoException::class)
fun exportAccountBackup(keyPair: KeyPair, passphrase: String): AccountBackup {
val saltBytes = Companion.generateRandomBytes(SALT_LENGTH_BYTES)

val passphraseBytes = passphrase.toUTF8ByteArray()
val hash = Companion.keyHash(passphraseBytes, saltBytes)
val secretSeedBytes = keyPair.rawSecretSeed
val secretSeedBytes = if (keyPair.rawSecretSeed != null && keyPair.rawSecretSeed!!.size == 32) {
keyPair.rawSecretSeed!!
} else {
keyPair.privateKey!!
}
val publicKey = if (secretSeedBytes.size == 32) {
keyPair.accountId
} else {
keyPair.asPublicKey().base58Encode()
}

val encryptedSeed = Companion.encryptSecretSeed(hash, secretSeedBytes!!)
val encryptedSeed = Companion.encryptSecretSeed(hash, secretSeedBytes)

val salt = saltBytes.bytesToHex()
val seed = encryptedSeed.bytesToHex()
return AccountBackup(keyPair.accountId, salt, seed)
return AccountBackup(publicKey, salt, seed)
}

@Throws(CryptoException::class)
Expand All @@ -131,7 +145,17 @@ class BackupRestoreImpl : BackupRestore {
val seedBytes = accountBackup.encryptedSeedHexString.hexStringToByteArray()

val decryptedBytes = Companion.decryptSecretSeed(seedBytes, keyHash)
return KeyPair.fromSecretSeed(decryptedBytes)
val privKeySpec = if (decryptedBytes.size == 32) {
EdDSAPrivateKeySpec(decryptedBytes, KeyPairJvmImpl.ed25519)
} else {
EdDSAPrivateKeySpec(KeyPairJvmImpl.ed25519, decryptedBytes)
}
val publicKeySpec = EdDSAPublicKeySpec(privKeySpec.a.toByteArray(), KeyPairJvmImpl.ed25519)
val i = KeyPairJvmImpl(
EdDSAPublicKey(publicKeySpec),
EdDSAPrivateKey(privKeySpec)
)
return KeyPair(i)
}

override fun exportWallet(keyPair: KeyPair, passphrase: String): String =
Expand Down
18 changes: 18 additions & 0 deletions base/src/main/java/org/kin/stellarfork/KeyPair.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ interface IKeyPair {
*/
val secretSeed: CharArray

/**
* Returns the 64 byte private key.
*/
val privateKey: ByteArray?

/**
* Returns the raw 32 byte secret seed.
*/
Expand Down Expand Up @@ -88,6 +93,8 @@ constructor(

override val publicKey: ByteArray
get() = impl.publicKey
override val privateKey: ByteArray?
get() = impl.privateKey

/**
* Sign the provided data with the keypair's private key.
Expand Down Expand Up @@ -158,6 +165,17 @@ constructor(
else KeyPairJvmImpl.fromSecretSeed(seed)
)

/**
* Creates a new keypair from a 64 byte private key.
*
* @param privateKey The 64 byte private key.
* @return [KeyPair]
*/
@JvmStatic
fun fromPrivateKey(privateKey: ByteArray): KeyPair = KeyPair(
KeyPairJvmImpl.fromPrivateKey(privateKey)
)

/**
* Creates a new Stellar KeyPair from a strkey encoded Stellar account ID.
*
Expand Down
22 changes: 20 additions & 2 deletions base/src/main/java/org/kin/stellarfork/KeyPairJvmImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ constructor(
return mPrivateKey != null
}

override val privateKey: ByteArray?
get() = mPrivateKey?.h

/**
* Returns the human readable account ID encoded in strkey.
*/
Expand Down Expand Up @@ -108,7 +111,7 @@ constructor(
}

companion object {
private val ed25519 = EdDSANamedCurveTable.ED_25519_CURVE_SPEC
val ed25519 = EdDSANamedCurveTable.ED_25519_CURVE_SPEC

/**
* Creates a new Stellar KeyPair from a strkey encoded Stellar secret seed.
Expand Down Expand Up @@ -149,7 +152,6 @@ constructor(
*/
@JvmStatic
fun fromSecretSeed(seed: ByteArray?): KeyPairJvmImpl {

val privKeySpec = EdDSAPrivateKeySpec(seed, ed25519)
val publicKeySpec = EdDSAPublicKeySpec(privKeySpec.a.toByteArray(), ed25519)
return KeyPairJvmImpl(
Expand All @@ -158,6 +160,22 @@ constructor(
)
}

/**
* Creates a new keypair from a 64 byte private key.
*
* @param privateKey The 64 byte private key.
* @return [KeyPair]
*/
@JvmStatic
fun fromPrivateKey(privateKey: ByteArray?): KeyPairJvmImpl {
val privKeySpec = EdDSAPrivateKeySpec(ed25519, privateKey)
val publicKeySpec = EdDSAPublicKeySpec(privKeySpec.a.toByteArray(), ed25519)
return KeyPairJvmImpl(
EdDSAPublicKey(publicKeySpec),
EdDSAPrivateKey(privKeySpec)
)
}

/**
* Creates a new Stellar KeyPair from a strkey encoded Stellar account ID.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ constructor(
*/
override val secretSeed: CharArray
get() = encodeStellarSecretSeed(mPrivateKey!!.seed)
override val privateKey: ByteArray?
get() = ByteArray(1)

/**
* Returns the raw 32 byte secret seed.
Expand Down
Loading

0 comments on commit ca2310d

Please sign in to comment.