Skip to content

Commit

Permalink
PERA-1263 :: Add Bip39 libraries and create/recover Algo25 using Algo…
Browse files Browse the repository at this point in the history
…SDK in common module (#59)
  • Loading branch information
michaeltchuang authored Dec 19, 2024
1 parent 3fea559 commit 5af4407
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 54 deletions.
9 changes: 9 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,15 @@ android {
exclude 'lib/libnarcissus-win-64.dll'
}

configurations.configureEach {
// exclude duplicate bouncycastle till walletconnect is no longer used, use
// common wallet-sdk module versions instead
exclude group: 'org.bouncycastle', module: 'bcprov-jdk15to18'
exclude group: 'org.bouncycastle', module: 'bcutil-jdk18on'
exclude group: 'org.bouncycastle', module: 'bcprov-jdk15on'
exclude group: 'org.bouncycastle', module: 'bcutil-jdk15on'
}

namespace libs.versions.android.namespace.get().toString()
}

Expand Down
2 changes: 2 additions & 0 deletions composeTestApp/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="false"
android:name=".AndroidApp"
android:icon="@android:drawable/ic_menu_compass"
android:label="Pera Wallet (Compose)"
Expand Down
28 changes: 28 additions & 0 deletions composeTestApp/src/androidMain/res/xml/data_extraction_rules.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 Pera Wallet, LDA
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License
-->

<data-extraction-rules>
<cloud-backup>
<exclude domain="root" />
<exclude domain="file" />
<exclude domain="database" />
<exclude domain="sharedpref" />
<exclude domain="external" />
</cloud-backup>
<device-transfer>
<exclude domain="root" />
<exclude domain="file" />
<exclude domain="database" />
<exclude domain="sharedpref" />
<exclude domain="external" />
</device-transfer>
</data-extraction-rules>
28 changes: 20 additions & 8 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ androidx-compose-ui = "1.7.5"
androidx-uiTest = "1.7.5"
appCompat = "1.7.0"
biometric = "1.1.0"
bip39 = "1.0.7"
ble = "2.8.0"
browser = "1.8.0"
buildConfig = "4.1.1"
Expand Down Expand Up @@ -75,12 +76,12 @@ sqlite = "2.5.0-alpha11"
runner = "1.6.2"
safeargs = "2.8.4"
tink = "1.14.0"
xhdwalletapi = "1.1.0"
walletConnectBom = "1.34.1"
zxing = "4.3.0"

