In this page, we cover the threat of reusing the paymaster’s signature.
Cross-chain signature replay attack possibility
The signature must include the chainId
Like in a VerifyingPaymaster, when the paymaster includes logic to verify signatures, there are cases where it generates a separate hash using the getHash function instead of using the userOpHash passed as a parameter. If the getHash function in the paymaster does not include the chainId, there is a risk of the paymaster’s signature being reused across different chains.
**The userOpHash is generated by the Entrypoint and includes the chainId.
Example Code:
src/vulnerableVerifyingPaymaster.sol
function getHash(UserOperation calldata userOp)
public view returns (bytes32) { // @audit change to view
//can't use userOp.hash(), since it contains also the paymasterAndData itself.
return keccak256(abi.encode(
userOp.getSender(),
userOp.nonce,
keccak256(userOp.initCode),
keccak256(userOp.callData),
userOp.callGasLimit,
userOp.verificationGasLimit,
userOp.preVerificationGas,
userOp.maxFeePerGas,
userOp.maxPriorityFeePerGas
));
}
Mitigation : Include the chainId in the getHash function, or use the userOpHash passed as a parameter.
src/goodVerifyingPaymaster.sol
function getHash(UserOperation calldata userOp)
public view returns (bytes32) { // @audit change to view
//can't use userOp.hash(), since it contains also the paymasterAndData itself.
return keccak256(abi.encode(
userOp.getSender(),
userOp.nonce,
keccak256(userOp.initCode),
keccak256(userOp.callData),
userOp.callGasLimit,
userOp.verificationGasLimit,
userOp.preVerificationGas,
userOp.maxFeePerGas,
userOp.maxPriorityFeePerGas,
+ block.chainid // @audit add chain id
));
}
Signature without Nonce.
The signature must include the Nonce.
Like in a VerifyingPaymaster, when the paymaster includes logic to verify signatures, there are cases where it generates a separate hash using the getHash function instead of using the userOpHash passed as a parameter. If the getHash function in the paymaster does not include the nonce, there is a risk of the paymaster’s signature being reused.
** The userOpHash is generated by the Entrypoint and includes the nonce of UserOperation.
If the paymaster’s signature is reused, users could repeatedly submit the same UserOperation, which may drain the paymaster’s funds.
Exmaple Code:
src/vurnerableVerifyingPaymaster.sol
function getHash(UserOperation calldata userOp)
public view returns (bytes32) { // @audit change to view
//can't use userOp.hash(), since it contains also the paymasterAndData itself.
return keccak256(abi.encode(
userOp.getSender(),
keccak256(userOp.initCode),
keccak256(userOp.callData),
userOp.callGasLimit,
userOp.verificationGasLimit,
userOp.preVerificationGas,
userOp.maxFeePerGas,
userOp.maxPriorityFeePerGas,
block.chainid
));
}
Mitigation : Include the nonce in the getHash function, or use the userOpHash passed as a parameter.
src/goodVerifyingPaymaster.sol
function getHash(UserOperation calldata userOp)
public view returns (bytes32) { // @audit change to view
//can't use userOp.hash(), since it contains also the paymasterAndData itself.
return keccak256(abi.encode(
userOp.getSender(),
+ userOp.nonce,
keccak256(userOp.initCode),
keccak256(userOp.callData),
userOp.callGasLimit,
userOp.verificationGasLimit,
userOp.preVerificationGas,
userOp.maxFeePerGas,
userOp.maxPriorityFeePerGas,
block.chainid
));
}