TEERollup is a base contract which is inherited by BitcoinProver contract.
This issue is about incorrect implementation of verifyComputations() function which would always return false for given input arguments. verifyComputations() function is implemented as:
function verifyComputations(FullComputationsProof memory fullProof) public view returns (bool) {
bool isContractSignatureValid = Sapphire.verify(
Sapphire.SigningAlg.Secp256k1PrehashedKeccak256,
_keyPair.publicKey,
@> abi.encodePacked(keccak256(fullProof.partialProof.computationsResult)),
"",
fullProof.partialProof.contractSignature
);
if (!isContractSignatureValid) {
return false;
}
if (fullProof.witnessSignatures.length < minWitnessSignatures) {
return false;
}
bytes[] memory _usedPubKeys = new bytes[](fullProof.witnessSignatures.length);
for (uint i = 0; i < fullProof.witnessSignatures.length; i++) {
if (!witnessPublicKeysSet[fullProof.witnessSignatures[i].publicKey]) {
return false;
}
for (uint j = 0; j < _usedPubKeys.length; j++) {
if (keccak256(_usedPubKeys[j]) == keccak256(fullProof.witnessSignatures[i].publicKey)) {
return false;
}
}
_usedPubKeys[i] = fullProof.witnessSignatures[i].publicKey;
bool isWitnessSignatureValid = Sapphire.verify(
Sapphire.SigningAlg.Secp256k1PrehashedKeccak256,
fullProof.witnessSignatures[i].publicKey,
@> abi.encodePacked(keccak256(fullProof.partialProof.computationsResult)),
"",
fullProof.witnessSignatures[i].signature
);
if (!isWitnessSignatureValid) {
return false;
}
}
return true;
}
To check, contract signature validation, the above function has implemented below logic:
If both of these conditions pass then it will return true which computations are verfied otherwise it will return false.
The issue is about both of these condition which will ALWAYS return false due to their incorrect implementation. Both of these conditions have used Sapphire.verify() function from oasisprotocol'sSapphire.sol contract.
It can be seen that, input arguments are taken in bytes which is partially followed in above both conditions while validating contract and witness signatures.
contextOrHash is passed as bytes argument in verify() but the verifyComputations() has passed this param as bytes32. This is because of below line of code:
This encode the computationsResult inbytes32 which is encoded with keccak256. This would be incorrect input argument which will restrict the byte size of computationsResult to bytes32 which is not expected in verify() function.
It should be noted that, computationsResult can be directly used which is in bytes data format and does not need to be encoded with keccak256.
struct PartialComputationsProof {
bytes computationsResult; @audit // already in bytes data type
bytes contractSignature;
}
Due to this issue, verifyComputations() will return false as verify() function will always fail.
The following functions have used verifyComputations() in BitcoinProver.sl
Inshort, whole contracts functionalities are broken due to incorrect implementation of verifyComputations(). This would make the contracts functionalities useless and all of these functions will always revert with their string message.
Recommendation to fix\
Consider below changes :
function verifyComputations(FullComputationsProof memory fullProof) public view returns (bool) {
bool isContractSignatureValid = Sapphire.verify(
Sapphire.SigningAlg.Secp256k1PrehashedKeccak256,
_keyPair.publicKey,
- abi.encodePacked(keccak256(fullProof.partialProof.computationsResult)),
+ fullProof.partialProof.computationsResult,
"",
fullProof.partialProof.contractSignature
);
if (!isContractSignatureValid) {
return false;
}
if (fullProof.witnessSignatures.length < minWitnessSignatures) {
return false;
}
bytes[] memory _usedPubKeys = new bytes[](fullProof.witnessSignatures.length);
for (uint i = 0; i < fullProof.witnessSignatures.length; i++) {
if (!witnessPublicKeysSet[fullProof.witnessSignatures[i].publicKey]) {
return false;
}
for (uint j = 0; j < _usedPubKeys.length; j++) {
if (keccak256(_usedPubKeys[j]) == keccak256(fullProof.witnessSignatures[i].publicKey)) {
return false;
}
}
_usedPubKeys[i] = fullProof.witnessSignatures[i].publicKey;
bool isWitnessSignatureValid = Sapphire.verify(
Sapphire.SigningAlg.Secp256k1PrehashedKeccak256,
fullProof.witnessSignatures[i].publicKey,
- abi.encodePacked(keccak256(fullProof.partialProof.computationsResult)),
+ fullProof.partialProof.computationsResult,
"",
fullProof.witnessSignatures[i].signature
);
if (!isWitnessSignatureValid) {
return false;
}
}
return true;
}
Github username: -- Twitter username: -- Submission hash (on-chain): 0x58b5f64779ca817c6a65b9056718a820ffad1afb09b60d9498fa7255fa8ffd42 Severity: high
Description: Description\
TEERollup
is a base contract which is inherited byBitcoinProver
contract.This issue is about incorrect implementation of
verifyComputations()
function which would always return false for given input arguments.verifyComputations()
function is implemented as:To check, contract signature validation, the above function has implemented below logic:
and To check, Witness signature validation, the above function has implemented below logic:
If both of these conditions pass then it will return
true
which computations are verfied otherwise it will returnfalse
.The issue is about both of these condition which will
ALWAYS return false
due to their incorrect implementation. Both of these conditions have usedSapphire.verify()
function fromoasisprotocol's
Sapphire.sol
contract.verify()
is implemented as:Reference: https://github.com/oasisprotocol/sapphire-paratime/blob/2ada53a4a25034f123a37dadd25981c9267874d3/contracts/contracts/Sapphire.sol#L478
It can be seen that, input arguments are taken in
bytes
which is partially followed in above both conditions while validating contract and witness signatures.contextOrHash
is passed asbytes
argument inverify()
but theverifyComputations()
has passed this param asbytes32
. This is because of below line of code:This encode the
computationsResult
inbytes32
which is encoded with keccak256. This would be incorrect input argument which will restrict the byte size ofcomputationsResult
tobytes32
which is not expected inverify()
function.It should be noted that,
computationsResult
can be directly used which is inbytes
data format and does not need to be encoded with keccak256.Due to this issue,
verifyComputations()
will return false asverify()
function will always fail.The following functions have used
verifyComputations()
inBitcoinProver.sl
1)
getBlockChunkProof()
2)getBlockChunkRollupProof()
3)ackTransaction()
4)ackAnchorBlock()
Inshort, whole contracts functionalities are broken due to incorrect implementation of
verifyComputations()
. This would make the contracts functionalities useless and all of these functions will always revert with their string message.Recommendation to fix\ Consider below changes :