An MLSAG is a generalized version of the bLSAG ring signature, that says:
Build a ring, where each ring 'member' is a key vector.
A key vector is a list of public keys.
To build a ring that successfully verifies, you must know the private keys corresponding to each of the public keys in a certain key vector.
As a matter of agreement between the signers and verifiers, it is decided which base points apply to each of the public keys in the key vector.
As a matter of agreement between the signers and verifiers, it is decided whether any particular public keys are also required to have key images proven.
If it is agreed that any particular public keys require key images, then this will prevent anyone from signing with the private keys for those particular public keys twice.
I was under the impression that there is a keyImage for each key in
the ring.
The Monero agreement is that you need a key image only for certain public keys listed in the key vector. The input public keys require a key image, but not the commitment public keys. This is because it would be unnecessary and more demanding both in terms of computation and storage to enforce that a certain commitment is never signed for twice.
It seems it is done by not including that section in the calculation
of the challenge. So to sign for an input(CommToZero, OneTimePubKey)
we will have one keyImage, two private keys and two public keys.
Yes, that is how it's done.
I was under the impression that there is a keyImage for each key in
the ring. So if the ring has 10 users and each user has 4 keys, then
the MLSAG will output 4 key images, 1 challenge value, 40 response
values and 40 public keys
I'm interpreting this as a ring size of 10, where someone is attempting to spend 4 inputs.
This is done using the RCTTypeSimple method in Monero, which creates 4 MLSAGs, one for each input being spent. Each of those MLSAGs would declare a key image for the input, there will be 10 key vectors, each with 2 public keys (the input public key and the commitment subtraction).
Therefore there would be a total of 4 key images, 40 key vectors, 80 public keys being signed for, and 80 response values.
You can double check this from the calculation in the answer here: What would the space saving difference be if MLSAG was applied over all inputs?
The space requirement for an RCTTypeSimple MLSAG signature is: 32*m*(2*n+3)
bytes of storage. In your example, m=4
and n=1
, therefore 2944
bytes of storage is required, which is a total of 2944/32=92
EC points and scalars that need to be stored (both EC points and scalars are represented using 32 bytes each). 92 = 4 key images + 4 challenges + 80 responses + 4 pseudo outs.