Accept mnemonic as hex in QR codes

This commit is contained in:
Andrew 2024-06-26 18:01:33 +09:30
parent 578c471f1e
commit ff6c0fb6f5
5 changed files with 50 additions and 8 deletions

View File

@ -4,13 +4,11 @@ import android.app.Application
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -18,7 +16,6 @@ import network.loki.messenger.R
import org.session.libsignal.crypto.MnemonicCodec import org.session.libsignal.crypto.MnemonicCodec
import org.session.libsignal.crypto.MnemonicCodec.DecodingError.InputTooShort import org.session.libsignal.crypto.MnemonicCodec.DecodingError.InputTooShort
import org.session.libsignal.crypto.MnemonicCodec.DecodingError.InvalidWord import org.session.libsignal.crypto.MnemonicCodec.DecodingError.InvalidWord
import org.session.libsignal.utilities.Hex
import org.thoughtcrime.securesms.crypto.MnemonicUtilities import org.thoughtcrime.securesms.crypto.MnemonicUtilities
import javax.inject.Inject import javax.inject.Inject
@ -48,7 +45,7 @@ internal class LinkDeviceViewModel @Inject constructor(
fun onContinue() { fun onContinue() {
viewModelScope.launch { viewModelScope.launch {
try { try {
decode(state.value.recoveryPhrase).let(::onSuccess) codec.decodeAsByteArray(state.value.recoveryPhrase).let(::onSuccess)
} catch (e: Exception) { } catch (e: Exception) {
onFailure(e) onFailure(e)
} }
@ -58,7 +55,7 @@ internal class LinkDeviceViewModel @Inject constructor(
fun onScanQrCode(string: String) { fun onScanQrCode(string: String) {
viewModelScope.launch { viewModelScope.launch {
try { try {
decode(string).let(::onSuccess) codec.decodeMnemonicOrHexAsByteArray(string).let(::onSuccess)
} catch (e: Exception) { } catch (e: Exception) {
onQrCodeScanFailure(e) onQrCodeScanFailure(e)
} }
@ -68,6 +65,7 @@ internal class LinkDeviceViewModel @Inject constructor(
fun onChange(recoveryPhrase: String) { fun onChange(recoveryPhrase: String) {
state.value = State(recoveryPhrase) state.value = State(recoveryPhrase)
} }
private fun onSuccess(seed: ByteArray) { private fun onSuccess(seed: ByteArray) {
viewModelScope.launch { _events.emit(LoadAccountEvent(seed)) } viewModelScope.launch { _events.emit(LoadAccountEvent(seed)) }
} }
@ -87,6 +85,4 @@ internal class LinkDeviceViewModel @Inject constructor(
private fun onQrCodeScanFailure(error: Throwable) { private fun onQrCodeScanFailure(error: Throwable) {
viewModelScope.launch { _qrErrors.emit(error) } viewModelScope.launch { _qrErrors.emit(error) }
} }
private fun decode(mnemonic: String) = codec.decode(mnemonic).let(Hex::fromStringCondensed)!!
} }

View File

@ -8,6 +8,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material.Card import androidx.compose.material.Card
import androidx.compose.material.Icon import androidx.compose.material.Icon
@ -30,6 +31,7 @@ import androidx.compose.ui.unit.dp
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import network.loki.messenger.R import network.loki.messenger.R
import org.thoughtcrime.securesms.ui.LocalDimensions
import org.thoughtcrime.securesms.ui.color.LocalColors import org.thoughtcrime.securesms.ui.color.LocalColors
import org.thoughtcrime.securesms.util.QRCodeUtilities import org.thoughtcrime.securesms.util.QRCodeUtilities
@ -69,6 +71,7 @@ private fun Content(
) { ) {
Box( Box(
modifier = modifier modifier = modifier
.padding(LocalDimensions.current.xxxsItemSpacing)
.fillMaxWidth() .fillMaxWidth()
.aspectRatio(1f) .aspectRatio(1f)
) { ) {

View File

@ -19,7 +19,7 @@ object QRCodeUtilities {
): Bitmap? = runCatching { ): Bitmap? = runCatching {
val hints = hashMapOf( val hints = hashMapOf(
EncodeHintType.MARGIN to 0, EncodeHintType.MARGIN to 0,
EncodeHintType.ERROR_CORRECTION to ErrorCorrectionLevel.H EncodeHintType.ERROR_CORRECTION to ErrorCorrectionLevel.M
) )
val color = if (isInverted) light else dark val color = if (isInverted) light else dark
val background = if (isInverted) dark else light val background = if (isInverted) dark else light

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,6 @@
package org.session.libsignal.crypto package org.session.libsignal.crypto
import org.session.libsignal.utilities.Hex
import java.util.zip.CRC32 import java.util.zip.CRC32
/** /**
@ -40,6 +41,9 @@ class MnemonicCodec(private val loadFileContents: (String) -> String) {
object VerificationFailed : DecodingError("Your mnemonic couldn't be verified. Please check what you entered and try again.") object VerificationFailed : DecodingError("Your mnemonic couldn't be verified. Please check what you entered and try again.")
} }
/**
* Accepts a [hexEncodedString] and return s a mnemonic.
*/
fun encode(hexEncodedString: String, languageConfiguration: Language.Configuration = Language.Configuration.english): String { fun encode(hexEncodedString: String, languageConfiguration: Language.Configuration = Language.Configuration.english): String {
var string = hexEncodedString var string = hexEncodedString
val language = Language(loadFileContents, languageConfiguration) val language = Language(loadFileContents, languageConfiguration)
@ -68,6 +72,9 @@ class MnemonicCodec(private val loadFileContents: (String) -> String) {
}.joinToString(" ") }.joinToString(" ")
} }
/**
* Accepts a [mnemonic] and returns a hexEncodedString
*/
fun decode(mnemonic: String, languageConfiguration: Language.Configuration = Language.Configuration.english): String { fun decode(mnemonic: String, languageConfiguration: Language.Configuration = Language.Configuration.english): String {
val words = mnemonic.split(" ") val words = mnemonic.split(" ")
val language = Language(loadFileContents, languageConfiguration) val language = Language(loadFileContents, languageConfiguration)
@ -107,6 +114,18 @@ class MnemonicCodec(private val loadFileContents: (String) -> String) {
}.joinToString(separator = "") { it } }.joinToString(separator = "") { it }
} }
fun decodeAsByteArray(mnemonic: String): ByteArray = decode(mnemonic = mnemonic).let(Hex::fromStringCondensed)
fun decodeMnemonicOrHexAsByteArray(mnemonicOrHex: String): ByteArray = try {
decode(mnemonic = mnemonicOrHex).let(Hex::fromStringCondensed)
} catch (decodeException: Exception) {
try {
Hex.fromStringCondensed(mnemonicOrHex)
} catch (_: Exception) {
throw decodeException
}
}
private fun swap(x: String): String { private fun swap(x: String): String {
val p1 = x.substring(6 until 8) val p1 = x.substring(6 until 8)
val p2 = x.substring(4 until 6) val p2 = x.substring(4 until 6)