Suppose I have devices which broadcast, on an insecure channel, 32-bit addresses. I want to make these devices untraceable - one way to do so is to allocate for each device a distinct subset of address, for example $X=256$ addresses for each device with no repetitions, and make each device change its address from time to time (to a different number in its subset). A secure receiver, who knows how the addresses-subsets are separated, could trace the origin device of the address.
However, this would require to store $X$ addresses on each device.
Another way is finding some injective function $f:Z_{2^{32}} \to Z_{2^{32}}$, where the first device will calculate on-the-fly the addresses $f(0), ..., f(X-1)$, the second device will calculate $f(X), ..., f(2X-1)$, etc... However, if the code of this function falls to the wrong hands, the whole mechanism becomes worthless (I'm talking about physically breaking and reading the code secrets of one of the devices, not only reading its addresses from outside).
A third way is to have a different salt in each device and a CSPRNG executed $X$ times per device - leaving out salts that generate addresses that are "already taken" by previous devices. In this case, after some time (around $2^{32}/X$ addresses, $2^{32}/{X^2}$ devices), it will become hard to find salts that do not generate "already taken" addresses.
Is there a mechanism that is both memory efficient and secure, i.e. breaking one device won't give too much information about the addresses of other devices (except that they are different from those of the broken device), without loosing too much of the address space (keep generating "devices" easily)?
Thanks
--- EDIT ---
Fourth way: Similar to the third way, I can create create pairs of $(s_1, l_1), (s_2, l_2), ...$ where $s_i$ are randomly chosen salts and $0 < l_i \leq L$ is the number of times I can execute the CSPRNG on $s_i$ before getting a collision with an address generated by a previous $s_j$ (for $j < i$).
Then all I have to do is to give some $(s_i, l_i)$ pairs to each device so it could generate the amount of addresses I want. For example, if every $l$ is a powers of 2, I could make devices that have pairs like: $(s_{i_1}, 128), (s_{i_2}, 64), (s_{i_3}, 32), (s_{i_3}, 16), (s_{i_4}, 8), ...$, and when I run out of $(s, 128)$ pairs, have devices that have: $(s_{i_1}, 64), (s_{i_2}, 64), (s_{i_3}, 64), (s_{i_4}, 32), (s_{i_5}, 16), (s_{i_6}, 8), ...$
I am not sure about the average amount of memory I need on each device, but I know that the generated addresses can use much more than $1/256$ of the address space (suggested in the "third way"). Obviously, when it comes to pairs like $(s, 1)$ it would be more efficient to simply store the generated number...
I think there is a fundamental problem with what I'm trying to do. Informally, I am trying to allocate random numbers to each device and "compress" them. The only way to do that is to generate the numbers pseudo-randomly based on a secret, but then other devices must "know" that secret to prevent collisions - i.e. if a device is compromised then the secrets of other devices are lost too. An asymmetric mechanism is needed - knowing that an address is taken by a different device without knowing the other device's secret. I don't believe it's possible with 32-bit numbers.