diff --git a/docs/rfcs/2026-09-10-wallet-keys.md b/docs/rfcs/2026-09-10-wallet-keys.md index f2923dd2c7..ecf5215a2a 100644 --- a/docs/rfcs/2026-09-10-wallet-keys.md +++ b/docs/rfcs/2026-09-10-wallet-keys.md @@ -68,7 +68,7 @@ Internal API. The names commands will call these; users will not. ``` /_wallet the next name addresses /_wallet create new generate the seed -/_wallet create mnemonic= take the seed from a mnemonic +/_wallet create mnemonic= take the entropy from a mnemonic /_wallet export the seed mnemonic /_wallet export one derived secret, as 0x and 64 hex digits /_wallet delete delete the seed @@ -86,7 +86,7 @@ not leave the device, and the raw command would be logged there. ```sql CREATE TABLE wallet_seeds ( wallet_seed_id INTEGER PRIMARY KEY AUTOINCREMENT, - seed BLOB NOT NULL, + entropy BLOB NOT NULL, next_name_index INTEGER NOT NULL DEFAULT 1, single_seed INTEGER NOT NULL DEFAULT 1 ); @@ -94,6 +94,11 @@ CREATE TABLE wallet_seeds ( No other table is touched. +What is stored is the BIP-39 entropy, 16 to 32 bytes. The mnemonic comes back +from it exactly, as the entropy and its checksum determine the words, and that +is what export returns. The seed itself, the 64 bytes PBKDF2 derives from the +mnemonic, is computed when a key is needed and never stored. + `next_name_index` is a high-water mark, not a count of names held. A name a device no longer tracks still owns its address, so an index is never reused. diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20260908_wallet_seeds.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20260908_wallet_seeds.hs index d8617d0841..7dd970d8f6 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/M20260908_wallet_seeds.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations/M20260908_wallet_seeds.hs @@ -11,7 +11,7 @@ m20260908_wallet_seeds = [r| CREATE TABLE wallet_seeds ( wallet_seed_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - seed BYTEA NOT NULL, + entropy BYTEA NOT NULL, -- see the SQLite migration next_name_index BIGINT NOT NULL DEFAULT 1, -- one seed per device for now diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql index c5e5fa52e5..1cdf43e011 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql @@ -1535,7 +1535,7 @@ ALTER TABLE test_chat_schema.users ALTER COLUMN user_id ADD GENERATED ALWAYS AS CREATE TABLE test_chat_schema.wallet_seeds ( wallet_seed_id bigint NOT NULL, - seed bytea NOT NULL, + entropy bytea NOT NULL, next_name_index bigint DEFAULT 1 NOT NULL, single_seed smallint DEFAULT 1 NOT NULL ); diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20260908_wallet_seeds.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20260908_wallet_seeds.hs index a2fdc2b500..9f3b0468c9 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/M20260908_wallet_seeds.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20260908_wallet_seeds.hs @@ -10,7 +10,7 @@ m20260908_wallet_seeds = [sql| CREATE TABLE wallet_seeds ( wallet_seed_id INTEGER PRIMARY KEY AUTOINCREMENT, - seed BLOB NOT NULL, -- BIP-39 entropy, 16-32 bytes + entropy BLOB NOT NULL, -- known issue: after an import this starts at 1, so it can hand out a name -- key at a path that already owns a name next_name_index INTEGER NOT NULL DEFAULT 1, diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql index 80c72a42e7..a3c86389db 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql @@ -854,7 +854,7 @@ CREATE TABLE rcv_roster_transfers( ) STRICT; CREATE TABLE wallet_seeds( wallet_seed_id INTEGER PRIMARY KEY AUTOINCREMENT, - seed BLOB NOT NULL, -- BIP-39 entropy, 16-32 bytes + entropy BLOB NOT NULL, -- known issue: after an import this starts at 1, so it can hand out a name -- key at a path that already owns a name next_name_index INTEGER NOT NULL DEFAULT 1, diff --git a/src/Simplex/Chat/Store/Wallets.hs b/src/Simplex/Chat/Store/Wallets.hs index bd09feb3a0..a9a098b96c 100644 --- a/src/Simplex/Chat/Store/Wallets.hs +++ b/src/Simplex/Chat/Store/Wallets.hs @@ -28,7 +28,7 @@ toSeed (sId, seed) = WalletSeed {wsId = sId, wsEntropy = seed} getDeviceSeed :: DB.Connection -> IO (Maybe WalletSeed) getDeviceSeed db = maybeFirstRow toSeed $ - DB.query_ db "SELECT wallet_seed_id, seed FROM wallet_seeds ORDER BY wallet_seed_id LIMIT 1" + DB.query_ db "SELECT wallet_seed_id, entropy FROM wallet_seeds ORDER BY wallet_seed_id LIMIT 1" -- | The index the next name bought on this device takes. getNextNameIndex :: DB.Connection -> SeedId -> IO NameIndex @@ -43,7 +43,7 @@ createSeed :: DB.Connection -> ByteString -> IO Bool createSeed db entropy = getDeviceSeed db >>= \case Just _ -> pure False - Nothing -> True <$ DB.execute db "INSERT INTO wallet_seeds (seed) VALUES (?)" (Only $ DB.Binary entropy) + Nothing -> True <$ DB.execute db "INSERT INTO wallet_seeds (entropy) VALUES (?)" (Only $ DB.Binary entropy) deleteSeed :: DB.Connection -> SeedId -> IO () deleteSeed db sId = DB.execute db "DELETE FROM wallet_seeds WHERE wallet_seed_id = ?" (Only sId)