Make it safe to set NumDirectoryGuards=1
Currently we have NumEntryGuards=1, which mitigates the "you expose your 3-hop circuits to too many relays" issue, but we still have NumDirectoryGuards=3, which means the "you have a unique guard fingerprint with respect to a local network observer" issue remains.
We should reduce NumDirectoryGuards to 1, but that's hard for now because, as Nick has pointed out, an attacking guard can withhold certain descriptors from a client to influence its path selection and thus harm its anonymity.
My first thought for a fix to that issue is that if you can't get all the microdescriptors you want from your guard, you should drop it and find a new one. But I think that could produce some really bad behavior network-wide if there are some microdescriptors that are hard to find in the network, or if there are race conditions where a consensus lists a relay but the guard doesn't quite have the microdescriptor for it yet so all its clients jump ship. Yuck.
My second thought for a fix is that if you can't get all the descriptors you want, but you can get some, then you could make a two-hop circuit through your guard to another relay, and ask it for the descriptors. That sounds great, except your guard could say "sorry that one's unreachable" and end up influencing which ones you can ask questions to, ending up with a variant of the original attack.
Nick suggested an extension of this idea where you try asking a few relays indirectly (using 2-hop circuits) and if you still can't get all your microdescriptors then you add another directory guard directly. I think that leads to the research question: how often does that situation happen naturally?
Specifically, if you pick three directory guards right now, what is the probability distribution over fetching a given fraction of the microdescriptors from the consensus? I can't imagine you'll get 100% of them consistently.
Once we have some expected threshold that we reach pretty consistently, should we call that good enough, since it matches what we get right now currently? Or should we worry that one guard could somehow cause the missing descriptors to be more precisely chosen than in the current design?
Another idea I had was to extend from the guard to a directory authority, since surely those won't all be down. That's sort of sad to make a bottleneck though.
Yet another idea is to do a three-hop circuit, so the guard doesn't know the chosen third hop. That ties directly into the path-bias field I guess.
Taking a step back: how about a simpler design where you do a 2-hop circuit through your guard, and you try like n=10 of them, and so long as k=3 of the extends succeed, you believe that the microdescriptor is indeed hard to get, and otherwise you ask another directory guard directly?
Lots of options -- somebody should analyze them, and ideally show that one of the simpler ones is adequate.