How do you package data before feeding it into a cryptographic algorithm, like Sign, Encrypt, MAC or Hash? This question has lingered for decades without a sufficient solution. There are at least two important problems to solve. First, the encoding ought to produce canonical outputs, as systems like Bitcoin have struggled when two different encodings decode to the same in-memory data. But more important, the encoding system ought to weigh in on the important problem of domain separation.
To get a sense for this issue, let’s look at a simple example, using a well-known IDL like protobufs. Imagine a distributed system that has two types of messages (among others): TreeRoot s that encapsulate the root of a transparency tree, and KeyRevoke s that signify a key being revoked:
message TreeRoot { int64 timestamp = 1; bytes hash = 2; } message KeyRevoke { int64 timestamp = 1; publicKeyFingerprint hash = 2; }
By a stroke of bad luck, these two data structures line up field-for-field, even though as far as the program and programmer are concerned, they mean totally different things. If a node in this system signs a TreeRoot and injects the signature into the network, an attacker might try to forge a KeyRevoke message that serializes byte-for-byte into the same message as the signed tree root, then staple the TreeRoot signature onto the KeyRevoke data structure. Now it looks like the signer signed a KeyRevoke when it never did, it only signed a TreeRoot . A verifier might be fooled into “verifying” a statement that the signer never intended.
This is not a theoretical attack. It has a long historical record of success, in the contexts of Bitcoin, DEXs in Ethereum, TLS, JWTs, and AWS, among others.
And though our small example concerns signing, the same idea is in play for MAC’ing (via HMAC or SHA-3), hashing, or even encryption, as most encryption these days is authenticated. In general, the cryptography should guarantee that the sender and receiver agree not only on the contents of the payload, but also the “type” of the data.
The systems that have taken stabs at domain separation use ad-hoc techniques, such as hashing the local name of surrounding program methods in Solana, best practices in Ethereum or “context strings” in TLS v1.3. Given the rich variety of serious bugs possible here, a more systematic approach is warranted. When building FOKS, we invented one.
The Idea: Domain Separators in the IDL
The main idea behind FOKS’s plan for serializing cryptographic data (called Snowpack) is to put random, immutable domain separators directly into the IDL:
struct TreeRoot @0x92880d38b74de9fb { timestamp @0 : Uint; hash @1 : Blob; }
... continue reading