Open hats-bug-reporter[bot] opened 1 year ago
Hey @QYuQianchen, I'm curious why the medium label was removed. Thank you!
Dear @0xfuje After an internal review, we decided that the reported issue is widely applicable to the entire ecosystem and not project specific. This is a very nice infrastructure topic and we encourage you to create an EIP to raise this issue and we are happy to support further discussion.
Github username: @0xfuje Submission hash (on-chain): 0xf9626ce34861c300afafbce036ca9d3a9b1860c8c651f8fbacec316607545b1a Severity: high
Description:
Impact
Winning tickets can be redeemed for rewards twice, functions can use incorrect domain separator
Description
In case of a hard fork a malicious node operator can claim his ticket rewards twice via
Channels.sol
-redeemTicket()
. The root of the problem is thatupdateDomainSeparator()
does not entirely prevent replay attacks when a hard fork happens because:domainSeparator
verifyblock.chainid
it is up to dateupdateDomainSeparator()
since none of the functions call itChannels.sol
-updateDomainSeparator()
A smart attacker can plan ahead on the news of a planned hard-fork and accumulate as much redeemable tickets as possible and setup a front-running bot to double claim his rewards before
updateDomainSeparator()
is called.Other vulnerable contracts
Rather then sending a different report, I decided to mention here that other contracts are vulnerable as well, however their impact is not as significant and can be classified at most medium or low severity
Ledger.sol
-updateLedgerDomainSeparator()
While redeeming a ticket in
Channel.sol
-redeemInternal()
will index events with the not yet up to dateledgerDomainSeparator
->indexEvent()
will save asnapshot
with the incorrectlatestRoot.rootHash
since it hashesledgerDomainSeparator
NodeSafeRegistry.sol
-updateDomainSeparator()
registerSafeWithNodeSig()
- node can theoretically register asafe
on both chains with the same signatureProof of Concept
packages/ethereum/contracts/test/Channels.t.sol
HoprChannelsTest
contractrun
forge test --match-test testHardForkDoubleRedeem -vvvv
Recommended Mitigation
Double Ticket Claim
Consider making an
updateDomainSeparator()
call at the start of the_redeemTicketInteran()
to prevent calling it with an outdated domain separator.An alternative solution that's more gas efficient, but adds a tiny bit of complexity is to only call
updateDomainSeparator()
in the start of_redeemTicketInternal()
if theblock.chainid
is different than the previouschainid
. To archive this,block.chainid
can be saved to a storage variablechainId
inupdateDomainSeparator()
.Ledger, NodeSafeRegistry
Choose one method from the above solutions, implement the fix in functions that use the
domainSeparator
. InLedger.sol
callupdateLedgerDomainSeparator()
at the start ofindexEvent()
. InNodeSafeRegistry.sol
callupdateDomainSeparator()
at the start ofregisterSafeWithNodeSig()
. Don't forget to do the same inChannels.sol
-_getTicketHash()
since it also usesdomainSeparator
to hash.