Formal Treatment#

This section is currently a start to a formal treatment of the snackabra design - this is early. The reality of the ~3 year development period has been to begin with a simple implementation using readily available building blocks, then iterating with input from internal discussions combined with progressively deeper research into academic work. We expect this to continue for some time, and we continue to appreciate any and all inputs, critiques, and pointers to relevant work.

Within the scope of tentative steps, we begin not with the core room (group) chat service, but with the storage service. This is for two reasons: firstly we have more confidence that any issues that arise with chat messaging can be addressed, given the large amount of work done in this space, whereas for the storage service, it feels like we are breaking some new ground, which is always worrisome. Probably for that reason, the storage design has attracted more initial interest and questions, which is the second reason to prioritize it.

Photos and Files (Blobs)#

For a new group chat service to be a credible alternative to the myriad existing alternatives, it needs these basic features: text chat (either one-to-one or group) and photo sharing (again, either one-to-one or group). Seen from a more abstract view, a minimal messaging fabric needs to be able to exchange simple (text) messages, as well as arbitrary blobs of data. We picked photos first for reasons explained elsewhere.

For the purposes of this section, we will refer to any blob of data as \(\mathcal{M}\). [1] Small messages (next section) are referred to as \(\mathcal{m}\). [2] Consider Alice wants to send \(\mathcal{M}\) to Bob:

  1. Alice creates a partial hash \(\mathfrak{H}_1(\mathcal{M})\). \(\mathfrak{H}_1\) and \(\mathfrak{H}_2\) are decompositions \(\mathfrak{H}(\mathcal{M}) = \mathfrak{H}_1(\mathcal{M}) | \mathfrak{H}_2(\mathcal{M})\). Alice sends this to the storage server, which returns a nonce \(\mathcal{iv}\) and salt \(\mathcal{s}\).

  2. The server mantains a mapping \(\mathcal{T}'(h)\longmapsto\langle\mathcal{iv},\mathcal{s}\rangle\) which relates half of the hash of the full plaintext to enryption parameters; if the values existed already, they are returned, otherwise, they are created on the fly (and saved and returned). [3] This is the point where deduplication occurs: any attempts to upload an identical message will always result in the same encryption parameters.

  3. Alice next generates an encryption key \(\mathcal{k}=\mathcal{K}(\mathfrak{H}_2(\mathcal{M}), \mathcal{s})\) from the second half of the hash of the plaintext message (and salted), then generates the cryptotext of the message \(\mathcal{C}=\mathscr{E}(\mathcal{k},\mathcal{iv}|\mathcal{M})\), and then constructs a new hash of the encrypted message \(\mathcal{c}=\mathcal{H}(\mathcal{C})\). Alice sends the full encrypted message \(\mathcal{C}\) to the server.

  4. The storage server maintains a second mapping \(\mathcal{T}''(c)\longmapsto\langle\mathcal{v},\mathcal{C}\rangle\), which simply relates a hash of the encrypted contents to the contents, as well as a random verification identifier \(\mathcal{v}\), which is returned only if the server receives the full encrypted message.

  5. At this point Alice has collected the full “manifest”: \(\langle\mathcal{H}(\mathcal{C}),\mathcal{k},\mathcal{iv},\mathcal{s},\mathcal{v}\rangle\), which can be sent to Bob.

When Bob wants to fetch the object, Bob sends \(\langle\mathcal{H}(\mathcal{C}),\mathcal{v}\rangle\) to the storage server which first confirms correct \(\mathcal{T}''(c)\longmapsto\langle\mathcal{v}\rangle\) and then returns \(\mathcal{C}\). Bob then has \(\mathcal{M}\) from \(\mathcal{D}(\mathcal{C},\mathcal{k},\mathcal{iv},\mathcal{s})\).

The storage server (obviously) never sees \(\mathcal{M}\). Furthermore, it doesn’t track an explicit relationship between \(\mathcal{M}\) and \(\mathcal{C}\) in any manner. [4]

Now, consider another user, Charles, who independently uploads the same object to share with some other party. The above process will play out the same, and the resulting \(\langle\mathcal{H}(\mathcal{C}),\mathcal{k},\mathcal{iv},\mathcal{s},\mathcal{v}\rangle\) will end up being identical. Thus, re-uploading or sharing (copying and distributing the manifest) results in the exact same data.

An outside adversary cannot determine what objects have been shared: all they can do is go through the above process and abort at some point, but won’t learn anything: they won’t receive the full manifest until all steps are completed, and at no point will the server act differently than if it had never seen the object.

The manifest is portable outside the system: it doesn’t matter if the manifest was shared within a chat room (see next section), or in some other manner (SMS, emailed, copied to file across flash USB, etc). Regardless of how Bob receives the manifest, Bob can ask for \(\mathcal{C}\) and can perform \(\mathcal{D}(\mathcal{C},\mathcal{k},\mathcal{iv},\mathcal{s})\).

Deduplication will occur at step “1” and “3”: when the server receives \(\mathcal{C}\) it calculates \(\mathcal{c}=\mathcal{H}(\mathcal{C})\), and if it has already seen it, it returns the stored value \(\mathcal{T}''(c)\longmapsto\langle\mathcal{v},\mathcal{C}\rangle\), otherwise it generates a new \(\mathcal{v}\) (and stores and returns it). Regardless, it goes through the motions of “storing” \(\mathcal{C}\), which will in effect be a no-op if it had already stored it.

The final result is a \(\mathcal{T}''(c)\longmapsto\langle\mathcal{v},\mathcal{C}\rangle\) storage system, that will only respond if you already have \(\mathcal{v}\), which you can only have if you either went through the above steps yourself, or somebody else did and gave you the results. And of course the resulting \(\mathcal{C}\) is of no use to you without \(\langle\mathcal{k},\mathcal{iv},\mathcal{s}\rangle\).

Future Design Directions#

The above design is the current one. The next generation will be a slight tweak: you can access either with the ID plus the verification, or a hash of both. It’s up to the shard server to decide on policy.

Ledger#

For a conversational (and more complete) exposition of how the Ledger currently works, see Storage Ledger Server. Note also that this (slightly more) formal presentation presuposes the next-generation usage of OPRF() functions (see this discussion). The Ledger complements the flow of uploading and sharing files and documents (blobs), thus make sure to have read those sections first.

A global ledger \(\mathfrak{D_1}\mathcal{(a)}\mapsto\mathcal{b}\) maintains account balance \(\mathcal{b}\) for every account \(\mathcal{a}\). Balance is a positive integer that defaults to zero (ergo all possible accounts ‘exist’), corresponding to bytes of storage. Account is either a Room Name, or one of two reserved account names that correspond to two special accounts: \(\mathcal{B_g}\) is the global budget for the service, and \(\mathcal{B_s}\) is the total spent so far.

Every Room maintains a budget \(\mathcal{B_r}\) that was assigned to it at creation by taking an initial budget from \(\mathcal{B_g}\) and adding it to both \(\mathcal{B_s}\) and \(\mathcal{B_r}\). [7]

When a client uploads an item \(\mathcal{M}\), it first needs to calculate what \(\mathcal{|C|}\) will become. [8] It first requests from the room to spend \(\mathcal{s = |C|}\) bytes out of the Room balance \(\mathcal{B_r}\).

If approved, the Room \(\mathcal{r}\) requests an identifier \(\mathcal{t}\) (elsewhere called <TID>) from the Ledger. [9] This is a random token generated by the Ledger which maintains \(\mathfrak{D_2}\mathcal{h(t)}\mapsto\langle\mathcal{s, u}\rangle\) where \(\mathcal{h()}\) is a hash function, \(\mathcal{s}\) is the size and \(\mathcal{u}\) is a boolean indicating if this has been ‘spent’ or not. Essentially, we are generating a local cryptocurrency token of sorts, that can be traded for an upload of \(\mathcal{s}\) bytes.

The Room never shares \(\mathcal{t}\) with the Client, instead it returns \(\mathcal{T=}\mathfrak{E}\mathcal{(}\langle\mathcal{h(t), R(t), R(h(t)}\mathcal{)}\rangle\) where \(\mathfrak{E}\mathcal{()}\) is encrypted into a magical token \(\mathcal{T}\) such that only the Storage server can untangle it. \(\mathcal{R()}\) is encryption using the LEDGER_KEY for future garbage collection.

The Client can now do the actual upload of \(\mathcal{v|C}\), sending along with it \(\mathcal{T}\).

Rooms, Chats, Groups#

For a conversational exposition of how Rooms work, see the Technical Overview.

To be written, we’re saving the formal treatment for this for last, since it’s quite conventional.




Footnotes