Skip to content

Commit

Permalink
ErgoAuth Android implementation done #112
Browse files Browse the repository at this point in the history
  • Loading branch information
MrStahlfelge committed Apr 19, 2022
1 parent ccbcbba commit ff755d7
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import org.ergoplatform.android.wallet.ChooseWalletListBottomSheetDialog
import org.ergoplatform.android.wallet.WalletChooserCallback
import org.ergoplatform.persistance.WalletConfig
import org.ergoplatform.transactions.MessageSeverity
import org.ergoplatform.uilogic.ergoauth.ErgoAuthUiLogic

class ErgoAuthenticationFragment : AbstractAuthenticationFragment(), WalletChooserCallback {
private var _binding: FragmentErgoAuthenticationBinding? = null
Expand Down Expand Up @@ -45,15 +46,17 @@ class ErgoAuthenticationFragment : AbstractAuthenticationFragment(), WalletChoos
AppDatabase.getInstance(context)
)

viewModel.authRequest.observe(viewLifecycleOwner) { authRequest ->
binding.layoutProgress.visibility = View.GONE
binding.layoutDoneInfo.visibility = if (authRequest == null) View.VISIBLE else View.GONE
viewModel.state.observe(viewLifecycleOwner) { state ->
binding.layoutProgress.visibility =
if (state == ErgoAuthUiLogic.State.FETCHING_DATA) View.VISIBLE else View.GONE
binding.layoutDoneInfo.visibility =
if (state == ErgoAuthUiLogic.State.DONE) View.VISIBLE else View.GONE
binding.layoutAuthenticate.visibility =
if (authRequest != null) View.VISIBLE else View.GONE
if (state == ErgoAuthUiLogic.State.WAIT_FOR_AUTH) View.VISIBLE else View.GONE

if (authRequest == null)
if (state == ErgoAuthUiLogic.State.DONE)
refreshDoneScreen()
else
else if (state == ErgoAuthUiLogic.State.WAIT_FOR_AUTH)
refreshAuthPrompt()
}

Expand All @@ -63,6 +66,9 @@ class ErgoAuthenticationFragment : AbstractAuthenticationFragment(), WalletChoos
binding.walletLabel.setOnClickListener {
ChooseWalletListBottomSheetDialog().show(childFragmentManager, null)
}
binding.buttonAuthenticate.setOnClickListener {
startAuthFlow(viewModel.uiLogic.walletConfig!!)
}
}

override fun onWalletChosen(walletConfig: WalletConfig) {
Expand Down Expand Up @@ -101,11 +107,14 @@ class ErgoAuthenticationFragment : AbstractAuthenticationFragment(), WalletChoos
}

override fun proceedAuthFlowFromBiometrics() {
TODO("Not yet implemented")
viewModel.startAuthenticationFromBiometrics(requireContext())
}

override fun proceedAuthFlowWithPassword(password: String): Boolean {
TODO("Not yet implemented")
return viewModel.startAuthenticationFromPassword(
password,
AndroidStringProvider(requireContext())
)
}

override fun onDestroyView() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,51 @@
package org.ergoplatform.android.ergoauth

import android.content.Context
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.CoroutineScope
import org.ergoplatform.ergoauth.ErgoAuthRequest
import org.ergoplatform.SigningSecrets
import org.ergoplatform.android.ui.AndroidStringProvider
import org.ergoplatform.api.AesEncryptionManager
import org.ergoplatform.api.AndroidEncryptionManager
import org.ergoplatform.uilogic.StringProvider
import org.ergoplatform.uilogic.ergoauth.ErgoAuthUiLogic

