Tangentially, depending on what your input and data model look like, canonicalisation takes O(nlogn) time (i.e. the cost of sorting your fields).
Here I describe an alternative approach that produces deterministic hashes without a distinct canonicalization step, using multiset hashing: https://www.da.vidbuchanan.co.uk/blog/signing-json.html
It doesn't matter if I sign the word "yes", if you don't know what question is being asked. The signature needs to included the necessary context for the signature to be meaningful.
Lots of ways of doing that, and you definitely need to be thoughtful about redundant data and storage overhead, but the concept isn't tricky.
I didn't know that Protobuf wasn't canonical but even without this knowledge, there are many other factors which make it an inferior format to JSON.
Also, on a related topic; it seems unwise that essentially all the cryptographic primitives that everyone is using are often distributed as compiled binaries. I cannot think of anything more antithetical to security than that.
I implemented my own stateful signature algorithm for my blockchain project from scratch using utf8 as the base format and HMAC-SHA256 for key derivation. It makes it so much easier to understand and implement correctly. It uses Lamport OTS with Merkel MSS. The whole thing including all dependencies is like 4000 lines of easy-to-read JavaScript code. About 300 lines of code for MSS and 300 lines for Lamport OTS... The rest are just generic utility functions. You don't need to trust anyone else to "do it right" when the logic is simple and you can read it and verify it yourself! Simplicity of implementation and verification of the code is a critical feature IMO.
If your perfect crypto library is so complex that only 10 people in the world can understand it, that's not very secure! There is massive centralization and supply chain risk. You're hoping that some of these 10 people will regularly review the code and dependencies... Will they? Can you even trust them?
Choosing to use a popular cryptographic library which distributes binaries is basically trading off the risk of implementation mistake for the risk of supply chain attack... Which seems like a greater risk.
Anyway it's kind of wild to now be reading this and seeing people finally coming round to this approach. I've been saying this for years. You can check out https://www.npmjs.com/package/lite-merkle feedback welcome.
#1 You sign a blob and you don't touch it before verifying the signature (aka "The Cryptographic Doom Principle") #2 Signatures are bound to a context which is _not_ transmitted but used for deriving the key or mixed into the MAC or what have you. This is called the Horton principle. It ensures that signer/verifier must cryptographically agree on which context the message is intended for. You essentially cannot implement this incorrectly because if you do, all signatures will fail to verify.
The article actually proposes to violate principle #2 (by embedding some magic numbers into the protocol headers and presuming that someone will check them), which is an incorrect design and will result in bad things if history is any indication.
Principles #1 and #2 are well-established cryptographic design principles for just a handful of decades each.
extend google.protobuf.MessageOptions {
optional uint64 domain_separator = 1234;
}
message TreeRoot {
option (domain_separator) = 4567;
...
}This solves the message differentiation problem explicitly, makes security and memory management easier, and reduces routing to:
switch(msg.msgId): …
Crypto is hard. Do it right. Get help from your tools. 'Nuff said.
Jeeze, I'm getting too old for this crap.
- to have a convention to, instead of signing “payloads, to always sign “type identifier + payload”, to prevent adversaries from reusing your signature to sign the same payload, interpreted as a different type.
- use 64-bit type identifiers
- put the identifiers in the IDL (may need augmenting IDL to allow that)
#1 makes sense to me; #3 also makes sense, as that’s the place where people will have to look to learn about your types.
#2, I think, is up for discussion. These could be longer, Java-like strings “com.example.Foo”, or whatever.
I think some people also may disagree with the argument that putting type identifiers inside the payload makes messages too large, but I don’t have enough experience on that to make a judgment.
This is another example where you would think that "who it's for" is something the sender would sign but nope!