Ethereum in practice: a durable medium for tender offers

Adam Warski
SoftwareMill Tech Blog
10 min readJul 10, 2019

--

Ethereum is the second-biggest blockchain; currently about 8000 nodes are actively participating and confirming transactions. The most common use-cases for Ethereum include transferring ETH — the blockchain’s native currency — as well as trading tokens. The latter is especially popular among ICOs (Initial Coin Offerings).

However, Ethereum’s use-cases can go far beyond that. Ethereum is a general-purpose smart contract platform. Or in other words, a programmable, distributed database, replicated world-wide. As we will see, even very basic contracts can provide interesting benefits.

The core characteristic of Ethereum that we will use is the durability of data that is written on the blockchain. Anything that we store on the chain, once confirmed by the system, will be replicated across all of the network’s nodes, and stored there as long as there’s at least one Ethereum node remaining (not counting all the read-only copies of data and blockchain snapshots).

The ability to run arbitrary computations, and store arbitrary data in a replicated fashion, makes it possible to use Ethereum as a durable medium (as defined e.g. by the European Union). The blockchain meets the main three requirements:

  1. information can be addressed personally to the recipient (by storing appropriate data on the blockchain)
  2. storability: the information is stored in a way that is accessible for future reference
  3. reproduction: information can be reproduced in unchanged form

Tenders

To illustrate some of these concepts in practice, let’s take a look at the process of submitting offers for tenders. Typically, such offers are confidential until the deadline of the tender passes, at which point the offers are “opened” (in physical sense — if the offer is printed on paper and submitted in a sealed envelope — or in a metaphorical sense if the offer is only available electronically).

This process has a couple of weak points, which are quite often visible in the domain of public tenders (but not only). It’s crucial that all the contents of one company’s offer are unknown to other companies before the deadline passes — as this would give them an unfair advantage.

There can be two sources of leaks: in the offering company itself (we can’t do much about that), and from within the procuring institution. A bad actor, bribed by a competitor, or with other unofficial connections, might reveal details of submitted offers, or even replace offers after they’ve been submitted!

What if there was a way to submit an offer, without actually revealing its content? Ethereum, treated as a durable medium, can help!

Secure tenders

The basic idea is very simple: submitting an offer will amount to writing the hash of the file with the offer’s contents to the Ethereum blockchain.

A hash of a file or text is another text of fixed length (e.g. 64 characters), which can be deterministically derived from the original, using a specific algorithm (such as SHA3). The process must be such that using the currently available computing power, it’s not possible to create another text, which hashes to the same result. The result looks like random text; an example of a SHA3 hash is: 9ac86698255146a92e31a8cf0e1163682cf28b81475bafd9b9fcaa34d7a1f0b516dd1340725aebc19dce5febd49446c1acf28ae04493ba020990cfad98638e3d

The offer hashes will be stored in a dedicated smart contract, which will accept at most one offer per account (without the possibility to replace offers), until the specified deadline.

Once the deadline passes, bidders can safely send the original files in a traditional fashion, using e.g. email, to the procuring institution.

That way, we will ensure that:

  1. the offers will be unknown until the deadline passes. The contract contains only hashes of files, which don’t reveal anything about the content,
  2. it is easily verifiable (by comparing the hash) that the full offer text, made available to the procuring institution after the deadline, matches the one that was submitted,
  3. every participant can verify that the offer that won, has been indeed submitted in the tender (assuming that the winning offer is made available to all the parties), again by comparing hashes.

The tendering process

How would the tendering process look like in this setting?

First, the procuring institution deploys a smart contract for the tender, with the deadline embedded. The deadline is an immutable value, there’s no way to change it.

To deploy a smart contract, some ETH is needed, to pay for the execution of the creation code. ETH can be obtained e.g. on an exchange, for fiat money. A successful deployment results in an address of the smart contract, e.g. 0xB560d9537AFa2b1D22da8EFCD15B4792E9b5ED15.

