Is it possible to implement the Signal Protocol without a server?
Expanding from the whiteboard example as that is just a transparent central server.

This is not an issue although with a different model, you just need to decide which properties you care about.


Without a trusted entity maintaining the lookup of verified phone numbers to public keys, you will need to rely on another means. I recommend using the public key directly to effectively null out this layer while keeping it orthogonal such that lookups may exist in the future.
Without a trusted store-and-forward service you'll need to establish a rendezvous point such a DHT or wrap another chat protocol that knows nothing about Signal. Wrapping another protocol can solve (1) opportunistically, although the service (like Signal) may return keys it controls and is just as trusted and centralized.
If you do not have a store-and-forward service then the signed prekeys are redundant. This does not reduce forward secrecy, but it does require the 3DH handshake to occur before messages may be sent. So no 0-RTT semi-forward-secure messages.
If delivery is synchronous and ordered you can simplify double-ratchet.
You may use an anonymity network such as Tor to hide who is talking with whom. Each user could be their own ed25519.onion service. But if they are, then double-ratchet and 3DH are both redundant and we've got ricochet.im.


Social graphs and deniability depend on the above choices. I.e. ricochet.im hides the social graph great using onion routing; if you want better, you'll need a mixnet. Or if you forward messages over some existing service, they can see the social graph prove you communicate and record the ciphertexts forever to verify any revealed keys and plaintexts.



On another note: you might want to take a look at matrix.org's libolm, a double-ratchet implementation that does not assume server cooperation.