Looking at the code, we can see how it's generated.
void crypto_ops::generate_key_image(const public_key &pub, const secret_key &sec, key_image &image) {
ge_p3 point;
ge_p2 point2;
assert(sc_check(&sec) == 0);
hash_to_ec(pub, point);
ge_scalarmult(&point2, &sec, &point);
ge_tobytes(&image, &point2);
}
The hash_to_ec
function is called to hash pub
(which is your P
) into point
, giving us your H(P)
. Then, scalar multiplication is performed and result passed into point2
, which would be your x*H(P)
. Some transfromation ge_tobytes
is applied and the result is passed into image
which is your I
.
I'm not sure what this ge_tobytes
transformation does, though. It could be just changing the format of I
in memory. Implementation specific, I suppose.
The hash_to_ec
function is presented below.
static void hash_to_ec(const public_key &key, ge_p3 &res) {
hash h;
ge_p2 point;
ge_p1p1 point2;
cn_fast_hash(std::addressof(key), sizeof(public_key), h);
ge_fromfe_frombytes_vartime(&point, reinterpret_cast<const unsigned char *>(&h));
ge_mul8(&point2, &point);
ge_p1p1_to_p3(&res, &point2);
}
We see that the cn_fast_hash
function is called, which would be the Keccak hash function.
After getting the hash h
, it is passed through some functions, which I suspect perform scalar multiplication of h
with something, but my understanding stops here. This ge_fromfe_frombytes_vartime
might be of interest as Shen Noether did a dedicated write-up on it. Also, here we see the importance of using a secure hash function for the key images. Apparently, to do this something after the Keccak is quite important.
Shen Noether's implementation in Python could provide some insights as well, but I'm not sure if it's up to date.