Next, bidders can submit offers by invoking a method on the smart contract, with the hash of the offer’s file. To do that, they will need an Ethereum account, again with some ETH on it (to pay for storage and computational power used), as well as a way of submitting a transaction to the blockchain network.

This last step can be made easier by using a DApp: a web, javascript/html-based interface, created to cooperate with a given smart contract.

To function, DApps need to access the owner’s Ethereum account. This should be available only locally — we shouldn’t share our Ethereum account with any web or cloud-based service!

For this purpose, the MetaMask browser extension was created. It works locally on the user’s machine, and stores credentials for the user’s Ethereum account locally. When the extension is installed, an object to interact with the blockchain is injected into the javascript environment available for web pages.

The MetaMask extension also allows submitting invocations of contracts — or more generally, arbitrary transactions on the Ethereum network — for inclusion in the blockchain. Such transactions need to be sent to some of the nodes that participate and confirm transactions in the Ethereum network. It is possible to run such a node locally, but it’s more convenient to use gateways such as Infura or Cloudflare.

These gateways only relay signed transactions (signed contract invocations), they do not get access to the submitting user’s account credentials, and have no way of changing what’s in the contract invocation, so they are safe to use.

The contract

Moving onto technical details: how does this look like in code? The smart contract is really short, but the example offers only the most basic functionality. The below smart contract is created using the Solidity language. We will store offers in a struct:

struct Offer {
address bidder;
string offerHash;
uint256 timestamp;
}

containing the Ethereum address of the bidder, the hash of the file with the offer, and the timestamp when the offer was made.

Each instance of the smart contract will correspond to a single tender, so in the constructor we will accept the deadline, and initialize the offer counter:

constructor(uint256 _dateEnd) public {
dateEnd = _dateEnd;
offersCount = 0;
}

The contract will store data in four fields:

  • the deadline
  • an offer counter
  • a mapping from the bidder’s address to its offer index
  • a mapping from the offer index to the offer itself

The counter is needed as there’s no way to iterate over all entries in a mapping in Ethereum, so if we will want to know all submitted offers, we will need the counter:

uint public offersCount;
mapping (address => uint) public bidderToIndex;
mapping (uint => Offer) public submittedOffers;
uint256 public dateEnd;

Some of these fields are public so they will be available to be read easily e.g. by a DApp. Note, however, that even if a field is not public, this only means it’s not easily accessible by applications. All of the data on the blockchain can be read by anybody, including non-public contract fields.

The contract contains a single method to add a new hash to the stored data:

function submitOffer(string memory offerHash) public {
require(now < dateEnd);
require(bidderToIndex[msg.sender] == 0);
offersCount++;
bidderToIndex[msg.sender] = offersCount;
submittedOffers[offersCount] = Offer(msg.sender, offerHash, now);
}

We first check the preconditions: that the deadline hasn’t passed (now returns the timestamp of the last block that was confirmed by the network), and that the given bidder hasn’t yet submitted an offer. Then, it updates the internal storage of the contract.

We can now test the contract and then deploy it to a network. There are tools which make this task easier — e.g. the Truffle suite. The sources of the example, which are available on GitHub, use Truffle for this purpose.

It is also possible to run a local, single-node Ethereum network for testing purposes. There’s a couple approaches, but the easiest is to run Ganache, which apart from starting an Ethereum node, also offers a UI to see what happens on the network.

The complete code for the contract is available here.

The DApp

Now let’s create a user interface, through which users will be able to submit offers. As already mentioned, they will need the MetaMask browser extension, so that the javascript application can access the user’s Ethereum account.

There are some great DApp tutorials available (see here, or here), so we’ll just go through the most important parts. web3 is the javascript library, which we’ll use to interact with the blockchain. An instance of a “web3 provider” is injected to the environment by the MetaMask extension.

We will also use Truffle’s truffle-contract library to create an interface to our contract, based on a json file, containing the contract’s meta-data, generated when the contract is compiled and deployed to the network:

$.getJSON("Tender.json", function(tender) {
App.contracts.Tender = TruffleContract(tender);
App.contracts.Tender.setProvider(App.web3Provider);
App.initAlreadySubmitted();
});

