The way I see it, for a tx hash to properly identify a tx, it means that two different txs cannot have the same hash. If all a tx can do is insert and consume private state (ie insert private data commitments and nullifiers), then it makes sense that the hash of the set of nullifiers and data commitments uniquely identifies it (provided those are unique as well).
But now a tx can also call a public function. This means that a tx can also affect the public state tree. So the changes to the nullifier and private data trees are no longer enough for identifying it. And including the changes to the public state tree in the tx hash do not work, because those are not known until the tx has been processed by the sequencer, and you need to be able to identify a tx since the moment the client assembles it.
So @Mike suggested going with the same model that Ethereum has, and just use the hash of the tx intent (ie tx request, the from/to/calldata/etc) as the identifier, which is a great idea. The new problem is that, if you’re an archiver, you can’t reconstruct the tx hash from the data that gets published on-chain (since it only has changes to the world state trees).
So the alternative is either including the full tx request in the chain (but this is costly, and it breaks privacy), or including the tx hash itself, and having a circuit verify that the tx hash is correct wrt the tx request. We considered including it as a separate field, but Mike suggested including it as a nullifier, since we get for free the check that the same tx is not included twice.
The changes for doing this are captured in this issue.