Summary
In LLMOracleCoordinator::request
, there is no check to ensure task.input
is non-empty. This allows users to leave task.input
empty, making it easier to pass the assertValidNonce
function.
Vulnerability Details
As given in the NatSpec the Input should be non-empty.
In the LLMOracleCoordinator::request
function, any requester can leave task.value
empty, which makes passing assertValidNonce
easier. Since task.value
is part of the message composition, an empty value reduces its uniqueness, lowering the nonce validation difficulty.
function assertValidNonce(uint256 taskId, TaskRequest storage task, uint256 nonce) internal view {
bytes memory message = abi.encodePacked(taskId, task.input, task.requester, msg.sender, nonce);
if (uint256(keccak256(message)) > type(uint256).max >> uint256(task.parameters.difficulty)) {
revert InvalidNonce(taskId, nonce);
}
}
@>
function request(
bytes32 protocol,
bytes memory input,
bytes memory models,
LLMOracleTaskParameters calldata parameters
) public onlyValidParameters(parameters) returns (uint256) {
(uint256 totalfee, uint256 generatorFee, uint256 validatorFee) = getFee(parameters);
uint256 allowance = feeToken.allowance(msg.sender, address(this));
if (allowance < totalfee) {
revert InsufficientFees(allowance, totalfee);
}
uint256 balance = feeToken.balanceOf(msg.sender);
if (balance < totalfee) {
revert InsufficientFees(balance, totalfee);
}
feeToken.transferFrom(msg.sender, address(this), totalfee);
uint256 taskId = nextTaskId;
unchecked {
++nextTaskId;
}
emit Request(taskId, msg.sender, protocol);
requests[taskId] = TaskRequest({
requester: msg.sender,
protocol: protocol,
input: input,
parameters: parameters,
status: TaskStatus.PendingGeneration,
generatorFee: generatorFee,
validatorFee: validatorFee,
platformFee: platformFee,
models: models
});
emit StatusUpdate(taskId, protocol, TaskStatus.None, TaskStatus.PendingGeneration);
return taskId;
}
Impact
A malicious user who realizes they can leave task.input
empty could exploit this to pass the assertValidNonce
check more easily, introducing a bias in the system. Without task.input
contributing to the message hash, the uniqueness of each request is reduced, lowering the computational effort needed to find a valid nonce and weakening the intended security.
Since the message in assertValidNonce
relies on task.input
for uniquiness, an empty task.input
simplifies the hash and reduces the difficulty of the nonce check. This allows users to bypass validation with minimal effort, undermining nonce integrity. Adding a requirement for a non-empty task.input
would help ensure the expected security level of the validation.
Tools Used
Manual Review
Recommendations
/// @notice Request LLM generation.
@> /// @dev Input must be non-empty.
/// @dev Reverts if contract has not enough allowance for the fee.
/// @dev Reverts if difficulty is out of range.
/// @param protocol The protocol string, should be a short 32-byte string (e.g., "dria/1.0.0").
/// @param input The input data for the LLM generation.
/// @param parameters The task parameters
/// @return task id
function request(
bytes32 protocol,
bytes memory input,
bytes memory models,
LLMOracleTaskParameters calldata parameters
) public onlyValidParameters(parameters) returns (uint256) {
(uint256 totalfee, uint256 generatorFee, uint256 validatorFee) = getFee(parameters);
+ // ensure the input parameter is not empty.
+ require(input.length != 0, "invalid input");
// check allowance requirements
//e all good- why this allowncce check without approve -> approved by buyer funciton called form there
uint256 allowance = feeToken.allowance(msg.sender, address(this));
if (allowance < totalfee) {
revert InsufficientFees(allowance, totalfee);
}
// ensure there is enough balance
uint256 balance = feeToken.balanceOf(msg.sender);
if (balance < totalfee) {
revert InsufficientFees(balance, totalfee);
}
// transfer tokens
feeToken.transferFrom(msg.sender, address(this), totalfee);
// increment the task id for later tasks & emit task request event
uint256 taskId = nextTaskId;
unchecked {
++nextTaskId;
}
emit Request(taskId, msg.sender, protocol);
// push request & emit status update for the task
requests[taskId] = TaskRequest({
requester: msg.sender,
protocol: protocol,
input: input,
parameters: parameters,
status: TaskStatus.PendingGeneration,
generatorFee: generatorFee,
validatorFee: validatorFee,
platformFee: platformFee,
models: models
});
emit StatusUpdate(taskId, protocol, TaskStatus.None, TaskStatus.PendingGeneration);
return taskId;
}