class ErgoAuthenticationViewModel: ViewModel() {
val uiLogic = AndroidUiLogic()

private val _authRequest = MutableLiveData<ErgoAuthRequest?>()
val authRequest: LiveData<ErgoAuthRequest?> get() = _authRequest
private val _state = MutableLiveData(ErgoAuthUiLogic.State.FETCHING_DATA)
val state: LiveData<ErgoAuthUiLogic.State?> get() = _state

fun startAuthenticationFromBiometrics(context: Context) {
uiLogic.walletConfig?.secretStorage?.let {
val decryptData = AndroidEncryptionManager.decryptDataWithDeviceKey(it)
val secrets = SigningSecrets.fromBytes(decryptData!!)
uiLogic.startResponse(secrets!!, AndroidStringProvider(context))
}
}

fun startAuthenticationFromPassword(password: String, texts: StringProvider): Boolean {
return uiLogic.walletConfig?.secretStorage?.let {
return try {
val decryptData = AesEncryptionManager.decryptData(password, it)
val secrets = SigningSecrets.fromBytes(decryptData!!)
uiLogic.startResponse(secrets!!, texts)
true
} catch (t: Throwable) {
false
}
} ?: false
}

inner class AndroidUiLogic: ErgoAuthUiLogic() {
override val coroutineScope: CoroutineScope
get() = viewModelScope

override fun notifyAuthRequestFetched() {
_authRequest.postValue(ergAuthRequest)
override fun notifyStateChanged(newState: State) {
_state.postValue(newState)
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@
tools:text="Wallet label" />

<Button
android:id="@+id/button_sign_tx"
android:id="@+id/button_authenticate"
style="?attr/materialButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
Expand Down
92 changes: 61 additions & 31 deletions common-jvm/src/main/java/org/ergoplatform/ErgoFacade.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ import org.ergoplatform.uilogic.STRING_ERROR_CHANGEBOX_AMOUNT
import org.ergoplatform.uilogic.STRING_ERROR_PROVER_CANT_SIGN
import org.ergoplatform.uilogic.StringProvider
import org.ergoplatform.utils.LogUtils
import org.ergoplatform.utils.getMessageOrName
import org.ergoplatform.wallet.boxes.`ErgoBoxSerializer$`
import org.ergoplatform.wallet.mnemonic.WordList
import org.ergoplatform.wallet.secrets.ExtendedPublicKey
import scala.collection.JavaConversions
import sigmastate.interpreter.HintsBag
import sigmastate.serialization.`SigmaSerializer$`
import java.nio.charset.StandardCharsets

const val MNEMONIC_WORDS_COUNT = 15
const val MNEMONIC_MIN_WORDS_COUNT = 12
Expand Down Expand Up @@ -122,22 +125,18 @@ fun sendErgoTx(
try {
val ergoClient = getRestErgoClient(prefs)
return ergoClient.execute { ctx: BlockchainContext ->
val proverBuilder = ctx.newProverBuilder()
.withMnemonic(
signingSecrets.mnemonic,
signingSecrets.password,
// TODO BIP-32 fix signingSecrets.deprecatedDerivation
)
derivedKeyIndices.forEach {
proverBuilder.withEip3Secret(it)
}
val prover = proverBuilder.build()
val prover = buildProver(ctx, signingSecrets, derivedKeyIndices)

val contract: ErgoContract = recipient.toErgoContract()
val unsignedTx = BoxOperations.createForEip3Prover(prover, ctx).withAmountToSpend(amountToSend)
.withFeeAmount(feeAmount)
.withInputBoxesLoader(ExplorerAndPoolUnspentBoxesLoader().withAllowChainedTx(true))
.withTokensToSpend(tokensToSend).putToContractTxUnsigned(contract)
val unsignedTx =
BoxOperations.createForEip3Prover(prover, ctx).withAmountToSpend(amountToSend)
.withFeeAmount(feeAmount)
.withInputBoxesLoader(
ExplorerAndPoolUnspentBoxesLoader().withAllowChainedTx(
true
)
)
.withTokensToSpend(tokensToSend).putToContractTxUnsigned(contract)
val signed = prover.sign(unsignedTx)
ctx.sendTransaction(signed)

Expand Down Expand Up @@ -193,10 +192,15 @@ fun prepareSerializedErgoTx(
val ergoClient = getRestErgoClient(prefs)
return ergoClient.execute { ctx: BlockchainContext ->
val contract: ErgoContract = recipient.toErgoContract()
val unsigned = BoxOperations.createForSenders(senderAddresses, ctx).withAmountToSpend(amountToSend)
.withFeeAmount(feeAmount)
.withInputBoxesLoader(ExplorerAndPoolUnspentBoxesLoader().withAllowChainedTx(true))
.withTokensToSpend(tokensToSend).putToContractTxUnsigned(contract)
val unsigned =
BoxOperations.createForSenders(senderAddresses, ctx).withAmountToSpend(amountToSend)
.withFeeAmount(feeAmount)
.withInputBoxesLoader(
ExplorerAndPoolUnspentBoxesLoader().withAllowChainedTx(
true
)
)
.withTokensToSpend(tokensToSend).putToContractTxUnsigned(contract)

val inputs = (unsigned as UnsignedTransactionImpl).boxesToSpend.map { box ->
val ergoBox = box.box()
Expand Down Expand Up @@ -234,17 +238,7 @@ fun signSerializedErgoTx(
): SigningResult {
try {
val signedTxSerialized = getColdErgoClient().execute { ctx ->
val proverBuilder = ctx.newProverBuilder()
.withMnemonic(
signingSecrets.mnemonic,
signingSecrets.password,
// TODO BIP-32 fix signingSecrets.deprecatedDerivation
)

derivedKeyIndices.forEach {
proverBuilder.withEip3Secret(it)
}
val prover = proverBuilder.build()
val prover = buildProver(ctx, signingSecrets, derivedKeyIndices)
val reducedTx = ctx.parseReducedTransaction(serializedTx)

return@execute prover.signReduced(reducedTx, ERG_BASE_COST).toBytes()
Expand All @@ -256,6 +250,42 @@ fun signSerializedErgoTx(
}
}

private fun buildProver(
ctx: BlockchainContext,
signingSecrets: SigningSecrets,
derivedKeyIndices: List<Int>
): ErgoProver {
val proverBuilder = ctx.newProverBuilder()
.withMnemonic(
signingSecrets.mnemonic,
signingSecrets.password,
// TODO BIP-32 fix signingSecrets.deprecatedDerivation
)
derivedKeyIndices.forEach {
proverBuilder.withEip3Secret(it)
}
val prover = proverBuilder.build()
return prover
}


fun signMessage(
signingSecrets: SigningSecrets,
derivedKeyIndices: List<Int>,
sigmaBoolean: SigmaProp,
signingMessage: String,
): ByteArray {
return getColdErgoClient().execute { ctx ->
val prover = buildProver(ctx, signingSecrets, derivedKeyIndices)

prover.signMessage(
sigmaBoolean,
signingMessage.toByteArray(StandardCharsets.UTF_8),
HintsBag.empty()
)
}
}

private fun getRestErgoClient(prefs: PreferencesProvider) =
RestApiErgoClient.createWithHttpClientBuilder(
prefs.prefNodeUrl,
Expand Down Expand Up @@ -291,7 +321,7 @@ fun sendSignedErgoTx(
}
}

fun getErrorMessage(t: Throwable, texts: StringProvider): String? {
fun getErrorMessage(t: Throwable, texts: StringProvider): String {
return if (t is InputBoxesSelectionException.NotEnoughErgsException) {
texts.getString(
STRING_ERROR_BALANCE_ERG,
Expand All @@ -305,7 +335,7 @@ fun getErrorMessage(t: Throwable, texts: StringProvider): String? {
// ProverInterpreter.scala
texts.getString(STRING_ERROR_PROVER_CANT_SIGN)
} else {
t.message
t.getMessageOrName()
}
}

Expand Down
10 changes: 7 additions & 3 deletions common-jvm/src/main/java/org/ergoplatform/SigningSecrets.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,13 @@ data class SigningSecrets(
// backwards compatibility: old, unsecure json call
fromJson(String(bytes))
} else {
val mnemonic = bytes.toSecretStringSecure()
Arrays.fill(bytes, 0)
SigningSecrets(mnemonic, true)
try {
val mnemonic = bytes.toSecretStringSecure()
Arrays.fill(bytes, 0)
SigningSecrets(mnemonic, true)
} catch (t: Throwable) {
null
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package org.ergoplatform.ergoauth

import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import org.ergoplatform.appkit.SigmaProp
import org.ergoplatform.transactions.*
import org.ergoplatform.utils.Base64Coder
import org.ergoplatform.utils.fetchHttpGetStringSync
import org.ergoplatform.utils.isLocalOrIpAddress
import org.ergoplatform.utils.*

private const val uriSchemePrefix = "ergoauth://"

Expand All @@ -19,6 +19,10 @@ fun getErgoAuthRequest(ergoAuthUrl: String): ErgoAuthRequest {
return parseErgoAuthRequestFromJson(jsonResponse)
}

fun postErgoAuthResponse(replyUrl: String, authResponse: ErgoAuthResponse) {
httpPostStringSync(replyUrl, authResponse.toJson(), MEDIA_TYPE_JSON)
}

private const val JSON_KEY_SIGMABOOLEAN = "sigmaBoolean"
private const val JSON_KEY_SIGNINGMESSAGE = "signingMessage"
private const val JSON_KEY_USERMESSAGE = "userMessage"
Expand Down Expand Up @@ -47,4 +51,20 @@ data class ErgoAuthRequest(
val userMessage: String?,
val messageSeverity: MessageSeverity = MessageSeverity.NONE,
val replyToUrl: String? = null
)
)

private const val JSON_KEY_PROOF= "proof"
private const val JSON_KEY_SIGNEDMESSAGE = "signedMessage"

data class ErgoAuthResponse(
val signedMessage: String,
val proof: ByteArray
) {
fun toJson(): String {
val gson = GsonBuilder().disableHtmlEscaping().create()
val root = JsonObject()
root.addProperty(JSON_KEY_SIGNEDMESSAGE, signedMessage)
root.addProperty(JSON_KEY_PROOF, String(Base64Coder.encode(proof)))
return gson.toJson(root)
}
}

0 comments on commit ff755d7

Please sign in to comment.