mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-09-27 17:58:47 +00:00
core: refuse an account index BIP-32 cannot harden, whichever way it arrives
The counter path checked it, the explicit one did not, and the parser is not the only caller: processChatCommand takes APIWalletBind from library callers too. hardened leaves an index at or above 2^31 alone, so account i and i + 2^31 are the same key while the duplicate check compares the stored integers, and two profiles could hold one account's names. The rfc now says what hiding a profile from /_wallet does and does not protect: the seed is one per device, so whoever unlocks any profile can derive every account. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rvc3HbiWBTqbAvRT45G5oX
This commit is contained in:
co-authored by
Claude Opus 5
parent
3ad17ca477
commit
472ee5ef10
@@ -124,6 +124,18 @@ import, so `/_wallet bind` with no account can hand out one that already owns
|
||||
names. Only a scan of owned names can restore the mark, and that lands with the
|
||||
registrar.
|
||||
|
||||
## Hidden profiles
|
||||
|
||||
`/_wallet` names the other profiles on the seed and never numbers them, so a
|
||||
hidden profile leaves no gap in a list of account indexes.
|
||||
|
||||
That hides its existence from the listing, and nothing more. The seed is one per
|
||||
device, so whoever unlocks any profile can export the phrase and derive every
|
||||
account, including a hidden profile's. A hidden profile's names are not
|
||||
pseudonymous against someone who already holds the device and one password. This
|
||||
is a consequence of one seed per device, and a key per profile rather than per
|
||||
device is what would change it.
|
||||
|
||||
## Scope
|
||||
|
||||
Not here, and unchanged from the prototype: buying a name, the names protocol,
|
||||
|
||||
@@ -88,20 +88,22 @@ bindAccountIndex db user@User {userId} sId = \case
|
||||
if acct >= 0x80000000
|
||||
then pure $ Left "no free account on this key"
|
||||
else Right () <$ bindUser db userId sId acct
|
||||
Just acct -> do
|
||||
taken <-
|
||||
maybeFirstRow fromOnly $
|
||||
DB.query
|
||||
db
|
||||
"SELECT 1 FROM users WHERE wallet_seed_id = ? AND wallet_account_index = ? AND user_id != ?"
|
||||
(sId, fromIntegral acct :: Int64, userId)
|
||||
case (taken :: Maybe Int64) of
|
||||
Just _ -> pure $ Left "another profile uses this account"
|
||||
Nothing -> do
|
||||
bindUser db userId sId (fromIntegral acct)
|
||||
-- the counter moves past it, so the next profile is not handed the same one
|
||||
setNextAccountIndex db sId (fromIntegral acct + 1)
|
||||
pure $ Right ()
|
||||
Just acct
|
||||
| acct >= 0x80000000 -> pure $ Left "account index too large"
|
||||
| otherwise -> do
|
||||
taken <-
|
||||
maybeFirstRow fromOnly $
|
||||
DB.query
|
||||
db
|
||||
"SELECT 1 FROM users WHERE wallet_seed_id = ? AND wallet_account_index = ? AND user_id != ?"
|
||||
(sId, fromIntegral acct :: Int64, userId)
|
||||
case (taken :: Maybe Int64) of
|
||||
Just _ -> pure $ Left "another profile uses this account"
|
||||
Nothing -> do
|
||||
bindUser db userId sId (fromIntegral acct)
|
||||
-- the counter moves past it, so the next profile is not handed the same one
|
||||
setNextAccountIndex db sId (fromIntegral acct + 1)
|
||||
pure $ Right ()
|
||||
|
||||
-- | So two profiles cannot be handed the same account.
|
||||
takeAccountIndex :: DB.Connection -> SeedId -> IO Int64
|
||||
|
||||
Reference in New Issue
Block a user