[libraries]
agp = { module = "com.android.tools.build:gradle", version.ref = "agp" }
algosdk = { module = "com.algorand:algosdk", version.ref = "algosdk" }
androidx-activityCompose = { module = "androidx.activity:activity-compose", version.ref = "androidx-compose-activity" }
androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "composeFoundation" }
androidx-compose-fragment = { module = "androidx.fragment:fragment-compose", version.ref = "androidx-compose-fragment" }
Expand Down Expand Up @@ -116,8 +117,6 @@ firebase-messaging = { module = "com.google.firebase:firebase-messaging" }
firebase-perf = { module = "com.google.firebase:firebase-perf" }
flexbox = { module = "com.google.android.flexbox:flexbox", version.ref = "flexbox" }
fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragmentKtx" }
glide-image = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
glide-compiler = { module = "com.github.bumptech.glide:compiler", version.ref = "glide" }
google-services = { module = "com.google.gms:google-services", version.ref = "googleServicesVersion" }
java-websocket = { module = "org.java-websocket:Java-WebSocket", version.ref = "javaWebSocket" }
junit = { module = "junit:junit", version.ref = "junit" }
Expand Down Expand Up @@ -163,14 +162,12 @@ moshi-core = { module = "com.squareup.moshi:moshi", version.ref = "moshi" }
moshi-kotlin = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" }
mpandroidchart = { module = "com.github.PhilJay:MPAndroidChart", version.ref = "mpAndroidChart" }
multidex = { module = "androidx.multidex:multidex", version.ref = "multidex" }
napier = { module = "io.github.aakira:napier", version.ref = "napier" }
navigation-compose = { group = "org.jetbrains.androidx.navigation", name = "navigation-compose", version.ref = "navigation-compose" }
navigation-fragment-ktx = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigation" }
navigation-safe-args-gradle-plugin = { module = "androidx.navigation:navigation-safe-args-gradle-plugin", version.ref = "safeargs" }
navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigation" }
paging-runtime-ktx = { module = "androidx.paging:paging-runtime-ktx", version.ref = "paging" }
perf-plugin = { module = "com.google.firebase:perf-plugin", version.ref = "firebasePerformance" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
review-ktx = { module = "com.google.android.play:review-ktx", version.ref = "review" }
sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" }
room-gradle-plugin = { module = "androidx.room:room-gradle-plugin", version.ref = "room" }
Expand All @@ -179,13 +176,28 @@ room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
room-testing = { module = "androidx.room:room-testing", version.ref = "room" }
runner = { module = "androidx.test:runner", version.ref = "runner" }
sign = { module = "com.walletconnect:sign" }
tink-android = { module = "com.google.crypto.tink:tink-android", version.ref = "tink" }
zxing-android-embedded = { module = "com.journeyapps:zxing-android-embedded", version.ref = "zxing" }

# Wallet SDK - Logging
napier = { module = "io.github.aakira:napier", version.ref = "napier" }

# Wallet SDK - Security / Blockchain / Crypto
algosdk = { module = "com.algorand:algosdk", version.ref = "algosdk" }
# jna = "net.java.dev.jna:jna:5.14.0@aar"
xhdwalletapi = { module = "foundation.algorand.xhdwalletapi:xhdwalletapi-android", version.ref = "xhdwalletapi" }
kotlin-bip39 = { module = "cash.z.ecc.android:kotlin-bip39", version.ref = "bip39" }

# App Module Only
glide-image = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
glide-compiler = { module = "com.github.bumptech.glide:compiler", version.ref = "glide" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }

# Wallet Connect
sign = { module = "com.walletconnect:sign" }
walletconnect = { module = "com.walletconnect:android-bom", version.ref = "walletConnectBom" }
walletconnect-android-core = { module = "com.walletconnect:android-core" }
web3wallet = { module = "com.walletconnect:web3wallet" }
zxing-android-embedded = { module = "com.journeyapps:zxing-android-embedded", version.ref = "zxing" }


[bundles]
firebase = ["firebase-analytics",
Expand Down
15 changes: 7 additions & 8 deletions wallet-sdk/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ kotlin {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
compilerOptions {
languageVersion.set(KotlinVersion.KOTLIN_2_0)
freeCompilerArgs.add("-Xexpect-actual-classes")
}

listOf(
Expand All @@ -55,8 +56,14 @@ kotlin {

sourceSets {
androidMain.dependencies {
// will turn this to implementation when app module references are moved to common
api(libs.algosdk)

// toml files don't support aar files yet
implementation("net.java.dev.jna:jna:5.14.0@aar")
implementation(libs.xhdwalletapi)
implementation(libs.kotlin.bip39)

implementation(compose.uiTooling)
implementation(libs.androidx.activityCompose)
implementation(libs.androidx.compose.foundation)
Expand Down Expand Up @@ -127,14 +134,6 @@ android {

sourceSets["main"].res.srcDirs("src/commonMain/composeResources", "src/androidMain/res")
sourceSets["main"].resources.srcDirs("src/commonMain/composeResources")

configurations.all {
// exclude duplicate bouncycastle till walletconnect is no longer used
exclude(group = "org.bouncycastle", module = "bcprov-jdk15to18")
exclude(group = "org.bouncycastle", module = "bcutil-jdk18on")
exclude(group = "org.bouncycastle", module = "bcprov-jdk15on")
exclude(group = "org.bouncycastle", module = "bcutil-jdk15on")
}
}

room {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,61 +12,72 @@

package com.algorand.common.algosdk

import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.Mnemonics.MnemonicCode
import cash.z.ecc.android.bip39.toSeed
import com.algorand.algosdk.account.Account
import com.algorand.algosdk.crypto.Address
import com.algorand.common.algosdk.model.Algo25Account
import com.algorand.common.algosdk.model.Bip39Account
import foundation.algorand.xhdwalletapi.KeyContext
import foundation.algorand.xhdwalletapi.XHDWalletAPIAndroid
import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.security.Security
import kotlin.random.Random


actual interface AlgoAccountSdk {
actual fun createBip39Account(): Bip39Account
actual fun recoverBip39Account(mnemonic: String): Bip39Account
actual fun recoverBip39Account(mnemonic: String): Bip39Account?
actual fun createAlgo25Account(): Algo25Account
actual fun recoverAlgo25Account(mnemonic: String): Algo25Account
actual fun recoverAlgo25Account(mnemonic: String): Algo25Account?
}

internal class AlgoAccountSdkImpl : AlgoAccountSdk {
init {
Security.removeProvider("BC")
Security.insertProviderAt(BouncyCastleProvider(), 0)
}

override fun createBip39Account(): Bip39Account {
val accountAddress = generateRandomAddress()
val mnemonic = generate24WordMnemonic()
return Bip39Account(accountAddress, mnemonic, byteArrayOf())
val generatedMnemonic = MnemonicCode(Mnemonics.WordCount.COUNT_24)
val wordsAsString = generatedMnemonic.joinToString(" ")
val accountAddress = generateBip39Address(generatedMnemonic)
return Bip39Account(accountAddress, wordsAsString, generatedMnemonic.toSeed())
}

override fun recoverBip39Account(mnemonic: String): Bip39Account {
return Bip39Account(mnemonic, mnemonic, byteArrayOf())
val m = MnemonicCode(mnemonic)
val accountAddress = generateBip39Address(m)
return Bip39Account(accountAddress, mnemonic, m.toSeed())
}

override fun createAlgo25Account(): Algo25Account {
val accountAddress = generateRandomAddress()
val mnemonic = generate25WordMnemonic()
return Algo25Account(accountAddress, mnemonic, byteArrayOf())
}

override fun recoverAlgo25Account(mnemonic: String): Algo25Account {
return Algo25Account(mnemonic, mnemonic, byteArrayOf())
}

private fun generateRandomAddress(): String {
val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" // Base32 characters
val addressLength = 58

return (1..addressLength)
.map { alphabet[Random.nextInt(alphabet.length)] }
.joinToString("")
val account = Account()
return Algo25Account(account.address.toString(), account.toMnemonic(), account.toSeed())
}

private fun generate24WordMnemonic(): String {
return """
Lorem ipsum dolor sit amet consectetur adipiscing elit
Mauris ornare orci et facilisis condimentum
Nunc imperdiet ultricies mi nec mattis erat In volutpat tempus
""".trimIndent().lowercase()
override fun recoverAlgo25Account(mnemonic: String): Algo25Account? {
try {
val account = Account(mnemonic)
return Algo25Account(account.address.toString(), account.toMnemonic(), account.toSeed())
} catch (e: Exception) {
return null
}
}

private fun generate25WordMnemonic(): String {
return """
Lorem ipsum dolor sit amet consectetur adipiscing elit
Mauris ornare orci et facilisis condimentum
Nunc imperdiet ultricies mi nec mattis erat In volutpat tempus tortor
""".trimIndent().lowercase()
private fun generateBip39Address(mnemonic: MnemonicCode): String {
val xHDWalletAPI = XHDWalletAPIAndroid(mnemonic.toSeed())
// Produce the PK and turn it into an Algorand formatted address
val algoAddress =
Address(
xHDWalletAPI.keyGen(
KeyContext.Address,
Random.nextInt().toUInt(),
Random.nextInt().toUInt(),
Random.nextInt().toUInt()
)
)
return algoAddress.toString()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ expect interface AlgoAccountSdk {

fun createBip39Account(): Bip39Account

fun recoverBip39Account(mnemonic: String): Bip39Account
fun recoverBip39Account(mnemonic: String): Bip39Account?

fun createAlgo25Account(): Algo25Account

fun recoverAlgo25Account(mnemonic: String): Algo25Account
fun recoverAlgo25Account(mnemonic: String): Algo25Account?
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ import kotlin.random.Random

actual interface AlgoAccountSdk {
actual fun createBip39Account(): Bip39Account
actual fun recoverBip39Account(mnemonic: String): Bip39Account
actual fun recoverBip39Account(mnemonic: String): Bip39Account?
actual fun createAlgo25Account(): Algo25Account
actual fun recoverAlgo25Account(mnemonic: String): Algo25Account
actual fun recoverAlgo25Account(mnemonic: String): Algo25Account?
}

internal class AlgoAccountSdkImpl : AlgoAccountSdk {
Expand Down

0 comments on commit 5af4407

Please sign in to comment.