Summary
The _domainSeparatorV4
is incorrectly calculated using BLUEPRINT_TYPE_HASH
instead of the EIP712_TYPE_HASH
.
Vulnerability Details
_domainSeparatorV4
is wrongly calculated as it uses BLUEPRINT_TYPE_HASH
instead of EIP712_TYPE_HASH
causing that the hash calculation be incorrect, making signing impossible.
Looking at the _domainSeparatorV4
, the name, version, chainId and address should be encoded, using the equivalent EIP712_TYPE_HASH
function _domainSeparatorV4() internal view returns (bytes32) {
return
keccak256(
abi.encode(
BLUEPRINT_TYPE_HASH,
TRACTOR_HASHED_NAME,
TRACTOR_HASHED_VERSION,
C.getChainId(),
address(this)
)
);
}
The EIP712_TYPE_HASH
goes like this, a hash of the name, version, chainId and verifyingContract.
bytes32 private constant EIP712_TYPE_HASH =
keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
);
However, _domainSeparatorV4
uses BLUEPRINT_TYPE_HASH
, which is a hash of the publisher, data, operatorData, maxNonce, startTime and endTime.
bytes32 public constant BLUEPRINT_TYPE_HASH =
keccak256(
"Blueprint(address publisher,bytes data,bytes operatorData,uint256 maxNonce,uint256 startTime,uint256 endTime)"
);
This makes the encoded data wrong.
Impact
The _domainSeparatorV4
is used to calculate _hashTypedDataV4
,
function _hashTypedDataV4(bytes32 structHash) internal view returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x01", _domainSeparatorV4(), structHash));
}
which is used to calculate blueprint hash.
function _getBlueprintHash(Blueprint calldata blueprint) internal view returns (bytes32) {
return
_hashTypedDataV4(
keccak256(
abi.encode(
BLUEPRINT_TYPE_HASH,
blueprint.publisher,
keccak256(blueprint.data),
keccak256(abi.encodePacked(blueprint.operatorPasteInstrs)),
blueprint.maxNonce,
blueprint.startTime,
blueprint.endTime
)
)
);
}
This is then used the verifyRequisition
modifier, which is used when publishing, cancelling and executing tractor blueprints.
modifier verifyRequisition(LibTractor.Requisition calldata requisition) {
bytes32 blueprintHash = LibTractor._getBlueprintHash(requisition.blueprint);
require(blueprintHash == requisition.blueprintHash, "TractorFacet: invalid hash");
address signer = ECDSA.recover(
MessageHashUtils.toEthSignedMessageHash(requisition.blueprintHash),
requisition.signature
);
require(signer == requisition.blueprint.publisher, "TractorFacet: signer mismatch");
_;
}
As established, since the calculated hash is incorrect, requisition verification will always fail when the signers attempt to perform the above mentioned operations in TractorFacet.sol (which are its core functions), rendering the contract practically unusable.
Tools Used
Manual Review
Recommendations
Recommend using the EIP712_TYPE_HASH
instead.
function _domainSeparatorV4() internal view returns (bytes32) {
return
keccak256(
abi.encode(
- BLUEPRINT_TYPE_HASH,
+ EIP712_TYPE_HASH,
TRACTOR_HASHED_NAME,
TRACTOR_HASHED_VERSION,
C.getChainId(),
address(this)
)
);
}