Skip to content

Provenance

We put procurement records on a blockchain. Here is what that actually bought.

A tamper-evident ledger protects a record after it is written, not the moment a person at a weighbridge types the wrong number in.

· 7 min read

A truck rolls onto a weighbridge at a food-processing plant. The gross weight comes up on a display, a man in the office reads it off, and types it into a form. That number, the one he typed, is the first line of a provenance record that a buyer or an auditor may one day want to trust. We anchored that record to a blockchain. What follows is an honest account of what doing that bought us, and what it did not, because this is the one project I would least like us to be smug about.

The system underneath is ordinary, and I mean that as praise. Django, MySQL, Celery for the slow jobs, Redis behind it. A truck arrives, gets weighed, gets sampled, gets graded against the deal struck with the farmer, and only then turns into an invoice that three separate people have to approve in turn. All of that lives in a relational database the way it would anywhere. The only exotic part is that when a procurement record is finalised, we sign it and push a representation of it onto a chain, and a scheduled job goes back later to check the chain actually agreed to hold it.

What the chain actually gives you

Tamper-evidence. That is the whole of it, and it is not nothing. Once a record is anchored, nobody can quietly go back and alter what a consignment weighed, or what it graded to, or what we agreed to pay for it, without the change being detectable. In food provenance that property has a buyer. When a customer or a regulator asks where this batch came from and who signed off on its quality, being able to show that the answer has not been edited since the day it was written is worth real money.

But look closely at the exact shape of what you get. The chain makes the record hard to change after the fact. It does nothing whatsoever about whether the record was true when it was written. Those are two different problems wearing the same coat, which takes us straight back to the man at the weighbridge.

The number he typed

If he fat-fingers the weight, the chain will preserve the typo faithfully, forever. If a farmer and a field officer have an understanding and the grade gets written a notch above what the consignment deserves, the chain preserves that too, permanently, with a beautiful cryptographic guarantee that nobody tampered with the lie. Garbage in is not garbage out here. Garbage in is garbage set in stone.

This is the oracle problem, and in a system like ours it is not a footnote, it is the entire game. A blockchain can guarantee the integrity of data once it has crossed the boundary onto the chain. It can say nothing about what happened on the other side of that boundary, where a person with a keyboard decides what to write down. Every hard question about whether our provenance record deserves trust is a question about that person and that keyboard, and the chain touches none of it.

What a boring Postgres table would have given us

Here is the uncomfortable bit. Most of the value we shipped, we could have shipped without a chain at all. Tamper-evidence does not need a distributed ledger. It needs an append-only store and a signature. We were already signing the record with a key before it ever went near the chain, so the primitive was sitting right there in our own code.

import hashlib, hmac, json

def anchor(record: dict, signing_key: bytes, prev_hash: str) -> dict:
    # Deterministic bytes for the row we are sealing.
    payload = json.dumps(record, sort_keys=True, separators=(',', ':')).encode()

    # Each row commits to the one before it. Change any earlier
    # row and every hash after it stops matching. That is the
    # tamper-evidence, and it is just a loop over rows.
    row_hash = hashlib.sha256(prev_hash.encode() + payload).hexdigest()

    # Sign the hash with a key we hold, so nobody can rebuild the
    # chain from scratch without it.
    signature = hmac.new(signing_key, row_hash.encode(), hashlib.sha256).hexdigest()

    return {
        'payload': payload.decode(),
        'prev_hash': prev_hash,
        'row_hash': row_hash,
        'signature': signature,
    }

That is a hash-linked, signed, append-only log. Put those rows in Postgres, revoke UPDATE and DELETE on the table at the database-role level, take the signing key off the application server, and you are done. An auditor can recompute every hash and verify every signature offline, on a laptop, with no node to talk to. It is tamper-evident in the sense that actually matters to a food buyer, and it costs you one table and a nightly verification job instead of a node, a wallet, a custodian key and a token model to maintain.

So why would any sane person reach for a chain instead? There is exactly one situation where it earns its keep, and it is worth being precise about, because most blockchain projects are quietly not in it. The append-only Postgres table protects you from everyone except the party who controls the database. If the entity that might tamper with the record is the same entity running the server the record lives on, no amount of REVOKE UPDATE saves you, because they own the role that granted it. They can drop the table and rebuild it. That, and only that, is the real case for a chain. Not "we would like tamper-evidence", but "the party who benefits from a lie is the party we are asking to store the truth". A single manufacturer anchoring its own procurement records does not obviously sit in that box. If the buyer at the far end does not trust us, the honest fix is for the record to live somewhere we cannot silently rewrite, and a ledger we do not solely control is one way to get there. We were, if I am being straight about it, closer to the boring case than the interesting one.

Running the thing

None of the sceptical points above are why a chain is expensive to operate. The cost is operational, and it is relentless. Our anchoring was asynchronous by nature: a record got marked as sent, and a scheduled scanner walked back through recently sent transactions asking the node whether it had confirmed them yet, promoting the ones it had. Sounds tidy. Then the node is unreachable, and now you have a pile of records in limbo, written in your own database but not yet confirmed on chain, and a UI that has to be honest about the difference without alarming a weighbridge operator who does not know what a chain is and should never need to. When the chain is down, your provenance guarantee is simply pending. You keep taking trucks in, because the trucks do not care. The records queue. Somebody has to decide how long a queue is too long a queue, and that somebody is you, not the chain.

And the key. A custodian wallet sat in our configuration, and the integrity of the whole scheme leaned on it not leaking. Lose it and you cannot anchor anything. Leak it and, depending on how you modelled the tokens, someone can impersonate the custodian and write records that look exactly as trustworthy as the real ones. We were now in the key-management business, a whole discipline with its own failure modes, which we took on more or less by accident because what we actually wanted was a tamper-evident log.

Would we do it again

Plainly, no. For this shape of problem, a single company anchoring its own records for its own buyers, we would not reach for a chain by default again. We would build the signed, append-only log, keep it in Postgres, get the key handling right, and spend every hour we saved on the part that actually decides whether the provenance is worth anything: the weighbridge, the sampling, the grading, the moment a human turns a physical fact into a row. That boundary is where provenance is genuinely won or lost, and it is the exact place a chain does nothing for you.

We would do it again the day the trust boundary moves. If several parties who do not trust each other have to share one record, say a growers' co-operative, its buyers, and an outside auditor, none of them willing to host the database the others depend on, then a ledger that none of them solely controls stops being ceremony and becomes the whole point. Show me that, a real multi-party arrangement where the party storing the data and the party who would gain from falsifying it are different people, and I will anchor to a chain gladly and defend it in the reference call. Two of us still argue about whether we were already there, on the grounds that a sceptical export buyer is a party of that kind. I do not think we were. But it is not a foolish position to hold, and that argument is roughly where the line sits.

Tell us what you're trying to build

Send us the problem, not a specification. We'll tell you honestly whether we're the right people for it, and if we aren't, we'll say so.