I’ve looked into Tornado Cash smart contracts and it looks like it could be optimized with Ethereum signed messages.
Currently the Withdraw function, https://github.com/tornadocash/tornado-core/blob/a533ad9ffb62163a42d4fa9a09984c5dd4e5c41d/contracts/Tornado.sol#L87, verify all parameters against the ZK-proof.
In the circuit we can see that all this parameters are only multiplied with themselves to proof that who knows the commitment is who is choosing these parameters:
I see is not a big problem to introduce parameters in a circuit this way, however is that really necessary, if the parameters are not part of the computation why they have to be part of the proof?
The exit (recipient) address is public anyway, so why not use a signed message coming from this address with those parameters?
Wouldn’t this be more efficient, as ethereum signed messages are less gas intensive than passing all parameters inside ZK-Snarks?
As example for:
Something like this would achieve the same result:
function withdraw(bytes calldata _proof, bytes32 _root, bytes32 _nullifierHash, address payable _recipient, address payable _relayer, uint256 _fee, uint256 _refund, bytes calldata _messageSignature) external payable nonReentrant {
require(_fee <= denomination, "Fee exceeds transfer value");
require(!nullifierHashes[_nullifierHash], "The note has been already spent");
require(isKnownRoot(_root), "Cannot find your merkle root"); // Make sure to use a recent one
require(verifier.verifyProof(_proof, [uint256(_root), uint256(_nullifierHash), uint256(_recipient)]), "Invalid withdraw proof");
require(
_recipient == recoverAddress(getSignHash(keccak256(abi.encodePacked(_proof, _relayer, _fee, _refund)), _messageSignature)),
"Invalid signature"
);
nullifierHashes[_nullifierHash] = true;
_processWithdraw(_recipient, _relayer, _fee, _refund);
emit Withdrawal(_recipient, _nullifierHash, _relayer, _fee);
}
A valid downside is that _recipient would have to be an externally owned account, however if we need to use it as a smart contract, than we could use a temporary key that would decide all the parameters.
This technique could also be used for supporting function with lots of parameters, as the zk-proof would be trimmed and the rest of the parameters could be passed in a second transaction.
What are your thoughts on this? This is would be indeed more efficient? Where else this could be used?