Skip to content

Commit

Permalink
Create account
Browse files Browse the repository at this point in the history
  • Loading branch information
SeniorZhai committed Oct 29, 2024
1 parent 8babe7e commit 12e2fbf
Show file tree
Hide file tree
Showing 16 changed files with 269 additions and 28 deletions.
10 changes: 5 additions & 5 deletions app/src/main/java/one/mixin/android/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import one.mixin.android.net.SequentialDns

object Constants {
object API {
const val DOMAIN = "https://mixin.one"
const val URL = "https://api.mixin.one/"
const val WS_URL = "wss://blaze.mixin.one"
const val Mixin_URL = "https://mixin-api.zeromesh.net/"
const val Mixin_WS_URL = "wss://mixin-blaze.zeromesh.net"
const val DOMAIN = "https://mixin.zone"
const val URL = "https://api.mixin.zone/"
const val WS_URL = "wss://blaze.mixin.zone"
const val Mixin_URL = "https://api.mixin.zone/"
const val Mixin_WS_URL = "wss://blaze.mixin.zone"

const val GIPHY_URL = "https://api.giphy.com/v1/"
const val FOURSQUARE_URL = "https://api.foursquare.com/v2/"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package one.mixin.android.api.request

import android.os.Build
import com.google.gson.annotations.SerializedName
import one.mixin.android.BuildConfig

data class AccountRequest(
val code: String?,
val code: String? = null,
val notification_token: String? = null,
val registration_id: Int? = null,
val platform: String = "Android",
Expand All @@ -14,4 +15,7 @@ data class AccountRequest(
var purpose: String = VerificationPurpose.SESSION.name,
val pin: String? = null,
val session_secret: String? = null,
val public_key_hex: String? = null,
val message_hex: String? = null,
val signature_hex: String? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package one.mixin.android.api.request

import com.google.gson.annotations.SerializedName
import one.mixin.android.extension.sha256
import one.mixin.android.extension.toHex
import one.mixin.android.util.GsonHelper
import timber.log.Timber
import kotlin.random.Random

class AnonymousMessage(
@SerializedName("random")
var random: String = "",
@SerializedName("created_at")
val createdAt: String
)

fun AnonymousMessage.doAnonymousPOW(): AnonymousMessage {
val anonymousNumberDifficulty = 1
val prefix = "0".repeat(anonymousNumberDifficulty)
val data = ByteArray(32)

while (true) {
Random.nextBytes(data)
this.random = data.toHex()
val messageBuf = GsonHelper.customGson.toJson(this).toByteArray()
val hash = messageBuf.sha256()
val hashHex = hash.toHex()
Timber.e("json ${GsonHelper.customGson.toJson(this)} hex:$hashHex")
if (hashHex.startsWith(prefix)) {
return this
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@ import com.google.gson.annotations.SerializedName
import one.mixin.android.BuildConfig

data class VerificationRequest(
val phone: String?,
val phone: String? = null,
val purpose: String,
@SerializedName("g_recaptcha_response")
var gRecaptchaResponse: String? = null,
@SerializedName("hcaptcha_response")
var hCaptchaResponse: String? = null,
val package_name: String = BuildConfig.APPLICATION_ID,
val public_key_hex: String? = null,
val message_hex: String? = null,
val signature_hex: String? = null
)

enum class VerificationPurpose {
SESSION,
PHONE,
DEACTIVATED,
ANONYMOUS_SESSION
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ interface AccountService {
@Body request: VerificationRequest,
): MixinResponse<VerificationResponse>

@POST("verifications")
suspend fun verification(
@Body request: AccountRequest,
): MixinResponse<VerificationResponse>

@POST("verifications/{id}")
suspend fun create(
@Path("id") id: String,
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/java/one/mixin/android/crypto/CryptoUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ fun calculateAgreement(
return Curve25519.getInstance(BEST).calculateAgreement(publicKey, privateKey)
}

fun calculateSignature(
privateKey: ByteArray,
message: ByteArray,
): ByteArray {
return Curve25519.getInstance(BEST).calculateSignature(privateKey, message)
}

fun initFromSeedAndSign(
seed: ByteArray,
signTarget: ByteArray,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ class AccountRepository
suspend fun verification(request: VerificationRequest): MixinResponse<VerificationResponse> =
accountService.verification(request)

suspend fun verification(request: AccountRequest): MixinResponse<VerificationResponse> =
accountService.verification(request)

suspend fun create(
id: String,
request: AccountRequest,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,24 @@ package one.mixin.android.ui.landing

import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import androidx.core.view.isVisible
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import one.mixin.android.R
import one.mixin.android.databinding.FragmentComposeBinding
import one.mixin.android.extension.viewDestroyed
import one.mixin.android.ui.common.BaseFragment
import one.mixin.android.ui.landing.components.MnemonicPhraseInput
import one.mixin.android.ui.landing.components.MnemonicState
import one.mixin.android.util.viewBinding
import one.mixin.android.widget.CaptchaView

@AndroidEntryPoint
class LandingMnemonicPhraseFragment: BaseFragment(R.layout.fragment_compose) {
class LandingMnemonicPhraseFragment : BaseFragment(R.layout.fragment_landing_mnemonic_phrase) {
companion object {
const val TAG: String = "MnemonicPhraseFragment"

Expand All @@ -36,7 +43,8 @@ class LandingMnemonicPhraseFragment: BaseFragment(R.layout.fragment_compose) {
}
binding.compose.setContent {
MnemonicPhraseInput(MnemonicState.Input, onComplete = {
})
}
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,49 @@ package one.mixin.android.ui.landing

import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.core.view.isVisible
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import one.mixin.android.R
import one.mixin.android.api.request.AccountRequest
import one.mixin.android.api.request.AnonymousMessage
import one.mixin.android.api.request.VerificationPurpose
import one.mixin.android.api.request.doAnonymousPOW
import one.mixin.android.crypto.CryptoPreference
import one.mixin.android.crypto.EdKeyPair
import one.mixin.android.crypto.SignalProtocol
import one.mixin.android.crypto.generateEd25519KeyPair
import one.mixin.android.crypto.initFromSeedAndSign
import one.mixin.android.crypto.sha3Sum256
import one.mixin.android.databinding.FragmentComposeBinding
import one.mixin.android.extension.base64Encode
import one.mixin.android.extension.hexString
import one.mixin.android.extension.hexStringToByteArray
import one.mixin.android.extension.nowInUtc
import one.mixin.android.extension.toHex
import one.mixin.android.extension.toast
import one.mixin.android.extension.viewDestroyed
import one.mixin.android.ui.common.BaseFragment
import one.mixin.android.ui.landing.components.MnemonicPhrasePage
import one.mixin.android.util.ErrorHandler.Companion.NEED_CAPTCHA
import one.mixin.android.util.GsonHelper
import one.mixin.android.util.viewBinding
import one.mixin.android.widget.CaptchaView
import timber.log.Timber
import java.nio.charset.Charset
import kotlin.random.Random

@AndroidEntryPoint
class MnemonicPhraseFragment: BaseFragment(R.layout.fragment_compose) {
class MnemonicPhraseFragment : BaseFragment(R.layout.fragment_compose) {
companion object {
const val TAG: String = "MnemonicPhraseFragment"

Expand All @@ -36,10 +68,72 @@ class MnemonicPhraseFragment: BaseFragment(R.layout.fragment_compose) {
}
binding.compose.setContent {
MnemonicPhrasePage {
// todo
toast(R.string.Success)
InitializeActivity.showSetupPin(requireContext())
requireActivity().finish()
lifecycleScope.launch {
val sessionKey = generateEd25519KeyPair()
val publicKey = sessionKey.publicKey
val message = AnonymousMessage("", nowInUtc()).doAnonymousPOW()
val messageHex = GsonHelper.customGson.toJson(message).toHex()
val signature = initFromSeedAndSign(sessionKey.privateKey.toTypedArray().toByteArray(), GsonHelper.customGson.toJson(message).toByteArray())
val r = mobileViewModel.anonymousRequest(publicKey.hexString(), messageHex, signature.hexString())
if (r.errorCode == NEED_CAPTCHA) {
initAndLoadCaptcha(sessionKey, publicKey.hexString(), messageHex, signature.hexString())
} else {
createAccount(sessionKey, r.data!!.id)
}
}
}
}
}

private var captchaView: CaptchaView? = null
private fun initAndLoadCaptcha(key: EdKeyPair, publicKeyHex: String, messageHex: String, signatureHex: String) =
lifecycleScope.launch {
if (captchaView == null) {
captchaView =
CaptchaView(
requireContext(),
object : CaptchaView.Callback {
override fun onStop() {
if (viewDestroyed()) return
binding.mobileCover.isVisible = false
}

override fun onPostToken(value: Pair<CaptchaView.CaptchaType, String>) {
val t = value.second
reSend(key, publicKeyHex, messageHex, signatureHex, if (!value.first.isG()) t else null, if (value.first.isG()) t else null)
}
},
)
(view as ViewGroup).addView(captchaView?.webView, MATCH_PARENT, MATCH_PARENT)
}
captchaView?.loadCaptcha(CaptchaView.CaptchaType.GCaptcha)
}

private fun reSend(key: EdKeyPair, publicKeyHex: String, messageHex: String, signatureHex: String, hCaptchaResponse: String? = null, gRecaptchaResponse: String? = null) {
lifecycleScope.launch {
val r = mobileViewModel.anonymousRequest(publicKeyHex, messageHex, signatureHex, hCaptchaResponse, gRecaptchaResponse)
if (r.isSuccess) {
createAccount(key, r.data!!.id)
}
}
}

private fun createAccount(key: EdKeyPair, verificationId: String) {
lifecycleScope.launch {
SignalProtocol.initSignal(requireContext().applicationContext)
val registrationId = CryptoPreference.getLocalRegistrationId(requireContext())
val sessionSecret = key.publicKey.base64Encode()
val r = mobileViewModel.create(
verificationId,
AccountRequest(
purpose = VerificationPurpose.ANONYMOUS_SESSION.name,
session_secret = sessionSecret,
signature_hex = initFromSeedAndSign(key.privateKey.toTypedArray().toByteArray(), r.data!!.id.toByteArray()).toHex(),
registration_id = registrationId,
)
)
if (r.isSuccess) {

}
}
}
Expand Down
51 changes: 48 additions & 3 deletions app/src/main/java/one/mixin/android/ui/landing/MobileViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.walletconnect.foundation.util.jwt.encodeEd25519DidKey
import dagger.hilt.android.lifecycle.HiltViewModel
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
Expand All @@ -15,19 +16,29 @@ import kotlinx.coroutines.withContext
import one.mixin.android.api.MixinResponse
import one.mixin.android.api.request.AccountRequest
import one.mixin.android.api.request.AccountUpdateRequest
import one.mixin.android.api.request.AnonymousMessage
import one.mixin.android.api.request.DeactivateVerificationRequest
import one.mixin.android.api.request.VerificationPurpose
import one.mixin.android.api.request.VerificationRequest
import one.mixin.android.api.response.VerificationResponse
import one.mixin.android.crypto.PinCipher
import one.mixin.android.crypto.generateEd25519KeyPair
import one.mixin.android.crypto.initFromSeedAndSign
import one.mixin.android.crypto.sha3Sum256
import one.mixin.android.extension.base64Encode
import one.mixin.android.extension.hexString
import one.mixin.android.extension.nowInUtc
import one.mixin.android.job.MixinJobManager
import one.mixin.android.repository.AccountRepository
import one.mixin.android.repository.UserRepository
import one.mixin.android.tip.TipBody
import one.mixin.android.ui.landing.vo.MnemonicPhraseState
import one.mixin.android.ui.landing.vo.SetupState
import one.mixin.android.util.ErrorHandler.Companion.NEED_CAPTCHA
import one.mixin.android.util.GsonHelper
import one.mixin.android.vo.Account
import one.mixin.android.vo.User
import timber.log.Timber
import javax.inject.Inject

@HiltViewModel
Expand Down Expand Up @@ -93,18 +104,52 @@ internal constructor(
val mnemonicPhraseState: LiveData<MnemonicPhraseState> get() = _mnemonicPhraseState

// Mock function to simulate the process
suspend fun mockCreateMnemonicPhrase(): MnemonicPhraseState? {
suspend fun mockCreateMnemonicPhrase(requestCaptcha: () -> Unit): MnemonicPhraseState? {
_mnemonicPhraseState.value = MnemonicPhraseState.Creating
delay(2000)
val sessionKey = generateEd25519KeyPair()
val publicKey = sessionKey.publicKey
val randomInt = (0..10).random()
val messageJsonString = GsonHelper.customGson.toJson(
AnonymousMessage(
randomInt.toString(),
nowInUtc()
)
)

if (Math.random() < 0.5) {
Timber.e(sessionKey.privateKey.base64Encode())
val signature = initFromSeedAndSign(sessionKey.privateKey.toTypedArray().toByteArray(), messageJsonString.encodeToByteArray())
val r = accountRepository.verification(
VerificationRequest(
purpose = VerificationPurpose.ANONYMOUS_SESSION.name,
public_key_hex = publicKey.hexString(),
message_hex = messageJsonString.sha3Sum256().hexString(),
signature_hex = signature.hexString(),
)
)
if (r.errorCode == NEED_CAPTCHA) {
requestCaptcha.invoke()
} else if (r.isSuccess) {
_mnemonicPhraseState.value = MnemonicPhraseState.Success
} else {
_mnemonicPhraseState.value = MnemonicPhraseState.Failure
}
return _mnemonicPhraseState.value
}

suspend fun anonymousRequest(publicKeyHex: String, messageHex: String, signatureHex: String, hCaptchaResponse: String? = null, gRecaptchaResponse: String? = null): MixinResponse<VerificationResponse> {
val r = accountRepository.verification(
VerificationRequest(
purpose = VerificationPurpose.ANONYMOUS_SESSION.name,
public_key_hex = publicKeyHex,
message_hex = messageHex,
signature_hex = signatureHex,
hCaptchaResponse = hCaptchaResponse,
gRecaptchaResponse = gRecaptchaResponse
)
)
return r
}

private val _setupState = MutableLiveData<SetupState>(SetupState.Loading)
val setupState: LiveData<SetupState> get() = _setupState

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import one.mixin.android.compose.theme.MixinAppTheme
fun MnemonicPhraseInput(
state: MnemonicState,
mnemonicList: List<String> = emptyList(),
onComplete: (List<String>) -> Unit,
onComplete: (List<String>) -> Unit
) {
var inputs by remember { mutableStateOf(List(13) { "" }) }
MixinAppTheme {
Expand Down
Loading

0 comments on commit 12e2fbf

Please sign in to comment.