Hacker News new | ask | show | jobs
by tptacek 526 days ago
Major Rust cryptography libraries (see for instance Ring) use assembly, too. It's a pretty normal thing to do.
2 comments

Sure but golang has its own special assembly flavor rather than using standard gcc flavor inline assembly. Probably because it's a soup to nuts compiler but still.
The point of this article is that Go-specific assembler generators (Avo in particular) are better than standard assembly for this purpose.
That doesn't preclude syntactic compatibility, does it?
This is not at all comparable to inline assembly which interleaves two different languages into the same source file.

The presented examples are just a straight distinct assembly language associated with the golang ecosystem used in their own dedicated source files called via a FFI. This is comparable to just writing a pure assembly file and linking it into your program which is actually a much more reasonable thing to do than the insanity of inline assembly.

The problems being highlighted are just cases of people who do not understand ABIs and ABI compatibility. This is extremely common when crossing a language boundary due to abstraction mismatches and is made worse, not better, by doing even more magic behind the scenes to implicitly paper over the mismatches.

>soup to nuts compiler

Any chance you can explain that to rubes like me?

Go isn't built on an existing compiler framework like LLVM. It does its own code generation, has its own assembler.
There is an accident of history here. Go was developed with the plan 9 C compiler suite as a starting point. Most notably those compilers did not generate assembler -- they emitted object code directly. This is described here: https://9p.io/sys/doc/compiler.html. The assembler facilitated transforming hand-written assembly to object code. And here the plan 9 folks chose a new syntax, probably because it was simpler to start afresh over using the existing "AT&T" or "Intel" syntax.
Typical plan 9, change for the sake of change. Second system effect writ large.
It's kinda weird that languages (or at least languages with pretensions to cryptography) are still forcing people to resort to asm directly rather than offering some sort of first-class support for constant time operations and not leaving secrets lying around in memory. It doesn't need to be super high level, it just needs to clear the infinitely low bar of assembly language. Does any language offer such a dedicated facility?
That's not why cryptographers use assembly. We use assembly because performance often requires instructions the complier will never use that the CPU maker makes for us. Intrinsics invite all sorts of spilling issues and aren't quite as good.
As someone who's made a very simple language, I would say there are far too many moving parts involved to guarantee anything of the sort. It's probably better to just integrate libsodium.

Interpreters will literally switch on the type of things in order to figure out what to do with the value. They've lost the side channel battle before it even began. Compilers? Who knows what sort of code they will generate? Who knows how many of your precautions they will delete in an effort to "optimize"? Libsodium has its own memory zeroing function because compilers were "optimizing" the usage of the standard ones.

If you're writing anything cryptography related, you probably want to be talking directly to the processor which will be running your code. And only after you've studied the entire manual. Because even CPUs have significant gaps in the properties they guarantee and the conditions they guarantee them in.

Cryptographers might even consider lowering the level even further. They might want to consider building their own cryptoprocessor that works exactly like they want it to work. Especially if you need to guarantee things like "it's impossible to copy keys and secrets". I own three yubikeys for the sole purpose of guaranteeing this.

Interpreters don't need to have dynamic typing: for example the JVM and the interpreters before JIT. Even with dynamic types there are some spectacularly clever tricks people use: Smalltalk VMs are where they were invented and practiced a bunch.

In crypto code branching is exactly what you don't want to do to guarantee security. Branches go both ways if an attacker can force a mispeculate and microarchitectural state is not rolled back because it can't be.

Most crypto wants constant-time execution, optimizing compilers are not designed with that in mind. They have optimization passes that will happily turn your carefully crafted constant-time code back into branches when their heuristics deem that profitable.

Currently the most reliable way to get exactly the assembly you want is to write the assembly you want.

Go does have first class support for this in the standard library, e.g. https://pkg.go.dev/crypto/subtle.

It's not at all weird that the language authors needed assembly to implement such a thing. They figured out the tricky bits so you don't have to.