Technical › Cryptography

Hashes: Keccak, Hs and Hp

How Monero turns arbitrary bytes into a number or into a point

In short

Monero uses the Keccak-256 hash function (original Keccak, not standardised SHA3-256). Two derived functions are built from it: Hs — "hash to scalar" and Hp — "hash to point".

Keccak-256

A hash function takes any data and outputs 32 bytes that look random. Changing one input bit changes the whole output; finding the input from the output, or two inputs with the same output, is infeasible in practice.

Monero (like Ethereum) uses Keccak as it won the SHA-3 competition — before NIST changed the padding byte. Because of that detail, sha3_256() from a standard library gives a different result. If your code doesn't match Monero, check this first.

FunctionHash of the empty string
Keccak-256 (Monero)c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
SHA3-256 (NIST)a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a

In Monero's code this function is called cn_fast_hash.

Hs: hash to scalar

Often we need not just bytes but a scalar — a number modulo l (the order of the base point). Hs does it directly:

Hs(data) = Keccak256(data) mod l the 32 hash bytes are read as a little-endian number and reduced modulo l (in code: sc_reduce32)

Where Hs is used:

Hp: hash to point

Sometimes we need a point whose discrete logarithm nobody knows. We can't just take Hs(data)·G — then the logarithm is Hs(data) and everyone knows it. Hp builds a point directly from the hash via a special map (in Monero a variant of Elligator 2, the function ge_fromfe_frombytes_vartime), then multiplies by the cofactor 8 so the point lands in the right subgroup.

Hp(data) = 8 · map_to_curve( Keccak256(data) )

Where Hp is used:

  • key images: I = x·Hp(P) — the main use;
  • the second generator H for Pedersen commitments: H = Hp(G) (up to encoding details). Nobody knows a k with H = k·G — and that's critical so amounts can't be forged.
Domain separation

Good practice is to prefix data with a label string ("SubAddr", "view_tag", "amount") before hashing, so a hash computed for one purpose can't be passed off for another. The old CryptoNote code has few labels; newer parts (view tags, Carrot, FCMP++) use them systematically.