diff --git a/app/build.gradle b/app/build.gradle index 8d45edf..3d7629d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,7 +8,7 @@ apply plugin: 'kotlin-kapt' archivesBaseName = 'zcash-android-wallet' group = 'cash.z.ecc.android' -version = '1.0.0-alpha03' +version = '1.0.0-alpha04' android { compileSdkVersion Deps.compileSdkVersion @@ -18,10 +18,12 @@ android { applicationId 'cash.z.ecc.android' minSdkVersion Deps.minSdkVersion targetSdkVersion Deps.targetSdkVersion - versionCode = 1_00_00_003 + versionCode = 1_00_00_004 // last digits are alpha(0XX) beta(2XX) rc(4XX) release(8XX). Ex: 1_08_04_401 is an release candidate build of version 1.8.4 and 1_08_04_800 would be the final release. versionName = "$version" testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + testInstrumentationRunnerArguments clearPackageData: 'true' + multiDexEnabled true } flavorDimensions 'network' productFlavors { @@ -40,11 +42,13 @@ android { buildTypes { release { minifyEnabled true + shrinkResources true useProguard false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } debug { - minifyEnabled true + minifyEnabled false + shrinkResources false useProguard false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } @@ -82,6 +86,7 @@ dependencies { implementation project(':feedback') implementation project(':mnemonic') implementation project(':lockbox') + implementation project(':sdk') // Kotlin implementation Deps.Kotlin.STDLIB @@ -102,6 +107,11 @@ dependencies { kapt Deps.Dagger.ANDROID_PROCESSOR kapt Deps.Dagger.COMPILER + // Testing these BIP39 dependencies + implementation 'com.madgag.spongycastle:core:1.58.0.0' + implementation 'io.github.novacrypto:BIP39:2019.01.27' + implementation 'io.github.novacrypto:securestring:2019.01.27' + // grpc-java implementation "io.grpc:grpc-okhttp:1.21.0" implementation "io.grpc:grpc-android:1.21.0" diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 62fb5e6..a90817f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,6 +1,12 @@ -dontobfuscate -keepattributes SourceFile,LineNumberTable +# Reports +-printusage build/outputs/logs/R8-removed-code-report.txt +-printseeds build/outputs/logs/R8-entry-points-report.txt + ## Okio # Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java --dontwarn org.codehaus.mojo.animal_sniffer.* \ No newline at end of file +-dontwarn org.codehaus.mojo.animal_sniffer.* + +#-keep class cash.z.** { *; } \ No newline at end of file diff --git a/app/src/androidTest/java/cash/z/ecc/android/integration/IntegrationTest.kt b/app/src/androidTest/java/cash/z/ecc/android/integration/IntegrationTest.kt index 70373df..6eebcc9 100644 --- a/app/src/androidTest/java/cash/z/ecc/android/integration/IntegrationTest.kt +++ b/app/src/androidTest/java/cash/z/ecc/android/integration/IntegrationTest.kt @@ -3,16 +3,116 @@ package cash.z.ecc.android.integration import android.content.Context import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import cash.z.ecc.android.lockbox.LockBox +import cash.z.ecc.kotlin.mnemonic.Mnemonics +import cash.z.wallet.sdk.Initializer +import okio.Buffer +import okio.GzipSink +import okio.Okio +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue import org.junit.Before +import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class IntegrationTest { private lateinit var appContext: Context + private val mnemonics = Mnemonics() + private val phrase = + "human pulse approve subway climb stairs mind gentle raccoon warfare fog roast sponsor" + + " under absorb spirit hurdle animal original honey owner upper empower describe" @Before fun start() { appContext = InstrumentationRegistry.getInstrumentation().targetContext } + + @Test + fun testSeed_generation() { + val seed = mnemonics.toSeed(phrase.toCharArray()) + assertEquals( + "Generated incorrect BIP-39 seed!", + "f4e3d38d9c244da7d0407e19a93c80429614ee82dcf62c141235751c9f1228905d12a1f275f" + + "5c22f6fb7fcd9e0a97f1676e0eec53fdeeeafe8ce8aa39639b9fe", + seed.toHex() + ) + } + + @Test + fun testSeed_storage() { + val seed = mnemonics.toSeed(phrase.toCharArray()) + val lb = LockBox(appContext) + lb.setBytes("seed", seed) + assertTrue(seed.contentEquals(lb.getBytes("seed")!!)) + } + + @Test + fun testPhrase_storage() { + val lb = LockBox(appContext) + val phraseChars = phrase.toCharArray() + lb.setCharsUtf8("phrase", phraseChars) + assertTrue(phraseChars.contentEquals(lb.getCharsUtf8("phrase")!!)) + } + + @Test + fun testPhrase_maxLengthStorage() { + val lb = LockBox(appContext) + // find and expose the max length + var acceptedSize = 256 + while (acceptedSize > 0) { + try { + lb.setCharsUtf8("temp", nextString(acceptedSize).toCharArray()) + break + } catch (t: Throwable) { + } + acceptedSize-- + } + + val maxSeedPhraseLength = 8 * 24 + 23 //215 (max length of each word is 8) + assertTrue( + "LockBox does not support the maximum length seed phrase." + + " Expected: $maxSeedPhraseLength but was: $acceptedSize", + acceptedSize > maxSeedPhraseLength + ) + } + + @Test + fun testAddress() { + val seed = mnemonics.toSeed(phrase.toCharArray()) + val initializer = Initializer(appContext).apply { + new(seed, overwrite = true) + } + assertEquals( + "Generated incorrect z-address!", + "zs1gn2ah0zqhsxnrqwuvwmgxpl5h3ha033qexhsz8tems53fw877f4gug353eefd6z8z3n4zxty65c", + initializer.rustBackend.getAddress() + ) + initializer.clear() + } + + + private fun ByteArray.toHex(): String { + val sb = StringBuilder(size * 2) + for (b in this) + sb.append(String.format("%02x", b)) + return sb.toString() + } + + fun String.gzip(): ByteArray { + val result = Buffer() + val sink = Okio.buffer(GzipSink(result)) + sink.use { + sink.write(toByteArray()) + } + return result.readByteArray() + } + + fun nextString(length: Int): String { + val allowedChars = "ACGT" + return (1..length) + .map { allowedChars.random() } + .joinToString("") + } } \ No newline at end of file diff --git a/app/src/main/java/cash/z/ecc/android/ZcashWalletApp.kt b/app/src/main/java/cash/z/ecc/android/ZcashWalletApp.kt index 8d606a2..769d6be 100644 --- a/app/src/main/java/cash/z/ecc/android/ZcashWalletApp.kt +++ b/app/src/main/java/cash/z/ecc/android/ZcashWalletApp.kt @@ -4,6 +4,9 @@ import android.content.Context import android.os.Build import cash.z.ecc.android.di.DaggerAppComponent import cash.z.ecc.android.feedback.FeedbackCoordinator +import cash.z.wallet.sdk.ext.TroubleshootingTwig +import cash.z.wallet.sdk.ext.Twig +import cash.z.wallet.sdk.ext.twig import dagger.android.AndroidInjector import dagger.android.DaggerApplication import javax.inject.Inject @@ -23,7 +26,7 @@ class ZcashWalletApp : DaggerApplication() { super.onCreate() Thread.setDefaultUncaughtExceptionHandler(ExceptionReporter(Thread.getDefaultUncaughtExceptionHandler())) -// Twig.plant(TroubleshootingTwig()) + Twig.plant(TroubleshootingTwig()) } /** @@ -46,6 +49,7 @@ class ZcashWalletApp : DaggerApplication() { override fun uncaughtException(t: Thread?, e: Throwable?) { // trackCrash(e, "Top-level exception wasn't caught by anything else!") // Analytics.clear() + twig("Uncaught Exception: $e") ogHandler.uncaughtException(t, e) } } diff --git a/app/src/test/java/cash/z/ecc/android/ScratchPad.kt b/app/src/test/java/cash/z/ecc/android/ScratchPad.kt new file mode 100644 index 0000000..562d7ad --- /dev/null +++ b/app/src/test/java/cash/z/ecc/android/ScratchPad.kt @@ -0,0 +1,54 @@ +package cash.z.ecc.android + +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertTrue +import org.junit.Test +import kotlin.math.round +import kotlin.math.roundToInt + +class ScratchPad { + + val t get() = System.currentTimeMillis() + var t0 = 0L + val Δt get() = t - t0 + + @Test + fun testMarblesCombine() = runBlocking { + var started = false + val flow = flowOf(1, 2, 3, 4, 5, 6, 7, 8, 9).onEach { + delay(100) + if (!started) { + t0 = t + started = true + } + println("$Δt\temitting $it"); + } + val flow2 = flowOf("a", "b", "c", "d", "e", "f").onEach { delay(150); println("$Δt\temitting $it")} + val flow3 = flowOf("A", "B").onEach { delay(450); println("$Δt\temitting $it")} + combine(flow, flow2, flow3) { i, s, t -> "$i$s$t" }.onStart { + t0 = t + }.collect { +// if (!started) { +// println("$Δt until first emission") +// t0 = t +// started = true +// } + println("$Δt\t$it") // Will print "1a 2a 2b 2c" + } + } + + @Test + fun testMarblesScan() = runBlocking { + val flow = flowOf(1, 2, 3, 4, 5) + + flow.scanReduce { accumulator, value -> + println("was: $accumulator now: $value") + value + }.collect { + println("got $it") + } + } + +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index f76c663..660a637 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,3 @@ rootProject.name='Zcash Wallet' -include ':app', ':qrecycler', ':feedback', ':mnemonic', ':lockbox' +include ':app', ':qrecycler', ':feedback', ':mnemonic', ':lockbox', ':sdk' +project(":sdk").projectDir = file("../zcash-android-wallet-sdk") \ No newline at end of file