The “high level” API give you a string-encoded hash, which includes a bunch of stuff: the hash bytes encoded a certain way, the salt bytes encoded a certain way, and information about some of the parameters you used (again encoded a certain way). You need all this information to later verify. That API doesn’t let you use a secret key.

The “low level” API lets you use a secret key, but just gives you raw hash bytes back. So, ultimately, by “expose something the C API doesn’t expose publicly,” all I really did is look at how the encoding in the C code (not exposed publicly) worked (how it packaged up the raw hash bytes, raw salt bytes, and those various parameters into a string) and rewrote that in Rust.

Said more simply (maybe), the only missing piece in the C API that was preventing folks from getting (publicly) a string-encoded hash while using a secret key was the (private) encoding function, which was easy to re-write in Rust.