I'm designing a dApp where users can submit work they've done (files) to ipfs/swarm. To get credit for the work they then reference the hash of the files when calling a smart contract. What are some ways I might avoid front-running attacks (i.e. someone trolling pending transactions for work submitted, read a file hash, and use more gas to jump to the front of the line, and get credit for someone else's work)?

I'm not very familiar with state channels, but based on my understanding it is one possible solution (although then I'm sacrificing decentralization by having to coordinate the transactions).

There is a reputation component to the app, so maybe some sort of consensus mechanism that burns user reputation/funds if they are deemed fraudulent. Although this requires another layer of approvals/consensus before the work can be considered 'done' and I'm not sure exactly how would be able to could detect fraud.

Is there a simpler solution I'm not seeing? Any thoughts are welcome.


EDIT: I can imagine a two-transaction sequence where someone submits an integer that is the hash modulo their address (or something) followed (once settled) by another transaction with the actual hash, which can now be verified. Not sure if there's anything better.


This isn't a simpler scheme than what you propose, but some of the details hashed out. One thing you can use is a commit-reveal scheme in a two-step process (we can improve this later with state channels or batching zero-knowledge proofs, as you suggested).

You can't use the IPFS hash modulo the sender's address as the commitment, b/c the front-runner will have access to both and can fake it.

However, you can hash the IPFS hash, using the one-way hardness of something like the built-in keccak function.

First step: submit the cipher of the IPFS hash of your work

mapping(uint256 => address) committedHashes;

function submitWork(uint256 _ipfsHashCommit) {
    require(committedHashes[_ipfsHashCommit] == 0, 'IPFS hash commit already submitted.');
    committedHashes[_ipfsHashCommit] = msg.sender;  

Second step: reveal the true IPFS hash on-chain, but only after you are the only one who can do so

function revealWork(uint256 _ipfsHashCommit, uint256 _ipfsHash) {
    require(committedHashes[_ipfsHashCommit] == msg.sender, 'Only original sender can reveal');
    require(keccak(_ipfsHash) == _ipfsHashCommit, 'IPFS hash does not match commit');
    // mark work as submitted here, or ready for human review, or payout occurs, etc.

As you've noted, this is less than ideal b/c it requires two on-chain transactions for each individual user. If instead you are part of a collective, you might batch a lot of commits and reveals from many users together (either as a state channel which requires validators, or the trusted organization that is rewarding the work) and have them submitted on-chain at once.

  • > but some of the details hashed out
    – JoeC
    Oct 2 '19 at 5:14
  • This is very helpful, thanks so much. I'm thinking that the sender's address should be worked into some version of the commit hash. As it is above I think it would still be susceptible to a DoS attack. I'll need to do some research, but maybe something like: keccak(keccak(_ipfsHash) + msg.sender) could work as the key.
    – JoeC
    Oct 2 '19 at 5:24
  • oh i unwittingly made a pun Adding msg.sender to the hash would defend against someone discovering / leaking the IPFS hash, before the legitimate author has a chance to reveal. You can keccak hash an arbitrarily long string, so you could do keccak(_ipfsHash + msg.sender) (string append, not number addition or bitwise XOR or anything)
    – Paul Pham
    Oct 3 '19 at 11:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.