We can now get the user’s account, by calling web3.eth.getAccounts, and verify if the user has already submitted an offer, or not yet:

App.contracts.Tender.deployed().then(function(instance) {
web3.eth.getAccounts(function(error, accounts) {
var account = accounts[0];
instance.bidderToIndex.call(account)
.then(function(indexResult) {
// indexResult is a BigNumber
if (indexResult.toString(10) != "0") {
instance.submittedOffers.call(indexResult)
.then(function(offerResult) {
App.appendLog("Already submitted offer with hash: " +
offerResult[1]);
});
}
});
});
});

Next, we can create a form with a single file-upload field, through which users will be able to submit offers:

<form id="submitOfferForm">
<label for="file">File with offer:</label>
<input type="file" id="fileInput" name="file">
<button type="submit" class="btn btn-primary">
Submit offer
</button>
</form>

And when the form is submitted, first calculate the file’s hash using the FileReader API:

var reader = new FileReader();
var file = $("#fileInput")[0].files[0];
reader.onload = function() {
var hash = web3.sha3(reader.result);
App.appendLog("File hash: " + hash);
// invoke smart contract with the hash
};
reader.readAsBinaryString(file);

And finally, invoke the smart contract:

web3.eth.getAccounts(function(error, accounts) {
var account = accounts[0];
App.appendLog("Sending using account: " + account);
return App.contracts.Tender.deployed().then(function(instance) {
return instance.submitOffer(hash, { from: account });
}).then(function(result) {
App.appendLog("Success! Transaction hash: " + result.tx);
});
});

When the contract is invoked, MetaMask will ask us to confirm, that we indeed want to invoke this smart contract method — as this means spending some ETH (nothing comes for free!).

Finally, we get back the hash of the transaction. This doesn’t mean that the data is on the blockchain just yet! The transaction needs to be included in the blockchain by the nodes that participate in the network. If we set the fee for the transaction too low, it might happen that no node will want to confirm that transaction — as there are other, more profitable transactions. Luckily, MetaMask does this automatically.

We can verify the state of a transaction either in the MetaMask extension, or online in a service such as EtherScan. As all the information on the blockchain is public, so is the state of our method call.

The complete code for the DApp is available here.

Summary

In the example we have created a very simple contract which aids the tendering process, making it more secure and less prone to bad, biased actors operating from within the procuring organization. We used Ethereum as a durable medium for storing the hashes of submitted offers.

The contract comes with a basic user interface, through which bidders can submit their offers. All of the operations on the blockchain involve paying a fee — some ETH, the currency of Ethereum.

As each system, the Ethereum blockchain has its downsides; all of the data is publicly available. Moreover, the throughput of the network is limited (to about 7 transactions/second), however this isn’t a factor in this use-case. Finally, each invocation of a smart contract, which stores data on the blockchain, costs ETH, and ETH in turn, translates to “real” money!

The contract can also be developed further, to include more business rules regarding the tender. The upside is that any business rule, that is included in the contract, is transparently enforced for all participants — as that’s how Ethereum operates. Extensions to the contract might include signatures of offers done using public key cryptography, supporting multi-stage tenders (where questions might be asked), or storing the winner on the blockchain as well.

The original idea for using Ethereum for a tendering platform by Łukasz Żuchowski.

Update 10/7/19: extending the contract

As Łukasz also suggested, the contract can also be extended to require a deposit that has to be done when submitting an offer. The deposit would be a fixed amount of ETH. The smart contract could contain logic, which makes it necessary to transfer ETH when invoking the contract’s submitOffer method.

Such a deposit could be automatically returned after the tender is finished (this could also be encoded in the contract). Or, it could be used to make sure that the bidder submits the text of the offer — but then, releasing the deposit would have to be external (triggered by the procuring institution).

These mechanisms could improve the tendering process so that “fake” bidders don’t scare others away by creating a lot of submissions, and that all of the submissions are serious.

--

--

Software engineer, Functional Programming and Scala enthusiast, SoftwareMill co-founder