This scheme has several major issues.
First off, don't roll your own, i.e., don't try to invent your own cryptographic schemes. There are many ideas which look good on paper but turn out to be fatally flawed. Note that even experts make mistakes, so the only valid schemes are those which have gone through years of peer reviews, practical applications and revisions.
Secondly, you talk about how the attacker first has to “guess” the algorithm before they can “potentially” derive all seeds. Trying to keep the algorithm secret is security through obscurity and a well-known anti-pattern in cryptography. You should always assume that attackers know the algorithm. So in your case, they can in fact derive all seeds when they know the initial seed.
Then there's a misunderstanding of what a salt is. The purpose of salts is that they are an additional parameter besides the hash input, usually random byte sequences. Your “deterministic salts” aren't salts at all – the entire scheme only depends on the hash input and nothing else. You can see that your scheme is fundamentally broken when you imagine that it's applied to general passphrases, not just seed passphrases (which are designed to have very high entropy). An attacker could precalculate the results of your scheme for many possible passphrases and create a lookup table to speed up future attacks – this is exactly what salts are supposed to prevent.
Last but not least, a lot of design choices look completely arbitrary. For example, your “salts” are generated by taking the seed phrase, repeating the first n
words of the same phrase and then hashing this string – it's entirely unclear what this is supposed to achieve.
Instead of trying to invent your own cryptographic schemes, use standard solutions. In your case, you want a key derivation which takes high-entropy input (the initial seed passphrase) and then expands it. A suitable choice would be HKDF which is available in the SubtleCrypto API.
As pseudo-code:
parameters:
initial_seed_passphrase
derived_seed_passphrases_count
hkdf_salt = generate_random_bytes(16)
# calculate required length of HKDF output
# I'm assuming 128 bits of initial entropy per seed passphrase
entropy_length_for_derived_seed_passphrases = derived_seed_passphrases_count * 128
# calculate initial entropy for derived seed passphrases with HKDF
entropy_for_derived_seed_passphrases = HKDF(
input: initial_seed_passphrase,
hash: "SHA-256",
salt: hkdf_salt,
length: entropy_length_for_derived_seed_passphrases
)
derived_seed_passphrases = []
for i from 0 to derived_seed_passphrases_count - 1:
# use 128-bit slice of the HKDF result as initial entropy
derived_seed_entropy = entropy_for_derived_seed_passphrases[16 * i, 16]
derived_seed_passphrase = standard_bip39_procedure(derived_seed_entropy)
derived_seed_passphrases += [derived_seed_passphrase]
Note that the random salt isn't secret and can therefore be stored as plaintext in arbitrary locations.
Using PBKDF2 instead of HKDF wouldn't make sense, because this algorithm is meant for low entropy input like user-chosen passwords and therefore has a cost factor to slow down brute-force attacks. This doesn't apply here. The seed passphrase is high-entropy input (at least 128 bits).
salt_n
to the new seed phrase? are the words coming from a common word-list (bip39)??entropyToMnemonic
to derive the new seed phrase