show name derivation index as well in list

This commit is contained in:
Alain Brenzikofer
2026-08-29 15:53:15 +02:00
parent 0f10cf30b0
commit 2f0f8a7499
6 changed files with 44 additions and 25 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ needs a wallet, ETH and a browser:
service - metering is off chain.
- See what you own with `/names` and `/name info`, and find names your keys
already hold with `/name rescan`.
- Recovery keys: `/name keys` (names grouped by the account they sit under),
- Recovery keys: `/name keys` (names grouped by account, each with its index),
`keys export` (every key, not just the one in use), `keys import` (adds, never
replaces), `keys init`, `keys use <n> [<account> [<name>]]`.
- Recovering on a new device: `/name rescan` restores the derivation marks from
@@ -74,9 +74,12 @@ Recovery keys:
```
/name keys 1: (in use)
account 0 alice.simplex
account 0
name 0 alice.simplex
name 1 lizzy.simplex
2: (not written down)
other lucy.simplex (m)
other
lucy.simplex m
/name keys export every key's phrase, each labelled with the names
it controls - never just the one in use
@@ -88,8 +91,9 @@ Recovery keys:
/name keys use 2 0 5 ...and where its next name sits
```
Names are listed under the account they were derived at, because that grouping
is the only surviving record of which profile owned what — see *Recovery*.
Names are listed under the account they were derived at, with their index in it
— the two numbers `/name keys use` takes. That grouping is the only surviving
record of which profile owned what; see *Recovery*.
A new device, with nothing but the phrase:
@@ -457,8 +461,8 @@ it, not on chain, and not derivable:
* **Which profile held which account.** Recreate two profiles on a new device and
nothing says which was account 0. Only the user knows, so `/name keys` groups
names by the account in their stored path and `/name keys use <n> <account>`
pins a profile back to one.
names by the account and index in their stored path, and
`/name keys use <n> <account>` pins a profile back to one.
* **Which indices are already taken.** Both high-water marks — `next_account_index`
per seed, `wallet_next_name_index` per profile — start at 0 after an import,
while accounts and names already exist under the phrase. A scan is the only
+1 -1
View File
@@ -861,7 +861,7 @@ data ChatResponse
| CRNameInfo {user :: User, regName :: Text, regOwner :: Text, regPath :: Text, nameContact :: [Text], nameChannel :: [Text], regExpiry :: UTCTime, nameEditsLeft :: Word32}
| CRNameLinkSet {user :: User, regName :: Text, nameRecord :: Text, regTxHash :: TxHash}
| CRNameRescan {user :: User, namesFound :: [(Text, Text)]}
| CRNameKeys {user :: User, walletKeys :: [(Int, [(Maybe AccountIndex, [(Text, Text)])], Bool, Bool)]}
| CRNameKeys {user :: User, walletKeys :: [(Int, [(Maybe AccountIndex, [(Maybe NameIndex, Text, Text)])], Bool, Bool)]}
| CRNameKeyPhrases {user :: User, walletPhrases :: [(Int, Text, [Text])]}
| CRUserAcceptedGroupSent {user :: User, groupInfo :: GroupInfo, hostContact :: Maybe Contact}
| CRUserDeletedMembers {user :: User, groupInfo :: GroupInfo, members :: [GroupMember], withMessages :: Bool, msgSigned :: Bool}
+11 -6
View File
@@ -66,7 +66,7 @@ import Simplex.Chat.Names.Protocol
import Simplex.Chat.Names.Snrc (Intent (..), SnrcDeployment (..), intent712, parseRecordKey)
import Simplex.Messaging.Eth.Address (Address, mkAddress)
import Simplex.Chat.Store.Wallets (bindSeedAccount, boundAccount, createSeed, currentSeed, getNameKeys, getOrCreateAccountRef, listSeeds, markBackedUp, nameKeyPathTaken, raiseNextAccountIndex, raiseNextNameIndex, recordNameKey, seedOfName, setCurrentSeed, setNextNameIndex, takeNameIndex)
import Simplex.Chat.Wallet (AccountIndex, AccountRef (..), SeedId, WalletAccount, WalletSeed (..), accountAddress, deriveAtPath, deriveNameKey, ethSignatureBytes, importRecoveryKey, newSeed, parseNameKeyPath, recoveryKeyPhrase, renderNameKeyPath, signIntent)
import Simplex.Chat.Wallet (AccountIndex, AccountRef (..), NameIndex, SeedId, WalletAccount, WalletSeed (..), accountAddress, deriveAtPath, deriveNameKey, ethSignatureBytes, importRecoveryKey, newSeed, parseNameKeyPath, recoveryKeyPhrase, renderNameKeyPath, signIntent)
import Simplex.Chat.Call
import Simplex.Chat.Controller
import Simplex.Chat.Delivery (DeliveryJobScope (..), DeliveryJobSpec (..), DeliveryWorkerScope (..))
@@ -5246,13 +5246,18 @@ deriveNameOwner user = do
taken <- withFastStore' $ \db -> nameKeyPathTaken db sId path
if taken then freeNameIndex sId acctIx (tries + 1) else pure (nameIx, path)
-- | Names a seed owns, grouped by the account they were derived under. Names on
-- a layout that is not ours have no account of ours and group under Nothing.
groupByAccount :: [(Text, Text)] -> [(Maybe AccountIndex, [(Text, Text)])]
-- | Names a seed owns, grouped by the account they were derived under and
-- carrying the name index within it — the two numbers @\/name keys use@ takes.
-- Names on a layout that is not ours have neither, and group under Nothing.
groupByAccount :: [(Text, Text)] -> [(Maybe AccountIndex, [(Maybe NameIndex, Text, Text)])]
groupByAccount ns =
-- stable, so the accounts keep their order and the ungrouped names sort last
sortOn (isNothing . fst) . M.toAscList . M.fromListWith (flip (<>)) $
map (\(n, path) -> (fst <$> parseNameKeyPath path, [(n, path)])) ns
map (fmap (sortOn (\(ix_, _, _) -> ix_))) . sortOn (isNothing . fst) . M.toAscList . M.fromListWith (flip (<>)) $
map entry ns
where
entry (n, path) = case parseNameKeyPath path of
Just (acctIx, nameIx) -> (Just acctIx, [(Just nameIx, n, path)])
Nothing -> (Nothing, [(Nothing, n, path)])
-- | Names this device holds a key for, with the path each key sits at.
ownedNames :: User -> CM [(Text, Text)]
+12 -7
View File
@@ -232,13 +232,18 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, showFullLinks, te
-- the phrase, so after importing on a new device this listing is what tells
-- the user which account to pin a profile back to.
CRNameKeys u rows ->
let name acct (n, path) = if isJust acct then n else n <> " (" <> path <> ")"
accountRow (acct, ns) =
let pad w t = t <> T.replicate (max 1 (w - T.length t)) " "
-- the index is shown because it is the third argument of
-- /name keys use; a name on a foreign layout has none, so it shows the
-- path that replaces it
nameRow (ix_, n, path) =
plain $
" "
<> maybe "other " (\a -> "account " <> tshow a) acct
<> " "
<> T.intercalate ", " (map (name acct) ns)
" "
<> pad 9 (maybe "" (\ix -> "name " <> tshow ix) ix_)
<> n
<> maybe (" " <> path) (const "") ix_
accountRow (acct, ns) =
plain (" " <> maybe "other" (\a -> "account " <> tshow a) acct) : map nameRow ns
keyRow (i, groups, cur, backed) =
plain
( tshow i
@@ -246,7 +251,7 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, showFullLinks, te
<> (if cur then " (in use)" else "")
<> (if backed then "" else " (not written down)")
)
: if null groups then [" no names yet"] else map accountRow groups
: if null groups then [" no names yet"] else concatMap accountRow groups
in ttyUser u $ case rows of
[] -> ["no recovery keys yet - one is created when you buy a name"]
_ -> concatMap keyRow rows
+9 -4
View File
@@ -154,7 +154,8 @@ testNameAddress ps =
-- and now exactly one key exists, controlling the name just bought
client ##> "/name keys"
client <## "1: (in use) (not written down)"
client <## " account 0 carolname.simplex"
client <## " account 0"
client <## " name 0 carolname.simplex"
-- | Recovery on a new device must not re-use a key a recovered name already
-- owns. Neither high-water mark survives an import - the phrase records only
@@ -197,8 +198,10 @@ testRecoverMarks ps = do
-- the scan moved both marks past index 0, so the new name is elsewhere
client ##> "/name keys"
client <## "1: (in use) (not written down)"
client <## " account 0 lostname.simplex"
client <## " account 1 foundname.simplex"
client <## " account 0"
client <## " name 0 lostname.simplex"
client <## " account 1"
client <## " name 1 foundname.simplex"
-- | A scan is one round trip per candidate path, taken sequentially, and
-- outruns the harness's five second per-line read timeout. Retrying is not
@@ -263,7 +266,9 @@ testSeedPersists ps = do
withTestChat ps "client" $ \client -> do
client ##> "/name keys"
client <## "1: (in use) (not written down)"
client <## " account 0 firstname.simplex, secondname.simplex"
client <## " account 0"
client <## " name 0 firstname.simplex"
client <## " name 1 secondname.simplex"
-- | Reads past startup and progress lines to the registration result, returning
-- the owner address. Keeps reading until the final progress event has arrived