When a transaction is made with monero there is a 'key image' in the transaction info. what is this exactly? is it like the diffie-hellman exchange to create the one time addresses?
Monero are always sent to the one-time destination (public key) P. In the CN whitepaper, the corresponding private key is referred to as x.
When you're spending, you're combining your P with a couple of other ones in a ring signature and signing with the x. Only one of the Ps will be spent, but the ring signatures hide which one. In other words, the signature is verified by all of the Ps, and it looks like any of their corresponding private keys could have signed the transaction, thus obfuscating the sender. But how do we know you're not double-spending? We need a way to prove that the private key used to sign was not used before, without revealing which one it is. That is where the key image comes in:
I = xHp(P)
Let's also take a look at how the one-time key-pair is derived:
Since I is a one-way function of x this means that:
- Nobody can recover the public key from the key image and identify the signer;
- The signer cannot make two signatures with different I’s and the same x which in effect means you can spend your P only once.
The layman's way to look at what a key image is would be that it allows the Monero network to quickly confirm whether an output as been spent or not. In other words, the key image is a necessary component of Monero's opaque blockchain ecosystem, because it allows for the prevention of double-spends.
For more information on this concept, please refer to this question and answer:
I created a small repository with basic monero functions:
def calc_key_image(a: bytes, b: bytes, R: bytes, i:int) -> bytes: """Calculate key image for input Args: a: bytes; Private view key b: bytes; Private spend key R: bytes; Transaction public key (rG) of refferenced output transaction. Stored in field extra[1:33]).hex(). i: int; output index of refferenced output Returns: Key image : string """ # x = H_s(aR) + b aR = ed25519.encodepoint(ed25519.scalarmult(ed25519.decodepoint(R), ed25519.decodeint(a))) aR = ed25519.encodepoint(ed25519.scalarmult(ed25519.decodepoint(aR), 8 )) # There is a mathematical reason for this... aR += bytes([i]) Hs = sc_reduce32(keccak_256(aR).digest()) x = int.from_bytes(Hs, byteorder='little') + int.from_bytes(b, byteorder='little') x = x % ed25519.l x = x.to_bytes(32, 'little') Hp = hashToPointCN(ed25519.publickey(x)) return ed25519.encodepoint(ed25519.scalarmult(Hp, ed25519.decodeint(x)))