Merge pull request #389 from greenart7c3/claude/mnemonic-account-path-option-fnVSY

Add mnemonic account index support for BIP-39 key derivation
This commit is contained in:
greenart7c3
2026-04-13 07:56:13 -03:00
committed by GitHub
4 changed files with 34 additions and 7 deletions
@@ -74,7 +74,7 @@ class AccountStateViewModel(npub: String?) : ViewModel() {
tryLoginExistingAccount(route, npub)
}
fun isValidKey(key: String, password: String): Pair<KeyPair?, String> {
fun isValidKey(key: String, password: String, accountIndex: Long = 0): Pair<KeyPair?, String> {
try {
val signer =
if (key.startsWith("ncryptsec")) {
@@ -83,7 +83,7 @@ class AccountStateViewModel(npub: String?) : ViewModel() {
} else if (key.startsWith("nsec")) {
NostrSignerInternal(KeyPair(privKey = key.bechToBytes()))
} else if (key.contains(" ") && Nip06().isValidMnemonic(key)) {
val keyPair = KeyPair(privKey = Nip06().privateKeyFromMnemonic(key))
val keyPair = KeyPair(privKey = Nip06().privateKeyFromMnemonic(key, accountIndex))
NostrSignerInternal(keyPair)
} else {
NostrSignerInternal(KeyPair(Hex.decode(key)))
@@ -833,6 +833,7 @@ fun LoginPage(
var isMnemonicMode by rememberSaveable { mutableStateOf(false) }
var mnemonicWordCount by rememberSaveable { mutableIntStateOf(12) }
var mnemonicWords by remember { mutableStateOf(List(12) { "" }) }
var mnemonicAccountIndex by rememberSaveable { mutableIntStateOf(0) }
val clipboardManager = LocalClipboard.current
Scaffold(
@@ -957,6 +958,28 @@ fun LoginPage(
}
},
)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(18.dp),
value = if (mnemonicAccountIndex == 0) "" else mnemonicAccountIndex.toString(),
onValueChange = { value ->
mnemonicAccountIndex = value.filter { it.isDigit() }.toIntOrNull() ?: 0
if (errorMessage.isNotEmpty()) errorMessage = ""
},
label = { Text(stringResource(R.string.mnemonic_account_index)) },
placeholder = { Text("0") },
supportingText = {
Text(stringResource(R.string.mnemonic_account_index_description, mnemonicAccountIndex))
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done,
),
singleLine = true,
)
} else {
Text(
stringResource(R.string.setup_amber_with_your_nostr_private_key_you_can_enter_different_versions_nsec_ncryptsec_or_hex_you_can_also_scan_it_from_a_qr_code),
@@ -1161,7 +1184,7 @@ fun LoginPage(
Amber.instance.applicationIOScope.launch {
isLoading = true
try {
val isValid = accountViewModel.isValidKey(mnemonicStr, "")
val isValid = accountViewModel.isValidKey(mnemonicStr, "", mnemonicAccountIndex.toLong())
isLoading = false
if (isValid.first != null) {
keyPair = isValid.first!!
@@ -149,10 +149,12 @@ private fun WordField(
nextFocusRequester: FocusRequester?,
imeAction: ImeAction,
) {
// Tracks the last word that was chosen from the dropdown so we can suppress
// the menu after selection (selecting changes `word`, which would otherwise
// re-open the menu immediately).
var selectedWord by remember { mutableStateOf("") }
// `remember(word)` resets this state synchronously whenever `word` changes.
// Initialising to `word` when it is already a complete BIP-39 entry
// suppresses the dropdown immediately — no async gap — covering both the
// paste case (word set externally, onValueChange never called) and the
// case where the user finishes typing a valid word.
var selectedWord by remember(word) { mutableStateOf(if (word in wordList) word else "") }
val suggestions = remember(word) {
if (word.length >= 2) {
+2
View File
@@ -385,6 +385,8 @@
<string name="mnemonic_12_words">12 words</string>
<string name="mnemonic_24_words">24 words</string>
<string name="mnemonic_words_entered">%1$d of %2$d words entered</string>
<string name="mnemonic_account_index">Account index</string>
<string name="mnemonic_account_index_description">Use a different index if this mnemonic was used to create multiple accounts (m/44\'/1237\'/%1$d\'/0/0)</string>
<string name="sign_policy">Sign policy</string>
<string name="handle_application_permissions">How to handle this application permissions?</string>
<string name="sign_policy_manual_new_app">Manually approve each permission</string>